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) || "destination_sign".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 = findVia(r, type); 617 List<Node> nodes = new ArrayList<>(); 618 if (via != null) { 619 if (via instanceof Node) { 620 nodes.add((Node) via); 621 } else if (via instanceof Way) { 622 nodes.add(((Way) via).lastNode()); 623 nodes.add(((Way) via).firstNode()); 624 } 625 } 626 Way res = null; 627 for (Node n : nodes) { 628 if (changedWay.isFirstLastNode(n)) { 629 res = way; 630 } 631 } 632 if (res == null) { 633 for (Way wayToAdd : newWays) { 634 for (Node n : nodes) { 635 if (wayToAdd.isFirstLastNode(n)) { 636 res = wayToAdd; 637 } 638 } 639 } 640 if (res != null) { 641 if (c == null) { 642 c = new Relation(r); 643 } 644 c.addMember(new RelationMember(role, res)); 645 c.removeMembersFor(way); 646 insert = false; 647 } 648 } else { 649 insert = false; 650 } 651 } else if (!"via".equals(role)) { 652 warnme = true; 653 } 654 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) { 655 warnme = true; 656 } 657 if (c == null) { 658 c = new Relation(r); 659 } 660 661 if (insert) { 662 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) { 663 warnmerole = true; 664 } 665 666 Boolean backwards = null; 667 int k = 1; 668 while (i_r - k >= 0 || i_r + k < relationMembers.size()) { 669 if ((i_r - k >= 0) && relationMembers.get(i_r - k).isWay()) { 670 Way w = relationMembers.get(i_r - k).getWay(); 671 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 672 backwards = Boolean.FALSE; 673 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 674 backwards = Boolean.TRUE; 675 } 676 break; 677 } 678 if ((i_r + k < relationMembers.size()) && relationMembers.get(i_r + k).isWay()) { 679 Way w = relationMembers.get(i_r + k).getWay(); 680 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) { 681 backwards = Boolean.TRUE; 682 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) { 683 backwards = Boolean.FALSE; 684 } 685 break; 686 } 687 k++; 688 } 689 690 int j = i_c; 691 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep); 692 for (Way wayToAdd : waysToAddBefore) { 693 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 694 j++; 695 if (Boolean.TRUE.equals(backwards)) { 696 c.addMember(i_c + 1, em); 697 } else { 698 c.addMember(j - 1, em); 699 } 700 } 701 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size()); 702 for (Way wayToAdd : waysToAddAfter) { 703 RelationMember em = new RelationMember(rm.getRole(), wayToAdd); 704 j++; 705 if (Boolean.TRUE.equals(backwards)) { 706 c.addMember(i_c, em); 707 } else { 708 c.addMember(j, em); 709 } 710 } 711 i_c = j; 712 } 713 } 714 i_c++; 715 i_r++; 716 } 717 718 if (c != null) { 719 commandList.add(new ChangeCommand(layer, r, c)); 720 } 721 } 722 if (warnmerole) { 723 new Notification( 724 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.")) 725 .setIcon(JOptionPane.WARNING_MESSAGE) 726 .show(); 727 } else if (warnme) { 728 new Notification( 729 tr("A 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 } 733 734 return new SplitWayResult( 735 new SequenceCommand( 736 /* for correct i18n of plural forms - see #9110 */ 737 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size() + 1, 738 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size() + 1), 739 commandList 740 ), 741 newSelection, 742 way, 743 newWays 744 ); 745 } 746 747 static OsmPrimitive findVia(Relation r, String type) { 748 for (RelationMember rmv : r.getMembers()) { 749 if (("restriction".equals(type) && "via".equals(rmv.getRole())) 750 || ("destination_sign".equals(type) && rmv.hasRole("sign", "intersection"))) { 751 return rmv.getMember(); 752 } 753 } 754 return null; 755 } 756 757 /** 758 * Splits the way {@code way} at the nodes in {@code atNodes} and replies 759 * the result of this process in an instance of {@link SplitWayResult}. 760 * 761 * Note that changes are not applied to the data yet. You have to 762 * submit the command in {@link SplitWayResult#getCommand()} first, 763 * i.e. {@code Main.main.undoredo.add(result.getCommand())}. 764 * 765 * Replies null if the way couldn't be split at the given nodes. 766 * 767 * @param layer the layer which the way belongs to. Must not be null. 768 * @param way the way to split. Must not be null. 769 * @param atNodes the list of nodes where the way is split. Must not be null. 770 * @param selection The list of currently selected primitives 771 * @return the result from the split operation 772 */ 773 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) { 774 List<List<Node>> chunks = buildSplitChunks(way, atNodes); 775 if (chunks == null) return null; 776 return splitWay(layer, way, chunks, selection); 777 } 778 779 @Override 780 protected void updateEnabledState() { 781 if (getCurrentDataSet() == null) { 782 setEnabled(false); 783 } else { 784 updateEnabledState(getCurrentDataSet().getSelected()); 785 } 786 } 787 788 @Override 789 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) { 790 if (selection == null) { 791 setEnabled(false); 792 return; 793 } 794 for (OsmPrimitive primitive: selection) { 795 if (primitive instanceof Node) { 796 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong 797 return; 798 } 799 } 800 setEnabled(false); 801 } 802}