source: josm/src/org/openstreetmap/josm/actions/mapmode/DeleteAction.java@ 22

Last change on this file since 22 was 22, checked in by imi, 19 years ago

starting restructure of dataset. Checkpoint is broken!

File size: 12.6 KB
Line 
1package org.openstreetmap.josm.actions.mapmode;
2
3import java.awt.event.KeyEvent;
4import java.awt.event.MouseEvent;
5import java.util.ArrayList;
6import java.util.HashMap;
7import java.util.LinkedList;
8import java.util.Map;
9
10import javax.swing.JOptionPane;
11
12import org.openstreetmap.josm.Main;
13import org.openstreetmap.josm.data.osm.Key;
14import org.openstreetmap.josm.data.osm.LineSegment;
15import org.openstreetmap.josm.data.osm.Node;
16import org.openstreetmap.josm.data.osm.OsmPrimitive;
17import org.openstreetmap.josm.data.osm.Track;
18import org.openstreetmap.josm.gui.MapFrame;
19
20/**
21 * An action that enables the user to delete nodes and other objects.
22 *
23 * The user can click on an object, which get deleted if possible. When Ctrl is
24 * pressed when releasing the button, the objects and all its references are
25 * deleted as well. The exact definition of "all its references" are in
26 * @see #deleteWithReferences(OsmPrimitive)
27 *
28 * Pressing Alt will select the track instead of a line segment, as usual.
29 *
30 * If the user presses Ctrl, no combining is possible. Otherwise, DeleteAction
31 * tries to combine the referencing objects as follows:
32 *
33 * If a node is part of exactly two line segments from a track, the two line
34 * segments are combined into one. The first line segment spans now to the end
35 * of the second and the second line segment gets deleted. This is checked for
36 * every track.
37 *
38 * If a node is the end of the ending line segment of one track and the start of
39 * exactly one other tracks start segment, the tracks are combined into one track,
40 * deleting the second track and keeping the first one. The ending line segment
41 * of the fist track is combined with the starting line segment of the second
42 * track.
43 *
44 * Combining is only possible, if both objects that should be combined have no
45 * key with a different property value. The remaining keys are merged together.
46 *
47 * If a node is part of an area with more than 3 nodes, the node is removed from
48 * the area and the area has now one fewer node.
49 *
50 * If combining fails, the node has still references and the user did not hold
51 * Ctrl down, the deleting fails, the action informs the user and nothing is
52 * deleted.
53 *
54 *
55 * If the user enters the mapmode and any object is selected, all selected
56 * objects get deleted. Combining applies to the selected objects.
57 *
58 * @author imi
59 */
60public class DeleteAction extends MapMode {
61
62 /**
63 * Construct a new DeleteAction. Mnemonic is the delete - key.
64 * @param mapFrame The frame this action belongs to.
65 */
66 public DeleteAction(MapFrame mapFrame) {
67 super("Delete", "delete", "Delete nodes, streets or areas.", KeyEvent.VK_DELETE, mapFrame);
68 }
69
70 @Override
71 public void registerListener() {
72 super.registerListener();
73 mv.addMouseListener(this);
74 }
75
76 @Override
77 public void unregisterListener() {
78 super.unregisterListener();
79 mv.removeMouseListener(this);
80 }
81
82 /**
83 * If user clicked with the left button, delete the nearest object.
84 * position.
85 */
86 @Override
87 public void mouseClicked(MouseEvent e) {
88 if (e.getButton() != MouseEvent.BUTTON1)
89 return;
90
91 OsmPrimitive sel = mv.getNearest(e.getPoint(), (e.getModifiersEx() & MouseEvent.ALT_DOWN_MASK) != 0);
92 if (sel == null)
93 return;
94
95 if ((e.getModifiersEx() & MouseEvent.CTRL_DOWN_MASK) != 0)
96 deleteWithReferences(sel);
97 else
98 delete(sel);
99
100 mv.repaint();
101 }
102
103 /**
104 * Delete the primitive and everything it references or beeing directly
105 * referenced by, except of nodes which are deleted only if passed
106 * directly or become unreferenced while deleting other objects.
107 *
108 * Nothing is combined as in @see #delete(OsmPrimitive).
109 *
110 * Example (A is a track of line segment a and b. z is a node):
111 *
112 * A
113 * B x z
114 * -----*--------+-----
115 * | a b
116 * |C
117 * |
118 * *y
119 *
120 * If you delete C, C and y (since now unreferenced) gets deleted.
121 * If you delete A, then A, a, b and z (since now unreferenced) gets deleted.
122 * If you delete y, then y and C gets deleted.
123 * TODO If you delete x, then a,B,C and x gets deleted. A now consist of b only.
124 * If you delete a or b, then A, a, b and z gets deleted.
125 *
126 * @param osm The object to delete.
127 */
128 private void deleteWithReferences(OsmPrimitive osm) {
129 // collect all tracks, areas and pending line segments that should be deleted
130 ArrayList<Track> tracksToDelete = new ArrayList<Track>();
131 ArrayList<LineSegment> lineSegmentsToDelete = new ArrayList<LineSegment>();
132
133 if (osm instanceof Node) {
134 // delete any track and line segment the node is in.
135 for (Track t : Main.main.ds.tracks())
136 for (LineSegment ls : t.segments())
137 if (ls.getStart() == osm || ls.getEnd() == osm)
138 tracksToDelete.add(t);
139 for (LineSegment ls : Main.main.ds.pendingLineSegments())
140 if (ls.getStart() == osm || ls.getEnd() == osm)
141 lineSegmentsToDelete.add(ls);
142
143 } else if (osm instanceof LineSegment) {
144 LineSegment lineSegment = (LineSegment)osm;
145 lineSegmentsToDelete.add(lineSegment);
146 for (Track t : Main.main.ds.tracks())
147 for (LineSegment ls : t.segments())
148 if (lineSegment == ls)
149 tracksToDelete.add(t);
150 } else if (osm instanceof Track) {
151 tracksToDelete.add((Track)osm);
152 }
153 // collect all nodes, that could be unreferenced after deletion
154 ArrayList<Node> checkUnreferencing = new ArrayList<Node>();
155 for (Track t : tracksToDelete) {
156 for (LineSegment ls : t.segments()) {
157 checkUnreferencing.add(ls.getStart());
158 checkUnreferencing.add(ls.getEnd());
159 }
160 }
161 for (LineSegment ls : lineSegmentsToDelete) {
162 checkUnreferencing.add(ls.getStart());
163 checkUnreferencing.add(ls.getEnd());
164 }
165
166 // delete tracks and areas
167 for (Track t : tracksToDelete)
168 Main.main.ds.removeTrack(t);
169 for (LineSegment ls : lineSegmentsToDelete)
170 Main.main.ds.destroyPendingLineSegment(ls);
171
172 // removing all unreferenced nodes
173 for (Node n : checkUnreferencing) {
174 if (!isReferenced(n))
175 Main.main.ds.nodes.remove(n);
176 }
177 // now, all references are killed. Delete the node (if it was a node)
178 if (osm instanceof Node)
179 Main.main.ds.nodes.remove(osm);
180 }
181
182 /**
183 * Try to delete the given primitive. If the primitive is a node and
184 * used somewhere, try to combine the references to make the node unused.
185 * If this fails, inform the user and do not delete.
186 *
187 * @param osm The object to delete.
188 */
189 private void delete(OsmPrimitive osm) {
190 if (osm instanceof Node) {
191 Node n = (Node)osm;
192 if (isReferenced(n)) {
193 String combined = combine(n);
194 if (combined != null) {
195 JOptionPane.showMessageDialog(Main.main, combined);
196 return;
197 }
198 }
199 // now, the node isn't referenced anymore, so delete it.
200 Main.main.ds.nodes.remove(n);
201 } else if (osm instanceof LineSegment) {
202 LinkedList<Track> tracksToDelete = new LinkedList<Track>();
203 for (Track t : Main.main.ds.tracks()) {
204 t.remove((LineSegment)osm);
205 if (t.segments().isEmpty())
206 tracksToDelete.add(t);
207 }
208 for (Track t : tracksToDelete)
209 Main.main.ds.removeTrack(t);
210 Main.main.ds.destroyPendingLineSegment((LineSegment)osm);
211 } else if (osm instanceof Track) {
212 Main.main.ds.removeTrack((Track)osm);
213 for (LineSegment ls : ((Track)osm).segments())
214 Main.main.ds.addPendingLineSegment(ls);
215 }
216 }
217
218
219 /**
220 * Return <code>true</code>, if the node is used by anything in the map.
221 * @param n The node to check.
222 * @return Whether the node is used by a track or area.
223 */
224 private boolean isReferenced(Node n) {
225 for (Track t : Main.main.ds.tracks())
226 for (LineSegment ls : t.segments())
227 if (ls.getStart() == n || ls.getEnd() == n)
228 return true;
229 for (LineSegment ls : Main.main.ds.pendingLineSegments())
230 if (ls.getStart() == n || ls.getEnd() == n)
231 return true;
232 // TODO areas
233 return false;
234 }
235
236 /**
237 * Try to combine all objects when deleting the node n. If combining is not
238 * possible, return an error string why. Otherwise, combine it and return
239 * <code>null</code>.
240 *
241 * @param n The node that is going to be deleted.
242 * @return <code>null</code> if combining suceded or an error string if there
243 * are problems combining the node.
244 */
245 private String combine(Node n) {
246 // first, check for pending line segments
247 for (LineSegment ls : Main.main.ds.pendingLineSegments())
248 if (n == ls.getStart() || n == ls.getEnd())
249 return "Node used by a line segment which is not part of any track. Remove this first.";
250
251 // These line segments must be combined within the track combining
252 ArrayList<LineSegment> pendingLineSegmentsForTrack = new ArrayList<LineSegment>();
253
254 // try to combine line segments
255
256 // These line segments are combinable. The inner arraylist has always
257 // two elements. The keys maps to the track, the line segments are in.
258 HashMap<ArrayList<LineSegment>, Track> lineSegments = new HashMap<ArrayList<LineSegment>, Track>();
259
260 for (Track t : Main.main.ds.tracks()) {
261 ArrayList<LineSegment> current = new ArrayList<LineSegment>();
262 for (LineSegment ls : t.segments())
263 if (ls.getStart() == n || ls.getEnd() == n)
264 current.add(ls);
265 if (!current.isEmpty()) {
266 if (current.size() > 2)
267 return "Node used by more than two line segments.";
268 if (current.size() == 1 &&
269 (current.get(0) == t.getStartingSegment() || current.get(0) == t.getEndingSegment()))
270 pendingLineSegmentsForTrack.add(current.get(0));
271 else if (current.get(0).getEnd() != current.get(1).getStart() &&
272 current.get(1).getEnd() != current.get(0).getStart())
273 return "Node used by line segments that points together.";
274 else if (!current.get(0).keyPropertiesMergable(current.get(1)))
275 return "Node used by line segments with different properties.";
276 else
277 lineSegments.put(current, t);
278 }
279 }
280
281 // try to combine tracks
282 ArrayList<Track> tracks = new ArrayList<Track>();
283 for (Track t : Main.main.ds.tracks())
284 if (t.getStartingNode() == n || t.getEndingNode() == n)
285 tracks.add(t);
286 if (!tracks.isEmpty()) {
287 if (tracks.size() > 2)
288 return "Node used by more than two tracks.";
289 if (tracks.size() == 1)
290 return "Node used by a track.";
291 Track t1 = tracks.get(0);
292 Track t2 = tracks.get(1);
293 if (t1.getStartingNode() != t2.getEndingNode() &&
294 t2.getStartingNode() != t1.getEndingNode()) {
295 if (t1.getStartingNode() == t2.getStartingNode() ||
296 t1.getEndingNode() == t2.getEndingNode())
297 return "Node used by tracks that point together.";
298 return "Node used by tracks that cannot be combined.";
299 }
300 if (!t1.keyPropertiesMergable(t2))
301 return "Node used by tracks with different properties.";
302 }
303
304 // try to match the pending line segments
305 if (pendingLineSegmentsForTrack.size() == 2) {
306 LineSegment l1 = pendingLineSegmentsForTrack.get(0);
307 LineSegment l2 = pendingLineSegmentsForTrack.get(1);
308 if (l1.getStart() == l2.getStart() || l1.getEnd() == l2.getEnd())
309 return "Node used by line segments that points together.";
310 if (l1.getStart() == l2.getEnd() || l2.getStart() == l1.getEnd())
311 pendingLineSegmentsForTrack.clear(); // resolved.
312 }
313
314 // still pending line segments?
315 if (!pendingLineSegmentsForTrack.isEmpty())
316 return "Node used by tracks that cannot be combined.";
317
318 // Ok, we can combine. Do it.
319 // line segments
320 for (ArrayList<LineSegment> list : lineSegments.keySet()) {
321 LineSegment first = list.get(0);
322 LineSegment second = list.get(1);
323 if (first.getStart() == second.getEnd()) {
324 first = second;
325 second = list.get(0);
326 }
327 first.setEnd(second.getEnd());
328 first.keys = mergeKeys(first.keys, second.keys);
329 lineSegments.get(list).remove(second);
330 }
331
332 // tracks
333 if (!tracks.isEmpty()) {
334 Track first = tracks.get(0);
335 Track second = tracks.get(1);
336 if (first.getStartingNode() == second.getEndingNode()) {
337 first = second;
338 second = tracks.get(0);
339 }
340 // concatenate the line segments.
341 LineSegment lastOfFirst = first.getEndingSegment();
342 LineSegment firstOfSecond = second.getStartingSegment();
343 lastOfFirst.setEnd(firstOfSecond.getEnd());
344 lastOfFirst.keys = mergeKeys(lastOfFirst.keys, firstOfSecond.keys);
345 second.remove(firstOfSecond);
346 // move the remaining line segments to first track.
347 first.addAll(second.segments());
348 Main.main.ds.removeTrack(second);
349 }
350
351 return null;
352 }
353
354 /**
355 * Merges the second parameter into the first and return the merged map.
356 * @param first The first map that will hold keys.
357 * @param second The map to merge with the first.
358 * @return The merged key map.
359 */
360 private Map<Key, String> mergeKeys(Map<Key, String> first, Map<Key, String> second) {
361 if (first == null)
362 first = second;
363 else if (second != null && first != null)
364 first.putAll(second);
365 return first;
366 }
367
368 @Override
369 protected boolean isEditMode() {
370 return true;
371 }
372}
Note: See TracBrowser for help on using the repository browser.