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

Last change on this file since 9783 was 9647, checked in by simon04, 8 years ago

fix #12347 - Split way breaks destination_sign relations (with intersection role)

  • Property svn:eol-style set to native
File size: 34.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.Set;
21import java.util.concurrent.atomic.AtomicInteger;
22
23import javax.swing.DefaultListCellRenderer;
24import javax.swing.JLabel;
25import javax.swing.JList;
26import javax.swing.JOptionPane;
27import javax.swing.JPanel;
28import javax.swing.ListSelectionModel;
29import javax.swing.event.ListSelectionEvent;
30import javax.swing.event.ListSelectionListener;
31
32import org.openstreetmap.josm.Main;
33import org.openstreetmap.josm.command.AddCommand;
34import org.openstreetmap.josm.command.ChangeCommand;
35import org.openstreetmap.josm.command.Command;
36import org.openstreetmap.josm.command.SequenceCommand;
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.DefaultNameFormatter;
45import org.openstreetmap.josm.gui.ExtendedDialog;
46import org.openstreetmap.josm.gui.Notification;
47import org.openstreetmap.josm.gui.layer.OsmDataLayer;
48import org.openstreetmap.josm.tools.CheckParameterUtil;
49import org.openstreetmap.josm.tools.GBC;
50import org.openstreetmap.josm.tools.Shortcut;
51
52/**
53 * Splits a way into multiple ways (all identical except for their node list).
54 *
55 * Ways are just split at the selected nodes. The nodes remain in their
56 * original order. Selected nodes at the end of a way are ignored.
57 */
58public class SplitWayAction extends JosmAction {
59
60 /**
61 * Represents the result of a {@link SplitWayAction}
62 * @see SplitWayAction#splitWay
63 * @see SplitWayAction#split
64 */
65 public static class SplitWayResult {
66 private final Command command;
67 private final List<? extends PrimitiveId> newSelection;
68 private final Way originalWay;
69 private final List<Way> newWays;
70
71 /**
72 * @param command The command to be performed to split the way (which is saved for later retrieval with {@link #getCommand})
73 * @param newSelection The new list of selected primitives ids (which is saved for later retrieval with {@link #getNewSelection})
74 * @param originalWay The original way being split (which is saved for later retrieval with {@link #getOriginalWay})
75 * @param newWays The resulting new ways (which is saved for later retrieval with {@link #getOriginalWay})
76 */
77 public SplitWayResult(Command command, List<? extends PrimitiveId> newSelection, Way originalWay, List<Way> newWays) {
78 this.command = command;
79 this.newSelection = newSelection;
80 this.originalWay = originalWay;
81 this.newWays = newWays;
82 }
83
84 /**
85 * Replies the command to be performed to split the way
86 * @return The command to be performed to split the way
87 */
88 public Command getCommand() {
89 return command;
90 }
91
92 /**
93 * Replies the new list of selected primitives ids
94 * @return The new list of selected primitives ids
95 */
96 public List<? extends PrimitiveId> getNewSelection() {
97 return newSelection;
98 }
99
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 = null;
617 for (RelationMember rmv : r.getMembers()) {
618 if ("restriction".equals(type) && "via".equals(rmv.getRole())
619 || "destination_sign".equals(type) && rmv.hasRole("sign", "intersection")) {
620 via = rmv.getMember();
621 }
622 }
623 List<Node> nodes = new ArrayList<>();
624 if (via != null) {
625 if (via instanceof Node) {
626 nodes.add((Node) via);
627 } else if (via instanceof Way) {
628 nodes.add(((Way) via).lastNode());
629 nodes.add(((Way) via).firstNode());
630 }
631 }
632 Way res = null;
633 for (Node n : nodes) {
634 if (changedWay.isFirstLastNode(n)) {
635 res = way;
636 }
637 }
638 if (res == null) {
639 for (Way wayToAdd : newWays) {
640 for (Node n : nodes) {
641 if (wayToAdd.isFirstLastNode(n)) {
642 res = wayToAdd;
643 }
644 }
645 }
646 if (res != null) {
647 if (c == null) {
648 c = new Relation(r);
649 }
650 c.addMember(new RelationMember(role, res));
651 c.removeMembersFor(way);
652 insert = false;
653 }
654 } else {
655 insert = false;
656 }
657 } else if (!"via".equals(role)) {
658 warnme = true;
659 }
660 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) {
661 warnme = true;
662 }
663 if (c == null) {
664 c = new Relation(r);
665 }
666
667 if (insert) {
668 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
669 warnmerole = true;
670 }
671
672 Boolean backwards = null;
673 int k = 1;
674 while (i_r - k >= 0 || i_r + k < relationMembers.size()) {
675 if ((i_r - k >= 0) && relationMembers.get(i_r - k).isWay()) {
676 Way w = relationMembers.get(i_r - k).getWay();
677 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
678 backwards = Boolean.FALSE;
679 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
680 backwards = Boolean.TRUE;
681 }
682 break;
683 }
684 if ((i_r + k < relationMembers.size()) && relationMembers.get(i_r + k).isWay()) {
685 Way w = relationMembers.get(i_r + k).getWay();
686 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
687 backwards = Boolean.TRUE;
688 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
689 backwards = Boolean.FALSE;
690 }
691 break;
692 }
693 k++;
694 }
695
696 int j = i_c;
697 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep);
698 for (Way wayToAdd : waysToAddBefore) {
699 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
700 j++;
701 if (Boolean.TRUE.equals(backwards)) {
702 c.addMember(i_c + 1, em);
703 } else {
704 c.addMember(j - 1, em);
705 }
706 }
707 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size());
708 for (Way wayToAdd : waysToAddAfter) {
709 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
710 j++;
711 if (Boolean.TRUE.equals(backwards)) {
712 c.addMember(i_c, em);
713 } else {
714 c.addMember(j, em);
715 }
716 }
717 i_c = j;
718 }
719 }
720 i_c++;
721 i_r++;
722 }
723
724 if (c != null) {
725 commandList.add(new ChangeCommand(layer, r, c));
726 }
727 }
728 if (warnmerole) {
729 new Notification(
730 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
731 .setIcon(JOptionPane.WARNING_MESSAGE)
732 .show();
733 } else if (warnme) {
734 new Notification(
735 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
736 .setIcon(JOptionPane.WARNING_MESSAGE)
737 .show();
738 }
739
740 return new SplitWayResult(
741 new SequenceCommand(
742 /* for correct i18n of plural forms - see #9110 */
743 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size(),
744 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size()),
745 commandList
746 ),
747 newSelection,
748 way,
749 newWays
750 );
751 }
752
753 /**
754 * Splits the way {@code way} at the nodes in {@code atNodes} and replies
755 * the result of this process in an instance of {@link SplitWayResult}.
756 *
757 * Note that changes are not applied to the data yet. You have to
758 * submit the command in {@link SplitWayResult#getCommand()} first,
759 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
760 *
761 * Replies null if the way couldn't be split at the given nodes.
762 *
763 * @param layer the layer which the way belongs to. Must not be null.
764 * @param way the way to split. Must not be null.
765 * @param atNodes the list of nodes where the way is split. Must not be null.
766 * @param selection The list of currently selected primitives
767 * @return the result from the split operation
768 */
769 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) {
770 List<List<Node>> chunks = buildSplitChunks(way, atNodes);
771 if (chunks == null) return null;
772 return splitWay(layer, way, chunks, selection);
773 }
774
775 @Override
776 protected void updateEnabledState() {
777 if (getCurrentDataSet() == null) {
778 setEnabled(false);
779 } else {
780 updateEnabledState(getCurrentDataSet().getSelected());
781 }
782 }
783
784 @Override
785 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
786 if (selection == null) {
787 setEnabled(false);
788 return;
789 }
790 for (OsmPrimitive primitive: selection) {
791 if (primitive instanceof Node) {
792 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
793 return;
794 }
795 }
796 setEnabled(false);
797 }
798}
Note: See TracBrowser for help on using the repository browser.