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

Last change on this file since 1335 was 1252, checked in by ulfl, 15 years ago

fix indent

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