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

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

add validator plugin to josm core. Original author: Francisco R. Santos (frsantos); major contributions by bilbo, daeron, delta_foxtrot, imi, jttt, jrreid, gabriel, guggis, pieren, rrankin, skela, stoecker, stotz and others

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