Ignore:
Timestamp:
2019-09-04T11:43:24+02:00 (5 years ago)
Author:
GerdP
Message:

fix #18051 and #18106
This is more or less a rewrite of the code, I hope I didn't introduce too many new problems.

  • always consider all ways with matching tag when calculating unconnected nodes
  • for partial tests filter the calculated errors (this means a small additional delay for this test when uploading data)
  • fix recursion problem in isConnectedTo so that it doesn't stop too early
  • remove code which excluded eg. ways with highway=cycleway + railway=abandoned or waterway=river + boundary=administrative
  • improve performance, esp. use HashSet when calculating types of nodes in method addNode() and QuadBuckets only when searching is done. Together with other changes this reduces runtime by ~50%, so I hope the delay for the upload is not too high.
File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java

    r14624 r15335  
    88import java.awt.geom.Area;
    99import java.awt.geom.Line2D;
    10 import java.awt.geom.Point2D;
    1110import java.util.ArrayList;
    1211import java.util.Collection;
     
    4746public abstract class UnconnectedWays extends Test {
    4847    private final int code;
     48    private final boolean isHighwayTest;
     49
     50    protected abstract boolean isCandidate(OsmPrimitive p);
     51
     52    @Override
     53    public boolean isPrimitiveUsable(OsmPrimitive p) {
     54        return super.isPrimitiveUsable(p) && ((partialSelection && p instanceof Node) || isCandidate(p));
     55    }
    4956
    5057    /**
     
    5865         */
    5966        public UnconnectedHighways() {
    60             super(tr("Unconnected highways"), UNCONNECTED_HIGHWAYS);
     67            super(tr("Unconnected highways"), UNCONNECTED_HIGHWAYS, true);
    6168        }
    6269
    6370        @Override
    64         public boolean isPrimitiveUsable(OsmPrimitive p) {
    65             return super.isPrimitiveUsable(p) && p.hasKey(HIGHWAY);
     71        protected boolean isCandidate(OsmPrimitive p) {
     72            return p.hasKey(HIGHWAY);
    6673        }
    6774    }
     
    7683         */
    7784        public UnconnectedRailways() {
    78             super(tr("Unconnected railways"), UNCONNECTED_RAILWAYS);
     85            super(tr("Unconnected railways"), UNCONNECTED_RAILWAYS, false);
    7986        }
    8087
    8188        @Override
    82         public boolean isPrimitiveUsable(OsmPrimitive p) {
    83             return super.isPrimitiveUsable(p) && p.hasKey("railway");
     89        protected boolean isCandidate(OsmPrimitive p) {
     90            return p.hasKey(RAILWAY) && !p.hasTag(RAILWAY, "abandoned");
    8491        }
    8592    }
     
    94101         */
    95102        public UnconnectedWaterways() {
    96             super(tr("Unconnected waterways"), UNCONNECTED_WATERWAYS);
     103            super(tr("Unconnected waterways"), UNCONNECTED_WATERWAYS, false);
    97104        }
    98105
    99106        @Override
    100         public boolean isPrimitiveUsable(OsmPrimitive p) {
    101             return super.isPrimitiveUsable(p) && p.hasKey("waterway");
     107        protected boolean isCandidate(OsmPrimitive p) {
     108            return p.hasKey("waterway");
    102109        }
    103110    }
     
    112119         */
    113120        public UnconnectedNaturalOrLanduse() {
    114             super(tr("Unconnected natural lands and landuses"), UNCONNECTED_NATURAL_OR_LANDUSE);
     121            super(tr("Unconnected natural lands and landuses"), UNCONNECTED_NATURAL_OR_LANDUSE, false);
    115122        }
    116123
    117124        @Override
    118         public boolean isPrimitiveUsable(OsmPrimitive p) {
    119             return super.isPrimitiveUsable(p) && p.hasKey("natural", "landuse");
     125        protected boolean isCandidate(OsmPrimitive p) {
     126            return p.hasKey("natural", "landuse") && !p.hasTag("natural", "tree_row", "cliff");
    120127        }
    121128    }
     
    130137         */
    131138        public UnconnectedPower() {
    132             super(tr("Unconnected power ways"), UNCONNECTED_POWER);
     139            super(tr("Unconnected power ways"), UNCONNECTED_POWER, false);
    133140        }
    134141
    135142        @Override
    136         public boolean isPrimitiveUsable(OsmPrimitive p) {
    137             return super.isPrimitiveUsable(p) && p.hasTag("power", "line", "minor_line", "cable");
     143        protected boolean isCandidate(OsmPrimitive p) {
     144            return p.hasTag("power", "line", "minor_line", "cable");
    138145        }
    139146    }
     
    142149    protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + UnconnectedWays.class.getSimpleName();
    143150
    144     private Set<MyWaySegment> ways;
    145     private QuadBuckets<Node> endnodes; // nodes at end of way
    146     private QuadBuckets<Node> endnodesHighway; // nodes at end of way
    147     private QuadBuckets<Node> middlenodes; // nodes in middle of way
     151    private List<MyWaySegment> waySegments;
     152    private Set<Node> endnodes; // nodes at end of way
     153    private Set<Node> middlenodes; // nodes in middle of way
    148154    private Set<Node> othernodes; // nodes appearing at least twice
     155    private QuadBuckets<Node> searchNodes;
     156    private Set<Way> waysToTest;
     157    private Set<Node> nodesToTest;
    149158    private Area dsArea;
    150159
    151160    private double mindist;
    152161    private double minmiddledist;
     162    private DataSet ds;
    153163
    154164    /**
     
    158168     */
    159169    public UnconnectedWays(String title) {
    160         this(title, UNCONNECTED_WAYS);
     170        this(title, UNCONNECTED_WAYS, false);
    161171
    162172    }
     
    166176     * @param title The test title
    167177     * @param code The test code
     178     * @param isHighwayTest use {@code true} if test concerns highways or railways
    168179     * @since 14468
    169180     */
    170     public UnconnectedWays(String title, int code) {
     181    public UnconnectedWays(String title, int code, boolean isHighwayTest) {
    171182        super(title, tr("This test checks if a way has an endpoint very near to another way."));
    172183        this.code = code;
     184        this.isHighwayTest = isHighwayTest;
    173185    }
    174186
     
    176188    public void startTest(ProgressMonitor monitor) {
    177189        super.startTest(monitor);
    178         ways = new HashSet<>();
    179         endnodes = new QuadBuckets<>();
    180         endnodesHighway = new QuadBuckets<>();
    181         middlenodes = new QuadBuckets<>();
     190        waySegments = new ArrayList<>();
     191        waysToTest = new HashSet<>();
     192        nodesToTest = new HashSet<>();
     193        endnodes = new HashSet<>();
     194        middlenodes = new HashSet<>();
    182195        othernodes = new HashSet<>();
    183196        mindist = Config.getPref().getDouble(PREFIX + ".node_way_distance", 10.0);
    184197        minmiddledist = Config.getPref().getDouble(PREFIX + ".way_way_distance", 0.0);
    185         DataSet dataSet = OsmDataManager.getInstance().getEditDataSet();
    186         dsArea = dataSet == null ? null : dataSet.getDataSourceArea();
     198        ds = OsmDataManager.getInstance().getEditDataSet();
     199        dsArea = ds == null ? null : ds.getDataSourceArea();
    187200    }
    188201
    189202    protected Map<Node, Way> getWayEndNodesNearOtherHighway() {
    190203        Map<Node, Way> map = new HashMap<>();
    191         for (int iter = 0; iter < 1; iter++) {
    192             for (MyWaySegment s : ways) {
    193                 if (isCanceled()) {
    194                     map.clear();
    195                     return map;
    196                 }
    197                 if (s.highway) {
    198                     for (Node en : s.nearbyNodes(mindist)) {
    199                         if (en == null || !endnodesHighway.contains(en)) {
    200                             continue;
    201                         }
    202                         if (en.hasTag(HIGHWAY, "turning_circle", "bus_stop")
    203                                 || en.hasTag("amenity", "parking_entrance")
    204                                 || en.hasTag(RAILWAY, "buffer_stop")
    205                                 || en.isKeyTrue("noexit")
    206                                 || en.hasKey("entrance", "barrier")) {
    207                             continue;
    208                         }
    209                         // to handle intersections of 't' shapes and similar
    210                         if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
    211                             continue;
    212                         }
    213                         map.put(en, s.w);
    214                     }
    215                 }
    216             }
    217         }
    218         return map;
    219     }
    220 
    221     protected Map<Node, Way> getWayEndNodesNearOtherWay() {
    222         Map<Node, Way> map = new HashMap<>();
    223         for (MyWaySegment s : ways) {
     204        for (MyWaySegment s : waySegments) {
    224205            if (isCanceled()) {
    225206                map.clear();
     
    227208            }
    228209            for (Node en : s.nearbyNodes(mindist)) {
    229                 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
     210                if (en.hasTag(HIGHWAY, "turning_circle", "bus_stop")
     211                        || en.hasTag("amenity", "parking_entrance")
     212                        || en.hasTag(RAILWAY, "buffer_stop")
     213                        || en.isKeyTrue("noexit")
     214                        || en.hasKey("entrance", "barrier")) {
    230215                    continue;
    231216                }
    232                 if (((!s.highway && endnodesHighway.contains(en)) || endnodes.contains(en)) && !s.w.concernsArea()) {
     217                // to handle intersections of 't' shapes and similar
     218                if (!en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
    233219                    map.put(en, s.w);
    234220                }
     
    238224    }
    239225
    240     protected Map<Node, Way> getWayNodesNearOtherWay() {
     226    protected Map<Node, Way> getWayEndNodesNearOtherWay() {
    241227        Map<Node, Way> map = new HashMap<>();
    242         for (MyWaySegment s : ways) {
     228        for (MyWaySegment s : waySegments) {
    243229            if (isCanceled()) {
    244230                map.clear();
    245231                return map;
    246232            }
    247             for (Node en : s.nearbyNodes(minmiddledist)) {
    248                 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
    249                     continue;
    250                 }
    251                 if (!middlenodes.contains(en)) {
    252                     continue;
    253                 }
    254                 map.put(en, s.w);
     233            if (!s.concernsArea) {
     234                for (Node en : s.nearbyNodes(mindist)) {
     235                    if (!en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
     236                        map.put(en, s.w);
     237                    }
     238                }
    255239            }
    256240        }
     
    258242    }
    259243
    260     protected Map<Node, Way> getConnectedWayEndNodesNearOtherWay() {
     244    protected Map<Node, Way> getWayNodesNearOtherWay() {
    261245        Map<Node, Way> map = new HashMap<>();
    262         for (MyWaySegment s : ways) {
     246        for (MyWaySegment s : waySegments) {
    263247            if (isCanceled()) {
    264248                map.clear();
     
    266250            }
    267251            for (Node en : s.nearbyNodes(minmiddledist)) {
    268                 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
    269                     continue;
    270                 }
    271                 if (!othernodes.contains(en)) {
    272                     continue;
    273                 }
    274                 map.put(en, s.w);
     252                if (!en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
     253                    map.put(en, s.w);
     254                }
    275255            }
    276256        }
     
    280260    protected final void addErrors(Severity severity, Map<Node, Way> errorMap, String message) {
    281261        for (Map.Entry<Node, Way> error : errorMap.entrySet()) {
     262            Node node = error.getKey();
     263            Way way = error.getValue();
     264            if (partialSelection && !nodesToTest.contains(node) && !waysToTest.contains(way))
     265                continue;
    282266            errors.add(TestError.builder(this, severity, code)
    283267                    .message(message)
    284                     .primitives(error.getKey(), error.getValue())
    285                     .highlight(error.getKey())
     268                    .primitives(node, way)
     269                    .highlight(node)
    286270                    .build());
    287271        }
     
    290274    @Override
    291275    public void endTest() {
    292         addErrors(Severity.WARNING, getWayEndNodesNearOtherHighway(), tr("Way end node near other highway"));
    293         addErrors(Severity.WARNING, getWayEndNodesNearOtherWay(), tr("Way end node near other way"));
     276        if (ds == null)
     277            return;
     278
     279        for (Way w : ds.getWays()) {
     280            if (w.isUsable() && isCandidate(w) && w.getRealNodesCount() > 1
     281                    // don't complain about highways ending near platforms
     282                    && !w.hasTag(HIGHWAY, "platform") && !w.hasTag(RAILWAY, "platform", "platform_edge")
     283                    ) {
     284                waySegments.addAll(getWaySegments(w));
     285                addNode(w.firstNode(), endnodes);
     286                addNode(w.lastNode(), endnodes);
     287            }
     288        }
     289        searchNodes = new QuadBuckets<>();
     290        searchNodes.addAll(endnodes);
     291        if (isHighwayTest) {
     292            addErrors(Severity.WARNING, getWayEndNodesNearOtherHighway(), tr("Way end node near other highway"));
     293        } else {
     294            addErrors(Severity.WARNING, getWayEndNodesNearOtherWay(), tr("Way end node near other way"));
     295        }
     296
    294297        /* the following two use a shorter distance */
    295         if (minmiddledist > 0.0) {
     298        boolean includeOther = isBeforeUpload ? ValidatorPrefHelper.PREF_OTHER_UPLOAD.get() : ValidatorPrefHelper.PREF_OTHER.get();
     299        if (minmiddledist > 0.0 && includeOther) {
     300            searchNodes.clear();
     301            searchNodes.addAll(middlenodes);
    296302            addErrors(Severity.OTHER, getWayNodesNearOtherWay(), tr("Way node near other way"));
    297             addErrors(Severity.OTHER, getConnectedWayEndNodesNearOtherWay(), tr("Connected way end node near other way"));
    298         }
    299         ways = null;
     303            searchNodes.clear();
     304            searchNodes.addAll(othernodes);
     305            addErrors(Severity.OTHER, getWayNodesNearOtherWay(), tr("Connected way end node near other way"));
     306        }
     307        waySegments = null;
    300308        endnodes = null;
    301         endnodesHighway = null;
    302309        middlenodes = null;
    303310        othernodes = null;
     311        searchNodes = null;
    304312        dsArea = null;
     313        ds = null;
    305314        super.endTest();
    306315    }
    307316
    308317    private class MyWaySegment {
    309         private final Line2D line;
    310318        public final Way w;
    311         public final boolean isAbandoned;
    312         public final boolean isBoundary;
    313         public final boolean highway;
    314         private final double len;
    315         private Set<Node> nearbyNodeCache;
    316         private double nearbyNodeCacheDist = -1.0;
    317319        private final Node n1;
    318320        private final Node n2;
    319 
    320         MyWaySegment(Way w, Node n1, Node n2) {
     321        private final boolean concernsArea;
     322
     323        MyWaySegment(Way w, Node n1, Node n2, boolean concersArea) {
    321324            this.w = w;
    322             String railway = w.get(RAILWAY);
    323             String highway = w.get(HIGHWAY);
    324             this.isAbandoned = "abandoned".equals(railway) || w.isKeyTrue("disused");
    325             this.highway = (highway != null || railway != null) && !isAbandoned;
    326             this.isBoundary = !this.highway && w.hasTag("boundary", "administrative");
    327             line = new Line2D.Double(n1.getEastNorth().east(), n1.getEastNorth().north(),
    328                     n2.getEastNorth().east(), n2.getEastNorth().north());
    329             len = line.getP1().distance(line.getP2());
    330325            this.n1 = n1;
    331326            this.n2 = n2;
     327            this.concernsArea = concersArea;
    332328        }
    333329
     
    344340            if (coord == null)
    345341                return false;
    346             Point2D p = new Point2D.Double(coord.east(), coord.north());
    347             if (line.getP1().distance(p) > len+dist)
    348                 return false;
    349             if (line.getP2().distance(p) > len+dist)
    350                 return false;
    351             return line.ptSegDist(p) < dist;
    352         }
    353 
    354         public List<LatLon> getBounds(double fudge) {
     342            EastNorth en1 = n1.getEastNorth();
     343            EastNorth en2 = n2.getEastNorth();
     344            return Line2D.ptSegDist(en1.getX(), en1.getY(), en2.getX(), en2.getY(), coord.getX(), coord.getY()) < dist;
     345        }
     346
     347        public BBox getBounds(double fudge) {
    355348            double x1 = n1.getCoor().lon();
    356349            double x2 = n2.getCoor().lon();
     
    369362            LatLon topLeft = new LatLon(y2+fudge, x1-fudge);
    370363            LatLon botRight = new LatLon(y1-fudge, x2+fudge);
    371             List<LatLon> ret = new ArrayList<>(2);
    372             ret.add(topLeft);
    373             ret.add(botRight);
    374             return ret;
     364            return new BBox(topLeft, botRight);
    375365        }
    376366
    377367        public Collection<Node> nearbyNodes(double dist) {
    378             // If you're looking for nodes that are farther away that we looked for last time,
    379             // the cached result is no good
    380             if (dist > nearbyNodeCacheDist) {
    381                 nearbyNodeCache = null;
    382             }
    383             if (nearbyNodeCache != null) {
    384                 // If we've cached an area greater than the
    385                 // one now being asked for...
    386                 if (nearbyNodeCacheDist > dist) {
    387                     // Used the cached result and trim out
    388                     // the nodes that are not in the smaller
    389                     // area, but keep the old larger cache.
    390                     Set<Node> trimmed = new HashSet<>(nearbyNodeCache);
    391                     Set<Node> initial = new HashSet<>(nearbyNodeCache);
    392                     for (Node n : initial) {
    393                         if (!nearby(n, dist)) {
    394                             trimmed.remove(n);
    395                         }
    396                     }
    397                     return trimmed;
    398                 }
    399                 return nearbyNodeCache;
    400             }
    401368            /*
    402              * We know that any point near the line must be at
     369             * We know that any point near the line segment must be at
    403370             * least as close as the other end of the line, plus
    404371             * a little fudge for the distance away ('dist').
    405372             */
    406373
    407             // This needs to be a hash set because the searches
    408             // overlap a bit and can return duplicate nodes.
    409             nearbyNodeCache = null;
    410             List<LatLon> bounds = this.getBounds(dist * (360.0d / (Ellipsoid.WGS84.a * 2 * Math.PI)));
    411             List<Node> foundNodes = endnodesHighway.search(new BBox(bounds.get(0), bounds.get(1)));
    412             foundNodes.addAll(endnodes.search(new BBox(bounds.get(0), bounds.get(1))));
    413 
     374            BBox bounds = this.getBounds(dist * (360.0d / (Ellipsoid.WGS84.a * 2 * Math.PI)));
     375            List<Node> result = null;
     376            List<Node> foundNodes = searchNodes.search(bounds);
    414377            for (Node n : foundNodes) {
    415378                if (!nearby(n, dist) || !n.getCoor().isIn(dsArea)) {
     
    419382                // so defer as much of the work as possible, like
    420383                // allocating the hash set
    421                 if (nearbyNodeCache == null) {
    422                     nearbyNodeCache = new HashSet<>();
    423                 }
    424                 nearbyNodeCache.add(n);
    425             }
    426             nearbyNodeCacheDist = dist;
    427             if (nearbyNodeCache == null) {
    428                 nearbyNodeCache = Collections.emptySet();
    429             }
    430             return nearbyNodeCache;
     384                if (result == null) {
     385                    result = new ArrayList<>();
     386                }
     387                result.add(n);
     388            }
     389            return result == null ? Collections.emptyList() : result;
    431390        }
    432391    }
     
    434393    List<MyWaySegment> getWaySegments(Way w) {
    435394        List<MyWaySegment> ret = new ArrayList<>();
    436         if (!w.isUsable()
    437                 || w.hasKey("barrier")
    438                 || w.hasTag("natural", "cliff"))
     395        if (!w.isUsable() || w.isKeyTrue("disused"))
    439396            return ret;
    440397
    441398        int size = w.getNodesCount();
    442         if (size < 2)
    443             return ret;
     399        boolean concersArea = w.concernsArea();
    444400        for (int i = 1; i < size; ++i) {
    445401            if (i < size-1) {
     
    449405            Node b = w.getNode(i);
    450406            if (a.isDrawable() && b.isDrawable()) {
    451                 MyWaySegment ws = new MyWaySegment(w, a, b);
    452                 if (ws.isBoundary || ws.isAbandoned) {
    453                     continue;
    454                 }
     407                MyWaySegment ws = new MyWaySegment(w, a, b, concersArea);
    455408                ret.add(ws);
    456409            }
     
    461414    @Override
    462415    public void visit(Way w) {
    463         // do not consider empty ways
    464         if (w.getNodesCount() > 0
    465                 // ignore addr:interpolation ways as they are not physical features and most of
    466                 // the time very near the associated highway, which is perfectly normal, see #9332
    467                 && !w.hasKey("addr:interpolation")
    468                 // similarly for public transport platforms, tree rows
    469                 && !w.hasTag(HIGHWAY, "platform") && !w.hasTag(RAILWAY, "platform", "platform_edge") && !w.hasTag("natural", "tree_row")
    470                 ) {
    471             ways.addAll(getWaySegments(w));
    472             QuadBuckets<Node> set = endnodes;
    473             if (w.hasKey(HIGHWAY, RAILWAY)) {
    474                 set = endnodesHighway;
    475             }
    476             addNode(w.firstNode(), set);
    477             addNode(w.lastNode(), set);
    478         }
    479     }
    480 
    481     private void addNode(Node n, QuadBuckets<Node> s) {
     416        if (partialSelection) {
     417            waysToTest.add(w);
     418        }
     419    }
     420
     421    @Override
     422    public void visit(Node n) {
     423        if (partialSelection) {
     424            nodesToTest.add(n);
     425        }
     426    }
     427
     428    private void addNode(Node n, Set<Node> s) {
    482429        boolean m = middlenodes.contains(n);
    483430        boolean e = endnodes.contains(n);
    484         boolean eh = endnodesHighway.contains(n);
    485431        boolean o = othernodes.contains(n);
    486         if (!m && !e && !o && !eh) {
     432        if (!m && !e && !o) {
    487433            s.add(n);
    488434        } else if (!o) {
     
    490436            if (e) {
    491437                endnodes.remove(n);
    492             } else if (eh) {
    493                 endnodesHighway.remove(n);
    494438            } else {
    495439                middlenodes.remove(n);
Note: See TracChangeset for help on using the changeset viewer.