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