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

Last change on this file since 10150 was 10131, checked in by Don-vip, 8 years ago

fix coverity 1354553, 1347477, 1347476, 1347474, 1347472, 1347471 - NPE

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