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

Last change on this file since 2484 was 2412, checked in by jttt, 14 years ago

Use refererrers in Draw, SplitWay and UnGlue actions

  • Property svn:eol-style set to native
File size: 14.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.HashMap;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Map;
18import java.util.Set;
19import java.util.Map.Entry;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.SequenceCommand;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.Relation;
31import org.openstreetmap.josm.data.osm.RelationMember;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
34import org.openstreetmap.josm.data.osm.visitor.Visitor;
35import org.openstreetmap.josm.gui.DefaultNameFormatter;
36import org.openstreetmap.josm.tools.Shortcut;
37
38/**
39 * Splits a way into multiple ways (all identical except for their node list).
40 *
41 * Ways are just split at the selected nodes. The nodes remain in their
42 * original order. Selected nodes at the end of a way are ignored.
43 */
44
45public class SplitWayAction extends JosmAction {
46
47 private Way selectedWay;
48 private List<Node> selectedNodes;
49
50 /**
51 * Create a new SplitWayAction.
52 */
53 public SplitWayAction() {
54 super(tr("Split Way"), "splitway", tr("Split a way at the selected node."),
55 Shortcut.registerShortcut("tools:splitway", tr("Tool: {0}", tr("Split Way")), KeyEvent.VK_P, Shortcut.GROUP_EDIT), true);
56 putValue("help", ht("/Action/SplitWay"));
57 }
58
59 /**
60 * Called when the action is executed.
61 *
62 * This method performs an expensive check whether the selection clearly defines one
63 * of the split actions outlined above, and if yes, calls the splitWay method.
64 */
65 public void actionPerformed(ActionEvent e) {
66
67 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
68
69 if (!checkSelection(selection)) {
70 JOptionPane.showMessageDialog(
71 Main.parent,
72 tr("The current selection cannot be used for splitting."),
73 tr("Warning"),
74 JOptionPane.WARNING_MESSAGE
75 );
76 return;
77 }
78
79 selectedWay = null;
80 selectedNodes = null;
81
82 Visitor splitVisitor = new AbstractVisitor() {
83 public void visit(Node n) {
84 if (selectedNodes == null) {
85 selectedNodes = new LinkedList<Node>();
86 }
87 selectedNodes.add(n);
88 }
89 public void visit(Way w) {
90 selectedWay = w;
91 }
92 public void visit(Relation e) {
93 // enties are not considered
94 }
95 };
96
97 for (OsmPrimitive p : selection) {
98 p.visit(splitVisitor);
99 }
100
101 // If only nodes are selected, try to guess which way to split. This works if there
102 // is exactly one way that all nodes are part of.
103 if (selectedWay == null && selectedNodes != null) {
104 Map<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
105 for (Node n : selectedNodes) {
106 for (Way w : OsmPrimitive.getFilteredList(n.getReferrers(), Way.class)) {
107 if (!w.isUsable()) {
108 continue;
109 }
110 int last = w.getNodesCount() - 1;
111 if (last <= 0) {
112 continue; // zero or one node ways
113 }
114 boolean circular = w.isClosed();
115 int i = 0;
116 for (Node wn : w.getNodes()) {
117 if ((circular || (i > 0 && i < last)) && n.equals(wn)) {
118 Integer old = wayOccurenceCounter.get(w);
119 wayOccurenceCounter.put(w, (old == null) ? 1 : old + 1);
120 break;
121 }
122 i++;
123 }
124 }
125 }
126 if (wayOccurenceCounter.isEmpty()) {
127 JOptionPane.showMessageDialog(Main.parent,
128 trn("The selected node is not in the middle of any way.",
129 "The selected nodes are not in the middle of any way.",
130 selectedNodes.size()),
131 tr("Warning"),
132 JOptionPane.WARNING_MESSAGE);
133 return;
134 }
135
136 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
137 if (entry.getValue().equals(selectedNodes.size())) {
138 if (selectedWay != null) {
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 selectedWay = entry.getKey();
148 }
149 }
150
151 if (selectedWay == null) {
152 JOptionPane.showMessageDialog(Main.parent,
153 tr("The selected nodes do not share the same way."),
154 tr("Warning"),
155 JOptionPane.WARNING_MESSAGE);
156 return;
157 }
158
159 // If a way and nodes are selected, verify that the nodes are part of the way.
160 } else if (selectedWay != null && selectedNodes != null) {
161
162 HashSet<Node> nds = new HashSet<Node>(selectedNodes);
163 for (Node n : selectedWay.getNodes()) {
164 nds.remove(n);
165 }
166 if (!nds.isEmpty()) {
167 JOptionPane.showMessageDialog(Main.parent,
168 trn("The selected way does not contain the selected node.",
169 "The selected way does not contain all the selected nodes.",
170 selectedNodes.size()),
171 tr("Warning"),
172 JOptionPane.WARNING_MESSAGE);
173 return;
174 }
175 }
176
177 // and then do the work.
178 splitWay();
179 }
180
181 /**
182 * Checks if the selection consists of something we can work with.
183 * Checks only if the number and type of items selected looks good;
184 * does not check whether the selected items are really a valid
185 * input for splitting (this would be too expensive to be carried
186 * out from the selectionChanged listener).
187 */
188 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
189 boolean way = false;
190 boolean node = false;
191 for (OsmPrimitive p : selection) {
192 if (p instanceof Way && !way) {
193 way = true;
194 } else if (p instanceof Node) {
195 node = true;
196 } else
197 return false;
198 }
199 return node;
200 }
201
202 /**
203 * Split a way into two or more parts, starting at a selected node.
204 */
205 private void splitWay() {
206 // We take our way's list of nodes and copy them to a way chunk (a
207 // list of nodes). Whenever we stumble upon a selected node, we start
208 // a new way chunk.
209
210 Set<Node> nodeSet = new HashSet<Node>(selectedNodes);
211 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
212 List<Node> currentWayChunk = new ArrayList<Node>();
213 wayChunks.add(currentWayChunk);
214
215 Iterator<Node> it = selectedWay.getNodes().iterator();
216 while (it.hasNext()) {
217 Node currentNode = it.next();
218 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
219 currentWayChunk.add(currentNode);
220 if (nodeSet.contains(currentNode) && !atEndOfWay) {
221 currentWayChunk = new ArrayList<Node>();
222 currentWayChunk.add(currentNode);
223 wayChunks.add(currentWayChunk);
224 }
225 }
226
227 // Handle circular ways specially.
228 // If you split at a circular way at two nodes, you just want to split
229 // it at these points, not also at the former endpoint.
230 // So if the last node is the same first node, join the last and the
231 // first way chunk.
232 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
233 if (wayChunks.size() >= 2
234 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
235 && !nodeSet.contains(wayChunks.get(0).get(0))) {
236 if (wayChunks.size() == 2) {
237 JOptionPane.showMessageDialog(
238 Main.parent,
239 tr("You must select two or more nodes to split a circular way."),
240 tr("Warning"),
241 JOptionPane.WARNING_MESSAGE);
242 return;
243 }
244 lastWayChunk.remove(lastWayChunk.size() - 1);
245 lastWayChunk.addAll(wayChunks.get(0));
246 wayChunks.remove(wayChunks.size() - 1);
247 wayChunks.set(0, lastWayChunk);
248 }
249
250 if (wayChunks.size() < 2) {
251 if (wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size() - 1)) {
252 JOptionPane.showMessageDialog(
253 Main.parent,
254 tr("You must select two or more nodes to split a circular way."),
255 tr("Warning"),
256 JOptionPane.WARNING_MESSAGE);
257 } else {
258 JOptionPane.showMessageDialog(
259 Main.parent,
260 tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"),
261 tr("Warning"),
262 JOptionPane.WARNING_MESSAGE);
263 }
264 return;
265 }
266 //Main.debug("wayChunks.size(): " + wayChunks.size());
267 //Main.debug("way id: " + selectedWay.id);
268
269 // build a list of commands, and also a new selection list
270 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
271 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
272
273 Iterator<List<Node>> chunkIt = wayChunks.iterator();
274
275 // First, change the original way
276 Way changedWay = new Way(selectedWay);
277 changedWay.setNodes(chunkIt.next());
278 commandList.add(new ChangeCommand(selectedWay, changedWay));
279 newSelection.add(selectedWay);
280
281 Collection<Way> newWays = new ArrayList<Way>();
282 // Second, create new ways
283 while (chunkIt.hasNext()) {
284 Way wayToAdd = new Way();
285 wayToAdd.setKeys(selectedWay.getKeys());
286 newWays.add(wayToAdd);
287 wayToAdd.setNodes(chunkIt.next());
288 commandList.add(new AddCommand(wayToAdd));
289 //Main.debug("wayToAdd: " + wayToAdd);
290 newSelection.add(wayToAdd);
291
292 }
293 Boolean warnmerole = false;
294 Boolean warnme = false;
295 // now copy all relations to new way also
296
297 for (Relation r : OsmPrimitive.getFilteredList(selectedWay.getReferrers(), Relation.class)) {
298 if (!r.isUsable()) {
299 continue;
300 }
301 Relation c = null;
302 String type = r.get("type");
303 if (type == null) {
304 type = "";
305 }
306 int i = 0;
307
308 for (RelationMember rm : r.getMembers()) {
309 if (rm.isWay()) {
310 if (rm.getMember() == selectedWay) {
311 if (!("route".equals(type)) && !("multipolygon".equals(type))) {
312 warnme = true;
313 }
314 if (c == null) {
315 c = new Relation(r);
316 }
317
318 int j = i;
319 boolean backwards = "backward".equals(rm.getRole());
320 for (Way wayToAdd : newWays) {
321 RelationMember em = new RelationMember(rm.getRole(), wayToAdd);
322 if (em.hasRole() && !("multipolygon".equals(type))) {
323 warnmerole = true;
324 }
325
326 j++;
327 if (backwards) {
328 c.addMember(i, em);
329 } else {
330 c.addMember(j, em);
331 }
332 }
333 i = j;
334 }
335 }
336 i++;
337 }
338
339 if (c != null) {
340 commandList.add(new ChangeCommand(r, c));
341 }
342 }
343 if (warnmerole) {
344 JOptionPane.showMessageDialog(
345 Main.parent,
346 tr("<html>A role based relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
347 tr("Warning"),
348 JOptionPane.WARNING_MESSAGE);
349 } else if (warnme) {
350 JOptionPane.showMessageDialog(
351 Main.parent,
352 tr("<html>A relation membership was copied to all new ways.<br>You should verify this and correct it when necessary.</html>"),
353 tr("Warning"),
354 JOptionPane.WARNING_MESSAGE);
355 }
356
357 Main.main.undoRedo.add(
358 new SequenceCommand(tr("Split way {0} into {1} parts",
359 selectedWay.getDisplayName(DefaultNameFormatter.getInstance()),
360 wayChunks.size()),
361 commandList));
362 getCurrentDataSet().setSelected(newSelection);
363 }
364
365 @Override
366 protected void updateEnabledState() {
367 if (getCurrentDataSet() == null) {
368 setEnabled(false);
369 } else {
370 updateEnabledState(getCurrentDataSet().getSelected());
371 }
372 }
373
374 @Override
375 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
376 if (selection == null) {
377 setEnabled(false);
378 return;
379 }
380 setEnabled(checkSelection(selection));
381 }
382}
Note: See TracBrowser for help on using the repository browser.