source: josm/trunk/src/org/openstreetmap/josm/command/DeleteCommand.java@ 2166

Last change on this file since 2166 was 2166, checked in by stoecker, 15 years ago

see #3475 - patch by Petr Dlouhý - improve speed

  • Property svn:eol-style set to native
File size: 19.7 KB
Line 
1// License: GPL. Copyright 2007 by Immanuel Scholz and others
2package org.openstreetmap.josm.command;
3
4import static org.openstreetmap.josm.tools.I18n.tr;
5import static org.openstreetmap.josm.tools.I18n.trn;
6
7import java.awt.GridBagLayout;
8import java.awt.geom.Area;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.Collections;
12import java.util.HashMap;
13import java.util.HashSet;
14import java.util.Iterator;
15import java.util.LinkedList;
16import java.util.List;
17import java.util.Set;
18
19import javax.swing.JLabel;
20import javax.swing.JOptionPane;
21import javax.swing.JPanel;
22import javax.swing.tree.DefaultMutableTreeNode;
23import javax.swing.tree.MutableTreeNode;
24
25import org.openstreetmap.josm.Main;
26import org.openstreetmap.josm.data.osm.Node;
27import org.openstreetmap.josm.data.osm.OsmPrimitive;
28import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
29import org.openstreetmap.josm.data.osm.Relation;
30import org.openstreetmap.josm.data.osm.RelationMember;
31import org.openstreetmap.josm.data.osm.Way;
32import org.openstreetmap.josm.data.osm.WaySegment;
33import org.openstreetmap.josm.data.osm.visitor.CollectBackReferencesVisitor;
34import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
35import org.openstreetmap.josm.gui.DefaultNameFormatter;
36import org.openstreetmap.josm.gui.ExtendedDialog;
37import org.openstreetmap.josm.gui.layer.OsmDataLayer;
38import org.openstreetmap.josm.tools.ImageProvider;
39
40/**
41 * A command to delete a number of primitives from the dataset.
42 * @author imi
43 */
44public class DeleteCommand extends Command {
45 /**
46 * The primitives that get deleted.
47 */
48 private final Collection<? extends OsmPrimitive> toDelete;
49
50 /**
51 * Constructor for a collection of data
52 */
53 public DeleteCommand(Collection<? extends OsmPrimitive> data) {
54 super();
55 this.toDelete = data;
56 }
57
58 /**
59 * Constructor for a single data item. Use the collection constructor to delete multiple
60 * objects.
61 */
62 public DeleteCommand(OsmPrimitive data) {
63 this.toDelete = Collections.singleton(data);
64 }
65
66 /**
67 * Constructor for a single data item. Use the collection constructor to delete multiple
68 * objects.
69 *
70 * @param layer the layer context for deleting this primitive
71 * @param data the primitive to delete
72 */
73 public DeleteCommand(OsmDataLayer layer, OsmPrimitive data) {
74 super(layer);
75 this.toDelete = Collections.singleton(data);
76 }
77
78 /**
79 * Constructor for a collection of data to be deleted in the context of
80 * a specific layer
81 *
82 * @param layer the layer context for deleting these primitives
83 * @param data the primitives to delete
84 */
85 public DeleteCommand(OsmDataLayer layer, Collection<? extends OsmPrimitive> data) {
86 super(layer);
87 this.toDelete = data;
88 }
89
90 @Override
91 public boolean executeCommand() {
92 super.executeCommand();
93 for (OsmPrimitive osm : toDelete) {
94 osm.setDeleted(true);
95 }
96 return true;
97 }
98
99 @Override
100 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted,
101 Collection<OsmPrimitive> added) {
102 deleted.addAll(toDelete);
103 }
104
105 @Override
106 public MutableTreeNode description() {
107 if (toDelete.size() == 1) {
108 OsmPrimitive primitive = toDelete.iterator().next();
109 String msg = "";
110 switch(OsmPrimitiveType.from(primitive)) {
111 case NODE: msg = "Delete node {0}"; break;
112 case WAY: msg = "Delete way {0}"; break;
113 case RELATION:msg = "Delete relation {0}"; break;
114 }
115
116 return new DefaultMutableTreeNode(new JLabel(tr(msg, primitive.getDisplayName(DefaultNameFormatter.getInstance())),
117 ImageProvider.get(OsmPrimitiveType.from(primitive)), JLabel.HORIZONTAL));
118 }
119
120 Set<OsmPrimitiveType> typesToDelete = new HashSet<OsmPrimitiveType>();
121 for (OsmPrimitive osm : toDelete) {
122 typesToDelete.add(OsmPrimitiveType.from(osm));
123 }
124 String msg = "";
125 String apiname = "object";
126 if (typesToDelete.size() > 1) {
127 msg = trn("Delete {0} object", "Delete {0} objects", toDelete.size(), toDelete.size());
128 } else {
129 OsmPrimitiveType t = typesToDelete.iterator().next();
130 apiname = t.getAPIName();
131 switch(t) {
132 case NODE: msg = trn("Delete {0} node", "Delete {0} nodes", toDelete.size(), toDelete.size()); break;
133 case WAY: msg = trn("Delete {0} way", "Delete {0} ways", toDelete.size(), toDelete.size()); break;
134 case RELATION: msg = trn("Delete {0} relation", "Delete {0} relations", toDelete.size(), toDelete.size()); break;
135 }
136 }
137 DefaultMutableTreeNode root = new DefaultMutableTreeNode(
138 new JLabel(msg, ImageProvider.get("data", apiname), JLabel.HORIZONTAL)
139 );
140 for (OsmPrimitive osm : toDelete) {
141 root.add(new DefaultMutableTreeNode(new JLabel(
142 osm.getDisplayName(DefaultNameFormatter.getInstance()),
143 ImageProvider.get(OsmPrimitiveType.from(osm)), JLabel.HORIZONTAL)));
144 }
145 return root;
146 }
147
148 /**
149 * Delete the primitives and everything they reference.
150 *
151 * If a node is deleted, the node and all ways and relations the node is part of are deleted as
152 * well.
153 *
154 * If a way is deleted, all relations the way is member of are also deleted.
155 *
156 * If a way is deleted, only the way and no nodes are deleted.
157 *
158 * @param layer
159 * @param selection The list of all object to be deleted.
160 * @param simulate Set to true if the user should not be bugged with additional dialogs
161 * @return command A command to perform the deletions, or null of there is nothing to delete.
162 */
163 public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection, boolean simulate) {
164 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data);
165 v.initialize();
166 for (OsmPrimitive osm : selection) {
167 osm.visit(v);
168 }
169 v.getData().addAll(selection);
170 if (v.getData().isEmpty())
171 return null;
172 if (!checkAndConfirmOutlyingDeletes(layer,v.getData()) && !simulate)
173 return null;
174 return new DeleteCommand(layer,v.getData());
175 }
176
177 public static Command deleteWithReferences(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
178 return deleteWithReferences(layer, selection, false);
179 }
180
181 private static int testRelation(Relation ref, OsmPrimitive osm, boolean simulate) {
182 // If this delete action is simulated, do not bug the user with dialogs
183 // and assume the relations should be deleted
184 if(simulate)
185 return 1;
186
187 String role = new String();
188 for (RelationMember m : ref.getMembers()) {
189 if (m.getMember() == osm) {
190 role = m.getRole();
191 break;
192 }
193 }
194 ExtendedDialog dialog = new ExtendedDialog(
195 Main.parent,
196 tr("Conflicting relation"),
197 new String[] { tr("Delete from relation"),tr("Cancel") }
198 );
199 dialog.setButtonIcons( new String[] { "dialogs/delete.png", "cancel.png" });
200 if (role.length() > 0) {
201 dialog.setContent(
202 tr(
203 "<html>Selection \"{0}\" is used by relation \"{1}\" with role {2}.<br>Delete from relation?</html>",
204 osm.getDisplayName(DefaultNameFormatter.getInstance()),
205 ref.getDisplayName(DefaultNameFormatter.getInstance()),
206 role
207 )
208 );
209 dialog.showDialog();
210 return dialog.getValue();
211 } else {
212 dialog.setContent(
213 tr(
214 "<html>Selection \"{0}\" is used by relation \"{1}\".<br>Delete from relation?</html>",
215 osm.getDisplayName(DefaultNameFormatter.getInstance()),
216 ref.getDisplayName(DefaultNameFormatter.getInstance())
217 )
218 );
219 dialog.showDialog();
220 return dialog.getValue();
221 }
222 }
223
224 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection) {
225 return delete(layer, selection, true, false);
226 }
227
228 /**
229 * Replies the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
230 * can be deleted too. A node can be deleted if
231 * <ul>
232 * <li>it is untagged (see {@see Node#isTagged()}</li>
233 * <li>it is not referred to by other primitives outside of <code>primitivesToDelete</code></li>
234 * <ul>
235 * @param layer the layer in whose context primitives are deleted
236 * @param primitivesToDelete the primitives to delete
237 * @return the collection of nodes referred to by primitives in <code>primitivesToDelete</code> which
238 * can be deleted too
239 */
240 protected static Collection<Node> computeNodesToDelete(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
241 Collection<Node> nodesToDelete = new HashSet<Node>();
242 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
243 for (OsmPrimitive osm : primitivesToDelete) {
244 if (! (osm instanceof Way) ) {
245 continue;
246 }
247 for (Node n : ((Way) osm).getNodes()) {
248 if (n.isTagged()) {
249 continue;
250 }
251 v.initialize();
252 n.visit(v);
253 v.getData().removeAll(primitivesToDelete);
254 if (v.getData().isEmpty()) {
255 nodesToDelete.add(n);
256 }
257 }
258 }
259 return nodesToDelete;
260 }
261
262 /**
263 * Try to delete all given primitives.
264 *
265 * If a node is used by a way, it's removed from that way. If a node or a way is used by a
266 * relation, inform the user and do not delete.
267 *
268 * If this would cause ways with less than 2 nodes to be created, delete these ways instead. If
269 * they are part of a relation, inform the user and do not delete.
270 *
271 * @param layer the {@see OsmDataLayer} in whose context a primitive the primitives are deleted
272 * @param selection The objects to delete.
273 * @param alsoDeleteNodesInWay <code>true</code> if nodes should be deleted as well
274 * @param simulate Set to true if the user should not be bugged with additional questions
275 * @return command a command to perform the deletions, or null if there is nothing to delete.
276 */
277 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
278 boolean alsoDeleteNodesInWay) {
279 return delete(layer, selection, alsoDeleteNodesInWay, false);
280 }
281
282 public static Command delete(OsmDataLayer layer, Collection<? extends OsmPrimitive> selection,
283 boolean alsoDeleteNodesInWay, boolean simulate) {
284 if (selection.isEmpty())
285 return null;
286
287 Collection<OsmPrimitive> primitivesToDelete = new HashSet<OsmPrimitive>(selection);
288 Collection<Way> waysToBeChanged = new HashSet<Way>();
289 HashMap<OsmPrimitive, Collection<OsmPrimitive>> relationsToBeChanged = new HashMap<OsmPrimitive, Collection<OsmPrimitive>>();
290
291 if (alsoDeleteNodesInWay) {
292 // delete untagged nodes only referenced by primitives in primitivesToDelete,
293 // too
294 Collection<Node> nodesToDelete = computeNodesToDelete(layer, primitivesToDelete);
295 primitivesToDelete.addAll(nodesToDelete);
296 }
297
298 if (!simulate && !checkAndConfirmOutlyingDeletes(layer,primitivesToDelete))
299 return null;
300
301 CollectBackReferencesVisitor v = new CollectBackReferencesVisitor(layer.data, false);
302 for (OsmPrimitive osm : primitivesToDelete) {
303 v.initialize();
304 osm.visit(v);
305 for (OsmPrimitive ref : v.getData()) {
306 if (primitivesToDelete.contains(ref)) {
307 continue;
308 }
309 if (ref instanceof Way) {
310 waysToBeChanged.add((Way) ref);
311 } else if (ref instanceof Relation) {
312 if (testRelation((Relation) ref, osm, simulate) == 1) {
313 Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
314 if (relset == null) {
315 relset = new HashSet<OsmPrimitive>();
316 }
317 relset.add(osm);
318 relationsToBeChanged.put(ref, relset);
319 } else
320 return null;
321 } else
322 return null;
323 }
324 }
325
326 Collection<Command> cmds = new LinkedList<Command>();
327 for (Way w : waysToBeChanged) {
328 Way wnew = new Way(w);
329 wnew.removeNodes(primitivesToDelete);
330 if (wnew.getNodesCount() < 2) {
331 primitivesToDelete.add(w);
332
333 v.initialize();
334 w.visit(v);
335 for (OsmPrimitive ref : v.getData()) {
336 if (primitivesToDelete.contains(ref)) {
337 continue;
338 }
339 if (ref instanceof Relation) {
340 Boolean found = false;
341 Collection<OsmPrimitive> relset = relationsToBeChanged.get(ref);
342 if (relset == null) {
343 relset = new HashSet<OsmPrimitive>();
344 } else {
345 for (OsmPrimitive m : relset) {
346 if (m == w) {
347 found = true;
348 break;
349 }
350 }
351 }
352 if (!found) {
353 if (testRelation((Relation) ref, w, simulate) == 1) {
354 relset.add(w);
355 relationsToBeChanged.put(ref, relset);
356 } else
357 return null;
358 }
359 } else
360 return null;
361 }
362 } else {
363 cmds.add(new ChangeCommand(w, wnew));
364 }
365 }
366
367 Iterator<OsmPrimitive> iterator = relationsToBeChanged.keySet().iterator();
368 while (iterator.hasNext()) {
369 Relation cur = (Relation) iterator.next();
370 Relation rel = new Relation(cur);
371 for (OsmPrimitive osm : relationsToBeChanged.get(cur)) {
372 rel.removeMembersFor(osm);
373 }
374 cmds.add(new ChangeCommand(cur, rel));
375 }
376
377 // #2707: ways to be deleted can include new nodes (with node.id == 0).
378 // Remove them from the way before the way is deleted. Otherwise the
379 // deleted way is saved (or sent to the API) with a dangling reference to a node
380 // Example:
381 // <node id='2' action='delete' visible='true' version='1' ... />
382 // <node id='1' action='delete' visible='true' version='1' ... />
383 // <!-- missing node with id -1 because new deleted nodes are not persisted -->
384 // <way id='3' action='delete' visible='true' version='1'>
385 // <nd ref='1' />
386 // <nd ref='-1' /> <!-- heres the problem -->
387 // <nd ref='2' />
388 // </way>
389 for (OsmPrimitive primitive : primitivesToDelete) {
390 if (!(primitive instanceof Way)) {
391 continue;
392 }
393 Way w = (Way) primitive;
394 if (w.getId() == 0) { // new ways with id == 0 are fine,
395 continue; // process existing ways only
396 }
397 Way wnew = new Way(w);
398 List<Node> nodesToKeep = new ArrayList<Node>();
399 // lookup new nodes which have been added to the set of deleted
400 // nodes ...
401 for (Node n : wnew.getNodes()) {
402 if (n.getId() != 0 || !primitivesToDelete.contains(n)) {
403 nodesToKeep.add(n);
404 }
405 }
406 // .. and remove them from the way
407 //
408 wnew.setNodes(nodesToKeep);
409 if (nodesToKeep.size() < w.getNodesCount()) {
410 cmds.add(new ChangeCommand(w, wnew));
411 }
412 }
413
414 if (!primitivesToDelete.isEmpty()) {
415 cmds.add(new DeleteCommand(layer,primitivesToDelete));
416 }
417
418 return new SequenceCommand(tr("Delete"), cmds);
419 }
420
421 public static Command deleteWaySegment(OsmDataLayer layer, WaySegment ws) {
422 List<Node> n1 = new ArrayList<Node>(), n2 = new ArrayList<Node>();
423
424 n1.addAll(ws.way.getNodes().subList(0, ws.lowerIndex + 1));
425 n2.addAll(ws.way.getNodes().subList(ws.lowerIndex + 1, ws.way.getNodesCount()));
426
427 if (n1.size() < 2 && n2.size() < 2)
428 return new DeleteCommand(layer, Collections.singleton(ws.way));
429
430 Way wnew = new Way(ws.way);
431
432 if (n1.size() < 2) {
433 wnew.setNodes(n2);
434 return new ChangeCommand(ws.way, wnew);
435 } else if (n2.size() < 2) {
436 wnew.setNodes(n1);
437 return new ChangeCommand(ws.way, wnew);
438 } else {
439 Collection<Command> cmds = new LinkedList<Command>();
440
441 wnew.setNodes(n1);
442 cmds.add(new ChangeCommand(ws.way, wnew));
443
444 Way wnew2 = new Way();
445 wnew2.setKeys(wnew.getKeys());
446 wnew2.setNodes(n2);
447 cmds.add(new AddCommand(wnew2));
448
449 return new SequenceCommand(tr("Split way segment"), cmds);
450 }
451 }
452
453 /**
454 * Check whether user is about to delete data outside of the download area. Request confirmation
455 * if he is.
456 *
457 * @param layer the layer in whose context data is deleted
458 * @param primitivesToDelete the primitives to delete
459 * @return true, if deleting outlying primitives is OK; false, otherwise
460 */
461 private static boolean checkAndConfirmOutlyingDeletes(OsmDataLayer layer, Collection<OsmPrimitive> primitivesToDelete) {
462 Area a = layer.data.getDataSourceArea();
463 if (a != null) {
464 for (OsmPrimitive osm : primitivesToDelete) {
465 if (osm instanceof Node && osm.getId() != 0) {
466 Node n = (Node) osm;
467 if (!a.contains(n.getCoor())) {
468 JPanel msg = new JPanel(new GridBagLayout());
469 msg.add(new JLabel(
470 "<html>" +
471 // leave message in one tr() as there is a grammatical
472 // connection.
473 tr("You are about to delete nodes outside of the area you have downloaded."
474 + "<br>"
475 + "This can cause problems because other objects (that you don't see) might use them."
476 + "<br>" + "Do you really want to delete?") + "</html>"));
477 return ConditionalOptionPaneUtil.showConfirmationDialog(
478 "delete_outside_nodes",
479 Main.parent,
480 msg,
481 tr("Delete confirmation"),
482 JOptionPane.YES_NO_OPTION,
483 JOptionPane.QUESTION_MESSAGE,
484 JOptionPane.YES_OPTION
485 );
486 }
487 }
488 }
489 }
490 return true;
491 }
492}
Note: See TracBrowser for help on using the repository browser.