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

Last change on this file since 1862 was 1862, checked in by jttt, 15 years ago

Way refactoring - added method that will in future replace public field nodes

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