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

Last change on this file since 4806 was 4806, checked in by stoecker, 12 years ago

unify texts for validator tests, move command where it belongs

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