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

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

sonar - Local variable and method parameter names should comply with a naming convention

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