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

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