Ticket #13307: improve_MultipolygonTest_v5.patch
File improve_MultipolygonTest_v5.patch, 20.1 KB (added by , 8 years ago) |
---|
-
src/org/openstreetmap/josm/data/osm/WaySegment.java
114 114 s2.getSecondNode().getEastNorth().east(), s2.getSecondNode().getEastNorth().north()); 115 115 } 116 116 117 /** 118 * Checks whether this segment and another way segment share the same points 119 * @param s2 The other segment 120 * @return true if other way segment is the same or reverse 121 * @since r10819 122 */ 123 public boolean isSimilar(WaySegment s2) { 124 if (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode())) 125 return true; 126 if (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode())) 127 return true; 128 return false; 129 } 130 117 131 @Override 118 132 public String toString() { 119 133 return "WaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']'; -
src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java
4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 import static org.openstreetmap.josm.tools.I18n.trn; 6 6 7 import java.awt.geom.GeneralPath; 7 import java.awt.geom.Path2D; 8 import java.awt.geom.Point2D; 8 9 import java.text.MessageFormat; 9 10 import java.util.ArrayList; 10 11 import java.util.Arrays; 11 12 import java.util.Collection; 12 13 import java.util.Collections; 14 import java.util.HashMap; 13 15 import java.util.HashSet; 14 16 import java.util.LinkedList; 15 17 import java.util.List; 18 import java.util.Map; 19 import java.util.Map.Entry; 16 20 import java.util.Set; 17 21 18 22 import org.openstreetmap.josm.Main; 19 23 import org.openstreetmap.josm.actions.CreateMultipolygonAction; 24 import org.openstreetmap.josm.data.coor.EastNorth; 25 import org.openstreetmap.josm.data.coor.LatLon; 20 26 import org.openstreetmap.josm.data.osm.Node; 21 27 import org.openstreetmap.josm.data.osm.OsmPrimitive; 22 28 import org.openstreetmap.josm.data.osm.Relation; 23 29 import org.openstreetmap.josm.data.osm.RelationMember; 24 30 import org.openstreetmap.josm.data.osm.Way; 31 import org.openstreetmap.josm.data.osm.WaySegment; 25 32 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 26 33 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 27 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;28 34 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 29 35 import org.openstreetmap.josm.data.validation.OsmValidator; 30 36 import org.openstreetmap.josm.data.validation.Severity; 31 37 import org.openstreetmap.josm.data.validation.Test; 32 38 import org.openstreetmap.josm.data.validation.TestError; 39 import org.openstreetmap.josm.data.validation.util.ValUtil; 33 40 import org.openstreetmap.josm.gui.DefaultNameFormatter; 34 41 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 35 42 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; … … 71 78 private static volatile ElemStyles styles; 72 79 73 80 private final Set<String> keysCheckedByAnotherTest = new HashSet<>(); 81 /** All way segments, grouped by cells */ 82 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 83 /** The already detected ways in error */ 84 private final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 74 85 75 86 /** 76 87 * Constructs a new {@code MultipolygonTest}. … … 103 114 super.endTest(); 104 115 } 105 116 106 private static GeneralPathcreatePath(List<Node> nodes) {107 GeneralPath result = new GeneralPath();108 result.moveTo( (float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());117 private static Path2D.Double createPath(List<Node> nodes) { 118 Path2D.Double result = new Path2D.Double(); 119 result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat()); 109 120 for (int i = 1; i < nodes.size(); i++) { 110 121 Node n = nodes.get(i); 111 result.lineTo( (float) n.getCoor().lat(), (float) n.getCoor().lon());122 result.lineTo(n.getCoor().lon(), n.getCoor().lat()); 112 123 } 113 124 return result; 114 125 } 115 126 116 private static List< GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {117 List< GeneralPath> result = new ArrayList<>();127 private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) { 128 List<Path2D.Double> result = new ArrayList<>(); 118 129 for (Multipolygon.PolyData way : joinedWays) { 119 130 result.add(createPath(way.getNodes())); 120 131 } … … 121 132 return result; 122 133 } 123 134 124 private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {125 boolean inside = false;126 boolean outside = false;127 128 for (Node n : inner) {129 boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());130 inside = inside | contains;131 outside = outside | !contains;132 if (inside & outside) {133 return Intersection.CROSSING;134 }135 }136 137 return inside ? Intersection.INSIDE : Intersection.OUTSIDE;138 }139 140 135 @Override 141 136 public void visit(Way w) { 142 137 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 155 150 @Override 156 151 public void visit(Relation r) { 157 152 if (r.isMultipolygon()) { 153 crossingWays.clear(); 154 cellSegments.clear(); 155 158 156 checkMembersAndRoles(r); 159 157 checkOuterWay(r); 160 158 … … 163 161 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r); 164 162 165 163 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. 166 checkMemberRoleCorrectness(r);164 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 167 165 checkStyleConsistency(r, polygon); 168 checkGeometry(r, polygon );166 checkGeometry(r, polygon, rolesWereChecked); 169 167 } 170 168 } 171 169 } … … 194 192 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 195 193 * </ul> 196 194 * @param r relation 195 * @return true if member roles were checked 197 196 */ 198 private voidcheckMemberRoleCorrectness(Relation r) {197 private boolean checkMemberRoleCorrectness(Relation r) { 199 198 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 200 199 if (newMP != null) { 201 200 for (RelationMember member : r.getMembers()) { … … 216 215 } 217 216 } 218 217 } 218 return newMP != null; 219 219 } 220 220 221 221 /** … … 285 285 } 286 286 } 287 287 288 private static class LatLonPolyData { 289 final PolyData pd; 290 final Path2D.Double latLonPath; 291 292 LatLonPolyData(PolyData polyData, Path2D.Double path) { 293 this.pd = polyData; 294 this.latLonPath = path; 295 } 296 } 297 288 298 /** 289 299 * Various geometry-related checks:<ul> 290 300 * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li> … … 293 303 * </ul> 294 304 * @param r relation 295 305 * @param polygon multipolygon 306 * @param rolesWereChecked might be used to skip most of the tests below 296 307 */ 297 private void checkGeometry(Relation r, Multipolygon polygon ) {308 private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) { 298 309 List<Node> openNodes = polygon.getOpenEnds(); 299 310 if (!openNodes.isEmpty()) { 300 311 List<OsmPrimitive> primitives = new LinkedList<>(); 301 312 primitives.add(r); 302 313 primitives.addAll(openNodes); 303 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 314 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, 315 primitives, openNodes)); 304 316 } 317 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 318 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 305 319 320 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 321 322 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 323 List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons); 324 List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons); 306 325 // For painting is used Polygon class which works with ints only. For validation we need more precision 307 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 308 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 309 List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons); 310 List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons); 311 for (int i = 0; i < outerPolygons.size(); i++) { 312 PolyData pdOuter = outerPolygons.get(i); 313 // Check for intersection between outer members 314 for (int j = i+1; j < outerPolygons.size(); j++) { 315 checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdOuter, j); 326 for (int i = 0; i + 1 < outer.size(); i++) { 327 // report outer polygons which lie inside another outer 328 LatLonPolyData outer1 = outer.get(i); 329 for (int j = 0; j < outer.size(); j++) { 330 if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) { 331 LatLon c = outer.get(j).pd.getNodes().get(0).getCoor(); 332 if (outer1.latLonPath.contains(c.lon(), c.lat())) { 333 addError(r, 334 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 335 CROSSING_WAYS, Collections.singletonList(r), 336 Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes()))); 337 } 338 } 316 339 } 317 340 } 318 for (int i = 0; i < innerPolygons.size(); i++) { 319 PolyData pdInner = innerPolygons.get(i); 320 // Check for intersection between inner members 321 for (int j = i+1; j < innerPolygons.size(); j++) { 322 checkCrossingWays(r, innerPolygons, innerPolygonsPaths, pdInner, j); 341 for (int i = 0; i < inner.size(); i++) { 342 LatLonPolyData inner1 = inner.get(i); 343 LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor(); 344 for (int j = 0; j < inner.size(); j++) { 345 LatLonPolyData inner2 = inner.get(j); 346 if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) { 347 if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())) { 348 addError(r, 349 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 350 CROSSING_WAYS, Collections.singletonList(r), 351 Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes()))); 352 } 353 } 323 354 } 324 // Check for intersection between inner and outer members 355 356 // Find inner polygons which are not inside any outer 325 357 boolean outside = true; 326 for (int o = 0; o < outerPolygons.size(); o++) { 327 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 358 boolean crossingWithOuter = false; 359 360 for (int o = 0; o < outer.size(); o++) { 361 if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) { 362 crossingWithOuter = true; 363 break; 364 } 365 outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false; 366 if (!outside) 367 break; 328 368 } 329 if (outside ) {369 if (outside && !crossingWithOuter) { 330 370 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 331 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList( pdInner.getNodes())));371 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes()))); 332 372 } 333 373 } 334 374 } 335 375 336 private Intersection checkCrossingWays(Relation r, List<PolyData> polygons, List<GeneralPath> polygonsPaths, PolyData pd, int idx) { 337 Intersection intersection = getPolygonIntersection(polygonsPaths.get(idx), pd.getNodes()); 338 if (intersection == Intersection.CROSSING) { 339 PolyData pdOther = polygons.get(idx); 340 if (pdOther != null) { 341 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 342 CROSSING_WAYS, Collections.singletonList(r), Arrays.asList(pd.getNodes(), pdOther.getNodes()))); 343 } 376 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 377 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 378 if (crossingWithFirst != null) { 379 if (crossingWithFirst.contains(pd2)) 380 return true; 344 381 } 345 return intersection; 382 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 383 if (crossingWith2nd != null) { 384 if (crossingWith2nd.contains(pd1)) 385 return true; 386 } 387 return false; 346 388 } 347 389 390 private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) { 391 if (polygons == null || polygons.isEmpty()) 392 return Collections.emptyList(); 393 List<LatLonPolyData> latLonPolygons = new ArrayList<>(); 394 List<Path2D.Double> polygonsPaths = createPolygons(polygons); 395 for (int i = 0; i < polygons.size(); i++) { 396 latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i))); 397 } 398 return latLonPolygons; 399 } 400 348 401 /** 349 402 * Check for:<ul> 350 403 * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li> … … 389 442 addRelationIfNeeded(error, r); 390 443 errors.add(error); 391 444 } 445 446 /** 447 * Determine multipolygon ways which are intersecting. This is now allowed. 448 * See {@link CrossingWays} 449 * @param r the relation (for error reporting) 450 * @param innerPolygons list of inner polygons 451 * @param outerPolygons list of outer polygons 452 * @return map of crossing polygons (including polygons touching outer) 453 */ 454 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 455 List<PolyData> outerPolygons) { 456 List<Way> innerWays = new ArrayList<>(); 457 List<Way> outerWays = new ArrayList<>(); 458 for (Way w : r.getMemberPrimitives(Way.class)) { 459 for (PolyData pd : innerPolygons) { 460 if (pd.getWayIds().contains(w.getUniqueId())) { 461 innerWays.add(w); 462 break; 463 } 464 } 465 for (PolyData pd : outerPolygons) { 466 if (pd.getWayIds().contains(w.getUniqueId())) { 467 outerWays.add(w); 468 break; 469 } 470 } 471 } 472 for (Way w : innerWays) { 473 checkCrossingWay(w, r, true /* allow shared ways */); 474 } 475 for (Way w : outerWays) { 476 checkCrossingWay(w, r, false/* don't allow shared ways */); 477 } 478 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 479 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 480 List<Way> ways = entry.getKey(); 481 PolyData[] crossingPolys = new PolyData[2]; 482 for (Way w : ways) { 483 for (int j = 0; j < crossingPolys.length; j++) { 484 for (PolyData pd : innerPolygons) { 485 if (pd.getWayIds().contains(w.getUniqueId())) { 486 crossingPolys[j] = pd; 487 break; 488 } 489 } 490 if (crossingPolys[j] != null) 491 break; 492 for (PolyData pd : outerPolygons) { 493 if (pd.getWayIds().contains(w.getUniqueId())) { 494 crossingPolys[j] = pd; 495 break; 496 } 497 } 498 } 499 } 500 if (crossingPolys[0] != null && crossingPolys[1] != null) { 501 List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]); 502 if (x == null) { 503 x = new ArrayList<>(); 504 crossingPolygonsMap.put(crossingPolys[0], x); 505 } 506 x.add(crossingPolys[1]); 507 } 508 } 509 return crossingPolygonsMap; 510 } 511 512 /** 513 * 514 * @param w way that is member of the relation 515 * @param r the relation (used for error messages) 516 * @param allowSharedWaySegment false: treat similar way segment as crossing 517 */ 518 private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) { 519 520 int nodesSize = w.getNodesCount(); 521 for (int i = 0; i < nodesSize - 1; i++) { 522 final WaySegment es1 = new WaySegment(w, i); 523 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 524 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 525 if (en1 == null || en2 == null) { 526 Main.warn("Crossing ways test skipped " + es1); 527 continue; 528 } 529 for (List<WaySegment> segments : getSegments(en1, en2)) { 530 for (WaySegment es2 : segments) { 531 532 List<WaySegment> highlight; 533 534 if (!es1.intersects(es2)) { 535 if (allowSharedWaySegment || !es1.isSimilar(es2)) 536 continue; 537 } 538 539 List<Way> prims = Arrays.asList(es1.way, es2.way); 540 if ((highlight = crossingWays.get(prims)) == null) { 541 highlight = new ArrayList<>(); 542 highlight.add(es1); 543 highlight.add(es2); 544 545 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 546 CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight)); 547 crossingWays.put(prims, highlight); 548 } else { 549 highlight.add(es1); 550 highlight.add(es2); 551 } 552 } 553 segments.add(es1); 554 } 555 } 556 } 557 558 /** 559 * Returns all the cells this segment crosses. Each cell contains the list 560 * of segments already processed 561 * 562 * @param n1 The first EastNorth 563 * @param n2 The second EastNorth 564 * @return A list with all the cells the segment crosses 565 */ 566 private List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) { 567 568 List<List<WaySegment>> cells = new ArrayList<>(); 569 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) { 570 List<WaySegment> segments = cellSegments.get(cell); 571 if (segments == null) { 572 segments = new ArrayList<>(); 573 cellSegments.put(cell, segments); 574 } 575 cells.add(segments); 576 } 577 return cells; 578 } 392 579 }