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

Last change on this file since 9072 was 9072, checked in by Don-vip, 8 years ago

partial revert of r9070 - make build run with Java 9 again

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