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

Last change on this file since 4058 was 4043, checked in by stoecker, 13 years ago

fix #6153 - spelling fixes

  • Property svn:eol-style set to native
File size: 17.1 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 @Override
73 public boolean equals(Object k, Object t) {
74 return getLatLon(k).equals(getLatLon(t));
75 }
76
77 @Override
78 public int getHashCode(Object k) {
79 return getLatLon(k).hashCode();
80 }
81 }
82
83 protected static int DUPLICATE_NODE = 1;
84 protected static int DUPLICATE_NODE_MIXED = 2;
85 protected static int DUPLICATE_NODE_OTHER = 3;
86 protected static int DUPLICATE_NODE_UNCLOSED = 4;
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 boolean oneWayClosed = false;
158
159 for (String type: types) {
160 typeMap.put(type, false);
161 }
162
163 for (OsmPrimitive p : mm.get(tagSet)) {
164 if (p.getType()==OsmPrimitiveType.NODE) {
165 Node n = (Node) p;
166 List<OsmPrimitive> lp=n.getReferrers();
167 for (OsmPrimitive sp: lp) {
168 if (sp.getType()==OsmPrimitiveType.WAY) {
169 boolean typed = false;
170 Way w=(Way) sp;
171 oneWayClosed = oneWayClosed || w.isClosed();
172 Map<String, String> keys = w.getKeys();
173 for (String type: typeMap.keySet()) {
174 if (keys.containsKey(type)) {
175 typeMap.put(type, true);
176 typed=true;
177 }
178 }
179 if (!typed) {
180 typeMap.put("none", true);
181 }
182 }
183 }
184
185 }
186 }
187
188 int nbType=0;
189 for (String type: typeMap.keySet()) {
190 if (typeMap.get(type)) {
191 nbType++;
192 }
193 }
194
195 if (!oneWayClosed) {
196 String msg = marktr("Duplicate nodes in two un-closed ways");
197 errors.add(new TestError(
198 parentTest,
199 Severity.WARNING,
200 tr("Duplicated nodes"),
201 tr(msg),
202 msg,
203 DUPLICATE_NODE_UNCLOSED,
204 mm.get(tagSet)
205 ));
206 } else if (nbType>1) {
207 String msg = marktr("Mixed type duplicated nodes");
208 errors.add(new TestError(
209 parentTest,
210 Severity.WARNING,
211 tr("Duplicated nodes"),
212 tr(msg),
213 msg,
214 DUPLICATE_NODE_MIXED,
215 mm.get(tagSet)
216 ));
217 } else if (typeMap.get("highway")) {
218 String msg = marktr("Highway duplicated nodes");
219 errors.add(new TestError(
220 parentTest,
221 Severity.ERROR,
222 tr("Duplicated nodes"),
223 tr(msg),
224 msg,
225 DUPLICATE_NODE_HIGHWAY,
226 mm.get(tagSet)
227 ));
228 } else if (typeMap.get("railway")) {
229 String msg = marktr("Railway duplicated nodes");
230 errors.add(new TestError(
231 parentTest,
232 Severity.ERROR,
233 tr("Duplicated nodes"),
234 tr(msg),
235 msg,
236 DUPLICATE_NODE_RAILWAY,
237 mm.get(tagSet)
238 ));
239 } else if (typeMap.get("waterway")) {
240 String msg = marktr("Waterway duplicated nodes");
241 errors.add(new TestError(
242 parentTest,
243 Severity.ERROR,
244 tr("Duplicated nodes"),
245 tr(msg),
246 msg,
247 DUPLICATE_NODE_WATERWAY,
248 mm.get(tagSet)
249 ));
250 } else if (typeMap.get("boundary")) {
251 String msg = marktr("Boundary duplicated nodes");
252 errors.add(new TestError(
253 parentTest,
254 Severity.ERROR,
255 tr("Duplicated nodes"),
256 tr(msg),
257 msg,
258 DUPLICATE_NODE_BOUNDARY,
259 mm.get(tagSet)
260 ));
261 } else if (typeMap.get("power")) {
262 String msg = marktr("Power duplicated nodes");
263 errors.add(new TestError(
264 parentTest,
265 Severity.ERROR,
266 tr("Duplicated nodes"),
267 tr(msg),
268 msg,
269 DUPLICATE_NODE_POWER,
270 mm.get(tagSet)
271 ));
272 } else if (typeMap.get("natural")) {
273 String msg = marktr("Natural duplicated nodes");
274 errors.add(new TestError(
275 parentTest,
276 Severity.ERROR,
277 tr("Duplicated nodes"),
278 tr(msg),
279 msg,
280 DUPLICATE_NODE_NATURAL,
281 mm.get(tagSet)
282 ));
283 } else if (typeMap.get("building")) {
284 String msg = marktr("Building duplicated nodes");
285 errors.add(new TestError(
286 parentTest,
287 Severity.ERROR,
288 tr("Duplicated nodes"),
289 tr(msg),
290 msg,
291 DUPLICATE_NODE_BUILDING,
292 mm.get(tagSet)
293 ));
294 } else if (typeMap.get("landuse")) {
295 String msg = marktr("Landuse duplicated nodes");
296 errors.add(new TestError(
297 parentTest,
298 Severity.ERROR,
299 tr("Duplicated nodes"),
300 tr(msg),
301 msg,
302 DUPLICATE_NODE_LANDUSE,
303 mm.get(tagSet)
304 ));
305 } else {
306 String msg = marktr("Other duplicated nodes");
307 errors.add(new TestError(
308 parentTest,
309 Severity.WARNING,
310 tr("Duplicated nodes"),
311 tr(msg),
312 msg,
313 DUPLICATE_NODE_OTHER,
314 mm.get(tagSet)
315 ));
316
317 }
318 it.remove();
319 }
320 }
321
322 // check whether we have multiple nodes at the same position with
323 // differing tag sets
324 //
325 if (!mm.isEmpty()) {
326 List<OsmPrimitive> duplicates = new ArrayList<OsmPrimitive>();
327 for (Set<OsmPrimitive> l: mm.values()) {
328 duplicates.addAll(l);
329 }
330 if (duplicates.size() > 1) {
331 errors.add(new TestError(
332 parentTest,
333 Severity.WARNING,
334 tr("Nodes at same position"),
335 DUPLICATE_NODE,
336 duplicates
337 ));
338 }
339 }
340 return errors;
341 }
342
343 @SuppressWarnings("unchecked")
344 @Override
345 public void visit(Node n) {
346 if (n.isUsable()) {
347 if (potentialDuplicates.get(n) == null) {
348 // in most cases there is just one node at a given position. We
349 // avoid to create an extra object and add remember the node
350 // itself at this position
351 potentialDuplicates.put(n);
352 } else if (potentialDuplicates.get(n) instanceof Node) {
353 // we have an additional node at the same position. Create an extra
354 // object to keep track of the nodes at this position.
355 //
356 Node n1 = (Node)potentialDuplicates.get(n);
357 List<Node> nodes = new ArrayList<Node>(2);
358 nodes.add(n1);
359 nodes.add(n);
360 potentialDuplicates.put(nodes);
361 } else if (potentialDuplicates.get(n) instanceof List<?>) {
362 // we have multiple nodes at the same position.
363 //
364 List<Node> nodes = (List<Node>)potentialDuplicates.get(n);
365 nodes.add(n);
366 }
367 }
368 }
369
370 /**
371 * Merge the nodes into one.
372 * Copied from UtilsPlugin.MergePointsAction
373 */
374 @Override
375 public Command fixError(TestError testError) {
376 if (!isFixable(testError)) return null;
377 Collection<OsmPrimitive> sel = new LinkedList<OsmPrimitive>(testError.getPrimitives());
378 LinkedHashSet<Node> nodes = new LinkedHashSet<Node>(OsmPrimitive.getFilteredList(sel, Node.class));
379
380 // Use first existing node or first node if all nodes are new
381 Node target = null;
382 for (Node n: nodes) {
383 if (!n.isNew()) {
384 target = n;
385 break;
386 }
387 }
388 if (target == null) {
389 target = nodes.iterator().next();
390 }
391
392 if (checkAndConfirmOutlyingDeletes(nodes, target))
393 return MergeNodesAction.mergeNodes(Main.main.getEditLayer(), nodes, target);
394
395 return null;// undoRedo handling done in mergeNodes
396 }
397
398 @Override
399 public boolean isFixable(TestError testError) {
400 if (!(testError.getTester() instanceof DuplicateNode)) return false;
401 // never merge nodes with different tags.
402 if (testError.getCode() == DUPLICATE_NODE) return false;
403 // never merge nodes from two different non-closed geometries
404 if (testError.getCode() == DUPLICATE_NODE_UNCLOSED) return false;
405 // everything else is ok to merge
406 return true;
407 }
408
409 /**
410 * Check whether user is about to delete data outside of the download area.
411 * Request confirmation if he is.
412 */
413 private static boolean checkAndConfirmOutlyingDeletes(LinkedHashSet<Node> del, Node ignore) {
414 Area a = Main.main.getCurrentDataSet().getDataSourceArea();
415 if (a != null) {
416 for (OsmPrimitive osm : del) {
417 if (osm instanceof Node && !osm.isNew() && osm != ignore) {
418 Node n = (Node) osm;
419 if (!a.contains(n.getCoor())) {
420 JPanel msg = new JPanel(new GridBagLayout());
421 msg.add(new JLabel(
422 "<html>" +
423 // leave message in one tr() as there is a grammatical
424 // connection.
425 tr("You are about to delete nodes outside of the area you have downloaded."
426 + "<br>"
427 + "This can cause problems because other objects (that you do not see) might use them."
428 + "<br>" + "Do you really want to delete?") + "</html>"));
429
430 return ConditionalOptionPaneUtil.showConfirmationDialog(
431 "delete_outside_nodes",
432 Main.parent,
433 msg,
434 tr("Delete confirmation"),
435 JOptionPane.YES_NO_OPTION,
436 JOptionPane.QUESTION_MESSAGE,
437 JOptionPane.YES_OPTION);
438 }
439 }
440 }
441 }
442 return true;
443 }
444}
Note: See TracBrowser for help on using the repository browser.