source: josm/trunk/src/org/openstreetmap/josm/actions/UnGlueAction.java@ 2381

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

Change most occurrences of Dataset.nodes/ways/relations with getNodes()/../.. or addPrimitive

File size: 15.3 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.gui.help.HelpUtil.ht;
6
7import java.awt.event.ActionEvent;
8import java.awt.event.KeyEvent;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashSet;
13import java.util.LinkedList;
14import java.util.List;
15
16import javax.swing.JOptionPane;
17import javax.swing.JPanel;
18
19import org.openstreetmap.josm.Main;
20import org.openstreetmap.josm.command.AddCommand;
21import org.openstreetmap.josm.command.ChangeCommand;
22import org.openstreetmap.josm.command.Command;
23import org.openstreetmap.josm.command.SequenceCommand;
24import org.openstreetmap.josm.data.osm.Node;
25import org.openstreetmap.josm.data.osm.OsmPrimitive;
26import org.openstreetmap.josm.data.osm.Relation;
27import org.openstreetmap.josm.data.osm.RelationMember;
28import org.openstreetmap.josm.data.osm.Way;
29import org.openstreetmap.josm.gui.MapView;
30import org.openstreetmap.josm.tools.Shortcut;
31
32/**
33 * Duplicate nodes that are used by multiple ways.
34 *
35 * Resulting nodes are identical, up to their position.
36 *
37 * This is the opposite of the MergeNodesAction.
38 *
39 * If a single node is selected, it will copy that node and remove all tags from the old one
40 */
41
42public class UnGlueAction extends JosmAction {
43
44 private Node selectedNode;
45 private Way selectedWay;
46 private ArrayList<Node> selectedNodes;
47
48 /**
49 * Create a new UnGlueAction.
50 */
51 public UnGlueAction() {
52 super(tr("UnGlue Ways"), "unglueways", tr("Duplicate nodes that are used by multiple ways."),
53 Shortcut.registerShortcut("tools:unglue", tr("Tool: {0}", tr("UnGlue Ways")), KeyEvent.VK_G, Shortcut.GROUP_EDIT), true);
54 putValue("help", ht("/Action/UnGlue"));
55 }
56
57 /**
58 * Called when the action is executed.
59 *
60 * This method does some checking on the selection and calls the matching unGlueWay method.
61 */
62 public void actionPerformed(ActionEvent e) {
63
64 Collection<OsmPrimitive> selection = getCurrentDataSet().getSelected();
65
66 String errMsg = null;
67 if (checkSelection(selection)) {
68 int count = 0;
69 for (Way w : getCurrentDataSet().getWays()) {
70 if (!w.isUsable() || w.getNodesCount() < 1) {
71 continue;
72 }
73 if (!w.containsNode(selectedNode)) {
74 continue;
75 }
76 count++;
77 }
78 if (count < 2) {
79 // If there aren't enough ways, maybe the user wanted to unglue the nodes
80 // (= copy tags to a new node)
81 if (checkForUnglueNode(selection)) {
82 unglueNode(e);
83 } else {
84 errMsg = tr("This node is not glued to anything else.");
85 }
86 } else {
87 // and then do the work.
88 unglueWays();
89 }
90 } else if (checkSelection2(selection)) {
91 ArrayList<Node> tmpNodes = new ArrayList<Node>();
92 for (Node n : selectedNodes) {
93 int count = 0;
94 for (Way w : getCurrentDataSet().getWays()) {
95 if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
96 continue;
97 }
98 if (!w.containsNode(n)) {
99 continue;
100 }
101 count++;
102 }
103 if (count >= 2) {
104 tmpNodes.add(n);
105 }
106 }
107 if (tmpNodes.size() < 1) {
108 if (selection.size() > 1) {
109 errMsg = tr("None of these nodes are glued to anything else.");
110 } else {
111 errMsg = tr("None of this way's nodes are glued to anything else.");
112 }
113 } else {
114 // and then do the work.
115 selectedNodes = tmpNodes;
116 unglueWays2();
117 }
118 } else {
119 errMsg =
120 tr("The current selection cannot be used for unglueing.")+"\n"+
121 "\n"+
122 tr("Select either:")+"\n"+
123 tr("* One tagged node, or")+"\n"+
124 tr("* One node that is used by more than one way, or")+"\n"+
125 tr("* One node that is used by more than one way and one of those ways, or")+"\n"+
126 tr("* One way that has one or more nodes that are used by more than one way, or")+"\n"+
127 tr("* One way and one or more of its nodes that are used by more than one way.")+"\n"+
128 "\n"+
129 tr("Note: If a way is selected, this way will get fresh copies of the unglued\n"+
130 "nodes and the new nodes will be selected. Otherwise, all ways will get their\n"+
131 "own copy and all nodes will be selected.");
132 }
133
134 if(errMsg != null) {
135 JOptionPane.showMessageDialog(
136 Main.parent,
137 errMsg,
138 tr("Error"),
139 JOptionPane.ERROR_MESSAGE);
140 }
141
142 selectedNode = null;
143 selectedWay = null;
144 selectedNodes = null;
145 }
146
147 /**
148 * Assumes there is one tagged Node stored in selectedNode that it will try to unglue
149 * (= copy node and remove all tags from the old one. Relations will not be removed)
150 */
151 private void unglueNode(ActionEvent e) {
152 LinkedList<Command> cmds = new LinkedList<Command>();
153
154 Node c = new Node(selectedNode);
155 c.removeAll();
156 getCurrentDataSet().clearSelection(c);
157 cmds.add(new ChangeCommand(selectedNode, c));
158
159 Node n = new Node(selectedNode);
160 n.clearOsmId();
161
162 // If this wasn't called from menu, place it where the cursor is/was
163 if(e.getSource() instanceof JPanel) {
164 MapView mv = Main.map.mapView;
165 n.setCoor(mv.getLatLon(mv.lastMEvent.getX(), mv.lastMEvent.getY()));
166 }
167
168 cmds.add(new AddCommand(n));
169
170 fixRelations(selectedNode, cmds, Collections.singletonList(n));
171
172 Main.main.undoRedo.add(new SequenceCommand(tr("Unglued Node"), cmds));
173 getCurrentDataSet().setSelected(n);
174 Main.map.mapView.repaint();
175 }
176
177 /**
178 * Checks if selection is suitable for ungluing. This is the case when there's a single,
179 * tagged node selected that's part of at least one way (ungluing an unconnected node does
180 * not make sense. Due to the call order in actionPerformed, this is only called when the
181 * node is only part of one or less ways.
182 *
183 * @param The selection to check against
184 * @return Selection is suitable
185 */
186 private boolean checkForUnglueNode(Collection<? extends OsmPrimitive> selection) {
187 if (selection.size() != 1)
188 return false;
189 OsmPrimitive n = (OsmPrimitive) selection.toArray()[0];
190 if (!(n instanceof Node))
191 return false;
192 boolean isPartOfWay = false;
193 for (Way w : getCurrentDataSet().getWays()) {
194 if (w.containsNode((Node) n)) {
195 isPartOfWay = true;
196 break;
197 }
198 }
199 if (!isPartOfWay)
200 return false;
201
202 selectedNode = (Node) n;
203 return selectedNode.isTagged();
204 }
205
206 /**
207 * Checks if the selection consists of something we can work with.
208 * Checks only if the number and type of items selected looks good;
209 * does not check whether the selected items are really a valid
210 * input for splitting (this would be too expensive to be carried
211 * out from the selectionChanged listener).
212 *
213 * If this method returns "true", selectedNode and selectedWay will
214 * be set.
215 *
216 * Returns true if either one node is selected or one node and one
217 * way are selected and the node is part of the way.
218 *
219 * The way will be put into the object variable "selectedWay", the
220 * node into "selectedNode".
221 */
222 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
223
224 int size = selection.size();
225 if (size < 1 || size > 2)
226 return false;
227
228 selectedNode = null;
229 selectedWay = null;
230
231 for (OsmPrimitive p : selection) {
232 if (p instanceof Node) {
233 selectedNode = (Node) p;
234 if (size == 1 || selectedWay != null)
235 return size == 1 || selectedWay.containsNode(selectedNode);
236 } else if (p instanceof Way) {
237 selectedWay = (Way) p;
238 if (size == 2 && selectedNode != null)
239 return selectedWay.containsNode(selectedNode);
240 }
241 }
242
243 return false;
244 }
245
246 /**
247 * Checks if the selection consists of something we can work with.
248 * Checks only if the number and type of items selected looks good;
249 * does not check whether the selected items are really a valid
250 * input for splitting (this would be too expensive to be carried
251 * out from the selectionChanged listener).
252 *
253 * Returns true if one way and any number of nodes that are part of
254 * that way are selected. Note: "any" can be none, then all nodes of
255 * the way are used.
256 *
257 * The way will be put into the object variable "selectedWay", the
258 * nodes into "selectedNodes".
259 */
260 private boolean checkSelection2(Collection<? extends OsmPrimitive> selection) {
261 if (selection.size() < 1)
262 return false;
263
264 selectedWay = null;
265 for (OsmPrimitive p : selection) {
266 if (p instanceof Way) {
267 if (selectedWay != null)
268 return false;
269 selectedWay = (Way) p;
270 }
271 }
272 if (selectedWay == null)
273 return false;
274
275 selectedNodes = new ArrayList<Node>();
276 for (OsmPrimitive p : selection) {
277 if (p instanceof Node) {
278 Node n = (Node) p;
279 if (!selectedWay.containsNode(n))
280 return false;
281 selectedNodes.add(n);
282 }
283 }
284
285 if (selectedNodes.size() < 1) {
286 selectedNodes.addAll(selectedWay.getNodes());
287 }
288
289 return true;
290 }
291
292 /**
293 * dupe the given node of the given way
294 *
295 * -> the new node will be put into the parameter newNodes.
296 * -> the add-node command will be put into the parameter cmds.
297 * -> the changed way will be returned and must be put into cmds by the caller!
298 */
299 private Way modifyWay(Node originalNode, Way w, List<Command> cmds, List<Node> newNodes) {
300 ArrayList<Node> nn = new ArrayList<Node>();
301 for (Node pushNode : w.getNodes()) {
302 if (originalNode == pushNode) {
303 // clone the node for all other ways
304 pushNode = new Node(pushNode);
305 pushNode.clearOsmId();
306 newNodes.add(pushNode);
307 cmds.add(new AddCommand(pushNode));
308 }
309 nn.add(pushNode);
310 }
311 Way newWay = new Way(w);
312 newWay.setNodes(nn);
313
314 return newWay;
315 }
316
317 /**
318 * put all newNodes into the same relation(s) that originalNode is in
319 */
320 private void fixRelations(Node originalNode, List<Command> cmds, List<Node> newNodes) {
321 // modify all relations containing the node
322 Relation newRel = null;
323 HashSet<String> rolesToReAdd = null;
324 for (Relation r : getCurrentDataSet().getRelations()) {
325 if (r.isDeleted() || r.incomplete) {
326 continue;
327 }
328 newRel = null;
329 rolesToReAdd = null;
330 for (RelationMember rm : r.getMembers()) {
331 if (rm.isNode()) {
332 if (rm.getMember() == originalNode) {
333 if (newRel == null) {
334 newRel = new Relation(r);
335 newRel.setMembers(null);
336 rolesToReAdd = new HashSet<String>();
337 }
338 rolesToReAdd.add(rm.getRole());
339 }
340 }
341 }
342 if (newRel != null) {
343 for (RelationMember rm : r.getMembers()) {
344 newRel.addMember(rm);
345 }
346 for (Node n : newNodes) {
347 for (String role : rolesToReAdd) {
348 newRel.addMember(new RelationMember(role, n));
349 }
350 }
351 cmds.add(new ChangeCommand(r, newRel));
352 }
353 }
354 }
355
356
357 /**
358 * dupe a single node into as many nodes as there are ways using it, OR
359 *
360 * dupe a single node once, and put the copy on the selected way
361 */
362 private void unglueWays() {
363 LinkedList<Command> cmds = new LinkedList<Command>();
364 List<Node> newNodes = new LinkedList<Node>();
365
366 if (selectedWay == null) {
367 boolean firstway = true;
368 // modify all ways containing the nodes
369 for (Way w : getCurrentDataSet().getWays()) {
370 if (w.isDeleted() || w.incomplete || w.getNodesCount() < 1) {
371 continue;
372 }
373 if (!w.containsNode(selectedNode)) {
374 continue;
375 }
376 if (!firstway) {
377 cmds.add(new ChangeCommand(w, modifyWay(selectedNode, w, cmds, newNodes)));
378 }
379 firstway = false;
380 }
381 } else {
382 cmds.add(new ChangeCommand(selectedWay, modifyWay(selectedNode, selectedWay, cmds, newNodes)));
383 }
384
385 fixRelations(selectedNode, cmds, newNodes);
386
387 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe into {0} nodes", newNodes.size()+1), cmds));
388 if (selectedWay == null) { // if a node has been selected, new selection is ALL nodes
389 newNodes.add(selectedNode);
390 } // if a node and a way has been selected, new selection is only the new node that was added to the selected way
391 getCurrentDataSet().setSelected(newNodes);
392 }
393
394 /**
395 * dupe all nodes that are selected, and put the copies on the selected way
396 *
397 */
398 private void unglueWays2() {
399 LinkedList<Command> cmds = new LinkedList<Command>();
400 List<Node> allNewNodes = new LinkedList<Node>();
401 Way tmpWay = selectedWay;
402
403 for (Node n : selectedNodes) {
404 List<Node> newNodes = new LinkedList<Node>();
405 tmpWay = modifyWay(n, tmpWay, cmds, newNodes);
406 fixRelations(n, cmds, newNodes);
407 allNewNodes.addAll(newNodes);
408 }
409 cmds.add(new ChangeCommand(selectedWay, tmpWay)); // only one changeCommand for a way, else garbage will happen
410
411 Main.main.undoRedo.add(new SequenceCommand(tr("Dupe {0} nodes into {1} nodes", selectedNodes.size(), selectedNodes.size()+allNewNodes.size()), cmds));
412 getCurrentDataSet().setSelected(allNewNodes);
413 }
414
415 @Override
416 protected void updateEnabledState() {
417 if (getCurrentDataSet() == null) {
418 setEnabled(false);
419 } else {
420 updateEnabledState(getCurrentDataSet().getSelected());
421 }
422 }
423
424 @Override
425 protected void updateEnabledState(Collection<? extends OsmPrimitive> selection) {
426 setEnabled(selection != null && !selection.isEmpty());
427 }
428}
Note: See TracBrowser for help on using the repository browser.