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

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

see #10730 - Way splitting: fix and test (route) relation adaption

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