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