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

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

fix javadoc errors/warnings

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