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 final SplitWayResult result = doSplitWay(getEditLayer(), selectedWay, wayToKeep, newWays, sel); 216 Main.main.undoRedo.add(result.getCommand()); 217 getCurrentDataSet().setSelected(result.getNewSelection()); 218 } 219 } 220 221 /** 222 * A dialog to query which way segment should reuse the history of the way to split. 223 */ 224 static class SegmentToKeepSelectionDialog extends ExtendedDialog { 225 static final AtomicInteger DISPLAY_COUNT = new AtomicInteger(); 226 final Way selectedWay; 227 final List<Way> newWays; 228 final JList<Way> list; 229 final List<OsmPrimitive> selection; 230 final Way wayToKeep; 231 232 SegmentToKeepSelectionDialog(Way selectedWay, List<Way> newWays, Way wayToKeep, List<OsmPrimitive> selection) { 233 super(Main.parent, tr("Which way segment should reuse the history of {0}?", selectedWay.getId()), 234 new String[]{tr("Ok"), tr("Cancel")}, true); 235 236 this.selectedWay = selectedWay; 237 this.newWays = newWays; 238 this.selection = selection; 239 this.wayToKeep = wayToKeep; 240 this.list = new JList<>(newWays.toArray(new Way[newWays.size()])); 241 configureList(); 242 243 setButtonIcons(new String[]{"ok", "cancel"}); 244 final JPanel pane = new JPanel(new GridBagLayout()); 245 pane.add(new JLabel(getTitle()), GBC.eol().fill(GBC.HORIZONTAL)); 246 pane.add(list, GBC.eop().fill(GBC.HORIZONTAL)); 247 setContent(pane); 248 setDefaultCloseOperation(HIDE_ON_CLOSE); 249 } 250 251 private void configureList() { 252 list.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); 253 list.addListSelectionListener(new ListSelectionListener() { 254 @Override 255 public void valueChanged(ListSelectionEvent e) { 256 final Way selected = list.getSelectedValue(); 257 if (Main.isDisplayingMapView() && selected != null && selected.getNodesCount() > 1) { 258 final Collection<WaySegment> segments = new ArrayList<>(selected.getNodesCount() - 1); 259 final Iterator<Node> it = selected.getNodes().iterator(); 260 Node previousNode = it.next(); 261 while (it.hasNext()) { 262 final Node node = it.next(); 263 segments.add(WaySegment.forNodePair(selectedWay, previousNode, node)); 264 previousNode = node; 265 } 266 setHighlightedWaySegments(segments); 267 } 268 } 269 }); 270 list.setCellRenderer(new DefaultListCellRenderer() { 271 @Override 272 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) { 273 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus); 274 final String name = DefaultNameFormatter.getInstance().format((Way) value); 275 // get rid of id from DefaultNameFormatter.decorateNameWithId() 276 final String nameWithoutId = name 277 .replace(tr(" [id: {0}]", ((Way) value).getId()), "") 278 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), ""); 279 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId)); 280 return c; 281 } 282 }); 283 } 284 285 protected void setHighlightedWaySegments(Collection<WaySegment> segments) { 286 selectedWay.getDataSet().setHighlightedWaySegments(segments); 287 Main.map.mapView.repaint(); 288 } 289 290 @Override 291 public void setVisible(boolean visible) { 292 super.setVisible(visible); 293 if (visible) { 294 DISPLAY_COUNT.incrementAndGet(); 295 list.setSelectedValue(wayToKeep, true); 296 } else { 297 setHighlightedWaySegments(Collections.<WaySegment>emptyList()); 298 DISPLAY_COUNT.decrementAndGet(); 299 } 300 } 301 302 @Override 303 protected void buttonAction(int buttonIndex, ActionEvent evt) { 304 super.buttonAction(buttonIndex, evt); 305 toggleSaveState(); // necessary since #showDialog() does not handle it due to the non-modal dialog 306 if (getValue() == 1) { 307 final Way wayToKeep = list.getSelectedValue(); 308 final SplitWayResult result = doSplitWay(getEditLayer(), selectedWay, wayToKeep, newWays, selection); 309 Main.main.undoRedo.add(result.getCommand()); 310 getCurrentDataSet().setSelected(result.getNewSelection()); 311 } 312 } 313 } 314 315 /** 316 * Determines which way chunk should reuse the old id and its history 317 * 318 * @since 8954 319 */ 320 public abstract static class Strategy { 321 322 /** 323 * Determines which way chunk should reuse the old id and its history. 324 * 325 * @param wayChunks the way chunks 326 * @return the way to keep 327 */ 328 public abstract Way determineWayToKeep(Iterable<Way> wayChunks); 329 330 /** 331 * Returns a strategy which selects the way chunk with the highest node count to keep. 332 * @return strategy which selects the way chunk with the highest node count to keep 333 */ 334 public static Strategy keepLongestChunk() { 335 return new Strategy() { 336 @Override 337 public Way determineWayToKeep(Iterable<Way> wayChunks) { 338 Way wayToKeep = null; 339 for (Way i : wayChunks) { 340 if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) { 341 wayToKeep = i; 342 } 343 } 344 return wayToKeep; 345 } 346 }; 347 } 348 349 /** 350 * Returns a strategy which selects the first way chunk. 351 * @return strategy which selects the first way chunk 352 */ 353 public static Strategy keepFirstChunk() { 354 return new Strategy() { 355 @Override 356 public Way determineWayToKeep(Iterable<Way> wayChunks) { 357 return wayChunks.iterator().next(); 358 } 359 }; 360 } 361 } 362 363 /** 364 * Determine which ways to split. 365 * @param selectedWays List of user selected ways. 366 * @param selectedNodes List of user selected nodes. 367 * @return List of ways to split 368 */ 369 private List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) { 370 if (selectedNodes.isEmpty()) 371 return null; 372 373 // Special case - one of the selected ways touches (not cross) way that we 374 // want to split 375 if (selectedNodes.size() == 1) { 376 Node n = selectedNodes.get(0); 377 List<Way> referedWays = 378 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class); 379 Way inTheMiddle = null; 380 for (Way w: referedWays) { 381 // Need to look at all nodes see #11184 for a case where node n is 382 // firstNode, lastNode and also in the middle 383 if (selectedWays.contains(w) && w.isInnerNode(n)) { 384 if (inTheMiddle == null) { 385 inTheMiddle = w; 386 } else { 387 inTheMiddle = null; 388 break; 389 } 390 } 391 } 392 if (inTheMiddle != null) 393 return Collections.singletonList(inTheMiddle); 394 } 395 396 // List of ways shared by all nodes 397 List<Way> result = 398 new ArrayList<>(OsmPrimitive.getFilteredList(selectedNodes.get(0).getReferrers(), 399 Way.class)); 400 for (int i = 1; i < selectedNodes.size(); i++) { 401 List<OsmPrimitive> ref = selectedNodes.get(i).getReferrers(); 402 for (Iterator<Way> it = result.iterator(); it.hasNext();) { 403 if (!ref.contains(it.next())) { 404 it.remove(); 405 } 406 } 407 } 408 409 // Remove broken ways 410 for (Iterator<Way> it = result.iterator(); it.hasNext();) { 411 if (it.next().getNodesCount() <= 2) { 412 it.remove(); 413 } 414 } 415 416 if (selectedWays.isEmpty()) 417 return result; 418 else { 419 // Return only selected ways 420 for (Iterator<Way> it = result.iterator(); it.hasNext();) { 421 if (!selectedWays.contains(it.next())) { 422 it.remove(); 423 } 424 } 425 return result; 426 } 427 } 428 429 /** 430 * Splits the nodes of {@code wayToSplit} into a list of node sequences 431 * which are separated at the nodes in {@code splitPoints}. 432 * 433 * This method displays warning messages if {@code wayToSplit} and/or 434 * {@code splitPoints} aren't consistent. 435 * 436 * Returns null, if building the split chunks fails. 437 * 438 * @param wayToSplit the way to split. Must not be null. 439 * @param splitPoints the nodes where the way is split. Must not be null. 440 * @return the list of chunks 441 */ 442 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) { 443 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit"); 444 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints"); 445 446 Set<Node> nodeSet = new HashSet<>(splitPoints); 447 List<List<Node>> wayChunks = new LinkedList<>(); 448 List<Node> currentWayChunk = new ArrayList<>(); 449 wayChunks.add(currentWayChunk); 450 451 Iterator<Node> it = wayToSplit.getNodes().iterator(); 452 while (it.hasNext()) { 453 Node currentNode = it.next(); 454 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext(); 455 currentWayChunk.add(currentNode); 456 if (nodeSet.contains(currentNode) && !atEndOfWay) { 457 currentWayChunk = new ArrayList<>(); 458 currentWayChunk.add(currentNode); 459 wayChunks.add(currentWayChunk); 460 } 461 } 462 463 // Handle circular ways specially. 464 // If you split at a circular way at two nodes, you just want to split 465 // it at these points, not also at the former endpoint. 466 // So if the last node is the same first node, join the last and the 467 // first way chunk. 468 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1); 469 if (wayChunks.size() >= 2 470 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1) 471 && !nodeSet.contains(wayChunks.get(0).get(0))) { 472 if (wayChunks.size() == 2) { 473 new Notification( 474 tr("You must select two or more nodes to split a circular way.")) 475 .setIcon(JOptionPane.WARNING_MESSAGE) 476 .show(); 477 return null; 478 } 479 lastWayChunk.remove(lastWayChunk.size() - 1); 480 lastWayChunk.addAll(wayChunks.get(0)); 481 wayChunks.remove(wayChunks.size() - 1); 482 wayChunks.set(0, lastWayChunk); 483 } 484 485 if (wayChunks.size() < 2) { 486 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) { 487 new Notification( 488 tr("You must select two or more nodes to split a circular way.")) 489 .setIcon(JOptionPane.WARNING_MESSAGE) 490 .show(); 491 } else { 492 new Notification( 493 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)")) 494 .setIcon(JOptionPane.WARNING_MESSAGE) 495 .show(); 496 } 497 return null; 498 } 499 return wayChunks; 500 } 501 502 /** 503 * Creates new way objects for the way chunks and transfers the keys from the original way. 504 * @param way the original way whose keys are transferred 505 * @param wayChunks the way chunks 506 * @return the new way objects 507 */ 508 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) { 509 final List<Way> newWays = new ArrayList<>(); 510 for (List<Node> wayChunk : wayChunks) { 511 Way wayToAdd = new Way(); 512 wayToAdd.setKeys(way.getKeys()); 513 wayToAdd.setNodes(wayChunk); 514 newWays.add(wayToAdd); 515 } 516 return newWays; 517 } 518 519 /** 520 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 521 * the result of this process in an instance of {@link SplitWayResult}. 522 * 523 * Note that changes are not applied to the data yet. You have to 524 * submit the command in {@link SplitWayResult#getCommand()} first, 525 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 526 * 527 * @param layer the layer which the way belongs to. Must not be null. 528 * @param way the way to split. Must not be null. 529 * @param wayChunks the list of way chunks into the way is split. Must not be null. 530 * @param selection The list of currently selected primitives 531 * @return the result from the split operation 532 */ 533 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 534 Collection<? extends OsmPrimitive> selection) { 535 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk()); 536 } 537 538 /** 539 * Splits the way {@code way} into chunks of {@code wayChunks} and replies 540 * the result of this process in an instance of {@link SplitWayResult}. 541 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which 542 * way chunk should reuse the old id and its history. 543 * 544 * Note that changes are not applied to the data yet. You have to 545 * submit the command in {@link SplitWayResult#getCommand()} first, 546 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 547 * 548 * @param layer the layer which the way belongs to. Must not be null. 549 * @param way the way to split. Must not be null. 550 * @param wayChunks the list of way chunks into the way is split. Must not be null. 551 * @param selection The list of currently selected primitives 552 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history 553 * @return the result from the split operation 554 * @since 8954 555 */ 556 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, 557 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) { 558 // build a list of commands, and also a new selection list 559 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size()); 560 newSelection.addAll(selection); 561 562 // Create all potential new ways 563 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks); 564 565 // Determine which part reuses the existing way 566 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays); 567 568 return doSplitWay(layer, way, wayToKeep, newWays, newSelection); 569 } 570 571 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays, 572 List<OsmPrimitive> newSelection) { 573 574 Collection<Command> commandList = new ArrayList<>(newWays.size()); 575 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn", 576 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west")); 577 578 // Change the original way 579 final Way changedWay = new Way(way); 580 changedWay.setNodes(wayToKeep.getNodes()); 581 commandList.add(new ChangeCommand(way, changedWay)); 582 if (!newSelection.contains(way)) { 583 newSelection.add(way); 584 } 585 final int indexOfWayToKeep = newWays.indexOf(wayToKeep); 586 newWays.remove(wayToKeep); 587 588 for (Way wayToAdd : newWays) { 589 commandList.add(new AddCommand(layer, wayToAdd)); 590 newSelection.add(wayToAdd); 591 } 592 593 boolean warnmerole = false; 594 boolean warnme = false; 595 // now copy all relations to new way also 596 597 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) { 598 if (!r.isUsable()) { 599 continue; 600 } 601 Relation c = null; 602 String type = r.get("type"); 603 if (type == null) { 604 type = ""; 605 } 606 607 int i_c = 0, i_r = 0; 608 List<RelationMember> relationMembers = r.getMembers(); 609 for (RelationMember rm: relationMembers) { 610 if (rm.isWay() && rm.getMember() == way) { 611 boolean insert = true; 612 if ("restriction".equals(type)) { 613 /* this code assumes the restriction is correct. No real error checking done */ 614 String role = rm.getRole(); 615 if ("from".equals(role) || "to".equals(role)) { 616 OsmPrimitive via = null; 617 for (RelationMember rmv : r.getMembers()) { 618 if ("via".equals(rmv.getRole())) { 619 via = rmv.getMember(); 620 } 621 } 622 List<Node> nodes = new ArrayList<>(); 623 if (via != null) { 624 if (via instanceof Node) { 625 nodes.add((Node) via); 626 } else if (via instanceof Way) { 627 nodes.add(((Way) via).lastNode()); 628 nodes.add(((Way) via).firstNode()); 629 } 630 } 631 Way res = null; 632 for (Node n : nodes) { 633 if (changedWay.isFirstLastNode(n)) { 634 res = way; 635 } 636 } 637 if (res == null) { 638 for (Way wayToAdd : newWays) { 639 for (Node n : nodes) { 640 if (wayToAdd.isFirstLastNode(n)) { 641 res = wayToAdd; 642 } 643 } 644 } 645 if (res != null) { 646 if (c == null) { 647 c = new Relation(r); 648 } 649 c.addMember(new RelationMember(role, res)); 650 c.removeMembersFor(way); 651 insert = false; 652 } 653 } else { 654 insert = false; 655 } 656 } else if (!"via".equals(role)) { 657 warnme = true; 658 } 659 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) { 660 warnme = true; 661 } 662 if (c == null) { 663 c = new Relation(r); 664 } 665 666 if (insert) { 667 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) { 668 warnmerole = true; 669 } 670 671 Boolean backwards = null; 672 int k = 1; 673 while (i_r - k >= 0 || i_r + k < relationMembers.size()) { 674 if ((i_r - k >= 0) && relationMembers.get(i_r - k).isWay()) { 675 Way w = relationMembers.get(i_r - k).getWay(); 676 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 677 backwards = Boolean.FALSE; 678 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 679 backwards = Boolean.TRUE; 680 } 681 break; 682 } 683 if ((i_r + k < relationMembers.size()) && relationMembers.get(i_r + k).isWay()) { 684 Way w = relationMembers.get(i_r + k).getWay(); 685 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 686 backwards = Boolean.TRUE; 687 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 688 backwards = Boolean.FALSE; 689 } 690 break; 691 } 692 k++; 693 } 694 695 int j = i_c; 696 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep); 697 for (Way wayToAdd : waysToAddBefore) { 698 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 699 j++; 700 if (Boolean.TRUE.equals(backwards)) { 701 c.addMember(i_c + 1, em); 702 } else { 703 c.addMember(j - 1, em); 704 } 705 } 706 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size()); 707 for (Way wayToAdd : waysToAddAfter) { 708 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 709 j++; 710 if (Boolean.TRUE.equals(backwards)) { 711 c.addMember(i_c, em); 712 } else { 713 c.addMember(j, em); 714 } 715 } 716 i_c = j; 717 } 718 } 719 i_c++; 720 i_r++; 721 } 722 723 if (c != null) { 724 commandList.add(new ChangeCommand(layer, r, c)); 725 } 726 } 727 if (warnmerole) { 728 new Notification( 729 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 730 .setIcon(JOptionPane.WARNING_MESSAGE) 731 .show(); 732 } else if (warnme) { 733 new Notification( 734 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 735 .setIcon(JOptionPane.WARNING_MESSAGE) 736 .show(); 737 } 738 739 return new SplitWayResult( 740 new SequenceCommand( 741 /* for correct i18n of plural forms - see #9110 */ 742 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size(), 743 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size()), 744 commandList 745 ), 746 newSelection, 747 way, 748 newWays 749 ); 750 } 751 752 /** 753 * Splits the way {@code way} at the nodes in {@code atNodes} and replies 754 * the result of this process in an instance of {@link SplitWayResult}. 755 * 756 * Note that changes are not applied to the data yet. You have to 757 * submit the command in {@link SplitWayResult#getCommand()} first, 758 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 759 * 760 * Replies null if the way couldn't be split at the given nodes. 761 * 762 * @param layer the layer which the way belongs to. Must not be null. 763 * @param way the way to split. Must not be null. 764 * @param atNodes the list of nodes where the way is split. Must not be null. 765 * @param selection The list of currently selected primitives 766 * @return the result from the split operation 767 */ 768 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) { 769 List<List<Node>> chunks = buildSplitChunks(way, atNodes); 770 if (chunks == null) return null; 771 return splitWay(layer, way, chunks, selection); 772 } 773 774 @Override 775 protected void updateEnabledState() { 776 if (getCurrentDataSet() == null) { 777 setEnabled(false); 778 } else { 779 updateEnabledState(getCurrentDataSet().getSelected()); 780 } 781 } 782 783 @Override 784 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 785 if (selection == null) { 786 setEnabled(false); 787 return; 788 } 789 for (OsmPrimitive primitive: selection) { 790 if (primitive instanceof Node) { 791 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong 792 return; 793 } 794 } 795 setEnabled(false); 796 } 797}