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

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

refactor handling of null values - use Java 8 Optional where possible

  • Property svn:eol-style set to native
File size: 32.7 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 getLayerManager().getEditDataSet().setSelected(result.getNewSelection());
217 }
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 transient Way selectedWay;
227 final transient List<Way> newWays;
228 final JList<Way> list;
229 final transient List<OsmPrimitive> selection;
230 final transient 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(e -> {
254 final Way selected = list.getSelectedValue();
255 if (selected != null && Main.isDisplayingMapView() && selected.getNodesCount() > 1) {
256 final Collection<WaySegment> segments = new ArrayList<>(selected.getNodesCount() - 1);
257 final Iterator<Node> it = selected.getNodes().iterator();
258 Node previousNode = it.next();
259 while (it.hasNext()) {
260 final Node node = it.next();
261 segments.add(WaySegment.forNodePair(selectedWay, previousNode, node));
262 previousNode = node;
263 }
264 setHighlightedWaySegments(segments);
265 }
266 });
267 list.setCellRenderer(new SegmentListCellRenderer());
268 }
269
270 protected void setHighlightedWaySegments(Collection<WaySegment> segments) {
271 selectedWay.getDataSet().setHighlightedWaySegments(segments);
272 Main.map.mapView.repaint();
273 }
274
275 @Override
276 public void setVisible(boolean visible) {
277 super.setVisible(visible);
278 if (visible) {
279 DISPLAY_COUNT.incrementAndGet();
280 list.setSelectedValue(wayToKeep, true);
281 } else {
282 setHighlightedWaySegments(Collections.<WaySegment>emptyList());
283 DISPLAY_COUNT.decrementAndGet();
284 }
285 }
286
287 @Override
288 protected void buttonAction(int buttonIndex, ActionEvent evt) {
289 super.buttonAction(buttonIndex, evt);
290 toggleSaveState(); // necessary since #showDialog() does not handle it due to the non-modal dialog
291 if (getValue() == 1) {
292 SplitWayResult result = doSplitWay(Main.getLayerManager().getEditLayer(),
293 selectedWay, list.getSelectedValue(), newWays, selection);
294 Main.main.undoRedo.add(result.getCommand());
295 Main.getLayerManager().getEditDataSet().setSelected(result.getNewSelection());
296 }
297 }
298 }
299
300 static class SegmentListCellRenderer extends DefaultListCellRenderer {
301 @Override
302 public Component getListCellRendererComponent(JList<?> list, Object value, int index, boolean isSelected, boolean cellHasFocus) {
303 final Component c = super.getListCellRendererComponent(list, value, index, isSelected, cellHasFocus);
304 final String name = DefaultNameFormatter.getInstance().format((Way) value);
305 // get rid of id from DefaultNameFormatter.decorateNameWithId()
306 final String nameWithoutId = name
307 .replace(tr(" [id: {0}]", ((Way) value).getId()), "")
308 .replace(tr(" [id: {0}]", ((Way) value).getUniqueId()), "");
309 ((JLabel) c).setText(tr("Segment {0}: {1}", index + 1, nameWithoutId));
310 return c;
311 }
312 }
313
314 /**
315 * Determines which way chunk should reuse the old id and its history
316 *
317 * @since 8954
318 * @since 10599 (functional interface)
319 */
320 @FunctionalInterface
321 public interface Strategy {
322
323 /**
324 * Determines which way chunk should reuse the old id and its history.
325 *
326 * @param wayChunks the way chunks
327 * @return the way to keep
328 */
329 Way determineWayToKeep(Iterable<Way> wayChunks);
330
331 /**
332 * Returns a strategy which selects the way chunk with the highest node count to keep.
333 * @return strategy which selects the way chunk with the highest node count to keep
334 */
335 static Strategy keepLongestChunk() {
336 return wayChunks -> {
337 Way wayToKeep = null;
338 for (Way i : wayChunks) {
339 if (wayToKeep == null || i.getNodesCount() > wayToKeep.getNodesCount()) {
340 wayToKeep = i;
341 }
342 }
343 return wayToKeep;
344 };
345 }
346
347 /**
348 * Returns a strategy which selects the first way chunk.
349 * @return strategy which selects the first way chunk
350 */
351 static Strategy keepFirstChunk() {
352 return wayChunks -> wayChunks.iterator().next();
353 }
354 }
355
356 /**
357 * Determine which ways to split.
358 * @param selectedWays List of user selected ways.
359 * @param selectedNodes List of user selected nodes.
360 * @return List of ways to split
361 */
362 static List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
363 if (selectedNodes.isEmpty())
364 return null;
365
366 // Special case - one of the selected ways touches (not cross) way that we want to split
367 if (selectedNodes.size() == 1) {
368 Node n = selectedNodes.get(0);
369 List<Way> referredWays =
370 OsmPrimitive.getFilteredList(n.getReferrers(), Way.class);
371 Way inTheMiddle = null;
372 for (Way w: referredWays) {
373 // Need to look at all nodes see #11184 for a case where node n is
374 // firstNode, lastNode and also in the middle
375 if (selectedWays.contains(w) && w.isInnerNode(n)) {
376 if (inTheMiddle == null) {
377 inTheMiddle = w;
378 } else {
379 inTheMiddle = null;
380 break;
381 }
382 }
383 }
384 if (inTheMiddle != null)
385 return Collections.singletonList(inTheMiddle);
386 }
387
388 // List of ways shared by all nodes
389 return UnJoinNodeWayAction.getApplicableWays(selectedWays, selectedNodes);
390 }
391
392 /**
393 * Splits the nodes of {@code wayToSplit} into a list of node sequences
394 * which are separated at the nodes in {@code splitPoints}.
395 *
396 * This method displays warning messages if {@code wayToSplit} and/or
397 * {@code splitPoints} aren't consistent.
398 *
399 * Returns null, if building the split chunks fails.
400 *
401 * @param wayToSplit the way to split. Must not be null.
402 * @param splitPoints the nodes where the way is split. Must not be null.
403 * @return the list of chunks
404 */
405 public static List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints) {
406 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit");
407 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints");
408
409 Set<Node> nodeSet = new HashSet<>(splitPoints);
410 List<List<Node>> wayChunks = new LinkedList<>();
411 List<Node> currentWayChunk = new ArrayList<>();
412 wayChunks.add(currentWayChunk);
413
414 Iterator<Node> it = wayToSplit.getNodes().iterator();
415 while (it.hasNext()) {
416 Node currentNode = it.next();
417 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
418 currentWayChunk.add(currentNode);
419 if (nodeSet.contains(currentNode) && !atEndOfWay) {
420 currentWayChunk = new ArrayList<>();
421 currentWayChunk.add(currentNode);
422 wayChunks.add(currentWayChunk);
423 }
424 }
425
426 // Handle circular ways specially.
427 // If you split at a circular way at two nodes, you just want to split
428 // it at these points, not also at the former endpoint.
429 // So if the last node is the same first node, join the last and the
430 // first way chunk.
431 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
432 if (wayChunks.size() >= 2
433 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
434 && !nodeSet.contains(wayChunks.get(0).get(0))) {
435 if (wayChunks.size() == 2) {
436 new Notification(
437 tr("You must select two or more nodes to split a circular way."))
438 .setIcon(JOptionPane.WARNING_MESSAGE)
439 .show();
440 return null;
441 }
442 lastWayChunk.remove(lastWayChunk.size() - 1);
443 lastWayChunk.addAll(wayChunks.get(0));
444 wayChunks.remove(wayChunks.size() - 1);
445 wayChunks.set(0, lastWayChunk);
446 }
447
448 if (wayChunks.size() < 2) {
449 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
450 new Notification(
451 tr("You must select two or more nodes to split a circular way."))
452 .setIcon(JOptionPane.WARNING_MESSAGE)
453 .show();
454 } else {
455 new Notification(
456 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"))
457 .setIcon(JOptionPane.WARNING_MESSAGE)
458 .show();
459 }
460 return null;
461 }
462 return wayChunks;
463 }
464
465 /**
466 * Creates new way objects for the way chunks and transfers the keys from the original way.
467 * @param way the original way whose keys are transferred
468 * @param wayChunks the way chunks
469 * @return the new way objects
470 */
471 protected static List<Way> createNewWaysFromChunks(Way way, Iterable<List<Node>> wayChunks) {
472 final List<Way> newWays = new ArrayList<>();
473 for (List<Node> wayChunk : wayChunks) {
474 Way wayToAdd = new Way();
475 wayToAdd.setKeys(way.getKeys());
476 wayToAdd.setNodes(wayChunk);
477 newWays.add(wayToAdd);
478 }
479 return newWays;
480 }
481
482 /**
483 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
484 * the result of this process in an instance of {@link SplitWayResult}.
485 *
486 * Note that changes are not applied to the data yet. You have to
487 * submit the command in {@link SplitWayResult#getCommand()} first,
488 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
489 *
490 * @param layer the layer which the way belongs to. Must not be null.
491 * @param way the way to split. Must not be null.
492 * @param wayChunks the list of way chunks into the way is split. Must not be null.
493 * @param selection The list of currently selected primitives
494 * @return the result from the split operation
495 */
496 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
497 Collection<? extends OsmPrimitive> selection) {
498 return splitWay(layer, way, wayChunks, selection, Strategy.keepLongestChunk());
499 }
500
501 /**
502 * Splits the way {@code way} into chunks of {@code wayChunks} and replies
503 * the result of this process in an instance of {@link SplitWayResult}.
504 * The {@link org.openstreetmap.josm.actions.SplitWayAction.Strategy} is used to determine which
505 * way chunk should reuse the old id and its history.
506 *
507 * Note that changes are not applied to the data yet. You have to
508 * submit the command in {@link SplitWayResult#getCommand()} first,
509 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
510 *
511 * @param layer the layer which the way belongs to. Must not be null.
512 * @param way the way to split. Must not be null.
513 * @param wayChunks the list of way chunks into the way is split. Must not be null.
514 * @param selection The list of currently selected primitives
515 * @param splitStrategy The strategy used to determine which way chunk should reuse the old id and its history
516 * @return the result from the split operation
517 * @since 8954
518 */
519 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks,
520 Collection<? extends OsmPrimitive> selection, Strategy splitStrategy) {
521 // build a list of commands, and also a new selection list
522 final List<OsmPrimitive> newSelection = new ArrayList<>(selection.size() + wayChunks.size());
523 newSelection.addAll(selection);
524
525 // Create all potential new ways
526 final List<Way> newWays = createNewWaysFromChunks(way, wayChunks);
527
528 // Determine which part reuses the existing way
529 final Way wayToKeep = splitStrategy.determineWayToKeep(newWays);
530
531 return wayToKeep != null ? doSplitWay(layer, way, wayToKeep, newWays, newSelection) : null;
532 }
533
534 static SplitWayResult doSplitWay(OsmDataLayer layer, Way way, Way wayToKeep, List<Way> newWays,
535 List<OsmPrimitive> newSelection) {
536
537 Collection<Command> commandList = new ArrayList<>(newWays.size());
538 Collection<String> nowarnroles = Main.pref.getCollection("way.split.roles.nowarn",
539 Arrays.asList("outer", "inner", "forward", "backward", "north", "south", "east", "west"));
540
541 // Change the original way
542 final Way changedWay = new Way(way);
543 changedWay.setNodes(wayToKeep.getNodes());
544 commandList.add(layer != null ? new ChangeCommand(layer, way, changedWay) : new ChangeCommand(way.getDataSet(), way, changedWay));
545 if (!newSelection.contains(way)) {
546 newSelection.add(way);
547 }
548 final int indexOfWayToKeep = newWays.indexOf(wayToKeep);
549 newWays.remove(wayToKeep);
550
551 newSelection.addAll(newWays);
552 for (Way wayToAdd : newWays) {
553 commandList.add(layer != null ? new AddCommand(layer, wayToAdd) : new AddCommand(way.getDataSet(), wayToAdd));
554 }
555
556 boolean warnmerole = false;
557 boolean warnme = false;
558 // now copy all relations to new way also
559
560 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
561 if (!r.isUsable()) {
562 continue;
563 }
564 Relation c = null;
565 String type = Optional.ofNullable(r.get("type")).orElse("");
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(layer != null ? new ChangeCommand(layer, r, c) : new ChangeCommand(r.getDataSet(), 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.