001// License: GPL. For details, see LICENSE file. 002package org.openstreetmap.josm.actions; 003 004import static org.openstreetmap.josm.gui.help.HelpUtil.ht; 005import static org.openstreetmap.josm.tools.I18n.tr; 006import static org.openstreetmap.josm.tools.I18n.trn; 007 008import java.awt.Component; 009import java.awt.GridBagLayout; 010import java.awt.event.ActionEvent; 011import java.awt.event.KeyEvent; 012import java.util.ArrayList; 013import java.util.Arrays; 014import java.util.Collection; 015import java.util.Collections; 016import java.util.HashSet; 017import java.util.Iterator; 018import java.util.LinkedList; 019import java.util.List; 020import java.util.Set; 021import java.util.concurrent.atomic.AtomicInteger; 022 023import javax.swing.DefaultListCellRenderer; 024import javax.swing.JLabel; 025import javax.swing.JList; 026import javax.swing.JOptionPane; 027import javax.swing.JPanel; 028import javax.swing.ListSelectionModel; 029import javax.swing.event.ListSelectionEvent; 030import javax.swing.event.ListSelectionListener; 031 032import org.openstreetmap.josm.Main; 033import org.openstreetmap.josm.command.AddCommand; 034import org.openstreetmap.josm.command.ChangeCommand; 035import org.openstreetmap.josm.command.Command; 036import org.openstreetmap.josm.command.SequenceCommand; 037import org.openstreetmap.josm.data.osm.Node; 038import org.openstreetmap.josm.data.osm.OsmPrimitive; 039import org.openstreetmap.josm.data.osm.PrimitiveId; 040import org.openstreetmap.josm.data.osm.Relation; 041import org.openstreetmap.josm.data.osm.RelationMember; 042import org.openstreetmap.josm.data.osm.Way; 043import org.openstreetmap.josm.data.osm.WaySegment; 044import org.openstreetmap.josm.gui.DefaultNameFormatter; 045import org.openstreetmap.josm.gui.ExtendedDialog; 046import org.openstreetmap.josm.gui.Notification; 047import org.openstreetmap.josm.gui.layer.OsmDataLayer; 048import org.openstreetmap.josm.tools.CheckParameterUtil; 049import org.openstreetmap.josm.tools.GBC; 050import org.openstreetmap.josm.tools.Shortcut; 051 052/** 053 * Splits a way into multiple ways (all identical except for their node list). 054 * 055 * Ways are just split at the selected nodes. The nodes remain in their 056 * original order. Selected nodes at the end of a way are ignored. 057 */ 058public class SplitWayAction extends JosmAction { 059 060 /** 061 * Represents the result of a {@link SplitWayAction} 062 * @see SplitWayAction#splitWay 063 * @see SplitWayAction#split 064 */ 065 public static class SplitWayResult { 066 private final Command command; 067 private final List<? extends PrimitiveId> newSelection; 068 private final Way originalWay; 069 private final List<Way> newWays; 070 071 /** 072 * @param command The command to be performed to split the way (which is saved for later retrieval with {@link #getCommand}) 073 * @param newSelection The new list of selected primitives ids (which is saved for later retrieval with {@link #getNewSelection}) 074 * @param originalWay The original way being split (which is saved for later retrieval with {@link #getOriginalWay}) 075 * @param newWays The resulting new ways (which is saved for later retrieval with {@link #getOriginalWay}) 076 */ 077 public SplitWayResult(Command command, List<? extends PrimitiveId> newSelection, Way originalWay, List<Way> newWays) { 078 this.command = command; 079 this.newSelection = newSelection; 080 this.originalWay = originalWay; 081 this.newWays = newWays; 082 } 083 084 /** 085 * Replies the command to be performed to split the way 086 * @return The command to be performed to split the way 087 */ 088 public Command getCommand() { 089 return command; 090 } 091 092 /** 093 * Replies the new list of selected primitives ids 094 * @return The new list of selected primitives ids 095 */ 096 public List<? extends PrimitiveId> getNewSelection() { 097 return newSelection; 098 } 099 100 /** 101 * Replies the original way being split 102 * @return The original way being split 103 */ 104 public Way getOriginalWay() { 105 return originalWay; 106 } 107 108 /** 109 * Replies the resulting new ways 110 * @return The resulting new ways 111 */ 112 public List<Way> getNewWays() { 113 return newWays; 114 } 115 } 116 117 /** 118 * Create a new SplitWayAction. 119 */ 120 public SplitWayAction() { 121 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."), 122 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.DIRECT), true); 123 putValue("help", ht("/Action/SplitWay")); 124 } 125 126 /** 127 * Called when the action is executed. 128 * 129 * This method performs an expensive check whether the selection clearly defines one 130 * of the split actions outlined above, and if yes, calls the splitWay method. 131 */ 132 @Override 133 public void actionPerformed(ActionEvent e) { 134 135 if (SegmentToKeepSelectionDialog.DISPLAY_COUNT.get() > 0) { 136 new Notification(tr("Cannot split since another split operation is already in progress")) 137 .setIcon(JOptionPane.WARNING_MESSAGE).show(); 138 return; 139 } 140 141 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected(); 142 143 List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class); 144 List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class); 145 List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes); 146 147 if (applicableWays == null) { 148 new Notification( 149 tr("The current selection cannot be used for splitting - no node is selected.")) 150 .setIcon(JOptionPane.WARNING_MESSAGE) 151 .show(); 152 return; 153 } else if (applicableWays.isEmpty()) { 154 new Notification( 155 tr("The selected nodes do not share the same way.")) 156 .setIcon(JOptionPane.WARNING_MESSAGE) 157 .show(); 158 return; 159 } 160 161 // If several ways have been found, remove ways that doesn't have selected 162 // node in the middle 163 if (applicableWays.size() > 1) { 164 for (Iterator<Way> it = applicableWays.iterator(); it.hasNext();) { 165 Way w = it.next(); 166 for (Node n : selectedNodes) { 167 if (!w.isInnerNode(n)) { 168 it.remove(); 169 break; 170 } 171 } 172 } 173 } 174 175 if (applicableWays.isEmpty()) { 176 new Notification( 177 trn("The selected node is not in the middle of any way.", 178 "The selected nodes are not in the middle of any way.", 179 selectedNodes.size())) 180 .setIcon(JOptionPane.WARNING_MESSAGE) 181 .show(); 182 return; 183 } else if (applicableWays.size() > 1) { 184 new Notification( 185 trn("There is more than one way using the node you selected. Please select the way also.", 186 "There is more than one way using the nodes you selected. Please select the way also.", 187 selectedNodes.size())) 188 .setIcon(JOptionPane.WARNING_MESSAGE) 189 .show(); 190 return; 191 } 192 193 // Finally, applicableWays contains only one perfect way 194 final Way selectedWay = applicableWays.get(0); 195 final List<List<Node>> wayChunks = buildSplitChunks(selectedWay, selectedNodes); 196 if (wayChunks != null) { 197 List<Relation> selectedRelations = 198 OsmPrimitive.getFilteredList(selection, Relation.class); 199 final List<OsmPrimitive> sel = new ArrayList<>(selectedWays.size() + selectedRelations.size()); 200 sel.addAll(selectedWays); 201 sel.addAll(selectedRelations); 202 203 final List<Way> newWays = createNewWaysFromChunks(selectedWay, wayChunks); 204 final Way wayToKeep = Strategy.keepLongestChunk().determineWayToKeep(newWays); 205 206 if (ExpertToggleAction.isExpert() && !selectedWay.isNew()) { 207 final ExtendedDialog dialog = new SegmentToKeepSelectionDialog(selectedWay, newWays, wayToKeep, sel); 208 dialog.toggleEnable("way.split.segment-selection-dialog"); 209 if (!dialog.toggleCheckState()) { 210 dialog.setModal(false); 211 dialog.showDialog(); 212 return; // splitting is performed in SegmentToKeepSelectionDialog.buttonAction() 213 } 214 } 215 if (wayToKeep != null) { 216 final SplitWayResult result = doSplitWay(getEditLayer(), selectedWay, wayToKeep, newWays, sel); 217 Main.main.undoRedo.add(result.getCommand()); 218 getCurrentDataSet().setSelected(result.getNewSelection()); 219 } 220 } 221 } 222 223 /** 224 * A dialog to query which way segment should reuse the history of the way to split. 225 */ 226 static class SegmentToKeepSelectionDialog extends ExtendedDialog { 227 static final AtomicInteger DISPLAY_COUNT = new AtomicInteger(); 228 final transient Way selectedWay; 229 final transient List<Way> newWays; 230 final JList<Way> list; 231 final transient List<OsmPrimitive> selection; 232 final transient Way wayToKeep; 233 234 SegmentToKeepSelectionDialog(Way selectedWay, List<Way> newWays, Way wayToKeep, List<OsmPrimitive> selection) { 235 super(Main.parent, tr("Which way segment should reuse the history of {0}?", selectedWay.getId()), 236 new String[]{tr("Ok"), tr("Cancel")}, true); 237 238 this.selectedWay = selectedWay; 239 this.newWays = newWays; 240 this.selection = selection; 241 this.wayToKeep = wayToKeep; 242 this.list = new JList<>(newWays.toArray(new Way[newWays.size()])); 243 configureList(); 244 245 setButtonIcons(new String[]{"ok", "cancel"}); 246 final JPanel pane = new JPanel(new GridBagLayout()); 247 pane.add(new JLabel(getTitle()), GBC.eol().fill(GBC.HORIZONTAL)); 248 pane.add(list, GBC.eop().fill(GBC.HORIZONTAL)); 249 setContent(pane); 250 setDefaultCloseOperation(HIDE_ON_CLOSE); 251 } 252 253 private void configureList() { 254 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 255 list.addListSelectionListener(new ListSelectionListener() { 256 @Override 257 public void valueChanged(ListSelectionEvent e) { 258 final Way selected = list.getSelectedValue(); 259 if (Main.isDisplayingMapView() && selected != null && selected.getNodesCount() > 1) { 260 final Collection<WaySegment> segments = new ArrayList<>(selected.getNodesCount() - 1); 261 final Iterator<Node> it = selected.getNodes().iterator(); 262 Node previousNode = it.next(); 263 while (it.hasNext()) { 264 final Node node = it.next(); 265 segments.add(WaySegment.forNodePair(selectedWay, previousNode, node)); 266 previousNode = node; 267 } 268 setHighlightedWaySegments(segments); 269 } 270 } 271 }); 272 list.setCellRenderer(new DefaultListCellRenderer() { 273 @Override 274 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 275 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 276 final String name = DefaultNameFormatter.getInstance().format((Way) value); 277 // get rid of id from DefaultNameFormatter.decorateNameWithId() 278 final String nameWithoutId = name 279 .replace(tr(" [id: {0}]", ((Way) value).getId()), "") 280 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), ""); 281 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId)); 282 return c; 283 } 284 }); 285 } 286 287 protected void setHighlightedWaySegments(Collection<WaySegment> segments) { 288 selectedWay.getDataSet().setHighlightedWaySegments(segments); 289 Main.map.mapView.repaint(); 290 } 291 292 @Override 293 public void setVisible(boolean visible) { 294 super.setVisible(visible); 295 if (visible) { 296 DISPLAY_COUNT.incrementAndGet(); 297 list.setSelectedValue(wayToKeep, true); 298 } else { 299 setHighlightedWaySegments(Collections.<WaySegment>emptyList()); 300 DISPLAY_COUNT.decrementAndGet(); 301 } 302 } 303 304 @Override 305 protected void buttonAction(int buttonIndex, ActionEvent evt) { 306 super.buttonAction(buttonIndex, evt); 307 toggleSaveState(); // necessary since #showDialog() does not handle it due to the non-modal dialog 308 if (getValue() == 1) { 309 final Way wayToKeep = list.getSelectedValue(); 310 final SplitWayResult result = doSplitWay(getEditLayer(), selectedWay, wayToKeep, newWays, selection); 311 Main.main.undoRedo.add(result.getCommand()); 312 getCurrentDataSet().setSelected(result.getNewSelection()); 313 } 314 } 315 } 316 317 /** 318 * Determines which way chunk should reuse the old id and its history 319 * 320 * @since 8954 321 */ 322 public abstract static class Strategy { 323 324 /** 325 * Determines which way chunk should reuse the old id and its history. 326 * 327 * @param wayChunks the way chunks 328 * @return the way to keep 329 */ 330 public abstract Way determineWayToKeep(Iterable<Way> wayChunks); 331 332 /** 333 * Returns a strategy which selects the way chunk with the highest node count to keep. 334 * @return strategy which selects the way chunk with the highest node count to keep 335 */ 336 public static Strategy keepLongestChunk() { 337 return new Strategy() { 338 @Override 339 public Way determineWayToKeep(Iterable<Way> wayChunks) { 340 Way wayToKeep = null; 341 for (Way i : wayChunks) { 342 if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) { 343 wayToKeep = i; 344 } 345 } 346 return wayToKeep; 347 } 348 }; 349 } 350 351 /** 352 * Returns a strategy which selects the first way chunk. 353 * @return strategy which selects the first way chunk 354 */ 355 public static Strategy keepFirstChunk() { 356 return new Strategy() { 357 @Override 358 public Way determineWayToKeep(Iterable<Way> wayChunks) { 359 return wayChunks.iterator().next(); 360 } 361 }; 362 } 363 } 364 365 /** 366 * Determine which ways to split. 367 * @param selectedWays List of user selected ways. 368 * @param selectedNodes List of user selected nodes. 369 * @return List of ways to split 370 */ 371 static List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) { 372 if (selectedNodes.isEmpty()) 373 return null; 374 375 // Special case - one of the selected ways touches (not cross) way that we want to split 376 if (selectedNodes.size() == 1) { 377 Node n = selectedNodes.get(0); 378 List<Way> referedWays = 379 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class); 380 Way inTheMiddle = null; 381 for (Way w: referedWays) { 382 // Need to look at all nodes see #11184 for a case where node n is 383 // firstNode, lastNode and also in the middle 384 if (selectedWays.contains(w) && w.isInnerNode(n)) { 385 if (inTheMiddle == null) { 386 inTheMiddle = w; 387 } else { 388 inTheMiddle = null; 389 break; 390 } 391 } 392 } 393 if (inTheMiddle != null) 394 return Collections.singletonList(inTheMiddle); 395 } 396 397 // List of ways shared by all nodes 398 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes); 399 } 400 401 /** 402 * Splits the nodes of {@code wayToSplit} into a list of node sequences 403 * which are separated at the nodes in {@code splitPoints}. 404 * 405 * This method displays warning messages if {@code wayToSplit} and/or 406 * {@code splitPoints} aren't consistent. 407 * 408 * Returns null, if building the split chunks fails. 409 * 410 * @param wayToSplit the way to split. Must not be null. 411 * @param splitPoints the nodes where the way is split. Must not be null. 412 * @return the list of chunks 413 */ 414 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) { 415 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit"); 416 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints"); 417 418 Set<Node> nodeSet = new HashSet<>(splitPoints); 419 List<List<Node>> wayChunks = new LinkedList<>(); 420 List<Node> currentWayChunk = new ArrayList<>(); 421 wayChunks.add(currentWayChunk); 422 423 Iterator<Node> it = wayToSplit.getNodes().iterator(); 424 while (it.hasNext()) { 425 Node currentNode = it.next(); 426 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext(); 427 currentWayChunk.add(currentNode); 428 if (nodeSet.contains(currentNode) && !atEndOfWay) { 429 currentWayChunk = new ArrayList<>(); 430 currentWayChunk.add(currentNode); 431 wayChunks.add(currentWayChunk); 432 } 433 } 434 435 // Handle circular ways specially. 436 // If you split at a circular way at two nodes, you just want to split 437 // it at these points, not also at the former endpoint. 438 // So if the last node is the same first node, join the last and the 439 // first way chunk. 440 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1); 441 if (wayChunks.size() >= 2 442 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1) 443 && !nodeSet.contains(wayChunks.get(0).get(0))) { 444 if (wayChunks.size() == 2) { 445 new Notification( 446 tr("You must select two or more nodes to split a circular way.")) 447 .setIcon(JOptionPane.WARNING_MESSAGE) 448 .show(); 449 return null; 450 } 451 lastWayChunk.remove(lastWayChunk.size() - 1); 452 lastWayChunk.addAll(wayChunks.get(0)); 453 wayChunks.remove(wayChunks.size() - 1); 454 wayChunks.set(0, lastWayChunk); 455 } 456 457 if (wayChunks.size() < 2) { 458 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) { 459 new Notification( 460 tr("You must select two or more nodes to split a circular way.")) 461 .setIcon(JOptionPane.WARNING_MESSAGE) 462 .show(); 463 } else { 464 new Notification( 465 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)")) 466 .setIcon(JOptionPane.WARNING_MESSAGE) 467 .show(); 468 } 469 return null; 470 } 471 return wayChunks; 472 } 473 474 /** 475 * Creates new way objects for the way chunks and transfers the keys from the original way. 476 * @param way the original way whose keys are transferred 477 * @param wayChunks the way chunks 478 * @return the new way objects 479 */ 480 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) { 481 final List<Way> newWays = new ArrayList<>(); 482 for (List<Node> wayChunk : wayChunks) { 483 Way wayToAdd = new Way(); 484 wayToAdd.setKeys(way.getKeys()); 485 wayToAdd.setNodes(wayChunk); 486 newWays.add(wayToAdd); 487 } 488 return newWays; 489 } 490 491 /** 492 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 493 * the result of this process in an instance of {@link SplitWayResult}. 494 * 495 * Note that changes are not applied to the data yet. You have to 496 * submit the command in {@link SplitWayResult#getCommand()} first, 497 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 498 * 499 * @param layer the layer which the way belongs to. Must not be null. 500 * @param way the way to split. Must not be null. 501 * @param wayChunks the list of way chunks into the way is split. Must not be null. 502 * @param selection The list of currently selected primitives 503 * @return the result from the split operation 504 */ 505 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 506 Collection<? extends OsmPrimitive> selection) { 507 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk()); 508 } 509 510 /** 511 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 512 * the result of this process in an instance of {@link SplitWayResult}. 513 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which 514 * way chunk should reuse the old id and its history. 515 * 516 * Note that changes are not applied to the data yet. You have to 517 * submit the command in {@link SplitWayResult#getCommand()} first, 518 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 519 * 520 * @param layer the layer which the way belongs to. Must not be null. 521 * @param way the way to split. Must not be null. 522 * @param wayChunks the list of way chunks into the way is split. Must not be null. 523 * @param selection The list of currently selected primitives 524 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history 525 * @return the result from the split operation 526 * @since 8954 527 */ 528 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 529 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) { 530 // build a list of commands, and also a new selection list 531 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size()); 532 newSelection.addAll(selection); 533 534 // Create all potential new ways 535 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks); 536 537 // Determine which part reuses the existing way 538 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays); 539 540 return wayToKeep != null ? doSplitWay(layer, way, wayToKeep, newWays, newSelection) : null; 541 } 542 543 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays, 544 List<OsmPrimitive> newSelection) { 545 546 Collection<Command> commandList = new ArrayList<>(newWays.size()); 547 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn", 548 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west")); 549 550 // Change the original way 551 final Way changedWay = new Way(way); 552 changedWay.setNodes(wayToKeep.getNodes()); 553 commandList.add(new ChangeCommand(way, changedWay)); 554 if (!newSelection.contains(way)) { 555 newSelection.add(way); 556 } 557 final int indexOfWayToKeep = newWays.indexOf(wayToKeep); 558 newWays.remove(wayToKeep); 559 560 newSelection.addAll(newWays); 561 for (Way wayToAdd : newWays) { 562 commandList.add(new AddCommand(layer, wayToAdd)); 563 } 564 565 boolean warnmerole = false; 566 boolean warnme = false; 567 // now copy all relations to new way also 568 569 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) { 570 if (!r.isUsable()) { 571 continue; 572 } 573 Relation c = null; 574 String type = r.get("type"); 575 if (type == null) { 576 type = ""; 577 } 578 579 int ic = 0; 580 int ir = 0; 581 List<RelationMember> relationMembers = r.getMembers(); 582 for (RelationMember rm: relationMembers) { 583 if (rm.isWay() && rm.getMember() == way) { 584 boolean insert = true; 585 if ("restriction".equals(type) || "destination_sign".equals(type)) { 586 /* this code assumes the restriction is correct. No real error checking done */ 587 String role = rm.getRole(); 588 if ("from".equals(role) || "to".equals(role)) { 589 OsmPrimitive via = findVia(r, type); 590 List<Node> nodes = new ArrayList<>(); 591 if (via != null) { 592 if (via instanceof Node) { 593 nodes.add((Node) via); 594 } else if (via instanceof Way) { 595 nodes.add(((Way) via).lastNode()); 596 nodes.add(((Way) via).firstNode()); 597 } 598 } 599 Way res = null; 600 for (Node n : nodes) { 601 if (changedWay.isFirstLastNode(n)) { 602 res = way; 603 } 604 } 605 if (res == null) { 606 for (Way wayToAdd : newWays) { 607 for (Node n : nodes) { 608 if (wayToAdd.isFirstLastNode(n)) { 609 res = wayToAdd; 610 } 611 } 612 } 613 if (res != null) { 614 if (c == null) { 615 c = new Relation(r); 616 } 617 c.addMember(new RelationMember(role, res)); 618 c.removeMembersFor(way); 619 insert = false; 620 } 621 } else { 622 insert = false; 623 } 624 } else if (!"via".equals(role)) { 625 warnme = true; 626 } 627 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) { 628 warnme = true; 629 } 630 if (c == null) { 631 c = new Relation(r); 632 } 633 634 if (insert) { 635 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) { 636 warnmerole = true; 637 } 638 639 Boolean backwards = null; 640 int k = 1; 641 while (ir - k >= 0 || ir + k < relationMembers.size()) { 642 if ((ir - k >= 0) && relationMembers.get(ir - k).isWay()) { 643 Way w = relationMembers.get(ir - k).getWay(); 644 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 645 backwards = Boolean.FALSE; 646 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 647 backwards = Boolean.TRUE; 648 } 649 break; 650 } 651 if ((ir + k < relationMembers.size()) && relationMembers.get(ir + k).isWay()) { 652 Way w = relationMembers.get(ir + k).getWay(); 653 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 654 backwards = Boolean.TRUE; 655 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 656 backwards = Boolean.FALSE; 657 } 658 break; 659 } 660 k++; 661 } 662 663 int j = ic; 664 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep); 665 for (Way wayToAdd : waysToAddBefore) { 666 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 667 j++; 668 if (Boolean.TRUE.equals(backwards)) { 669 c.addMember(ic + 1, em); 670 } else { 671 c.addMember(j - 1, em); 672 } 673 } 674 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size()); 675 for (Way wayToAdd : waysToAddAfter) { 676 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 677 j++; 678 if (Boolean.TRUE.equals(backwards)) { 679 c.addMember(ic, em); 680 } else { 681 c.addMember(j, em); 682 } 683 } 684 ic = j; 685 } 686 } 687 ic++; 688 ir++; 689 } 690 691 if (c != null) { 692 commandList.add(new ChangeCommand(layer, r, c)); 693 } 694 } 695 if (warnmerole) { 696 new Notification( 697 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 698 .setIcon(JOptionPane.WARNING_MESSAGE) 699 .show(); 700 } else if (warnme) { 701 new Notification( 702 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 703 .setIcon(JOptionPane.WARNING_MESSAGE) 704 .show(); 705 } 706 707 return new SplitWayResult( 708 new SequenceCommand( 709 /* for correct i18n of plural forms - see #9110 */ 710 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size() + 1, 711 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size() + 1), 712 commandList 713 ), 714 newSelection, 715 way, 716 newWays 717 ); 718 } 719 720 static OsmPrimitive findVia(Relation r, String type) { 721 for (RelationMember rmv : r.getMembers()) { 722 if (("restriction".equals(type) && "via".equals(rmv.getRole())) 723 || ("destination_sign".equals(type) && rmv.hasRole("sign", "intersection"))) { 724 return rmv.getMember(); 725 } 726 } 727 return null; 728 } 729 730 /** 731 * Splits the way {@code way} at the nodes in {@code atNodes} and replies 732 * the result of this process in an instance of {@link SplitWayResult}. 733 * 734 * Note that changes are not applied to the data yet. You have to 735 * submit the command in {@link SplitWayResult#getCommand()} first, 736 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 737 * 738 * Replies null if the way couldn't be split at the given nodes. 739 * 740 * @param layer the layer which the way belongs to. Must not be null. 741 * @param way the way to split. Must not be null. 742 * @param atNodes the list of nodes where the way is split. Must not be null. 743 * @param selection The list of currently selected primitives 744 * @return the result from the split operation 745 */ 746 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) { 747 List<List<Node>> chunks = buildSplitChunks(way, atNodes); 748 return chunks != null ? splitWay(layer, way, chunks, selection) : null; 749 } 750 751 @Override 752 protected void updateEnabledState() { 753 if (getCurrentDataSet() == null) { 754 setEnabled(false); 755 } else { 756 updateEnabledState(getCurrentDataSet().getSelected()); 757 } 758 } 759 760 @Override 761 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 762 if (selection == null) { 763 setEnabled(false); 764 return; 765 } 766 for (OsmPrimitive primitive: selection) { 767 if (primitive instanceof Node) { 768 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong 769 return; 770 } 771 } 772 setEnabled(false); 773 } 774}