source: josm/trunk/src/org/openstreetmap/josm/data/validation/tests/DuplicateNode.java@ 3715

Last change on this file since 3715 was 3700, checked in by bastiK, 13 years ago

fixed #4742 - validator: do not warn about deleting a "out-of-downloaded-area node" with id:0 when repairing "doubled nodes"

  • Property svn:eol-style set to native
File size: 16.0 KB
Line 
1// License: GPL. See LICENSE file for details.
2package org.openstreetmap.josm.data.validation.tests;
3
4import static org.openstreetmap.josm.tools.I18n.marktr;
5import static org.openstreetmap.josm.tools.I18n.tr;
6
7import java.awt.GridBagLayout;
8import java.awt.geom.Area;
9import java.util.ArrayList;
10import java.util.Collection;
11import java.util.HashMap;
12import java.util.Iterator;
13import java.util.LinkedHashSet;
14import java.util.LinkedList;
15import java.util.List;
16import java.util.Map;
17import java.util.Set;
18
19import javax.swing.JLabel;
20import javax.swing.JOptionPane;
21import javax.swing.JPanel;
22
23import org.openstreetmap.josm.Main;
24import org.openstreetmap.josm.actions.MergeNodesAction;
25import org.openstreetmap.josm.command.Command;
26import org.openstreetmap.josm.data.coor.LatLon;
27import org.openstreetmap.josm.data.osm.Hash;
28import org.openstreetmap.josm.data.osm.Node;
29import org.openstreetmap.josm.data.osm.OsmPrimitive;
30import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
31import org.openstreetmap.josm.data.osm.Storage;
32import org.openstreetmap.josm.data.osm.Way;
33import org.openstreetmap.josm.data.validation.Severity;
34import org.openstreetmap.josm.data.validation.Test;
35import org.openstreetmap.josm.data.validation.TestError;
36import org.openstreetmap.josm.gui.ConditionalOptionPaneUtil;
37import org.openstreetmap.josm.gui.progress.ProgressMonitor;
38import org.openstreetmap.josm.tools.MultiMap;
39
40/**
41 * Tests if there are duplicate nodes
42 *
43 * @author frsantos
44 */
45public class DuplicateNode extends Test {
46
47 private class NodeHash implements Hash<Object, Object> {
48
49 double precision = Main.pref.getDouble("validator.duplicatenodes.precision", 0.);
50
51 private LatLon RoundCoord(Node o) {
52 return new LatLon(
53 Math.round(o.getCoor().lat() / precision) * precision,
54 Math.round(o.getCoor().lon() / precision) * precision
55 );
56 }
57
58 @SuppressWarnings("unchecked")
59 private LatLon getLatLon(Object o) {
60 if (o instanceof Node) {
61 if (precision==0)
62 return ((Node) o).getCoor().getRoundedToOsmPrecision();
63 return RoundCoord((Node) o);
64 } else if (o instanceof List<?>) {
65 if (precision==0)
66 return ((List<Node>) o).get(0).getCoor().getRoundedToOsmPrecision();
67 return RoundCoord(((List<Node>) o).get(0));
68 } else {
69 throw new AssertionError();
70 }
71 }
72
73 @Override
74 public boolean equals(Object k, Object t) {
75 return getLatLon(k).equals(getLatLon(t));
76 }
77
78 @Override
79 public int getHashCode(Object k) {
80 return getLatLon(k).hashCode();
81 }
82 }
83
84 protected static int DUPLICATE_NODE = 1;
85 protected static int DUPLICATE_NODE_MIXED = 2;
86 protected static int DUPLICATE_NODE_OTHER = 3;
87 protected static int DUPLICATE_NODE_BUILDING = 10;
88 protected static int DUPLICATE_NODE_BOUNDARY = 11;
89 protected static int DUPLICATE_NODE_HIGHWAY = 12;
90 protected static int DUPLICATE_NODE_LANDUSE = 13;
91 protected static int DUPLICATE_NODE_NATURAL = 14;
92 protected static int DUPLICATE_NODE_POWER = 15;
93 protected static int DUPLICATE_NODE_RAILWAY = 16;
94 protected static int DUPLICATE_NODE_WATERWAY = 17;
95
96 /** The map of potential duplicates.
97 *
98 * If there is exactly one node for a given pos, the map includes a pair <pos, Node>.
99 * If there are multiple nodes for a given pos, the map includes a pair
100 * <pos, NodesByEqualTagsMap>
101 */
102 Storage<Object> potentialDuplicates;
103
104 /**
105 * Constructor
106 */
107 public DuplicateNode() {
108 super(tr("Duplicated nodes")+".",
109 tr("This test checks that there are no nodes at the very same location."));
110 }
111
112 @Override
113 public void startTest(ProgressMonitor monitor) {
114 super.startTest(monitor);
115 potentialDuplicates = new Storage<Object>(new NodeHash());
116 }
117
118
119 @SuppressWarnings("unchecked")
120 @Override
121 public void endTest() {
122 for (Object v: potentialDuplicates) {
123 if (v instanceof Node) {
124 // just one node at this position. Nothing to report as
125 // error
126 continue;
127 }
128
129 // multiple nodes at the same position -> report errors
130 //
131 List<Node> nodes = (List<Node>) v;
132 errors.addAll(buildTestErrors(this, nodes));
133 }
134 super.endTest();
135 potentialDuplicates = null;
136 }
137
138 public List<TestError> buildTestErrors(Test parentTest, List<Node> nodes) {
139 List<TestError> errors = new ArrayList<TestError>();
140
141 MultiMap<Map<String,String>, OsmPrimitive> mm = new MultiMap<Map<String,String>, OsmPrimitive>();
142 for (Node n: nodes) {
143 mm.put(n.getKeys(), n);
144 }
145
146 Map<String,Boolean> typeMap=new HashMap<String,Boolean>();
147 String[] types = {"none", "highway", "railway", "waterway", "boundary", "power", "natural", "landuse", "building"};
148
149
150 // check whether we have multiple nodes at the same position with
151 // the same tag set
152 //
153 for (Iterator<Map<String,String>> it = mm.keySet().iterator(); it.hasNext();) {
154 Map<String,String> tagSet = it.next();
155 if (mm.get(tagSet).size() > 1) {
156
157 for (String type: types) {
158 typeMap.put(type, false);
159 }
160
161 for (OsmPrimitive p : mm.get(tagSet)) {
162 if (p.getType()==OsmPrimitiveType.NODE) {
163 Node n = (Node) p;
164 List<OsmPrimitive> lp=n.getReferrers();
165 for (OsmPrimitive sp: lp) {
166 if (sp.getType()==OsmPrimitiveType.WAY) {
167 boolean typed = false;
168 Way w=(Way) sp;
169 Map<String, String> keys = w.getKeys();
170 for (String type: typeMap.keySet()) {
171 if (keys.containsKey(type)) {
172 typeMap.put(type, true);
173 typed=true;
174 }
175 }
176 if (!typed) {
177 typeMap.put("none", true);
178 }
179 }
180 }
181
182 }
183 }
184
185 int nbType=0;
186 for (String type: typeMap.keySet()) {
187 if (typeMap.get(type)) {
188 nbType++;
189 }
190 }
191
192 if (nbType>1) {
193 String msg = marktr("Mixed type duplicated nodes");
194 errors.add(new TestError(
195 parentTest,
196 Severity.WARNING,
197 tr("Duplicated nodes"),
198 tr(msg),
199 msg,
200 DUPLICATE_NODE_MIXED,
201 mm.get(tagSet)
202 ));
203 } else if (typeMap.get("highway")) {
204 String msg = marktr("Highway duplicated nodes");
205 errors.add(new TestError(
206 parentTest,
207 Severity.ERROR,
208 tr("Duplicated nodes"),
209 tr(msg),
210 msg,
211 DUPLICATE_NODE_HIGHWAY,
212 mm.get(tagSet)
213 ));
214 } else if (typeMap.get("railway")) {
215 String msg = marktr("Railway duplicated nodes");
216 errors.add(new TestError(
217 parentTest,
218 Severity.ERROR,
219 tr("Duplicated nodes"),
220 tr(msg),
221 msg,
222 DUPLICATE_NODE_RAILWAY,
223 mm.get(tagSet)
224 ));
225 } else if (typeMap.get("waterway")) {
226 String msg = marktr("Waterway duplicated nodes");
227 errors.add(new TestError(
228 parentTest,
229 Severity.ERROR,
230 tr("Duplicated nodes"),
231 tr(msg),
232 msg,
233 DUPLICATE_NODE_WATERWAY,
234 mm.get(tagSet)
235 ));
236 } else if (typeMap.get("boundary")) {
237 String msg = marktr("Boundary duplicated nodes");
238 errors.add(new TestError(
239 parentTest,
240 Severity.ERROR,
241 tr("Duplicated nodes"),
242 tr(msg),
243 msg,
244 DUPLICATE_NODE_BOUNDARY,
245 mm.get(tagSet)
246 ));
247 } else if (typeMap.get("power")) {
248 String msg = marktr("Power duplicated nodes");
249 errors.add(new TestError(
250 parentTest,
251 Severity.ERROR,
252 tr("Duplicated nodes"),
253 tr(msg),
254 msg,
255 DUPLICATE_NODE_POWER,
256 mm.get(tagSet)
257 ));
258 } else if (typeMap.get("natural")) {
259 String msg = marktr("Natural duplicated nodes");
260 errors.add(new TestError(
261 parentTest,
262 Severity.ERROR,
263 tr("Duplicated nodes"),
264 tr(msg),
265 msg,
266 DUPLICATE_NODE_NATURAL,
267 mm.get(tagSet)
268 ));
269 } else if (typeMap.get("building")) {
270 String msg = marktr("Building duplicated nodes");
271 errors.add(new TestError(
272 parentTest,
273 Severity.ERROR,
274 tr("Duplicated nodes"),
275 tr(msg),
276 msg,
277 DUPLICATE_NODE_BUILDING,
278 mm.get(tagSet)
279 ));
280 } else if (typeMap.get("landuse")) {
281 String msg = marktr("Landuse duplicated nodes");
282 errors.add(new TestError(
283 parentTest,
284 Severity.ERROR,
285 tr("Duplicated nodes"),
286 tr(msg),
287 msg,
288 DUPLICATE_NODE_LANDUSE,
289 mm.get(tagSet)
290 ));
291 } else {
292 String msg = marktr("Other duplicated nodes");
293 errors.add(new TestError(
294 parentTest,
295 Severity.WARNING,
296 tr("Duplicated nodes"),
297 tr(msg),
298 msg,
299 DUPLICATE_NODE_OTHER,
300 mm.get(tagSet)
301 ));
302
303 }
304 it.remove();
305 }
306 }
307
308 // check whether we have multiple nodes at the same position with
309 // differing tag sets
310 //
311 if (!mm.isEmpty()) {
312 List<OsmPrimitive> duplicates = new ArrayList<OsmPrimitive>();
313 for (Set<OsmPrimitive> l: mm.values()) {
314 duplicates.addAll(l);
315 }
316 if (duplicates.size() > 1) {
317 errors.add(new TestError(
318 parentTest,
319 Severity.WARNING,
320 tr("Nodes at same position"),
321 DUPLICATE_NODE,
322 duplicates
323 ));
324 }
325 }
326 return errors;
327 }
328
329 @SuppressWarnings("unchecked")
330 @Override
331 public void visit(Node n) {
332 if (n.isUsable()) {
333 if (potentialDuplicates.get(n) == null) {
334 // in most cases there is just one node at a given position. We
335 // avoid to create an extra object and add remember the node
336 // itself at this position
337 potentialDuplicates.put(n);
338 } else if (potentialDuplicates.get(n) instanceof Node) {
339 // we have an additional node at the same position. Create an extra
340 // object to keep track of the nodes at this position.
341 //
342 Node n1 = (Node)potentialDuplicates.get(n);
343 List<Node> nodes = new ArrayList<Node>(2);
344 nodes.add(n1);
345 nodes.add(n);
346 potentialDuplicates.put(nodes);
347 } else if (potentialDuplicates.get(n) instanceof List<?>) {
348 // we have multiple nodes at the same position.
349 //
350 List<Node> nodes = (List<Node>)potentialDuplicates.get(n);
351 nodes.add(n);
352 }
353 }
354 }
355
356 /**
357 * Merge the nodes into one.
358 * Copied from UtilsPlugin.MergePointsAction
359 */
360 @Override
361 public Command fixError(TestError testError) {
362 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>(testError.getPrimitives());
363 LinkedHashSet<Node> nodes = new LinkedHashSet<Node>(OsmPrimitive.getFilteredList(sel, Node.class));
364
365 // Use first existing node or first node if all nodes are new
366 Node target = null;
367 for (Node n: nodes) {
368 if (!n.isNew()) {
369 target = n;
370 break;
371 }
372 }
373 if (target == null) {
374 target = nodes.iterator().next();
375 }
376
377 if(checkAndConfirmOutlyingDeletes(nodes, target))
378 return MergeNodesAction.mergeNodes(Main.main.getEditLayer(), nodes, target);
379
380 return null;// undoRedo handling done in mergeNodes
381 }
382
383 @Override
384 public boolean isFixable(TestError testError) {
385 return (testError.getTester() instanceof DuplicateNode);
386 }
387
388 /**
389 * Check whether user is about to delete data outside of the download area.
390 * Request confirmation if he is.
391 */
392 private static boolean checkAndConfirmOutlyingDeletes(LinkedHashSet<Node> del, Node ignore) {
393 Area a = Main.main.getCurrentDataSet().getDataSourceArea();
394 if (a != null) {
395 for (OsmPrimitive osm : del) {
396 if (osm instanceof Node && !osm.isNew() && osm != ignore) {
397 Node n = (Node) osm;
398 if (!a.contains(n.getCoor())) {
399 JPanel msg = new JPanel(new GridBagLayout());
400 msg.add(new JLabel(
401 "<html>" +
402 // leave message in one tr() as there is a grammatical
403 // connection.
404 tr("You are about to delete nodes outside of the area you have downloaded."
405 + "<br>"
406 + "This can cause problems because other objects (that you do not see) might use them."
407 + "<br>" + "Do you really want to delete?") + "</html>"));
408
409 return ConditionalOptionPaneUtil.showConfirmationDialog(
410 "delete_outside_nodes",
411 Main.parent,
412 msg,
413 tr("Delete confirmation"),
414 JOptionPane.YES_NO_OPTION,
415 JOptionPane.QUESTION_MESSAGE,
416 JOptionPane.YES_OPTION);
417 }
418 }
419 }
420 }
421 return true;
422 }
423}
Note: See TracBrowser for help on using the repository browser.