Ticket #18051: 18051.patch

File 18051.patch, 16.3 KB (added by GerdP, 5 years ago)
  • src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java

     
    77
    88import java.awt.geom.Area;
    99import java.awt.geom.Line2D;
    10 import java.awt.geom.Point2D;
    1110import java.util.ArrayList;
    1211import java.util.Collection;
    1312import java.util.Collections;
     
    6261
    6362        @Override
    6463        public boolean isPrimitiveUsable(OsmPrimitive p) {
    65             return super.isPrimitiveUsable(p) && p.hasKey(HIGHWAY);
     64            return super.isPrimitiveUsable(p) && ((partialSelection && p instanceof Node) || p.hasKey(HIGHWAY));
    6665        }
    6766    }
    6867
     
    8079
    8180        @Override
    8281        public boolean isPrimitiveUsable(OsmPrimitive p) {
    83             return super.isPrimitiveUsable(p) && p.hasKey("railway");
     82            return super.isPrimitiveUsable(p) && ((partialSelection && p instanceof Node) || p.hasKey("railway"));
    8483        }
    8584    }
    8685
     
    9897
    9998        @Override
    10099        public boolean isPrimitiveUsable(OsmPrimitive p) {
    101             return super.isPrimitiveUsable(p) && p.hasKey("waterway");
     100            return super.isPrimitiveUsable(p) && ((partialSelection && p instanceof Node) || p.hasKey("waterway"));
    102101        }
    103102    }
    104103
     
    116115
    117116        @Override
    118117        public boolean isPrimitiveUsable(OsmPrimitive p) {
    119             return super.isPrimitiveUsable(p) && p.hasKey("natural", "landuse");
     118            return super.isPrimitiveUsable(p) && ((partialSelection && p instanceof Node) || p.hasKey("natural", "landuse"));
    120119        }
    121120    }
    122121
     
    142141    protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + UnconnectedWays.class.getSimpleName();
    143142
    144143    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
     144    private Set<Node> endnodes; // nodes at end of way
     145    private Set<Node> endnodesHighway; // nodes at end of way
     146    private Set<Node> middlenodes; // nodes in middle of way
    148147    private Set<Node> othernodes; // nodes appearing at least twice
     148    private QuadBuckets<Node> searchNodes;
     149    private Set<Way> waysToTest;
     150    private Set<Node> nodesToTest;
    149151    private Area dsArea;
    150152
    151153    private double mindist;
    152154    private double minmiddledist;
     155    private DataSet ds;
    153156
    154157    /**
    155158     * Constructs a new {@code UnconnectedWays} test.
     
    176179    public void startTest(ProgressMonitor monitor) {
    177180        super.startTest(monitor);
    178181        ways = new HashSet<>();
    179         endnodes = new QuadBuckets<>();
    180         endnodesHighway = new QuadBuckets<>();
    181         middlenodes = new QuadBuckets<>();
     182        waysToTest = new HashSet<>();
     183        nodesToTest = new HashSet<>();
     184        endnodes = new HashSet<>();
     185        endnodesHighway = new HashSet<>();
     186        middlenodes = new HashSet<>();
    182187        othernodes = new HashSet<>();
    183188        mindist = Config.getPref().getDouble(PREFIX + ".node_way_distance", 10.0);
    184189        minmiddledist = Config.getPref().getDouble(PREFIX + ".way_way_distance", 0.0);
    185         DataSet dataSet = OsmDataManager.getInstance().getEditDataSet();
    186         dsArea = dataSet == null ? null : dataSet.getDataSourceArea();
     190        ds = OsmDataManager.getInstance().getEditDataSet();
     191        dsArea = ds == null ? null : ds.getDataSourceArea();
    187192    }
    188193
    189194    protected Map<Node, Way> getWayEndNodesNearOtherHighway() {
    190195        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);
     196        for (MyWaySegment s : ways) {
     197            if (isCanceled()) {
     198                map.clear();
     199                return map;
     200            }
     201            if (s.highway) {
     202                for (Node en : s.nearbyNodes(mindist)) {
     203                    if (en.hasTag(HIGHWAY, "turning_circle", "bus_stop")
     204                            || en.hasTag("amenity", "parking_entrance")
     205                            || en.hasTag(RAILWAY, "buffer_stop")
     206                            || en.isKeyTrue("noexit")
     207                            || en.hasKey("entrance", "barrier")) {
     208                        continue;
    214209                    }
     210                    // to handle intersections of 't' shapes and similar
     211                    if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
     212                        continue;
     213                    }
     214                    map.put(en, s.w);
    215215                }
    216216            }
    217217        }
     
    244244                map.clear();
    245245                return map;
    246246            }
     247            s.nearbyNodeCache = null;
    247248            for (Node en : s.nearbyNodes(minmiddledist)) {
    248249                if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) {
    249250                    continue;
    250251                }
    251                 if (!middlenodes.contains(en)) {
    252                     continue;
    253                 }
    254252                map.put(en, s.w);
    255253            }
    256254        }
     
    257255        return map;
    258256    }
    259257
    260     protected Map<Node, Way> getConnectedWayEndNodesNearOtherWay() {
    261         Map<Node, Way> map = new HashMap<>();
    262         for (MyWaySegment s : ways) {
    263             if (isCanceled()) {
    264                 map.clear();
    265                 return map;
    266             }
    267             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);
    275             }
    276         }
    277         return map;
    278     }
    279 
    280258    protected final void addErrors(Severity severity, Map<Node, Way> errorMap, String message) {
    281259        for (Map.Entry<Node, Way> error : errorMap.entrySet()) {
     260            Node node = error.getKey();
     261            Way way = error.getValue();
     262            if (partialSelection && !nodesToTest.contains(node) && !waysToTest.contains(way))
     263                continue;
    282264            errors.add(TestError.builder(this, severity, code)
    283265                    .message(message)
    284                     .primitives(error.getKey(), error.getValue())
    285                     .highlight(error.getKey())
     266                    .primitives(node, way)
     267                    .highlight(node)
    286268                    .build());
    287269        }
    288270    }
     
    289271
    290272    @Override
    291273    public void endTest() {
     274        for (Way w : ds.getWays()) {
     275            if (isPrimitiveUsable(w)
     276                    // ignore addr:interpolation ways as they are not physical features and most of
     277                    // the time very near the associated highway, which is perfectly normal, see #9332
     278                    && !w.hasKey("addr:interpolation")
     279                    // similarly for public transport platforms, tree rows
     280                    && !w.hasTag(HIGHWAY, "platform") && !w.hasTag(RAILWAY, "platform", "platform_edge") && !w.hasTag("natural", "tree_row")
     281                    ) {
     282                ways.addAll(getWaySegments(w));
     283                Set<Node> set = endnodes;
     284                if (w.hasKey(HIGHWAY, RAILWAY)) {
     285                    set = endnodesHighway;
     286                }
     287                addNode(w.firstNode(), set);
     288                addNode(w.lastNode(), set);
     289            }
     290        }
     291        searchNodes = new QuadBuckets<>();
     292        searchNodes.addAll(endnodes);
     293        searchNodes.addAll(endnodesHighway);
    292294        addErrors(Severity.WARNING, getWayEndNodesNearOtherHighway(), tr("Way end node near other highway"));
    293295        addErrors(Severity.WARNING, getWayEndNodesNearOtherWay(), tr("Way end node near other way"));
    294296        /* the following two use a shorter distance */
    295297        if (minmiddledist > 0.0) {
     298            searchNodes.clear();
     299            searchNodes.addAll(middlenodes);
    296300            addErrors(Severity.OTHER, getWayNodesNearOtherWay(), tr("Way node near other way"));
    297             addErrors(Severity.OTHER, getConnectedWayEndNodesNearOtherWay(), tr("Connected way end node near other way"));
     301            searchNodes.clear();
     302            searchNodes.addAll(othernodes);
     303            addErrors(Severity.OTHER, getWayNodesNearOtherWay(), tr("Connected way end node near other way"));
    298304        }
    299305        ways = null;
    300306        endnodes = null;
     
    301307        endnodesHighway = null;
    302308        middlenodes = null;
    303309        othernodes = null;
     310        searchNodes = null;
    304311        dsArea = null;
     312        ds = null;
    305313        super.endTest();
    306314    }
    307315
    308316    private class MyWaySegment {
    309         private final Line2D line;
    310317        public final Way w;
    311318        public final boolean isAbandoned;
    312319        public final boolean isBoundary;
    313320        public final boolean highway;
    314         private final double len;
    315321        private Set<Node> nearbyNodeCache;
    316322        private double nearbyNodeCacheDist = -1.0;
    317323        private final Node n1;
     
    320326        MyWaySegment(Way w, Node n1, Node n2) {
    321327            this.w = w;
    322328            String railway = w.get(RAILWAY);
    323             String highway = w.get(HIGHWAY);
    324329            this.isAbandoned = "abandoned".equals(railway) || w.isKeyTrue("disused");
    325             this.highway = (highway != null || railway != null) && !isAbandoned;
     330            this.highway = (w.hasKey(HIGHWAY) || railway != null) && !isAbandoned;
    326331            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());
    330332            this.n1 = n1;
    331333            this.n2 = n2;
    332334        }
     
    343345            EastNorth coord = n.getEastNorth();
    344346            if (coord == null)
    345347                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;
     348            EastNorth en1 = n1.getEastNorth();
     349            EastNorth en2 = n2.getEastNorth();
     350            return Line2D.ptSegDist(en1.getX(), en1.getY(), en2.getX(), en2.getY(), coord.getX(), coord.getY()) < dist;
    352351        }
    353352
    354         public List<LatLon> getBounds(double fudge) {
     353        public BBox getBounds(double fudge) {
    355354            double x1 = n1.getCoor().lon();
    356355            double x2 = n2.getCoor().lon();
    357356            if (x1 > x2) {
     
    368367            }
    369368            LatLon topLeft = new LatLon(y2+fudge, x1-fudge);
    370369            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;
     370            return new BBox(topLeft, botRight);
    375371        }
    376372
    377373        public Collection<Node> nearbyNodes(double dist) {
     
    407403            // This needs to be a hash set because the searches
    408404            // overlap a bit and can return duplicate nodes.
    409405            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 
     406            BBox bounds = this.getBounds(dist * (360.0d / (Ellipsoid.WGS84.a * 2 * Math.PI)));
     407            List<Node> foundNodes = searchNodes.search(bounds);
    414408            for (Node n : foundNodes) {
    415                 if (!nearby(n, dist) || !n.getCoor().isIn(dsArea)) {
     409                if (!nearby(n, dist) || (!n.isModified() && !n.getCoor().isIn(dsArea))) {
    416410                    continue;
    417411                }
    418412                // It is actually very rare for us to find a node
     
    460454
    461455    @Override
    462456    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);
     457        if (partialSelection) {
     458            waysToTest.add(w);
    478459        }
    479460    }
    480461
    481     private void addNode(Node n, QuadBuckets<Node> s) {
     462    @Override
     463    public void visit(Node n) {
     464        if (partialSelection) {
     465            nodesToTest.add(n);
     466        }
     467    }
     468
     469    private void addNode(Node n, Set<Node> s) {
    482470        boolean m = middlenodes.contains(n);
    483471        boolean e = endnodes.contains(n);
    484472        boolean eh = endnodesHighway.contains(n);
  • test/unit/org/openstreetmap/josm/data/validation/tests/UnconnectedWaysTest.java

     
    1414import org.junit.Test;
    1515import org.openstreetmap.josm.JOSMFixture;
    1616import org.openstreetmap.josm.data.osm.DataSet;
     17import org.openstreetmap.josm.gui.MainApplication;
     18import org.openstreetmap.josm.gui.layer.OsmDataLayer;
    1719import org.openstreetmap.josm.gui.progress.NullProgressMonitor;
    1820import org.openstreetmap.josm.io.IllegalDataException;
    1921import org.openstreetmap.josm.io.OsmReader;
     22import org.openstreetmap.josm.testutils.JOSMTestRules;
    2023
    2124/**
    2225 * Unit tests of {@code UnconnectedWays} class.
     
    2427public class UnconnectedWaysTest {
    2528
    2629    private UnconnectedWays bib;
    27 
     30    public JOSMTestRules test = new JOSMTestRules().main();
    2831    /**
    2932     * Setup test.
    3033     * @throws Exception if the test cannot be initialized
     
    3437        bib = new UnconnectedWays.UnconnectedHighways();
    3538        JOSMFixture.createUnitTestFixture().init();
    3639        bib.initialize();
    37         bib.startTest(null);
    3840    }
    3941
    4042    /**
     
    4749    public void testTicket6313() throws IOException, IllegalDataException, FileNotFoundException {
    4850        try (InputStream fis = Files.newInputStream(Paths.get("data_nodist/UnconnectedWaysTest.osm"))) {
    4951            final DataSet ds = OsmReader.parseDataSet(fis, NullProgressMonitor.INSTANCE);
     52            MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, null, null));
     53
     54            bib.startTest(null);
    5055            bib.visit(ds.allPrimitives());
    5156            bib.endTest();
    5257            assertThat(bib.getErrors(), isEmpty());