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

Last change on this file since 5299 was 5266, checked in by bastiK, 12 years ago

fixed majority of javadoc warnings by replacing "{@see" by "{@link"

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