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

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

see #12943 - gsoc-core - fix most of deprecation warnings (static accesses must be fixed)

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