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

Last change on this file since 11268 was 11268, checked in by simon04, 7 years ago

fix #13995 - Duplicate nodes validator warnings are not translated

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