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

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

sonar - fb-contrib:SEO_SUBOPTIMAL_EXPRESSION_ORDER - Performance - Method orders expressions in a conditional in a sub optimal way

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