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

Last change on this file since 3486 was 3437, checked in by bastiK, 14 years ago

fixed #5328 - Tried to split way JOSM crashed

  • Property svn:eol-style set to native
File size: 20.8 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
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.event.ActionEvent;
9import java.awt.event.KeyEvent;
10import java.util.ArrayList;
11import java.util.Collection;
12import java.util.HashSet;
13import java.util.Iterator;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Set;
17
18import javax.swing.JOptionPane;
19
20import org.openstreetmap.josm.Main;
21import org.openstreetmap.josm.command.AddCommand;
22import org.openstreetmap.josm.command.ChangeCommand;
23import org.openstreetmap.josm.command.Command;
24import org.openstreetmap.josm.command.SequenceCommand;
25import org.openstreetmap.josm.data.osm.Node;
26import org.openstreetmap.josm.data.osm.OsmPrimitive;
27import org.openstreetmap.josm.data.osm.PrimitiveId;
28import org.openstreetmap.josm.data.osm.Relation;
29import org.openstreetmap.josm.data.osm.RelationMember;
30import org.openstreetmap.josm.data.osm.Way;
31import org.openstreetmap.josm.gui.DefaultNameFormatter;
32import org.openstreetmap.josm.gui.layer.OsmDataLayer;
33import org.openstreetmap.josm.tools.CheckParameterUtil;
34import org.openstreetmap.josm.tools.Shortcut;
35
36/**
37 * Splits a way into multiple ways (all identical except for their node list).
38 *
39 * Ways are just split at the selected nodes. The nodes remain in their
40 * original order. Selected nodes at the end of a way are ignored.
41 */
42
43public class SplitWayAction extends JosmAction {
44
45
46 public static class SplitWayResult {
47 private final Command command;
48 private final List<? extends PrimitiveId> newSelection;
49 private Way originalWay;
50 private List<Way> newWays;
51
52 public SplitWayResult(Command command, List<? extends PrimitiveId> newSelection, Way originalWay, List<Way> newWays) {
53 this.command = command;
54 this.newSelection = newSelection;
55 this.originalWay = originalWay;
56 this.newWays = newWays;
57 }
58
59 public Command getCommand() {
60 return command;
61 }
62
63 public List<? extends PrimitiveId> getNewSelection() {
64 return newSelection;
65 }
66
67 public Way getOriginalWay() {
68 return originalWay;
69 }
70
71 public List<Way> getNewWays() {
72 return newWays;
73 }
74 }
75
76 /**
77 * Create a new SplitWayAction.
78 */
79 public SplitWayAction() {
80 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."),
81 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.GROUP_EDIT), true);
82 putValue("help", ht("/Action/SplitWay"));
83 }
84
85 /**
86 * Called when the action is executed.
87 *
88 * This method performs an expensive check whether the selection clearly defines one
89 * of the split actions outlined above, and if yes, calls the splitWay method.
90 */
91 public void actionPerformed(ActionEvent e) {
92
93 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
94
95 List<Node> selectedNodes = OsmPrimitive.getFilteredList(selection, Node.class);
96 List<Way> selectedWays = OsmPrimitive.getFilteredList(selection, Way.class);
97 List<Relation> selectedRelations = OsmPrimitive.getFilteredList(selection, Relation.class);
98 List<Way> applicableWays = getApplicableWays(selectedWays, selectedNodes);
99
100 if (applicableWays == null) {
101 JOptionPane.showMessageDialog(
102 Main.parent,
103 tr("The current selection cannot be used for splitting - no node is selected."),
104 tr("Warning"),
105 JOptionPane.WARNING_MESSAGE);
106 return;
107 } else if (applicableWays.isEmpty()) {
108 JOptionPane.showMessageDialog(Main.parent,
109 tr("The selected nodes do not share the same way."),
110 tr("Warning"),
111 JOptionPane.WARNING_MESSAGE);
112 return;
113 }
114
115 { // Remove ways that doesn't have selected node in the middle
116 Iterator<Way> it = applicableWays.iterator();
117 WAY_LOOP:
118 while (it.hasNext()) {
119 Way w = it.next();
120 assert w.isUsable(); // Way is referrer of selected node(s) so it must be usable
121 int last = w.getNodesCount() - 1;
122 boolean circular = w.isClosed();
123
124 for (Node n : selectedNodes) {
125 int i = w.getNodes().indexOf(n);
126 if (!(circular || (i > 0 && i < last))) {
127 it.remove();
128 continue WAY_LOOP;
129 }
130 }
131 }
132 }
133
134 if (applicableWays.isEmpty()) {
135 JOptionPane.showMessageDialog(Main.parent,
136 trn("The selected node is not in the middle of any way.",
137 "The selected nodes are not in the middle of any way.",
138 selectedNodes.size()),
139 tr("Warning"),
140 JOptionPane.WARNING_MESSAGE);
141 return;
142 } else if (applicableWays.size() > 1) {
143 JOptionPane.showMessageDialog(Main.parent,
144 trn("There is more than one way using the node you selected. Please select the way also.",
145 "There is more than one way using the nodes you selected. Please select the way also.",
146 selectedNodes.size()),
147 tr("Warning"),
148 JOptionPane.WARNING_MESSAGE);
149 return;
150 }
151
152 // Finally, applicableWays contains only one perfect way
153 Way selectedWay = applicableWays.get(0);
154
155 List<List<Node>> wayChunks = buildSplitChunks(selectedWay, selectedNodes);
156 if (wayChunks != null) {
157 List<OsmPrimitive> sel = new ArrayList<OsmPrimitive>(selectedWays.size() + selectedRelations.size());
158 sel.addAll(selectedWays);
159 sel.addAll(selectedRelations);
160 SplitWayResult result = splitWay(getEditLayer(),selectedWay, wayChunks, sel);
161 Main.main.undoRedo.add(result.getCommand());
162 getCurrentDataSet().setSelected(result.getNewSelection());
163 }
164 }
165
166 private List<Way> getApplicableWays(List<Way> selectedWays, List<Node> selectedNodes) {
167 if (selectedNodes.isEmpty())
168 return null;
169
170 // List of ways shared by all nodes
171 List<Way> result = new ArrayList<Way>(OsmPrimitive.getFilteredList(selectedNodes.get(0).getReferrers(), Way.class));
172 for (int i=1; i<selectedNodes.size(); i++) {
173 Iterator<Way> it = result.iterator();
174 List<OsmPrimitive> ref = selectedNodes.get(i).getReferrers();
175 while (it.hasNext()) {
176 if (!ref.contains(it.next())) {
177 it.remove();
178 }
179 }
180 }
181
182 { // Remove broken ways
183 Iterator<Way> it = result.iterator();
184 while (it.hasNext()) {
185 if (it.next().getNodesCount() <= 2) {
186 it.remove();
187 }
188 }
189 }
190
191 if (selectedWays.isEmpty())
192 return result;
193 else {
194 // Return only selected ways
195 Iterator<Way> it = result.iterator();
196 while (it.hasNext()) {
197 if (!selectedWays.contains(it.next())) {
198 it.remove();
199 }
200 }
201 return result;
202 }
203
204 }
205
206 /**
207 * Splits the nodes of {@code wayToSplit} into a list of node sequences
208 * which are separated at the nodes in {@code splitPoints}.
209 *
210 * This method displays warning messages if {@code wayToSplit} and/or
211 * {@code splitPoints} aren't consistent.
212 *
213 * Returns null, if building the split chunks fails.
214 *
215 * @param wayToSplit the way to split. Must not be null.
216 * @param splitPoints the nodes where the way is split. Must not be null.
217 * @return the list of chunks
218 */
219 static public List<List<Node>> buildSplitChunks(Way wayToSplit, List<Node> splitPoints){
220 CheckParameterUtil.ensureParameterNotNull(wayToSplit, "wayToSplit");
221 CheckParameterUtil.ensureParameterNotNull(splitPoints, "splitPoints");
222
223 Set<Node> nodeSet = new HashSet<Node>(splitPoints);
224 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
225 List<Node> currentWayChunk = new ArrayList<Node>();
226 wayChunks.add(currentWayChunk);
227
228 Iterator<Node> it = wayToSplit.getNodes().iterator();
229 while (it.hasNext()) {
230 Node currentNode = it.next();
231 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
232 currentWayChunk.add(currentNode);
233 if (nodeSet.contains(currentNode) && !atEndOfWay) {
234 currentWayChunk = new ArrayList<Node>();
235 currentWayChunk.add(currentNode);
236 wayChunks.add(currentWayChunk);
237 }
238 }
239
240 // Handle circular ways specially.
241 // If you split at a circular way at two nodes, you just want to split
242 // it at these points, not also at the former endpoint.
243 // So if the last node is the same first node, join the last and the
244 // first way chunk.
245 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
246 if (wayChunks.size() >= 2
247 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
248 && !nodeSet.contains(wayChunks.get(0).get(0))) {
249 if (wayChunks.size() == 2) {
250 JOptionPane.showMessageDialog(
251 Main.parent,
252 tr("You must select two or more nodes to split a circular way."),
253 tr("Warning"),
254 JOptionPane.WARNING_MESSAGE);
255 return null;
256 }
257 lastWayChunk.remove(lastWayChunk.size() - 1);
258 lastWayChunk.addAll(wayChunks.get(0));
259 wayChunks.remove(wayChunks.size() - 1);
260 wayChunks.set(0, lastWayChunk);
261 }
262
263 if (wayChunks.size() < 2) {
264 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
265 JOptionPane.showMessageDialog(
266 Main.parent,
267 tr("You must select two or more nodes to split a circular way."),
268 tr("Warning"),
269 JOptionPane.WARNING_MESSAGE);
270 } else {
271 JOptionPane.showMessageDialog(
272 Main.parent,
273 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"),
274 tr("Warning"),
275 JOptionPane.WARNING_MESSAGE);
276 }
277 return null;
278 }
279 return wayChunks;
280 }
281
282 /**
283 * Splits a way
284 * @param layer
285 * @param way
286 * @param wayChunks
287 * @return
288 */
289 public static SplitWayResult splitWay(OsmDataLayer layer, Way way, List<List<Node>> wayChunks, Collection<? extends OsmPrimitive> selection) {
290 // build a list of commands, and also a new selection list
291 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
292 List<OsmPrimitive> newSelection = new ArrayList<OsmPrimitive>(selection.size() + wayChunks.size());
293 newSelection.addAll(selection);
294
295 Iterator<List<Node>> chunkIt = wayChunks.iterator();
296
297 // First, change the original way
298 Way changedWay = new Way(way);
299 changedWay.setNodes(chunkIt.next());
300 commandList.add(new ChangeCommand(way, changedWay));
301 if (!newSelection.contains(way)) {
302 newSelection.add(way);
303 }
304
305 List<Way> newWays = new ArrayList<Way>();
306 // Second, create new ways
307 while (chunkIt.hasNext()) {
308 Way wayToAdd = new Way();
309 wayToAdd.setKeys(way.getKeys());
310 newWays.add(wayToAdd);
311 wayToAdd.setNodes(chunkIt.next());
312 commandList.add(new AddCommand(layer,wayToAdd));
313 newSelection.add(wayToAdd);
314
315 }
316 boolean warnmerole = false;
317 boolean warnme = false;
318 // now copy all relations to new way also
319
320 for (Relation r : OsmPrimitive.getFilteredList(way.getReferrers(), Relation.class)) {
321 if (!r.isUsable()) {
322 continue;
323 }
324 Relation c = null;
325 String type = r.get("type");
326 if (type == null) {
327 type = "";
328 }
329
330 int i_c = 0, i_r = 0;
331 List<RelationMember> relationMembers = r.getMembers();
332 for (RelationMember rm: relationMembers) {
333 if (rm.isWay() && rm.getMember() == way) {
334 boolean insert = true;
335 if ("restriction".equals(type))
336 {
337 /* this code assumes the restriction is correct. No real error checking done */
338 String role = rm.getRole();
339 if("from".equals(role) || "to".equals(role))
340 {
341 OsmPrimitive via = null;
342 for (RelationMember rmv : r.getMembers()) {
343 if("via".equals(rmv.getRole())){
344 via = rmv.getMember();
345 }
346 }
347 List<Node> nodes = new ArrayList<Node>();
348 if(via != null) {
349 if(via instanceof Node) {
350 nodes.add((Node)via);
351 } else if(via instanceof Way) {
352 nodes.add(((Way)via).lastNode());
353 nodes.add(((Way)via).firstNode());
354 }
355 }
356 Way res = null;
357 for(Node n : nodes) {
358 if(changedWay.isFirstLastNode(n)) {
359 res = way;
360 }
361 }
362 if(res == null)
363 {
364 for (Way wayToAdd : newWays) {
365 for(Node n : nodes) {
366 if(wayToAdd.isFirstLastNode(n)) {
367 res = wayToAdd;
368 }
369 }
370 }
371 if(res != null)
372 {
373 if (c == null) {
374 c = new Relation(r);
375 }
376 c.addMember(new RelationMember(role, res));
377 c.removeMembersFor(way);
378 insert = false;
379 }
380 } else {
381 insert = false;
382 }
383 }
384 else if(!"via".equals(role)) {
385 warnme = true;
386 }
387 }
388 else if (!("route".equals(type)) && !("multipolygon".equals(type))) {
389 warnme = true;
390 }
391 if (c == null) {
392 c = new Relation(r);
393 }
394
395 if(insert)
396 {
397 if (rm.hasRole() && !("multipolygon".equals(type))) {
398 warnmerole = true;
399 }
400
401 Boolean backwards = null;
402 int k = 1;
403 while (i_r - k >= 0 || i_r + k < relationMembers.size()) {
404 if ((i_r - k >= 0) && relationMembers.get(i_r - k).isWay()){
405 Way w = relationMembers.get(i_r - k).getWay();
406 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
407 backwards = false;
408 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
409 backwards = true;
410 }
411 break;
412 }
413 if ((i_r + k < relationMembers.size()) && relationMembers.get(i_r + k).isWay()){
414 Way w = relationMembers.get(i_r + k).getWay();
415 if ((w.lastNode() == way.firstNode()) || w.firstNode() == way.firstNode()) {
416 backwards = true;
417 } else if ((w.firstNode() == way.lastNode()) || w.lastNode() == way.lastNode()) {
418 backwards = false;
419 }
420 break;
421 }
422 k++;
423 }
424
425 int j = i_c;
426 for (Way wayToAdd : newWays) {
427 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
428 j++;
429 if ((backwards != null) && backwards) {
430 c.addMember(i_c, em);
431 } else {
432 c.addMember(j, em);
433 }
434 }
435 i_c = j;
436 }
437 }
438 i_c++; i_r++;
439 }
440
441 if (c != null) {
442 commandList.add(new ChangeCommand(layer,r, c));
443 }
444 }
445 if (warnmerole) {
446 JOptionPane.showMessageDialog(
447 Main.parent,
448 tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
449 tr("Warning"),
450 JOptionPane.WARNING_MESSAGE);
451 } else if (warnme) {
452 JOptionPane.showMessageDialog(
453 Main.parent,
454 tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
455 tr("Warning"),
456 JOptionPane.WARNING_MESSAGE);
457 }
458
459 return new SplitWayResult(
460 new SequenceCommand(
461 tr("Split way {0} into {1} parts", way.getDisplayName(DefaultNameFormatter.getInstance()),wayChunks.size()),
462 commandList
463 ),
464 newSelection,
465 way,
466 newWays
467 );
468 }
469
470 /**
471 * Splits the way {@code way} at the nodes in {@code atNodes} and replies
472 * the result of this process in an instance of {@see SplitWayResult}.
473 *
474 * Note that changes are not applied to the data yet. You have to
475 * submit the command in {@see SplitWayResult#getCommand()} first,
476 * i.e. {@code Main.main.undoredo.add(result.getCommand())}.
477 *
478 * Replies null if the way couldn't be split at the given nodes.
479 *
480 * @param layer the layer which the way belongs to. Must not be null.
481 * @param way the way to split. Must not be null.
482 * @param atNodes the list of nodes where the way is split. Must not be null.
483 * @return the result from the split operation
484 */
485 static public SplitWayResult split(OsmDataLayer layer, Way way, List<Node> atNodes, Collection<? extends OsmPrimitive> selection){
486 List<List<Node>> chunks = buildSplitChunks(way, atNodes);
487 if (chunks == null) return null;
488 return splitWay(layer,way, chunks, selection);
489 }
490
491 @Override
492 protected void updateEnabledState() {
493 if (getCurrentDataSet() == null) {
494 setEnabled(false);
495 } else {
496 updateEnabledState(getCurrentDataSet().getSelected());
497 }
498 }
499
500 @Override
501 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
502 if (selection == null) {
503 setEnabled(false);
504 return;
505 }
506 for (OsmPrimitive primitive: selection) {
507 if (primitive instanceof Node) {
508 setEnabled(true); // Selection still can be wrong, but let SplitWayAction process and tell user what's wrong
509 return;
510 }
511 }
512 setEnabled(false);
513 }
514}
Note: See TracBrowser for help on using the repository browser.