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

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

fix #11992 see #10730 - Joining Overlapping Areas results in RuntimeException

JoinAreasAction somehow requires that the first way chunk keeps the id of the split way.
SplitWayAction#Strategy allows to specify how to determine the way chunk which keeps the to id.

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