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

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

fix #2630 - patch by Teemu Koskinen - Split backward-role ways correctly

  • Property svn:eol-style set to native
File size: 12.5 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.SelectionChangedListener;
27import org.openstreetmap.josm.data.osm.DataSet;
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.NameVisitor;
35import org.openstreetmap.josm.data.osm.visitor.Visitor;
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 implements SelectionChangedListener {
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 DataSet.selListeners.add(this);
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 = Main.ds.getSelected();
68
69 if (!checkSelection(selection)) {
70 JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting."));
71 return;
72 }
73
74 selectedWay = null;
75 selectedNodes = null;
76
77 Visitor splitVisitor = new AbstractVisitor() {
78 public void visit(Node n) {
79 if (selectedNodes == null)
80 selectedNodes = new LinkedList<Node>();
81 selectedNodes.add(n);
82 }
83 public void visit(Way w) {
84 selectedWay = w;
85 }
86 public void visit(Relation e) {
87 // enties are not considered
88 }
89 };
90
91 for (OsmPrimitive p : selection)
92 p.visit(splitVisitor);
93
94 // If only nodes are selected, try to guess which way to split. This works if there
95 // is exactly one way that all nodes are part of.
96 if (selectedWay == null && selectedNodes != null) {
97 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
98 for (Node n : selectedNodes) {
99 for (Way w : Main.ds.ways) {
100 if (w.deleted || w.incomplete) continue;
101 int last = w.nodes.size()-1;
102 if(last <= 0) continue; // zero or one node ways
103 Boolean circular = w.nodes.get(0).equals(w.nodes.get(last));
104 int i = 0;
105 for (Node wn : w.nodes) {
106 if ((circular || (i > 0 && i < last)) && n.equals(wn)) {
107 Integer old = wayOccurenceCounter.get(w);
108 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
109 break;
110 }
111 i++;
112 }
113 }
114 }
115 if (wayOccurenceCounter.isEmpty()) {
116 JOptionPane.showMessageDialog(Main.parent,
117 trn("The selected node is not in the middle of any way.",
118 "The selected nodes are not in the middle of any way.",
119 selectedNodes.size()));
120 return;
121 }
122
123 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
124 if (entry.getValue().equals(selectedNodes.size())) {
125 if (selectedWay != null) {
126 JOptionPane.showMessageDialog(Main.parent,
127 trn("There is more than one way using the node you selected. Please select the way also.",
128 "There is more than one way using the nodes you selected. Please select the way also.",
129 selectedNodes.size()));
130 return;
131 }
132 selectedWay = entry.getKey();
133 }
134 }
135
136 if (selectedWay == null) {
137 JOptionPane.showMessageDialog(Main.parent,
138 tr("The selected nodes do not share the same way."));
139 return;
140 }
141
142 // If a way and nodes are selected, verify that the nodes are part of the way.
143 } else if (selectedWay != null && selectedNodes != null) {
144
145 HashSet<Node> nds = new HashSet<Node>(selectedNodes);
146 for (Node n : selectedWay.nodes) {
147 nds.remove(n);
148 }
149 if (!nds.isEmpty()) {
150 JOptionPane.showMessageDialog(Main.parent,
151 trn("The selected way does not contain the selected node.",
152 "The selected way does not contain all the selected nodes.",
153 selectedNodes.size()));
154 return;
155 }
156 }
157
158 // and then do the work.
159 splitWay();
160 }
161
162 /**
163 * Checks if the selection consists of something we can work with.
164 * Checks only if the number and type of items selected looks good;
165 * does not check whether the selected items are really a valid
166 * input for splitting (this would be too expensive to be carried
167 * out from the selectionChanged listener).
168 */
169 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
170 boolean way = false;
171 boolean node = false;
172 for (OsmPrimitive p : selection) {
173 if (p instanceof Way && !way) {
174 way = true;
175 } else if (p instanceof Node) {
176 node = true;
177 } else {
178 return false;
179 }
180 }
181 return node;
182 }
183
184 /**
185 * Split a way into two or more parts, starting at a selected node.
186 */
187 private void splitWay() {
188 // We take our way's list of nodes and copy them to a way chunk (a
189 // list of nodes). Whenever we stumble upon a selected node, we start
190 // a new way chunk.
191
192 Set<Node> nodeSet = new HashSet<Node>(selectedNodes);
193 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
194 List<Node> currentWayChunk = new ArrayList<Node>();
195 wayChunks.add(currentWayChunk);
196
197 Iterator<Node> it = selectedWay.nodes.iterator();
198 while (it.hasNext()) {
199 Node currentNode = it.next();
200 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
201 currentWayChunk.add(currentNode);
202 if (nodeSet.contains(currentNode) && !atEndOfWay) {
203 currentWayChunk = new ArrayList<Node>();
204 currentWayChunk.add(currentNode);
205 wayChunks.add(currentWayChunk);
206 }
207 }
208
209 // Handle circular ways specially.
210 // If you split at a circular way at two nodes, you just want to split
211 // it at these points, not also at the former endpoint.
212 // So if the last node is the same first node, join the last and the
213 // first way chunk.
214 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
215 if (wayChunks.size() >= 2
216 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
217 && !nodeSet.contains(wayChunks.get(0).get(0))) {
218 if (wayChunks.size() == 2) {
219 JOptionPane.showMessageDialog(Main.parent, tr("You must select two or more nodes to split a circular way."));
220 return;
221 }
222 lastWayChunk.remove(lastWayChunk.size() - 1);
223 lastWayChunk.addAll(wayChunks.get(0));
224 wayChunks.remove(wayChunks.size() - 1);
225 wayChunks.set(0, lastWayChunk);
226 }
227
228 if (wayChunks.size() < 2) {
229 if(wayChunks.get(0).get(0) == wayChunks.get(0).get(wayChunks.get(0).size()-1))
230 JOptionPane.showMessageDialog(Main.parent, tr("You must select two or more nodes to split a circular way."));
231 else
232 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"));
233 return;
234 }
235 //Main.debug("wayChunks.size(): " + wayChunks.size());
236 //Main.debug("way id: " + selectedWay.id);
237
238 // build a list of commands, and also a new selection list
239 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
240 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
241
242 Iterator<List<Node>> chunkIt = wayChunks.iterator();
243
244 // First, change the original way
245 Way changedWay = new Way(selectedWay);
246 changedWay.nodes.clear();
247 changedWay.nodes.addAll(chunkIt.next());
248 commandList.add(new ChangeCommand(selectedWay, changedWay));
249 newSelection.add(selectedWay);
250
251 Collection<Way> newWays = new ArrayList<Way>();
252 // Second, create new ways
253 while (chunkIt.hasNext()) {
254 Way wayToAdd = new Way();
255 if (selectedWay.keys != null) {
256 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys);
257 }
258 newWays.add(wayToAdd);
259 wayToAdd.nodes.addAll(chunkIt.next());
260 commandList.add(new AddCommand(wayToAdd));
261 //Main.debug("wayToAdd: " + wayToAdd);
262 newSelection.add(wayToAdd);
263
264 }
265 Boolean warnme=false;
266 // now copy all relations to new way also
267 for (Relation r : Main.ds.relations) {
268 if (r.deleted || r.incomplete) continue;
269 Relation c = null;
270 int i = 0;
271
272 for (RelationMember rm : r.members) {
273 if (rm.member instanceof Way) {
274 if (rm.member == selectedWay)
275 {
276 if (c == null)
277 c = new Relation(r);
278
279 int j = i;
280 boolean backwards = rm.role.equals("backward");
281 for(Way wayToAdd : newWays)
282 {
283 RelationMember em = new RelationMember();
284 em.member = wayToAdd;
285 em.role = rm.role;
286 if(em.role.length() > 0)
287 warnme = true;
288
289 j++;
290 if (backwards)
291 c.members.add(i, em);
292 else
293 c.members.add(j, em);
294 }
295 i = j;
296 }
297 }
298 i++;
299 }
300
301 if (c != null)
302 commandList.add(new ChangeCommand(r, c));
303 }
304 if(warnme)
305 JOptionPane.showMessageDialog(Main.parent, tr("A role based relation membership was copied to all new ways.\nYou should verify this and correct it when necessary."));
306
307 NameVisitor v = new NameVisitor();
308 v.visit(selectedWay);
309 Main.main.undoRedo.add(
310 new SequenceCommand(tr("Split way {0} into {1} parts",
311 v.name, wayChunks.size()),
312 commandList));
313 Main.ds.setSelected(newSelection);
314 }
315
316 /**
317 * Enable the "split way" menu option if the selection looks like we could use it.
318 */
319 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
320 setEnabled(checkSelection(newSelection));
321 }
322}
Note: See TracBrowser for help on using the repository browser.