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

Last change on this file since 11057 was 10775, checked in by simon04, 8 years ago

Typo (referred)

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