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

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

added mapmodes for adding and combining stuff. Reorganized images

File size: 12.2 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.Iterator;
8import java.util.Map;
9
10import javax.swing.JOptionPane;
11
12import org.openstreetmap.josm.data.osm.Key;
13import org.openstreetmap.josm.data.osm.LineSegment;
14import org.openstreetmap.josm.data.osm.Node;
15import org.openstreetmap.josm.data.osm.OsmPrimitive;
16import org.openstreetmap.josm.data.osm.Track;
17import org.openstreetmap.josm.gui.Main;
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 : ds.tracks)
136 for (LineSegment ls : t.segments)
137 if (ls.start == osm || ls.end == osm)
138 tracksToDelete.add(t);
139 for (LineSegment ls : ds.pendingLineSegments)
140 if (ls.start == osm || ls.end == 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 : 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.start);
158 checkUnreferencing.add(ls.end);
159 }
160 }
161 for (LineSegment ls : lineSegmentsToDelete) {
162 checkUnreferencing.add(ls.start);
163 checkUnreferencing.add(ls.end);
164 }
165
166 // delete tracks and areas
167 ds.tracks.removeAll(tracksToDelete);
168 ds.pendingLineSegments.removeAll(lineSegmentsToDelete);
169
170 // removing all unreferenced nodes
171 for (Node n : checkUnreferencing) {
172 if (!isReferenced(n))
173 ds.nodes.remove(n);
174 }
175 // now, all references are killed. Delete the node (if it was a node)
176 if (osm instanceof Node)
177 ds.nodes.remove(osm);
178 }
179
180 /**
181 * Try to delete the given primitive. If the primitive is a node and
182 * used somewhere, try to combine the references to make the node unused.
183 * If this fails, inform the user and do not delete.
184 *
185 * @param osm The object to delete.
186 */
187 private void delete(OsmPrimitive osm) {
188 if (osm instanceof Node) {
189 Node n = (Node)osm;
190 if (isReferenced(n)) {
191 String combined = combine(n);
192 if (combined != null) {
193 JOptionPane.showMessageDialog(Main.main, combined);
194 return;
195 }
196 }
197 // now, the node isn't referenced anymore, so delete it.
198 ds.nodes.remove(n);
199 } else if (osm instanceof LineSegment) {
200 for (Iterator<Track> it = ds.tracks.iterator(); it.hasNext();) {
201 Track t = it.next();
202 t.segments.remove(osm);
203 if (t.segments.isEmpty())
204 it.remove();
205 }
206 ds.pendingLineSegments.remove(osm);
207 } else if (osm instanceof Track) {
208 ds.tracks.remove(osm);
209 ds.pendingLineSegments.addAll(((Track)osm).segments);
210 }
211 }
212
213
214 /**
215 * Return <code>true</code>, if the node is used by anything in the map.
216 * @param n The node to check.
217 * @return Whether the node is used by a track or area.
218 */
219 private boolean isReferenced(Node n) {
220 for (Track t : ds.tracks)
221 for (LineSegment ls : t.segments)
222 if (ls.start == n || ls.end == n)
223 return true;
224 for (LineSegment ls : ds.pendingLineSegments)
225 if (ls.start == n || ls.end == n)
226 return true;
227 // TODO areas
228 return false;
229 }
230
231 /**
232 * Try to combine all objects when deleting the node n. If combining is not
233 * possible, return an error string why. Otherwise, combine it and return
234 * <code>null</code>.
235 *
236 * @param n The node that is going to be deleted.
237 * @return <code>null</code> if combining suceded or an error string if there
238 * are problems combining the node.
239 */
240 private String combine(Node n) {
241 // first, check for pending line segments
242 for (LineSegment ls : ds.pendingLineSegments)
243 if (n == ls.start || n == ls.end)
244 return "Node used by a line segment which is not part of any track. Remove this first.";
245
246 // These line segments must be combined within the track combining
247 ArrayList<LineSegment> pendingLineSegmentsForTrack = new ArrayList<LineSegment>();
248
249 // try to combine line segments
250
251 // These line segments are combinable. The inner arraylist has always
252 // two elements. The keys maps to the track, the line segments are in.
253 HashMap<ArrayList<LineSegment>, Track> lineSegments = new HashMap<ArrayList<LineSegment>, Track>();
254
255 for (Track t : ds.tracks) {
256 ArrayList<LineSegment> current = new ArrayList<LineSegment>();
257 for (LineSegment ls : t.segments)
258 if (ls.start == n || ls.end == n)
259 current.add(ls);
260 if (!current.isEmpty()) {
261 if (current.size() > 2)
262 return "Node used by more than two line segments.";
263 if (current.size() == 1 &&
264 (current.get(0) == t.segments.get(0) || current.get(0) == t.segments.get(t.segments.size()-1)))
265 pendingLineSegmentsForTrack.add(current.get(0));
266 else if (current.get(0).end != current.get(1).start &&
267 current.get(1).end != current.get(0).start)
268 return "Node used by line segments that points together.";
269 else if (!current.get(0).keyPropertiesMergable(current.get(1)))
270 return "Node used by line segments with different properties.";
271 else
272 lineSegments.put(current, t);
273 }
274 }
275
276 // try to combine tracks
277 ArrayList<Track> tracks = new ArrayList<Track>();
278 for (Track t : ds.tracks)
279 if (!t.segments.isEmpty() && (t.segments.get(0).start == n || t.segments.get(t.segments.size()-1).end == n))
280 tracks.add(t);
281 if (!tracks.isEmpty()) {
282 if (tracks.size() > 2)
283 return "Node used by more than two tracks.";
284 if (tracks.size() == 1)
285 return "Node used by a track.";
286 Track t1 = tracks.get(0);
287 Track t2 = tracks.get(1);
288 if (t1.segments.get(0).start != t2.segments.get(t2.segments.size()-1).end &&
289 t2.segments.get(0).start != t1.segments.get(t1.segments.size()-1).end) {
290 if (t1.segments.get(0).start == t2.segments.get(t2.segments.size()-1).start ||
291 t1.segments.get(0).end == t2.segments.get(t2.segments.size()-1).end)
292 return "Node used by tracks that point together.";
293 return "Node used by tracks that cannot be combined.";
294 }
295 if (!t1.keyPropertiesMergable(t2))
296 return "Node used by tracks with different properties.";
297 }
298
299 // try to match the pending line segments
300 if (pendingLineSegmentsForTrack.size() == 2) {
301 LineSegment l1 = pendingLineSegmentsForTrack.get(0);
302 LineSegment l2 = pendingLineSegmentsForTrack.get(1);
303 if (l1.start == l2.start || l1.end == l2.end)
304 return "Node used by line segments that points together.";
305 if (l1.start == l2.end || l2.start == l1.end)
306 pendingLineSegmentsForTrack.clear(); // resolved.
307 }
308
309 // still pending line segments?
310 if (!pendingLineSegmentsForTrack.isEmpty())
311 return "Node used by tracks that cannot be combined.";
312
313 // Ok, we can combine. Do it.
314 // line segments
315 for (ArrayList<LineSegment> list : lineSegments.keySet()) {
316 LineSegment first = list.get(0);
317 LineSegment second = list.get(1);
318 if (first.start == second.end) {
319 first = second;
320 second = list.get(0);
321 }
322 first.end = second.end;
323 first.keys = mergeKeys(first.keys, second.keys);
324 lineSegments.get(list).segments.remove(second);
325 }
326
327 // tracks
328 if (!tracks.isEmpty()) {
329 Track first = tracks.get(0);
330 Track second = tracks.get(1);
331 if (first.segments.get(0).start == second.segments.get(second.segments.size()-1).end) {
332 first = second;
333 second = tracks.get(0);
334 }
335 // concatenate the line segments.
336 LineSegment lastOfFirst = first.segments.get(first.segments.size()-1);
337 LineSegment firstOfSecond = second.segments.get(0);
338 lastOfFirst.end = firstOfSecond.end;
339 lastOfFirst.keys = mergeKeys(lastOfFirst.keys, firstOfSecond.keys);
340 second.segments.remove(firstOfSecond);
341 // move the remaining line segments to first track.
342 first.segments.addAll(second.segments);
343 ds.tracks.remove(second);
344 }
345
346 return null;
347 }
348
349 /**
350 * Merges the second parameter into the first and return the merged map.
351 * @param first The first map that will hold keys.
352 * @param second The map to merge with the first.
353 * @return The merged key map.
354 */
355 private Map<Key, String> mergeKeys(Map<Key, String> first, Map<Key, String> second) {
356 if (first == null)
357 first = second;
358 else if (second != null && first != null)
359 first.putAll(second);
360 return first;
361 }
362}
Note: See TracBrowser for help on using the repository browser.