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

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

add Node.getParentWays()

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