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