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

Revision 5060, 15.5 KB checked in by simon04, 2 months ago (diff)

fix #7470 - tune warning of nodes out of downloaded bbox

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