source: josm/trunk/src/org/openstreetmap/josm/actions/SplitWayAction.java@ 12785

Last change on this file since 12785 was 12726, checked in by Don-vip, 7 years ago

see #13036 - deprecate Command() default constructor, fix unit tests and java warnings

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