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

Last change on this file since 2120 was 2120, checked in by stoecker, 15 years ago

see #3475 - patch by Petr Dlouhý - code rework for display filtering

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