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

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

fix #5262 - do not change selection after splitting a way in "draw nodes" map mode

  • 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 =
374 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
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. Must not be null.
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 */
500 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
501 Collection<? extends OsmPrimitive> selection) {
502 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk());
503 }
504
505 /**
506 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
507 * the result of this process in an instance of {@link SplitWayResult}.
508 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which
509 * way chunk should reuse the old id and its history.
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 layer the layer which the way belongs to. Must not be null.
516 * @param way the way to split. Must not be null.
517 * @param wayChunks the list of way chunks into the way is split. Must not be null.
518 * @param selection The list of currently selected primitives
519 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history
520 * @return the result from the split operation
521 * @since 8954
522 */
523 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
524 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) {
525 // build a list of commands, and also a new selection list
526 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size());
527 newSelection.addAll(selection);
528
529 // Create all potential new ways
530 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks);
531
532 // Determine which part reuses the existing way
533 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays);
534
535 return wayToKeep != null ? doSplitWay(layer, way, wayToKeep, newWays, newSelection) : null;
536 }
537
538 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays,
539 List<OsmPrimitive> newSelection) {
540
541 Collection<Command> commandList = new ArrayList<>(newWays.size());
542 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn",
543 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west"));
544
545 final boolean isMapModeDraw = Main.map != null && Main.map.mapMode == Main.map.mapModeDraw;
546
547 // Change the original way
548 final Way changedWay = new Way(way);
549 changedWay.setNodes(wayToKeep.getNodes());
550 commandList.add(layer != null ? new ChangeCommand(layer, way, changedWay) : new ChangeCommand(way.getDataSet(), way, changedWay));
551 if (!isMapModeDraw && !newSelection.contains(way)) {
552 newSelection.add(way);
553 }
554 final int indexOfWayToKeep = newWays.indexOf(wayToKeep);
555 newWays.remove(wayToKeep);
556
557 if (!isMapModeDraw) {
558 newSelection.addAll(newWays);
559 }
560 for (Way wayToAdd : newWays) {
561 commandList.add(layer != null ? new AddCommand(layer, wayToAdd) : new AddCommand(way.getDataSet(), wayToAdd));
562 }
563
564 boolean warnmerole = false;
565 boolean warnme = false;
566 // now copy all relations to new way also
567
568 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
569 if (!r.isUsable()) {
570 continue;
571 }
572 Relation c = null;
573 String type = Optional.ofNullable(r.get("type")).orElse("");
574
575 int ic = 0;
576 int ir = 0;
577 List<RelationMember> relationMembers = r.getMembers();
578 for (RelationMember rm: relationMembers) {
579 if (rm.isWay() && rm.getMember() == way) {
580 boolean insert = true;
581 if ("restriction".equals(type) || "destination_sign".equals(type)) {
582 /* this code assumes the restriction is correct. No real error checking done */
583 String role = rm.getRole();
584 if ("from".equals(role) || "to".equals(role)) {
585 OsmPrimitive via = findVia(r, type);
586 List<Node> nodes = new ArrayList<>();
587 if (via != null) {
588 if (via instanceof Node) {
589 nodes.add((Node) via);
590 } else if (via instanceof Way) {
591 nodes.add(((Way) via).lastNode());
592 nodes.add(((Way) via).firstNode());
593 }
594 }
595 Way res = null;
596 for (Node n : nodes) {
597 if (changedWay.isFirstLastNode(n)) {
598 res = way;
599 }
600 }
601 if (res == null) {
602 for (Way wayToAdd : newWays) {
603 for (Node n : nodes) {
604 if (wayToAdd.isFirstLastNode(n)) {
605 res = wayToAdd;
606 }
607 }
608 }
609 if (res != null) {
610 if (c == null) {
611 c = new Relation(r);
612 }
613 c.addMember(new RelationMember(role, res));
614 c.removeMembersFor(way);
615 insert = false;
616 }
617 } else {
618 insert = false;
619 }
620 } else if (!"via".equals(role)) {
621 warnme = true;
622 }
623 } else if (!("route".equals(type)) && !("multipolygon".equals(type))) {
624 warnme = true;
625 }
626 if (c == null) {
627 c = new Relation(r);
628 }
629
630 if (insert) {
631 if (rm.hasRole() && !nowarnroles.contains(rm.getRole())) {
632 warnmerole = true;
633 }
634
635 Boolean backwards = null;
636 int k = 1;
637 while (ir - k >= 0 || ir + k < relationMembers.size()) {
638 if ((ir - k >= 0) && relationMembers.get(ir - k).isWay()) {
639 Way w = relationMembers.get(ir - k).getWay();
640 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
641 backwards = Boolean.FALSE;
642 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
643 backwards = Boolean.TRUE;
644 }
645 break;
646 }
647 if ((ir + k < relationMembers.size()) && relationMembers.get(ir + k).isWay()) {
648 Way w = relationMembers.get(ir + k).getWay();
649 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
650 backwards = Boolean.TRUE;
651 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
652 backwards = Boolean.FALSE;
653 }
654 break;
655 }
656 k++;
657 }
658
659 int j = ic;
660 final List<Way> waysToAddBefore = newWays.subList(0, indexOfWayToKeep);
661 for (Way wayToAdd : waysToAddBefore) {
662 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
663 j++;
664 if (Boolean.TRUE.equals(backwards)) {
665 c.addMember(ic + 1, em);
666 } else {
667 c.addMember(j - 1, em);
668 }
669 }
670 final List<Way> waysToAddAfter = newWays.subList(indexOfWayToKeep, newWays.size());
671 for (Way wayToAdd : waysToAddAfter) {
672 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
673 j++;
674 if (Boolean.TRUE.equals(backwards)) {
675 c.addMember(ic, em);
676 } else {
677 c.addMember(j, em);
678 }
679 }
680 ic = j;
681 }
682 }
683 ic++;
684 ir++;
685 }
686
687 if (c != null) {
688 commandList.add(layer != null ? new ChangeCommand(layer, r, c) : new ChangeCommand(r.getDataSet(), r, c));
689 }
690 }
691 if (warnmerole) {
692 new Notification(
693 tr("A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
694 .setIcon(JOptionPane.WARNING_MESSAGE)
695 .show();
696 } else if (warnme) {
697 new Notification(
698 tr("A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary."))
699 .setIcon(JOptionPane.WARNING_MESSAGE)
700 .show();
701 }
702
703 return new SplitWayResult(
704 new SequenceCommand(
705 /* for correct i18n of plural forms - see #9110 */
706 trn("Split way {0} into {1} part", "Split way {0} into {1} parts", newWays.size() + 1,
707 way.getDisplayName(DefaultNameFormatter.getInstance()), newWays.size() + 1),
708 commandList
709 ),
710 newSelection,
711 way,
712 newWays
713 );
714 }
715
716 static OsmPrimitive findVia(Relation r, String type) {
717 for (RelationMember rmv : r.getMembers()) {
718 if (("restriction".equals(type) && "via".equals(rmv.getRole()))
719 || ("destination_sign".equals(type) && rmv.hasRole("sign", "intersection"))) {
720 return rmv.getMember();
721 }
722 }
723 return null;
724 }
725
726 /**
727 * Splits the way {@code way} at the nodes in {@code atNodes} and replies
728 * the result of this process in an instance of {@link SplitWayResult}.
729 *
730 * Note that changes are not applied to the data yet. You have to
731 * submit the command in {@link SplitWayResult#getCommand()} first,
732 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
733 *
734 * Replies null if the way couldn't be split at the given nodes.
735 *
736 * @param layer the layer which the way belongs to. Must not be null.
737 * @param way the way to split. Must not be null.
738 * @param atNodes the list of nodes where the way is split. Must not be null.
739 * @param selection The list of currently selected primitives
740 * @return the result from the split operation
741 */
742 public static SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection) {
743 List<List<Node>> chunks = buildSplitChunks(way, atNodes);
744 return chunks != null ? splitWay(layer, way, chunks, selection) : null;
745 }
746
747 @Override
748 protected void updateEnabledState() {
749 updateEnabledStateOnCurrentSelection();
750 }
751
752 @Override
753 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
754 if (selection == null) {
755 setEnabled(false);
756 return;
757 }
758 for (OsmPrimitive primitive: selection) {
759 if (primitive instanceof Node) {
760 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
761 return;
762 }
763 }
764 setEnabled(false);
765 }
766}
Note: See TracBrowser for help on using the repository browser.