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

Last change on this file since 10124 was 10074, checked in by Don-vip, 8 years ago

refactor duplicated code

  • 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.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 static 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 want to split
374 if (selectedNodes.size() == 1) {
375 Node n = selectedNodes.get(0);
376 List<Way> referedWays =
377 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
378 Way inTheMiddle = null;
379 for (Way w: referedWays) {
380 // Need to look at all nodes see #11184 for a case where node n is
381 // firstNode, lastNode and also in the middle
382 if (selectedWays.contains(w) && w.isInnerNode(n)) {
383 if (inTheMiddle == null) {
384 inTheMiddle = w;
385 } else {
386 inTheMiddle = null;
387 break;
388 }
389 }
390 }
391 if (inTheMiddle != null)
392 return Collections.singletonList(inTheMiddle);
393 }
394
395 // List of ways shared by all nodes
396 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes);
397 }
398
399 /**
400 * Splits the nodes of {@code wayToSplit} into a list of node sequences
401 * which are separated at the nodes in {@code splitPoints}.
402 *
403 * This method displays warning messages if {@code wayToSplit} and/or
404 * {@code splitPoints} aren't consistent.
405 *
406 * Returns null, if building the split chunks fails.
407 *
408 * @param wayToSplit the way to split. Must not be null.
409 * @param splitPoints the nodes where the way is split. Must not be null.
410 * @return the list of chunks
411 */
412 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) {
413 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit");
414 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints");
415
416 Set<Node> nodeSet = new HashSet<>(splitPoints);
417 List<List<Node>> wayChunks = new LinkedList<>();
418 List<Node> currentWayChunk = new ArrayList<>();
419 wayChunks.add(currentWayChunk);
420
421 Iterator<Node> it = wayToSplit.getNodes().iterator();
422 while (it.hasNext()) {
423 Node currentNode = it.next();
424 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
425 currentWayChunk.add(currentNode);
426 if (nodeSet.contains(currentNode) && !atEndOfWay) {
427 currentWayChunk = new ArrayList<>();
428 currentWayChunk.add(currentNode);
429 wayChunks.add(currentWayChunk);
430 }
431 }
432
433 // Handle circular ways specially.
434 // If you split at a circular way at two nodes, you just want to split
435 // it at these points, not also at the former endpoint.
436 // So if the last node is the same first node, join the last and the
437 // first way chunk.
438 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
439 if (wayChunks.size() >= 2
440 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
441 && !nodeSet.contains(wayChunks.get(0).get(0))) {
442 if (wayChunks.size() == 2) {
443 new Notification(
444 tr("You must select two or more nodes to split a circular way."))
445 .setIcon(JOptionPane.WARNING_MESSAGE)
446 .show();
447 return null;
448 }
449 lastWayChunk.remove(lastWayChunk.size() - 1);
450 lastWayChunk.addAll(wayChunks.get(0));
451 wayChunks.remove(wayChunks.size() - 1);
452 wayChunks.set(0, lastWayChunk);
453 }
454
455 if (wayChunks.size() < 2) {
456 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
457 new Notification(
458 tr("You must select two or more nodes to split a circular way."))
459 .setIcon(JOptionPane.WARNING_MESSAGE)
460 .show();
461 } else {
462 new Notification(
463 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"))
464 .setIcon(JOptionPane.WARNING_MESSAGE)
465 .show();
466 }
467 return null;
468 }
469 return wayChunks;
470 }
471
472 /**
473 * Creates new way objects for the way chunks and transfers the keys from the original way.
474 * @param way the original way whose keys are transferred
475 * @param wayChunks the way chunks
476 * @return the new way objects
477 */
478 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) {
479 final List<Way> newWays = new ArrayList<>();
480 for (List<Node> wayChunk : wayChunks) {
481 Way wayToAdd = new Way();
482 wayToAdd.setKeys(way.getKeys());
483 wayToAdd.setNodes(wayChunk);
484 newWays.add(wayToAdd);
485 }
486 return newWays;
487 }
488
489 /**
490 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
491 * the result of this process in an instance of {@link SplitWayResult}.
492 *
493 * Note that changes are not applied to the data yet. You have to
494 * submit the command in {@link SplitWayResult#getCommand()} first,
495 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
496 *
497 * @param layer the layer which the way belongs to. Must not be null.
498 * @param way the way to split. Must not be null.
499 * @param wayChunks the list of way chunks into the way is split. Must not be null.
500 * @param selection The list of currently selected primitives
501 * @return the result from the split operation
502 */
503 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
504 Collection<? extends OsmPrimitive> selection) {
505 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk());
506 }
507
508 /**
509 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
510 * the result of this process in an instance of {@link SplitWayResult}.
511 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which
512 * way chunk should reuse the old id and its history.
513 *
514 * Note that changes are not applied to the data yet. You have to
515 * submit the command in {@link SplitWayResult#getCommand()} first,
516 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
517 *
518 * @param layer the layer which the way belongs to. Must not be null.
519 * @param way the way to split. Must not be null.
520 * @param wayChunks the list of way chunks into the way is split. Must not be null.
521 * @param selection The list of currently selected primitives
522 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history
523 * @return the result from the split operation
524 * @since 8954
525 */
526 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
527 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) {
528 // build a list of commands, and also a new selection list
529 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size());
530 newSelection.addAll(selection);
531
532 // Create all potential new ways
533 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks);
534
535 // Determine which part reuses the existing way
536 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays);
537
538 return doSplitWay(layer, way, wayToKeep, newWays, newSelection);
539 }
540
541 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays,
542 List<OsmPrimitive> newSelection) {
543
544 Collection<Command> commandList = new ArrayList<>(newWays.size());
545 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn",
546 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west"));
547
548 // Change the original way
549 final Way changedWay = new Way(way);
550 changedWay.setNodes(wayToKeep.getNodes());
551 commandList.add(new ChangeCommand(way, changedWay));
552 if (!newSelection.contains(way)) {
553 newSelection.add(way);
554 }
555 final int indexOfWayToKeep = newWays.indexOf(wayToKeep);
556 newWays.remove(wayToKeep);
557
558 newSelection.addAll(newWays);
559 for (Way wayToAdd : newWays) {
560 commandList.add(new AddCommand(layer, 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 = r.get("type");
573 if (type == null) {
574 type = "";
575 }
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(new ChangeCommand(layer, 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. Must not be null.
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 if (chunks == null) return null;
747 return splitWay(layer, way, chunks, selection);
748 }
749
750 @Override
751 protected void updateEnabledState() {
752 if (getCurrentDataSet() == null) {
753 setEnabled(false);
754 } else {
755 updateEnabledState(getCurrentDataSet().getSelected());
756 }
757 }
758
759 @Override
760 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
761 if (selection == null) {
762 setEnabled(false);
763 return;
764 }
765 for (OsmPrimitive primitive: selection) {
766 if (primitive instanceof Node) {
767 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
768 return;
769 }
770 }
771 setEnabled(false);
772 }
773}
Note: See TracBrowser for help on using the repository browser.