Ticket #18051: 18051.patch
File 18051.patch, 16.3 KB (added by , 5 years ago) |
---|
-
src/org/openstreetmap/josm/data/validation/tests/UnconnectedWays.java
7 7 8 8 import java.awt.geom.Area; 9 9 import java.awt.geom.Line2D; 10 import java.awt.geom.Point2D;11 10 import java.util.ArrayList; 12 11 import java.util.Collection; 13 12 import java.util.Collections; … … 62 61 63 62 @Override 64 63 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)); 66 65 } 67 66 } 68 67 … … 80 79 81 80 @Override 82 81 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")); 84 83 } 85 84 } 86 85 … … 98 97 99 98 @Override 100 99 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")); 102 101 } 103 102 } 104 103 … … 116 115 117 116 @Override 118 117 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")); 120 119 } 121 120 } 122 121 … … 142 141 protected static final String PREFIX = ValidatorPrefHelper.PREFIX + "." + UnconnectedWays.class.getSimpleName(); 143 142 144 143 private Set<MyWaySegment> ways; 145 private QuadBuckets<Node> endnodes; // nodes at end of way146 private QuadBuckets<Node> endnodesHighway; // nodes at end of way147 private QuadBuckets<Node> middlenodes; // nodes in middle of way144 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 148 147 private Set<Node> othernodes; // nodes appearing at least twice 148 private QuadBuckets<Node> searchNodes; 149 private Set<Way> waysToTest; 150 private Set<Node> nodesToTest; 149 151 private Area dsArea; 150 152 151 153 private double mindist; 152 154 private double minmiddledist; 155 private DataSet ds; 153 156 154 157 /** 155 158 * Constructs a new {@code UnconnectedWays} test. … … 176 179 public void startTest(ProgressMonitor monitor) { 177 180 super.startTest(monitor); 178 181 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<>(); 182 187 othernodes = new HashSet<>(); 183 188 mindist = Config.getPref().getDouble(PREFIX + ".node_way_distance", 10.0); 184 189 minmiddledist = Config.getPref().getDouble(PREFIX + ".way_way_distance", 0.0); 185 DataSet dataSet= OsmDataManager.getInstance().getEditDataSet();186 dsArea = d ataSet == null ? null : dataSet.getDataSourceArea();190 ds = OsmDataManager.getInstance().getEditDataSet(); 191 dsArea = ds == null ? null : ds.getDataSourceArea(); 187 192 } 188 193 189 194 protected Map<Node, Way> getWayEndNodesNearOtherHighway() { 190 195 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; 214 209 } 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); 215 215 } 216 216 } 217 217 } … … 244 244 map.clear(); 245 245 return map; 246 246 } 247 s.nearbyNodeCache = null; 247 248 for (Node en : s.nearbyNodes(minmiddledist)) { 248 249 if (en.isConnectedTo(s.w.getNodes(), 3 /* hops */, null)) { 249 250 continue; 250 251 } 251 if (!middlenodes.contains(en)) {252 continue;253 }254 252 map.put(en, s.w); 255 253 } 256 254 } … … 257 255 return map; 258 256 } 259 257 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 280 258 protected final void addErrors(Severity severity, Map<Node, Way> errorMap, String message) { 281 259 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; 282 264 errors.add(TestError.builder(this, severity, code) 283 265 .message(message) 284 .primitives( error.getKey(), error.getValue())285 .highlight( error.getKey())266 .primitives(node, way) 267 .highlight(node) 286 268 .build()); 287 269 } 288 270 } … … 289 271 290 272 @Override 291 273 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); 292 294 addErrors(Severity.WARNING, getWayEndNodesNearOtherHighway(), tr("Way end node near other highway")); 293 295 addErrors(Severity.WARNING, getWayEndNodesNearOtherWay(), tr("Way end node near other way")); 294 296 /* the following two use a shorter distance */ 295 297 if (minmiddledist > 0.0) { 298 searchNodes.clear(); 299 searchNodes.addAll(middlenodes); 296 300 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")); 298 304 } 299 305 ways = null; 300 306 endnodes = null; … … 301 307 endnodesHighway = null; 302 308 middlenodes = null; 303 309 othernodes = null; 310 searchNodes = null; 304 311 dsArea = null; 312 ds = null; 305 313 super.endTest(); 306 314 } 307 315 308 316 private class MyWaySegment { 309 private final Line2D line;310 317 public final Way w; 311 318 public final boolean isAbandoned; 312 319 public final boolean isBoundary; 313 320 public final boolean highway; 314 private final double len;315 321 private Set<Node> nearbyNodeCache; 316 322 private double nearbyNodeCacheDist = -1.0; 317 323 private final Node n1; … … 320 326 MyWaySegment(Way w, Node n1, Node n2) { 321 327 this.w = w; 322 328 String railway = w.get(RAILWAY); 323 String highway = w.get(HIGHWAY);324 329 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; 326 331 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());330 332 this.n1 = n1; 331 333 this.n2 = n2; 332 334 } … … 343 345 EastNorth coord = n.getEastNorth(); 344 346 if (coord == null) 345 347 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; 352 351 } 353 352 354 public List<LatLon>getBounds(double fudge) {353 public BBox getBounds(double fudge) { 355 354 double x1 = n1.getCoor().lon(); 356 355 double x2 = n2.getCoor().lon(); 357 356 if (x1 > x2) { … … 368 367 } 369 368 LatLon topLeft = new LatLon(y2+fudge, x1-fudge); 370 369 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); 375 371 } 376 372 377 373 public Collection<Node> nearbyNodes(double dist) { … … 407 403 // This needs to be a hash set because the searches 408 404 // overlap a bit and can return duplicate nodes. 409 405 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); 414 408 for (Node n : foundNodes) { 415 if (!nearby(n, dist) || !n.getCoor().isIn(dsArea)) {409 if (!nearby(n, dist) || (!n.isModified() && !n.getCoor().isIn(dsArea))) { 416 410 continue; 417 411 } 418 412 // It is actually very rare for us to find a node … … 460 454 461 455 @Override 462 456 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); 478 459 } 479 460 } 480 461 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) { 482 470 boolean m = middlenodes.contains(n); 483 471 boolean e = endnodes.contains(n); 484 472 boolean eh = endnodesHighway.contains(n); -
test/unit/org/openstreetmap/josm/data/validation/tests/UnconnectedWaysTest.java
14 14 import org.junit.Test; 15 15 import org.openstreetmap.josm.JOSMFixture; 16 16 import org.openstreetmap.josm.data.osm.DataSet; 17 import org.openstreetmap.josm.gui.MainApplication; 18 import org.openstreetmap.josm.gui.layer.OsmDataLayer; 17 19 import org.openstreetmap.josm.gui.progress.NullProgressMonitor; 18 20 import org.openstreetmap.josm.io.IllegalDataException; 19 21 import org.openstreetmap.josm.io.OsmReader; 22 import org.openstreetmap.josm.testutils.JOSMTestRules; 20 23 21 24 /** 22 25 * Unit tests of {@code UnconnectedWays} class. … … 24 27 public class UnconnectedWaysTest { 25 28 26 29 private UnconnectedWays bib; 27 30 public JOSMTestRules test = new JOSMTestRules().main(); 28 31 /** 29 32 * Setup test. 30 33 * @throws Exception if the test cannot be initialized … … 34 37 bib = new UnconnectedWays.UnconnectedHighways(); 35 38 JOSMFixture.createUnitTestFixture().init(); 36 39 bib.initialize(); 37 bib.startTest(null);38 40 } 39 41 40 42 /** … … 47 49 public void testTicket6313() throws IOException, IllegalDataException, FileNotFoundException { 48 50 try (InputStream fis = Files.newInputStream(Paths.get("data_nodist/UnconnectedWaysTest.osm"))) { 49 51 final DataSet ds = OsmReader.parseDataSet(fis, NullProgressMonitor.INSTANCE); 52 MainApplication.getLayerManager().addLayer(new OsmDataLayer(ds, null, null)); 53 54 bib.startTest(null); 50 55 bib.visit(ds.allPrimitives()); 51 56 bib.endTest(); 52 57 assertThat(bib.getErrors(), isEmpty());