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

Last change on this file since 485 was 485, checked in by gebner, 16 years ago

When splitting ways, don't complain if the nodes are used by deleted ways.

File size: 8.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.Collections;
12import java.util.Comparator;
13import java.util.HashMap;
14import java.util.HashSet;
15import java.util.Iterator;
16import java.util.LinkedList;
17import java.util.List;
18import java.util.Set;
19import java.util.Map.Entry;
20
21import javax.swing.JOptionPane;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.command.AddCommand;
25import org.openstreetmap.josm.command.ChangeCommand;
26import org.openstreetmap.josm.command.Command;
27import org.openstreetmap.josm.command.SequenceCommand;
28import org.openstreetmap.josm.data.SelectionChangedListener;
29import org.openstreetmap.josm.data.osm.DataSet;
30import org.openstreetmap.josm.data.osm.Relation;
31import org.openstreetmap.josm.data.osm.Node;
32import org.openstreetmap.josm.data.osm.OsmPrimitive;
33import org.openstreetmap.josm.data.osm.Way;
34import org.openstreetmap.josm.data.osm.visitor.NameVisitor;
35import org.openstreetmap.josm.data.osm.visitor.Visitor;
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."), KeyEvent.VK_P, 0, true);
54 DataSet.selListeners.add(this);
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 = Main.ds.getSelected();
66
67 if (!checkSelection(selection)) {
68 JOptionPane.showMessageDialog(Main.parent, tr("The current selection cannot be used for splitting."));
69 return;
70 }
71
72 selectedWay = null;
73 selectedNodes = null;
74
75 Visitor splitVisitor = new Visitor(){
76 public void visit(Node n) {
77 if (selectedNodes == null)
78 selectedNodes = new LinkedList<Node>();
79 selectedNodes.add(n);
80 }
81 public void visit(Way w) {
82 selectedWay = w;
83 }
84 public void visit(Relation e) {
85 // enties are not considered
86 }
87 };
88
89 for (OsmPrimitive p : selection)
90 p.visit(splitVisitor);
91
92 // If only nodes are selected, try to guess which way to split. This works if there
93 // is exactly one way that all nodes are part of.
94 if (selectedWay == null && selectedNodes != null) {
95 HashMap<Way, Integer> wayOccurenceCounter = new HashMap<Way, Integer>();
96 for (Node n : selectedNodes) {
97 for (Way w : Main.ds.ways) {
98 if (w.deleted || w.incomplete) continue;
99 for (Node wn : w.nodes) {
100 if (n.equals(wn)) {
101 Integer old = wayOccurenceCounter.get(w);
102 wayOccurenceCounter.put(w, (old == null) ? 1 : old+1);
103 break;
104 }
105 }
106 }
107 }
108 if (wayOccurenceCounter.isEmpty()) {
109 JOptionPane.showMessageDialog(Main.parent,
110 trn("The selected node is not part of any way.",
111 "The selected nodes are not part of any way.", selectedNodes.size()));
112 return;
113 }
114
115 for (Entry<Way, Integer> entry : wayOccurenceCounter.entrySet()) {
116 if (entry.getValue().equals(selectedNodes.size())) {
117 if (selectedWay != null) {
118 JOptionPane.showMessageDialog(Main.parent, tr("There is more than one way using the node(s) you selected. Please select the way also."));
119 return;
120 }
121 selectedWay = entry.getKey();
122 }
123 }
124
125 if (selectedWay == null) {
126 JOptionPane.showMessageDialog(Main.parent, tr("The selected nodes do not share the same way."));
127 return;
128 }
129
130 // If a way and nodes are selected, verify that the nodes are part of the way.
131 } else if (selectedWay != null && selectedNodes != null) {
132
133 HashSet<Node> nds = new HashSet<Node>(selectedNodes);
134 for (Node n : selectedWay.nodes) {
135 nds.remove(n);
136 }
137 if (!nds.isEmpty()) {
138 JOptionPane.showMessageDialog(Main.parent,
139 trn("The selected way does not contain the selected node.",
140 "The selected way does not contain all the selected nodes.", selectedNodes.size()));
141 return;
142 }
143 }
144
145 // and then do the work.
146 splitWay();
147 }
148
149 /**
150 * Checks if the selection consists of something we can work with.
151 * Checks only if the number and type of items selected looks good;
152 * does not check whether the selected items are really a valid
153 * input for splitting (this would be too expensive to be carried
154 * out from the selectionChanged listener).
155 */
156 private boolean checkSelection(Collection<? extends OsmPrimitive> selection) {
157 boolean way = false;
158 boolean node = false;
159 for (OsmPrimitive p : selection) {
160 if (p instanceof Way && !way) {
161 way = true;
162 } else if (p instanceof Node) {
163 node = true;
164 } else {
165 return false;
166 }
167 }
168 return node;
169 }
170
171 /**
172 * Split a way into two or more parts, starting at a selected node.
173 *
174 * FIXME: what do the following "arguments" refer to?
175 * @param way the way to split
176 * @param nodes the node(s) to split the way at; must be part of the way.
177 */
178 private void splitWay() {
179 // We take our way's list of nodes and copy them to a way chunk (a
180 // list of nodes). Whenever we stumble upon a selected node, we start
181 // a new way chunk.
182
183 Set<Node> nodeSet = new HashSet<Node>(selectedNodes);
184 List<List<Node>> wayChunks = new LinkedList<List<Node>>();
185 List<Node> currentWayChunk = new ArrayList<Node>();
186 wayChunks.add(currentWayChunk);
187
188 Iterator<Node> it = selectedWay.nodes.iterator();
189 while (it.hasNext()) {
190 Node currentNode = it.next();
191 boolean atEndOfWay = currentWayChunk.isEmpty() || !it.hasNext();
192 currentWayChunk.add(currentNode);
193 if (nodeSet.contains(currentNode) && !atEndOfWay) {
194 currentWayChunk = new ArrayList<Node>();
195 currentWayChunk.add(currentNode);
196 wayChunks.add(currentWayChunk);
197 }
198 }
199
200 // Handle circular ways specially.
201 // If you split at a circular way at two nodes, you just want to split
202 // it at these points, not also at the former endpoint.
203 // So if the last node is the same first node, join the last and the
204 // first way chunk.
205 List<Node> lastWayChunk = wayChunks.get(wayChunks.size() - 1);
206 if (wayChunks.size() >= 2
207 && wayChunks.get(0).get(0) == lastWayChunk.get(lastWayChunk.size() - 1)
208 && !nodeSet.contains(wayChunks.get(0).get(0))) {
209 lastWayChunk.remove(lastWayChunk.size() - 1);
210 lastWayChunk.addAll(wayChunks.get(0));
211 wayChunks.remove(wayChunks.size() - 1);
212 wayChunks.set(0, lastWayChunk);
213 }
214
215 if (wayChunks.size() < 2) {
216 JOptionPane.showMessageDialog(Main.parent, tr("The way cannot be split at the selected nodes. (Hint: Select nodes in the middle of the way.)"));
217 return;
218 }
219
220 // build a list of commands, and also a new selection list
221 Collection<Command> commandList = new ArrayList<Command>(wayChunks.size());
222 Collection<Way> newSelection = new ArrayList<Way>(wayChunks.size());
223
224 Iterator<List<Node>> chunkIt = wayChunks.iterator();
225
226 // First, change the original way
227 Way changedWay = new Way(selectedWay);
228 changedWay.nodes.clear();
229 changedWay.nodes.addAll(chunkIt.next());
230 commandList.add(new ChangeCommand(selectedWay, changedWay));
231 newSelection.add(selectedWay);
232
233 // Second, create new ways
234 while (chunkIt.hasNext()) {
235 Way wayToAdd = new Way();
236 if (selectedWay.keys != null) {
237 wayToAdd.keys = new HashMap<String, String>(selectedWay.keys);
238 wayToAdd.checkTagged();
239 }
240 wayToAdd.nodes.addAll(chunkIt.next());
241 commandList.add(new AddCommand(wayToAdd));
242 newSelection.add(wayToAdd);
243 }
244
245 NameVisitor v = new NameVisitor();
246 v.visit(selectedWay);
247 Main.main.undoRedo.add(
248 new SequenceCommand(tr("Split way {0} into {1} parts",
249 v.name, wayChunks.size()),
250 commandList));
251 Main.ds.setSelected(newSelection);
252 }
253
254 /**
255 * Enable the "split way" menu option if the selection looks like we could use it.
256 */
257 public void selectionChanged(Collection<? extends OsmPrimitive> newSelection) {
258 setEnabled(checkSelection(newSelection));
259 }
260}
Note: See TracBrowser for help on using the repository browser.