| 114 | | private static GeneralPath createPath(List<Node> nodes) { |
| 115 | | GeneralPath result = new GeneralPath(); |
| 116 | | result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon()); |
| 117 | | for (int i = 1; i < nodes.size(); i++) { |
| 118 | | Node n = nodes.get(i); |
| 119 | | result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon()); |
| 120 | | } |
| 121 | | return result; |
| 122 | | } |
| 123 | | |
| 124 | | private static List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) { |
| 125 | | List<GeneralPath> result = new ArrayList<>(); |
| 126 | | for (Multipolygon.PolyData way : joinedWays) { |
| 127 | | result.add(createPath(way.getNodes())); |
| 128 | | } |
| 129 | | return result; |
| 130 | | } |
| 131 | | |
| 132 | | private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) { |
| 133 | | boolean inside = false; |
| 134 | | boolean outside = false; |
| 135 | | |
| 136 | | for (Node n : inner) { |
| 137 | | boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon()); |
| 138 | | inside = inside | contains; |
| 139 | | outside = outside | !contains; |
| 140 | | if (inside & outside) { |
| 141 | | return Intersection.CROSSING; |
| 142 | | } |
| 143 | | } |
| 144 | | |
| 145 | | return inside ? Intersection.INSIDE : Intersection.OUTSIDE; |
| 146 | | } |
| 147 | | |
| 172 | | |
| 173 | | // Rest of checks is only for complete multipolygons |
| 174 | | if (!r.hasIncompleteMembers()) { |
| 175 | | Multipolygon polygon = new Multipolygon(r); |
| 176 | | |
| 177 | | // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. |
| 178 | | checkMemberRoleCorrectness(r); |
| 179 | | checkStyleConsistency(r, polygon); |
| 180 | | checkGeometry(r, polygon); |
| | 136 | boolean hasRepeatedMembers = checkRepeatedWayMembers(r); |
| | 137 | if (!hasRepeatedMembers) { |
| | 138 | List<Node> intersectionNodes = checkIntersectionAtNodes(r); |
| | 139 | // Rest of checks is only for complete multipolygons |
| | 140 | if (!r.hasIncompleteMembers()) { |
| | 141 | checkMemberRoleCorrectness(r); |
| | 142 | Multipolygon polygon = new Multipolygon(r); |
| | 143 | checkStyleConsistency(r, polygon); |
| | 144 | checkGeometry(r, polygon, intersectionNodes); |
| | 145 | } |
| 330 | | List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons); |
| 331 | | List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons); |
| 332 | | for (int i = 0; i < outerPolygons.size(); i++) { |
| 333 | | PolyData pdOuter = outerPolygons.get(i); |
| 334 | | // Check for intersection between outer members |
| 335 | | for (int j = i+1; j < outerPolygons.size(); j++) { |
| 336 | | checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdOuter, j); |
| 337 | | } |
| | 298 | checkCrossingWays(r, innerPolygons, outerPolygons); |
| | 299 | |
| | 300 | } |
| | 301 | |
| | 302 | /** |
| | 303 | * Determine multipolygon ways which are intersecting (crossing without a common node). This is not allowed. |
| | 304 | * See {@link CrossingWays} |
| | 305 | * @param r the relation (for error reporting) |
| | 306 | * @param innerPolygons list of inner polygons |
| | 307 | * @param outerPolygons list of outer polygons |
| | 308 | */ |
| | 309 | private void checkCrossingWays(Relation r, List<PolyData> innerPolygons, |
| | 310 | List<PolyData> outerPolygons) { |
| | 311 | /** All way segments, grouped by cells */ |
| | 312 | final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); |
| | 313 | /** The already detected ways in error */ |
| | 314 | final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); |
| | 315 | |
| | 316 | for (Way w : r.getMemberPrimitives(Way.class)) { |
| | 317 | checkCrossingWay(w, r, cellSegments, crossingWays); |
| 339 | | for (int i = 0; i < innerPolygons.size(); i++) { |
| 340 | | PolyData pdInner = innerPolygons.get(i); |
| 341 | | // Check for intersection between inner members |
| 342 | | for (int j = i+1; j < innerPolygons.size(); j++) { |
| 343 | | checkCrossingWays(r, innerPolygons, innerPolygonsPaths, pdInner, j); |
| | 319 | } |
| | 320 | |
| | 321 | /** |
| | 322 | * Find ways which are crossing without sharing a node. |
| | 323 | * @param w way that is member of the relation |
| | 324 | * @param r the relation (used for error messages) |
| | 325 | * @param crossingWays |
| | 326 | * @param cellSegments |
| | 327 | */ |
| | 328 | private void checkCrossingWay(Way w, Relation r, Map<Point2D, List<WaySegment>> cellSegments, Map<List<Way>, List<WaySegment>> crossingWays) { |
| | 329 | int nodesSize = w.getNodesCount(); |
| | 330 | for (int i = 0; i < nodesSize - 1; i++) { |
| | 331 | final WaySegment es1 = new WaySegment(w, i); |
| | 332 | final EastNorth en1 = es1.getFirstNode().getEastNorth(); |
| | 333 | final EastNorth en2 = es1.getSecondNode().getEastNorth(); |
| | 334 | if (en1 == null || en2 == null) { |
| | 335 | Main.warn("Crossing ways test skipped " + es1); |
| | 336 | continue; |
| 345 | | // Check for intersection between inner and outer members |
| 346 | | boolean outside = true; |
| 347 | | for (int o = 0; o < outerPolygons.size(); o++) { |
| 348 | | outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; |
| | 338 | for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) { |
| | 339 | for (WaySegment es2 : segments) { |
| | 340 | |
| | 341 | List<WaySegment> highlight; |
| | 342 | if (es2.way == w) |
| | 343 | continue; // reported by CrossingWays.SelfIntersection |
| | 344 | if (!es1.intersects(es2)) |
| | 345 | continue; |
| | 346 | |
| | 347 | List<Way> prims = Arrays.asList(es1.way, es2.way); |
| | 348 | if ((highlight = crossingWays.get(prims)) == null) { |
| | 349 | highlight = new ArrayList<>(); |
| | 350 | highlight.add(es1); |
| | 351 | highlight.add(es2); |
| | 352 | errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) |
| | 353 | .message(tr("Intersection between multipolygon ways")) |
| | 354 | .primitives(Arrays.asList(r, es1.way, es2.way)) |
| | 355 | .highlightWaySegments(highlight) |
| | 356 | .build()); |
| | 357 | crossingWays.put(prims, highlight); |
| | 358 | } else { |
| | 359 | highlight.add(es1); |
| | 360 | highlight.add(es2); |
| | 361 | } |
| | 362 | } |
| | 363 | segments.add(es1); |
| 360 | | private Intersection checkCrossingWays(Relation r, List<PolyData> polygons, List<GeneralPath> polygonsPaths, PolyData pd, int idx) { |
| 361 | | Intersection intersection = getPolygonIntersection(polygonsPaths.get(idx), pd.getNodes()); |
| 362 | | if (intersection == Intersection.CROSSING) { |
| 363 | | PolyData pdOther = polygons.get(idx); |
| 364 | | if (pdOther != null) { |
| | 368 | /** |
| | 369 | * Detect intersections of multipolygon ways at nodes. If any way node is used by more than two ways |
| | 370 | * or two times in one way and at least once in another way we found an intersection. |
| | 371 | * @param r the relation |
| | 372 | * @return List of nodes were ways intersect |
| | 373 | */ |
| | 374 | private List<Node> checkIntersectionAtNodes(Relation r) { |
| | 375 | List<Node> intersectionNodes = new ArrayList<>(); |
| | 376 | List<Pair<Set<Way>,List<Node>>> intersectionErrors = new ArrayList<>(); |
| | 377 | HashMap<Way, String> wayRoleMap = new HashMap<>(); |
| | 378 | for (RelationMember rm : r.getMembers()) { |
| | 379 | if (rm.isWay()) |
| | 380 | wayRoleMap.put(rm.getWay(), rm.getRole()); |
| | 381 | } |
| | 382 | Map<Node, List<Way>> nodeMap = new HashMap<>(); |
| | 383 | for (RelationMember rm : r.getMembers()) { |
| | 384 | if (!rm.isWay()) |
| | 385 | continue; |
| | 386 | int numNodes = rm.getWay().getNodesCount(); |
| | 387 | for (int i = 0; i < numNodes; i++) { |
| | 388 | Node n = rm.getWay().getNode(i); |
| | 389 | if (n.getReferrers().size() <= 1) { |
| | 390 | continue; // cannot be a problem node |
| | 391 | } |
| | 392 | List<Way> ways = nodeMap.get(n); |
| | 393 | if (ways == null) { |
| | 394 | ways = new ArrayList<>(); |
| | 395 | nodeMap.put(n, ways); |
| | 396 | } |
| | 397 | ways.add(rm.getWay()); |
| | 398 | if (ways.size() > 2 || (ways.size() == 2 && i != 0 && i + 1 != numNodes)) { |
| | 399 | intersectionNodes.add(n); |
| | 400 | boolean allInner = true; |
| | 401 | for (Way w : ways) { |
| | 402 | String role = wayRoleMap.get(w); |
| | 403 | if (!"inner".equals(role)) |
| | 404 | allInner = false; |
| | 405 | } |
| | 406 | if (!allInner) { |
| | 407 | Set<Way> errorWays = new HashSet<>(ways); |
| | 408 | boolean addNew = true; |
| | 409 | for (Pair<Set<Way>, List<Node>> pair : intersectionErrors) { |
| | 410 | if (pair.a.size() == errorWays.size() && pair.a.containsAll(errorWays)) { |
| | 411 | pair.b.add(n); |
| | 412 | addNew = false; |
| | 413 | break; |
| | 414 | } |
| | 415 | } |
| | 416 | if (addNew) { |
| | 417 | List<Node> errNodes = new ArrayList<>(); |
| | 418 | errNodes.add(n); |
| | 419 | intersectionErrors.add(new Pair<>(errorWays, errNodes)); |
| | 420 | } |
| | 421 | } |
| | 422 | } |
| | 423 | } |
| | 424 | } |
| | 425 | for (Pair<Set<Way>,List<Node>> pair : intersectionErrors) { |
| | 426 | // A single shared node between two ways ("touching ways") is considered okay. |
| | 427 | if (pair.b.size() > 1) { |