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

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

see #15182 - deprecate Main.getLayerManager(). Replacement: gui.MainApplication.getLayerManager()

  • Property svn:eol-style set to native
File size: 33.1 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.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(getLayerManager().getEditLayer(), selectedWay, wayToKeep, newWays, sel);
217 Main.main.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(MainApplication.getLayerManager().getEditLayer(),
297 selectedWay, list.getSelectedValue(), newWays, selection);
298 Main.main.undoRedo.add(result.getCommand());
299 if (!result.getNewSelection().isEmpty()) {
300 MainApplication.getLayerManager().getEditDataSet().setSelected(result.getNewSelection());
301 }
302 }
303 }
304 }
305
306 static class SegmentListCellRenderer extends DefaultListCellRenderer {
307 @Override
308 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
309 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
310 final String name = DefaultNameFormatter.getInstance().format((Way) value);
311 // get rid of id from DefaultNameFormatter.decorateNameWithId()
312 final String nameWithoutId = name
313 .replace(tr(" [id: {0}]", ((Way) value).getId()), "")
314 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), "");
315 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId));
316 return c;
317 }
318 }
319
320 /**
321 * Determines which way chunk should reuse the old id and its history
322 *
323 * @since 8954
324 * @since 10599 (functional interface)
325 */
326 @FunctionalInterface
327 public interface Strategy {
328
329 /**
330 * Determines which way chunk should reuse the old id and its history.
331 *
332 * @param wayChunks the way chunks
333 * @return the way to keep
334 */
335 Way determineWayToKeep(Iterable<Way> wayChunks);
336
337 /**
338 * Returns a strategy which selects the way chunk with the highest node count to keep.
339 * @return strategy which selects the way chunk with the highest node count to keep
340 */
341 static Strategy keepLongestChunk() {
342 return wayChunks -> {
343 Way wayToKeep = null;
344 for (Way i : wayChunks) {
345 if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) {
346 wayToKeep = i;
347 }
348 }
349 return wayToKeep;
350 };
351 }
352
353 /**
354 * Returns a strategy which selects the first way chunk.
355 * @return strategy which selects the first way chunk
356 */
357 static Strategy keepFirstChunk() {
358 return wayChunks -> wayChunks.iterator().next();
359 }
360 }
361
362 /**
363 * Determine which ways to split.
364 * @param selectedWays List of user selected ways.
365 * @param selectedNodes List of user selected nodes.
366 * @return List of ways to split
367 */
368 static List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
369 if (selectedNodes.isEmpty())
370 return null;
371
372 // Special case - one of the selected ways touches (not cross) way that we want to split
373 if (selectedNodes.size() == 1) {
374 Node n = selectedNodes.get(0);
375 List<Way> referredWays = n.getParentWays();
376 Way inTheMiddle = null;
377 for (Way w: referredWays) {
378 // Need to look at all nodes see #11184 for a case where node n is
379 // firstNode, lastNode and also in the middle
380 if (selectedWays.contains(w) && w.isInnerNode(n)) {
381 if (inTheMiddle == null) {
382 inTheMiddle = w;
383 } else {
384 inTheMiddle = null;
385 break;
386 }
387 }
388 }
389 if (inTheMiddle != null)
390 return Collections.singletonList(inTheMiddle);
391 }
392
393 // List of ways shared by all nodes
394 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes);
395 }
396
397 /**
398 * Splits the nodes of {@code wayToSplit} into a list of node sequences
399 * which are separated at the nodes in {@code splitPoints}.
400 *
401 * This method displays warning messages if {@code wayToSplit} and/or
402 * {@code splitPoints} aren't consistent.
403 *
404 * Returns null, if building the split chunks fails.
405 *
406 * @param wayToSplit the way to split. Must not be null.
407 * @param splitPoints the nodes where the way is split. Must not be null.
408 * @return the list of chunks
409 */
410 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) {
411 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit");
412 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints");
413
414 Set<Node> nodeSet = new HashSet<>(splitPoints);
415 List<List<Node>> wayChunks = new LinkedList<>();
416 List<Node> currentWayChunk = new ArrayList<>();
417 wayChunks.add(currentWayChunk);
418
419 Iterator<Node> it = wayToSplit.getNodes().iterator();
420 while (it.hasNext()) {
421 Node currentNode = it.next();
422 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
423 currentWayChunk.add(currentNode);
424 if (nodeSet.contains(currentNode) && !atEndOfWay) {
425 currentWayChunk = new ArrayList<>();
426 currentWayChunk.add(currentNode);
427 wayChunks.add(currentWayChunk);
428 }
429 }
430
431 // Handle circular ways specially.
432 // If you split at a circular way at two nodes, you just want to split
433 // it at these points, not also at the former endpoint.
434 // So if the last node is the same first node, join the last and the
435 // first way chunk.
436 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
437 if (wayChunks.size() >= 2
438 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
439 && !nodeSet.contains(wayChunks.get(0).get(0))) {
440 if (wayChunks.size() == 2) {
441 new Notification(
442 tr("You must select two or more nodes to split a circular way."))
443 .setIcon(JOptionPane.WARNING_MESSAGE)
444 .show();
445 return null;
446 }
447 lastWayChunk.remove(lastWayChunk.size() - 1);
448 lastWayChunk.addAll(wayChunks.get(0));
449 wayChunks.remove(wayChunks.size() - 1);
450 wayChunks.set(0, lastWayChunk);
451 }
452
453 if (wayChunks.size() < 2) {
454 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
455 new Notification(
456 tr("You must select two or more nodes to split a circular way."))
457 .setIcon(JOptionPane.WARNING_MESSAGE)
458 .show();
459 } else {
460 new Notification(
461 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"))
462 .setIcon(JOptionPane.WARNING_MESSAGE)
463 .show();
464 }
465 return null;
466 }
467 return wayChunks;
468 }
469
470 /**
471 * Creates new way objects for the way chunks and transfers the keys from the original way.
472 * @param way the original way whose keys are transferred
473 * @param wayChunks the way chunks
474 * @return the new way objects
475 */
476 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) {
477 final List<Way> newWays = new ArrayList<>();
478 for (List<Node> wayChunk : wayChunks) {
479 Way wayToAdd = new Way();
480 wayToAdd.setKeys(way.getKeys());
481 wayToAdd.setNodes(wayChunk);
482 newWays.add(wayToAdd);
483 }
484 return newWays;
485 }
486
487 /**
488 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
489 * the result of this process in an instance of {@link SplitWayResult}.
490 *
491 * Note that changes are not applied to the data yet. You have to
492 * submit the command in {@link SplitWayResult#getCommand()} first,
493 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
494 *
495 * @param layer the layer which the way belongs to.
496 * @param way the way to split. Must not be null.
497 * @param wayChunks the list of way chunks into the way is split. Must not be null.
498 * @param selection The list of currently selected primitives
499 * @return the result from the split operation
500 */
501 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
502 Collection<? extends OsmPrimitive> selection) {
503 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk());
504 }
505
506 /**
507 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
508 * the result of this process in an instance of {@link SplitWayResult}.
509 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which
510 * way chunk should reuse the old id and its history.
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.
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 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history
521 * @return the result from the split operation
522 * @since 8954
523 */
524 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
525 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) {
526 // build a list of commands, and also a new selection list
527 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size());
528 newSelection.addAll(selection);
529
530 // Create all potential new ways
531 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks);
532
533 // Determine which part reuses the existing way
534 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays);
535
536 return wayToKeep != null ? doSplitWay(layer, way, wayToKeep, newWays, newSelection) : null;
537 }
538
539 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays,
540 List<OsmPrimitive> newSelection) {
541
542 Collection<Command> commandList = new ArrayList<>(newWays.size());
543 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn",
544 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west"));
545
546 final MapFrame map = MainApplication.getMap();
547 final boolean isMapModeDraw = map != null && map.mapMode == map.mapModeDraw;
548
549 // Change the original way
550 final Way changedWay = new Way(way);
551 changedWay.setNodes(wayToKeep.getNodes());
552 commandList.add(layer != null ? new ChangeCommand(layer, way, changedWay) : new ChangeCommand(way.getDataSet(), way, changedWay));
553 if (!isMapModeDraw && !newSelection.contains(way)) {
554 newSelection.add(way);
555 }
556 final int indexOfWayToKeep = newWays.indexOf(wayToKeep);
557 newWays.remove(wayToKeep);
558
559 if (!isMapModeDraw) {
560 newSelection.addAll(newWays);
561 }
562 for (Way wayToAdd : newWays) {
563 commandList.add(layer != null ? new AddCommand(layer, wayToAdd) : new AddCommand(way.getDataSet(), wayToAdd));
564 }
565
566 boolean warnmerole = false;
567 boolean warnme = false;
568 // now copy all relations to new way also
569
570 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
571 if (!r.isUsable()) {
572 continue;
573 }
574 Relation c = null;
575 String type = Optional.ofNullable(r.get("type")).orElse("");
576
577 int ic = 0;
578 int ir = 0;
579 List<RelationMember> relationMembers = r.getMembers();
580 for (RelationMember rm: relationMembers) {
581 if (rm.isWay() && rm.getMember() == way) {
582 boolean insert = true;
583 if ("restriction".equals(type) || "destination_sign".equals(type)) {
584 /* this code assumes the restriction is correct. No real error checking done */
585 String role = rm.getRole();
586 if ("from".equals(role) || "to".equals(role)) {
587 OsmPrimitive via = findVia(r, type);
588 List<Node> nodes = new ArrayList<>();
589 if (via != null) {
590 if (via instanceof Node) {
591 nodes.add((Node) via);
592 } else if (via instanceof Way) {
593 nodes.add(((Way) via).lastNode());
594 nodes.add(((Way) via).firstNode());
595 }
596 }
597 Way res = null;
598 for (Node n : nodes) {
599 if (changedWay.isFirstLastNode(n)) {
600 res = way;
601 }
602 }
603 if (res == null) {
604 for (Way wayToAdd : newWays) {
605 for (Node n : nodes) {
606 if (wayToAdd.isFirstLastNode(n)) {
607 res = wayToAdd;
608 }
609 }
610 }
611 if (res != null) {
612 if (c == null) {
613 c = new Relation(r);
614 }
615 c.addMember(new RelationMember(role, res));
616 c.removeMembersFor(way);
617 insert = false;
618 }
619 } else {
620 insert = false;
621 }
622 } else if (!"via".equals(role)) {
623 warnme = true;
624 }
625 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) {
626 warnme = true;
627 }
628 if (c == null) {
629 c = new Relation(r);
630 }
631
632 if (insert) {
633 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
634 warnmerole = true;
635 }
636
637 Boolean backwards = null;
638 int k = 1;
639 while (ir - k >= 0 || ir + k < relationMembers.size()) {
640 if ((ir - k >= 0) && relationMembers.get(ir - k).isWay()) {
641 Way w = relationMembers.get(ir - k).getWay();
642 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
643 backwards = Boolean.FALSE;
644 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
645 backwards = Boolean.TRUE;
646 }
647 break;
648 }
649 if ((ir + k < relationMembers.size()) && relationMembers.get(ir + k).isWay()) {
650 Way w = relationMembers.get(ir + k).getWay();
651 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
652 backwards = Boolean.TRUE;
653 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
654 backwards = Boolean.FALSE;
655 }
656 break;
657 }
658 k++;
659 }
660
661 int j = ic;
662 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep);
663 for (Way wayToAdd : waysToAddBefore) {
664 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
665 j++;
666 if (Boolean.TRUE.equals(backwards)) {
667 c.addMember(ic + 1, em);
668 } else {
669 c.addMember(j - 1, em);
670 }
671 }
672 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size());
673 for (Way wayToAdd : waysToAddAfter) {
674 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
675 j++;
676 if (Boolean.TRUE.equals(backwards)) {
677 c.addMember(ic, em);
678 } else {
679 c.addMember(j, em);
680 }
681 }
682 ic = j;
683 }
684 }
685 ic++;
686 ir++;
687 }
688
689 if (c != null) {
690 commandList.add(layer != null ? new ChangeCommand(layer, r, c) : new ChangeCommand(r.getDataSet(), r, c));
691 }
692 }
693 if (warnmerole) {
694 new Notification(
695 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
696 .setIcon(JOptionPane.WARNING_MESSAGE)
697 .show();
698 } else if (warnme) {
699 new Notification(
700 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
701 .setIcon(JOptionPane.WARNING_MESSAGE)
702 .show();
703 }
704
705 return new SplitWayResult(
706 new SequenceCommand(
707 /* for correct i18n of plural forms - see #9110 */
708 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size() + 1,
709 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size() + 1),
710 commandList
711 ),
712 newSelection,
713 way,
714 newWays
715 );
716 }
717
718 static OsmPrimitive findVia(Relation r, String type) {
719 for (RelationMember rmv : r.getMembers()) {
720 if (("restriction".equals(type) && "via".equals(rmv.getRole()))
721 || ("destination_sign".equals(type) && rmv.hasRole("sign", "intersection"))) {
722 return rmv.getMember();
723 }
724 }
725 return null;
726 }
727
728 /**
729 * Splits the way {@code way} at the nodes in {@code atNodes} and replies
730 * the result of this process in an instance of {@link SplitWayResult}.
731 *
732 * Note that changes are not applied to the data yet. You have to
733 * submit the command in {@link SplitWayResult#getCommand()} first,
734 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
735 *
736 * Replies null if the way couldn't be split at the given nodes.
737 *
738 * @param layer the layer which the way belongs to.
739 * @param way the way to split. Must not be null.
740 * @param atNodes the list of nodes where the way is split. Must not be null.
741 * @param selection The list of currently selected primitives
742 * @return the result from the split operation
743 */
744 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) {
745 List<List<Node>> chunks = buildSplitChunks(way, atNodes);
746 return chunks != null ? splitWay(layer, way, chunks, selection) : null;
747 }
748
749 @Override
750 protected void updateEnabledState() {
751 updateEnabledStateOnCurrentSelection();
752 }
753
754 @Override
755 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
756 if (selection == null) {
757 setEnabled(false);
758 return;
759 }
760 for (OsmPrimitive primitive: selection) {
761 if (primitive instanceof Node) {
762 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
763 return;
764 }
765 }
766 setEnabled(false);
767 }
768}
Note: See TracBrowser for help on using the repository browser.