Ticket #13307: improve_MultipolygonTest_v3.patch
File improve_MultipolygonTest_v3.patch, 19.5 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 */ 122 public boolean isSimilar(WaySegment s2){ 123 if (getFirstNode().equals(s2.getFirstNode()) && getSecondNode().equals(s2.getSecondNode())) 124 return true; 125 if (getFirstNode().equals(s2.getSecondNode()) && getSecondNode().equals(s2.getFirstNode())) 126 return true; 127 return false; 128 } 117 129 @Override 118 130 public String toString() { 119 131 return "WaySegment [way=" + way.getUniqueId() + ", lowerIndex=" + lowerIndex + ']'; 120 132 } 133 134 121 135 } -
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 /** … … 284 284 } 285 285 } 286 286 } 287 private static class LatLonPolyData{ 288 final PolyData pd; 289 final Path2D.Double latLonPath; 290 public LatLonPolyData(PolyData polyData, Path2D.Double path) { 291 this.pd = polyData; 292 this.latLonPath = path; 293 } 294 } 287 295 288 296 /** 289 297 * Various geometry-related checks:<ul> … … 293 301 * </ul> 294 302 * @param r relation 295 303 * @param polygon multipolygon 304 * @param rolesWereChecked might be used to skip most of the tests below 296 305 */ 297 private void checkGeometry(Relation r, Multipolygon polygon ) {306 private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) { 298 307 List<Node> openNodes = polygon.getOpenEnds(); 299 308 if (!openNodes.isEmpty()) { 300 309 List<OsmPrimitive> primitives = new LinkedList<>(); … … 302 311 primitives.addAll(openNodes); 303 312 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 304 313 } 314 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 315 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 305 316 317 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 318 319 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 320 List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons); 321 List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons); 306 322 // 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); 323 for (int i = 0; i+1 < outer.size(); i++) { 324 // report outer polygons which lie inside another outer 325 LatLonPolyData outer1 = outer.get(i); 326 for (int j = 0; j < outer.size(); j++) { 327 if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)){ 328 LatLon c = outer.get(j).pd.getNodes().get(0).getCoor(); 329 if (outer1.latLonPath.contains(c.lon(), c.lat())){ 330 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 331 CROSSING_WAYS, Collections.singletonList(r), Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes()))); 332 } 333 } 316 334 } 317 335 } 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); 336 for (int i = 0; i < inner.size(); i++) { 337 LatLonPolyData inner1 = inner.get(i); 338 LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor(); 339 for (int j = 0; j < inner.size(); j++) { 340 LatLonPolyData inner2 = inner.get(j); 341 if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)){ 342 if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())){ 343 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 344 CROSSING_WAYS, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes()))); 345 } 346 } 323 347 } 324 // Check for intersection between inner and outer members 348 349 // Find inner polygons which are not inside any outer 325 350 boolean outside = true; 326 for (int o = 0; o < outerPolygons.size(); o++) { 327 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 351 boolean crossingWithOuter = false; 352 353 for (int o = 0; o < outer.size(); o++) { 354 if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)){ 355 crossingWithOuter = true; 356 break; 357 } 358 outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false; 359 if (!outside) 360 break; 328 361 } 329 if (outside ) {362 if (outside && !crossingWithOuter) { 330 363 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 331 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList( pdInner.getNodes())));364 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes()))); 332 365 } 333 366 } 334 367 } 335 368 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 } 369 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 370 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 371 if (crossingWithFirst != null){ 372 if (crossingWithFirst.contains(pd2)) 373 return true; 344 374 } 345 return intersection; 375 List<PolyData> crossingWith2nd= crossingPolyMap.get(pd2); 376 if (crossingWith2nd != null){ 377 if (crossingWith2nd.contains(pd1)) 378 return true; 379 } 380 return false; 346 381 } 347 382 383 private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) { 384 if (polygons == null || polygons.isEmpty()) 385 return Collections.emptyList(); 386 List<LatLonPolyData> latLonPolygons = new ArrayList<>(); 387 List<Path2D.Double> polygonsPaths = createPolygons(polygons); 388 for (int i = 0; i < polygons.size(); i++) { 389 latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i))); 390 } 391 return latLonPolygons; 392 } 393 348 394 /** 349 395 * Check for:<ul> 350 396 * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li> … … 389 435 addRelationIfNeeded(error, r); 390 436 errors.add(error); 391 437 } 438 439 /** 440 * Determine multipolygon ways which are intersecting. This is now allowed. 441 * See {@link CrossingWays} 442 * @param r the relation (for error reporting) 443 * @param innerPolygons list of inner polygons 444 * @param outerPolygons list of outer polygons 445 * @return map of crossing polygons (including polygons touching outer) 446 */ 447 private HashMap<PolyData, List<PolyData>> checkCrossingWays (Relation r, List<PolyData> innerPolygons, List<PolyData> outerPolygons){ 448 List<Way> innerWays = new ArrayList<>(); 449 List<Way> outerWays = new ArrayList<>(); 450 for (Way w : r.getMemberPrimitives(Way.class)){ 451 for (PolyData pd : innerPolygons){ 452 if (pd.getWayIds().contains(w.getUniqueId())){ 453 innerWays.add(w); 454 break; 455 } 456 } 457 for (PolyData pd : outerPolygons){ 458 if (pd.getWayIds().contains(w.getUniqueId())){ 459 outerWays.add(w); 460 break; 461 } 462 } 463 } 464 for (Way w : innerWays){ 465 checkCrossingWay(w, r, true /* allow shared ways */); 466 } 467 for (Way w : outerWays){ 468 checkCrossingWay(w, r, false/* don't allow shared ways */); 469 } 470 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 471 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()){ 472 List<Way> ways = entry.getKey(); 473 PolyData[] crossingPolys = new PolyData[2]; 474 for (Way w: ways){ 475 for (int j = 0; j < crossingPolys.length; j++){ 476 for (PolyData pd : innerPolygons){ 477 if (pd.getWayIds().contains(w.getUniqueId())){ 478 crossingPolys[j] = pd; 479 break; 480 } 481 } 482 if (crossingPolys[j] != null) 483 break; 484 for (PolyData pd : outerPolygons){ 485 if (pd.getWayIds().contains(w.getUniqueId())){ 486 crossingPolys[j] = pd; 487 break; 488 } 489 } 490 } 491 } 492 if (crossingPolys[0] != null && crossingPolys[1] != null){ 493 List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]); 494 if (x == null){ 495 x = new ArrayList<>(); 496 crossingPolygonsMap.put(crossingPolys[0], x); 497 } 498 x.add(crossingPolys[1]); 499 } 500 } 501 return crossingPolygonsMap; 502 } 503 504 /** 505 * 506 * @param w 507 * @param r 508 * @param allowSharedWaySegment 509 */ 510 private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) { 511 512 int nodesSize = w.getNodesCount(); 513 for (int i = 0; i < nodesSize - 1; i++) { 514 final WaySegment es1 = new WaySegment(w, i); 515 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 516 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 517 if (en1 == null || en2 == null) { 518 Main.warn("Crossing ways test skipped "+es1); 519 continue; 520 } 521 for (List<WaySegment> segments : getSegments(en1, en2)) { 522 for (WaySegment es2 : segments) { 523 524 List<WaySegment> highlight; 525 526 if (!es1.intersects(es2)) { 527 if (allowSharedWaySegment || !es1.isSimilar(es2)) 528 continue; 529 } 530 531 List<Way> prims = Arrays.asList(es1.way, es2.way); 532 if ((highlight = crossingWays.get(prims)) == null) { 533 highlight = new ArrayList<>(); 534 highlight.add(es1); 535 highlight.add(es2); 536 537 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 538 CROSSING_WAYS, Arrays.asList(r,es1.way,es2.way), highlight )); 539 crossingWays.put(prims, highlight); 540 } else { 541 highlight.add(es1); 542 highlight.add(es2); 543 } 544 } 545 segments.add(es1); 546 } 547 } 548 } 549 550 /** 551 * Returns all the cells this segment crosses. Each cell contains the list 552 * of segments already processed 553 * 554 * @param n1 The first EastNorth 555 * @param n2 The second EastNorth 556 * @return A list with all the cells the segment crosses 557 */ 558 private List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) { 559 560 List<List<WaySegment>> cells = new ArrayList<>(); 561 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) { 562 List<WaySegment> segments = cellSegments.get(cell); 563 if (segments == null) { 564 segments = new ArrayList<>(); 565 cellSegments.put(cell, segments); 566 } 567 cells.add(segments); 568 } 569 return cells; 570 } 392 571 }