Ticket #13307: improve_MultipolygonTest_v11.patch
| File improve_MultipolygonTest_v11.patch, 27.4 KB (added by , 10 years ago) |
|---|
-
src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
9 9 import java.util.Collections; 10 10 import java.util.HashSet; 11 11 import java.util.Iterator; 12 import java.util.LinkedHashSet; 12 13 import java.util.List; 13 14 import java.util.Set; 14 15 … … 462 463 463 464 private void load(Relation r) { 464 465 MultipolygonRoleMatcher matcher = getMultipolygonRoleMatcher(); 465 466 // use HashSet to eliminate repeated members 467 Set<Way> tmpInnerWays = new LinkedHashSet<>(); 468 Set<Way> tmpOuterWays = new LinkedHashSet<>(); 466 469 // Fill inner and outer list with valid ways 467 470 for (RelationMember m : r.getMembers()) { 468 471 if (m.getMember().isIncomplete()) { … … 475 478 } 476 479 477 480 if (matcher.isInnerRole(m.getRole())) { 478 innerWays.add(w);481 tmpInnerWays.add(w); 479 482 } else if (!m.hasRole() || matcher.isOuterRole(m.getRole())) { 480 outerWays.add(w);483 tmpOuterWays.add(w); 481 484 } // Remaining roles ignored 482 485 } // Non ways ignored 483 486 } 484 487 innerWays.addAll(tmpInnerWays); 488 outerWays.addAll(tmpOuterWays); 485 489 final List<PolyData> innerPolygons = new ArrayList<>(); 486 490 final List<PolyData> outerPolygons = new ArrayList<>(); 487 491 createPolygons(innerWays, innerPolygons); -
src/org/openstreetmap/josm/data/validation/tests/MultipolygonTest.java
5 5 import static org.openstreetmap.josm.tools.I18n.tr; 6 6 import static org.openstreetmap.josm.tools.I18n.trn; 7 7 8 import java.awt.geom. GeneralPath;8 import java.awt.geom.Point2D; 9 9 import java.util.ArrayList; 10 10 import java.util.Arrays; 11 11 import java.util.Collection; 12 12 import java.util.Collections; 13 import java.util.HashMap; 13 14 import java.util.HashSet; 14 15 import java.util.List; 16 import java.util.Map; 17 import java.util.Map.Entry; 15 18 import java.util.Set; 16 19 17 20 import org.openstreetmap.josm.Main; 18 21 import org.openstreetmap.josm.actions.CreateMultipolygonAction; 22 import org.openstreetmap.josm.command.ChangeCommand; 23 import org.openstreetmap.josm.command.Command; 24 import org.openstreetmap.josm.data.coor.EastNorth; 19 25 import org.openstreetmap.josm.data.osm.Node; 20 26 import org.openstreetmap.josm.data.osm.OsmPrimitive; 21 27 import org.openstreetmap.josm.data.osm.Relation; 22 28 import org.openstreetmap.josm.data.osm.RelationMember; 23 29 import org.openstreetmap.josm.data.osm.Way; 30 import org.openstreetmap.josm.data.osm.WaySegment; 24 31 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 25 32 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData; 26 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData.Intersection;27 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;28 33 import org.openstreetmap.josm.data.validation.OsmValidator; 29 34 import org.openstreetmap.josm.data.validation.Severity; 30 35 import org.openstreetmap.josm.data.validation.Test; … … 66 71 public static final int NO_STYLE_POLYGON = 1611; 67 72 /** Area style on outer way */ 68 73 public static final int OUTER_STYLE = 1613; 74 /** Multipolygon member repeated (same primitive, same role */ 75 public static final int REPEATED_MEMBER_SAME_ROLE = 1614; 76 /** Multipolygon member repeated (same primitive, different role) */ 77 public static final int REPEATED_MEMBER_DIFF_ROLE = 1615; 69 78 70 79 private static volatile ElemStyles styles; 71 80 … … 102 111 super.endTest(); 103 112 } 104 113 105 private static GeneralPath createPath(List<Node> nodes) {106 GeneralPath result = new GeneralPath();107 result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());108 for (int i = 1; i < nodes.size(); i++) {109 Node n = nodes.get(i);110 result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());111 }112 return result;113 }114 115 private static List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {116 List<GeneralPath> result = new ArrayList<>();117 for (Multipolygon.PolyData way : joinedWays) {118 result.add(createPath(way.getNodes()));119 }120 return result;121 }122 123 private static Intersection getPolygonIntersection(GeneralPath outer, List<Node> inner) {124 boolean inside = false;125 boolean outside = false;126 127 for (Node n : inner) {128 boolean contains = outer.contains(n.getCoor().lat(), n.getCoor().lon());129 inside = inside | contains;130 outside = outside | !contains;131 if (inside & outside) {132 return Intersection.CROSSING;133 }134 }135 136 return inside ? Intersection.INSIDE : Intersection.OUTSIDE;137 }138 139 114 @Override 140 115 public void visit(Way w) { 141 116 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 158 133 public void visit(Relation r) { 159 134 if (r.isMultipolygon()) { 160 135 checkMembersAndRoles(r); 136 checkRepeatedWayMembers(r); 161 137 checkOuterWay(r); 138 List<Node> intersectionNodes = checkIntersectionAtNodes(r); 162 139 163 140 // Rest of checks is only for complete multipolygons 164 141 if (!r.hasIncompleteMembers()) { 165 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r); 166 167 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. 168 checkMemberRoleCorrectness(r); 142 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 143 Multipolygon polygon = new Multipolygon(r); 169 144 checkStyleConsistency(r, polygon); 170 checkGeometry(r, polygon );145 checkGeometry(r, polygon, intersectionNodes, rolesWereChecked); 171 146 } 172 147 } 173 148 } … … 195 170 } 196 171 197 172 /** 198 * Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match:<ul> 173 * If simple joining of ways doesn't work, create new multipolygon using the logics from 174 * CreateMultipolygonAction and see if roles match:<ul> 199 175 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 200 176 * </ul> 201 177 * @param r relation 178 * @return true if member roles were checked 202 179 */ 203 private voidcheckMemberRoleCorrectness(Relation r) {180 private boolean checkMemberRoleCorrectness(Relation r) { 204 181 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 182 205 183 if (newMP != null) { 206 184 for (RelationMember member : r.getMembers()) { 207 185 final Collection<RelationMember> memberInNewMP = newMP.b.getMembersFor(Collections.singleton(member.getMember())); … … 219 197 } 220 198 } 221 199 } 200 return newMP != null; 222 201 } 223 202 224 203 /** … … 303 282 * </ul> 304 283 * @param r relation 305 284 * @param polygon multipolygon 285 * @param intersectionNodes known nodes where ways of this multipolygon intersect or touch 286 * @param rolesWereChecked might be used to skip most of the tests below 306 287 */ 307 private void checkGeometry(Relation r, Multipolygon polygon ) {288 private void checkGeometry(Relation r, Multipolygon polygon, List<Node> intersectionNodes, boolean rolesWereChecked) { 308 289 List<Node> openNodes = polygon.getOpenEnds(); 309 290 if (!openNodes.isEmpty()) { 310 291 errors.add(TestError.builder(this, Severity.WARNING, NON_CLOSED_WAY) … … 314 295 .build()); 315 296 } 316 297 317 // For painting is used Polygon class which works with ints only. For validation we need more precision318 298 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 319 299 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 320 List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons); 321 List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons); 300 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 301 302 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 322 303 for (int i = 0; i < outerPolygons.size(); i++) { 323 PolyData pdOuter = outerPolygons.get(i); 324 // Check for intersection between outer members 325 for (int j = i+1; j < outerPolygons.size(); j++) { 326 checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdOuter, j); 304 // report outer polygons which lie inside another outer 305 PolyData outer1 = outerPolygons.get(i); 306 for (int j = 0; j < outerPolygons.size(); j++) { 307 if (i != j && !crossing(crossingPolyMap, outer1, outerPolygons.get(j))) { 308 EastNorth en1 = null; 309 // find node which is not a intersection of multipolygon ways 310 for (int k = 0; k < outerPolygons.get(j).getNodes().size(); k++) { 311 Node n = outerPolygons.get(j).getNodes().get(k); 312 if (intersectionNodes.contains(n) == false) 313 en1 = n.getEastNorth(); 314 } 315 if (en1 != null && outer1.get().contains(en1.getX(), en1.getY())) { 316 ArrayList<OsmPrimitive> hilite = new ArrayList<>(); 317 hilite.addAll(outer1.getNodes()); 318 hilite.addAll(outerPolygons.get(j).getNodes()); 319 errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) 320 .message(tr("Outer inside outer")) 321 .primitives(r) 322 .highlight(hilite) 323 .build()); 324 325 } 326 } 327 327 } 328 328 } 329 for (int i = 0; i < innerPolygons.size(); i++) { 330 PolyData pdInner = innerPolygons.get(i); 331 // Check for intersection between inner members 332 for (int j = i+1; j < innerPolygons.size(); j++) { 333 checkCrossingWays(r, innerPolygons, innerPolygonsPaths, pdInner, j); 329 for (PolyData inner1 : innerPolygons) { 330 for (PolyData inner2 : innerPolygons) { 331 if (inner1 != inner2 && !crossing(crossingPolyMap, inner1, inner2)) { 332 // we must allow that inner polygons share some nodes 333 boolean allInside = true; 334 for (Node innerPoint : inner2.getNodes()) { 335 EastNorth en = innerPoint.getEastNorth(); 336 if (!inner1.get().contains(en.getX(), en.getY())) { 337 allInside = false; 338 break; 339 } 340 } 341 if (allInside) { 342 ArrayList<OsmPrimitive> hilite = new ArrayList<>(); 343 hilite.addAll(inner1.getNodes()); 344 hilite.addAll(inner2.getNodes()); 345 errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) 346 .message(tr("Inner inside inner")) 347 .primitives(r) 348 .highlight(hilite) 349 .build()); 350 } 351 } 334 352 } 335 // Check for intersection between inner and outer members 336 boolean outside = true; 337 for (int o = 0; o < outerPolygons.size(); o++) { 338 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 339 } 340 if (outside) { 341 errors.add(TestError.builder(this, Severity.WARNING, INNER_WAY_OUTSIDE) 342 .message(tr("Multipolygon inner way is outside")) 343 .primitives(r) 344 .highlightNodePairs(Collections.singletonList(pdInner.getNodes())) 345 .build()); 346 } 353 // Find inner polygons which are not inside any outer 354 // MAYBE enable the following line to reduce number of warnings for same problem (and adapt multipolygon.osm) 355 // if (!rolesWereChecked) { 356 boolean outside = true; 357 boolean crossingWithOuter = false; 358 EastNorth innerPoint = inner1.getNodes().get(0).getEastNorth(); 359 for (PolyData outer : outerPolygons) { 360 if (crossing(crossingPolyMap, inner1, outer)) { 361 crossingWithOuter = true; 362 break; 363 } 364 outside &= !outer.get().contains(innerPoint.getX(), innerPoint.getY()); 365 if (!outside) 366 break; 367 } 368 if (outside && !crossingWithOuter) { 369 errors.add(TestError.builder(this, Severity.WARNING, INNER_WAY_OUTSIDE) 370 .message(tr("Multipolygon inner way is outside")) 371 .primitives(r) 372 .highlightNodePairs(Collections.singletonList(inner1.getNodes())) 373 .build()); 374 } 375 // } 347 376 } 348 377 } 349 378 350 private Intersection checkCrossingWays(Relation r, List<PolyData> polygons, List<GeneralPath> polygonsPaths, PolyData pd, int idx) { 351 Intersection intersection = getPolygonIntersection(polygonsPaths.get(idx), pd.getNodes()); 352 if (intersection == Intersection.CROSSING) { 353 PolyData pdOther = polygons.get(idx); 354 if (pdOther != null) { 355 errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) 356 .message(tr("Intersection between multipolygon ways")) 357 .primitives(r) 358 .highlightNodePairs(Arrays.asList(pd.getNodes(), pdOther.getNodes())) 359 .build()); 360 } 379 /** 380 * Check if crossing map contains combination of two given polygons. 381 * @param crossingPolyMap the map 382 * @param pd1 1st polygon 383 * @param pd2 2nd polygon 384 * @return true if the polygons are crossing without sharing a node 385 */ 386 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 387 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 388 if (crossingWithFirst != null) { 389 if (crossingWithFirst.contains(pd2)) 390 return true; 361 391 } 362 return intersection; 392 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 393 if (crossingWith2nd != null) { 394 if (crossingWith2nd.contains(pd1)) 395 return true; 396 } 397 return false; 363 398 } 364 399 365 400 /** … … 412 447 } 413 448 } 414 449 450 /** 451 * Determine multipolygon ways which are intersecting (crossing without a common node). This is not allowed. 452 * See {@link CrossingWays} 453 * @param r the relation (for error reporting) 454 * @param innerPolygons list of inner polygons 455 * @param outerPolygons list of outer polygons 456 * @return map of crossing polygons (including polygons touching outer) 457 */ 458 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 459 List<PolyData> outerPolygons) { 460 /** All way segments, grouped by cells */ 461 final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 462 /** The already detected ways in error */ 463 final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 464 465 for (Way w : r.getMemberPrimitives(Way.class)) { 466 checkCrossingWay(w, r, cellSegments, crossingWays); 467 } 468 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 469 if (!crossingWays.isEmpty()) { 470 List<PolyData> allPolygons = new ArrayList<>(innerPolygons.size() + outerPolygons.size()); 471 allPolygons.addAll(innerPolygons); 472 allPolygons.addAll(outerPolygons); 473 474 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 475 List<Way> ways = entry.getKey(); 476 if (ways.size() != 2) 477 continue; 478 PolyData[] crossingPolys = new PolyData[2]; 479 for (int i = 0; i < 2; i++) { 480 Way w = ways.get(i); 481 for (PolyData pd : allPolygons) { 482 if (pd.getWayIds().contains(w.getUniqueId())) { 483 crossingPolys[i] = pd; 484 break; 485 } 486 } 487 } 488 if (crossingPolys[0] != null && crossingPolys[1] != null) { 489 List<PolyData> crossingPolygons = crossingPolygonsMap.get(crossingPolys[0]); 490 if (crossingPolygons == null) { 491 crossingPolygons = new ArrayList<>(); 492 crossingPolygonsMap.put(crossingPolys[0], crossingPolygons); 493 } 494 crossingPolygons.add(crossingPolys[1]); 495 } 496 } 497 } 498 return crossingPolygonsMap; 499 } 500 501 /** 502 * Find ways which are crossing without sharing a node. 503 * @param w way that is member of the relation 504 * @param r the relation (used for error messages) 505 * @param crossingWays 506 * @param cellSegments 507 */ 508 private void checkCrossingWay(Way w, Relation r, Map<Point2D, List<WaySegment>> cellSegments, Map<List<Way>, List<WaySegment>> crossingWays) { 509 int nodesSize = w.getNodesCount(); 510 for (int i = 0; i < nodesSize - 1; i++) { 511 final WaySegment es1 = new WaySegment(w, i); 512 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 513 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 514 if (en1 == null || en2 == null) { 515 Main.warn("Crossing ways test skipped " + es1); 516 continue; 517 } 518 for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) { 519 for (WaySegment es2 : segments) { 520 521 List<WaySegment> highlight; 522 if (es2.way == w) 523 continue; // reported by CrossingWays.SelfIntersection 524 if (!es1.intersects(es2)) 525 continue; 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 errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) 533 .message(tr("Intersection between multipolygon ways")) 534 .primitives(Arrays.asList(r, es1.way, es2.way)) 535 .highlightWaySegments(highlight) 536 .build()); 537 crossingWays.put(prims, highlight); 538 } else { 539 highlight.add(es1); 540 highlight.add(es2); 541 } 542 } 543 segments.add(es1); 544 } 545 } 546 } 547 548 /** 549 * Detect intersections of multipolygon ways at nodes. If any way node is used by more than two ways 550 * or two times in one way and at least once in another way we found an intersection. 551 * @param r the relation 552 * @return List of nodes were ways intersect 553 */ 554 private List<Node> checkIntersectionAtNodes(Relation r) { 555 List<Node> intersectionNodes = new ArrayList<>(); 556 List<Pair<Set<Way>,List<Node>>> intersectionErrors = new ArrayList<>(); 557 HashMap<Way, String> wayRoleMap = new HashMap<>(); 558 for (RelationMember rm : r.getMembers()) { 559 if (rm.isWay()) 560 wayRoleMap.put(rm.getWay(), rm.getRole()); 561 } 562 Map<Node, List<Way>> nodeMap = new HashMap<>(); 563 for (RelationMember rm : r.getMembers()) { 564 if (!rm.isWay()) 565 continue; 566 int numNodes = rm.getWay().getNodesCount(); 567 for (int i = 0; i < numNodes; i++) { 568 Node n = rm.getWay().getNode(i); 569 if (n.getReferrers().size() <= 1) { 570 continue; // cannot be a problem node 571 } 572 List<Way> ways = nodeMap.get(n); 573 if (ways == null) { 574 ways = new ArrayList<>(); 575 nodeMap.put(n, ways); 576 } 577 ways.add(rm.getWay()); 578 if (ways.size() > 2 || (ways.size() == 2 && i != 0 && i + 1 != numNodes)) { 579 intersectionNodes.add(n); 580 boolean allInner = true; 581 for (Way w : ways) { 582 String role = wayRoleMap.get(w); 583 if (!"inner".equals(role)) 584 allInner = false; 585 } 586 if (!allInner) { 587 Set<Way> errorWays = new HashSet<>(ways); 588 boolean addNew = true; 589 for (Pair<Set<Way>, List<Node>> pair : intersectionErrors) { 590 if (pair.a.size() == errorWays.size() && pair.a.containsAll(errorWays)) { 591 pair.b.add(n); 592 addNew = false; 593 break; 594 } 595 } 596 if (addNew) { 597 List<Node> errNodes = new ArrayList<>(); 598 errNodes.add(n); 599 intersectionErrors.add(new Pair<>(errorWays, errNodes)); 600 } 601 } 602 } 603 } 604 } 605 for (Pair<Set<Way>,List<Node>> pair : intersectionErrors) { 606 errors.add(TestError.builder(this, Severity.WARNING, CROSSING_WAYS) 607 .message(tr("Multipolygon ways share node(s)")) 608 .primitives(pair.a) 609 .highlight(pair.b) 610 .build()); 611 } 612 return intersectionNodes; 613 } 614 615 /** 616 * Check for:<ul> 617 * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li> 618 * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li> 619 * </ul> 620 * @param r relation 621 */ 622 private void checkRepeatedWayMembers(Relation r) { 623 boolean hasDups = false; 624 Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>(); 625 for (RelationMember rm : r.getMembers()) { 626 List<RelationMember> list = seenMemberPrimitives.get(rm.getMember()); 627 if (list == null) { 628 list = new ArrayList<>(2); 629 seenMemberPrimitives.put(rm.getMember(), list); 630 } else { 631 hasDups = true; 632 } 633 list.add(rm); 634 } 635 if (!hasDups) 636 return; 637 638 List<OsmPrimitive> repeatedSameRole = new ArrayList<>(); 639 List<OsmPrimitive> repeatedDiffRole = new ArrayList<>(); 640 for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) { 641 List<RelationMember> visited = e.getValue(); 642 if (e.getValue().size() == 1) 643 continue; 644 // we found a duplicate member, check if the roles differ 645 boolean rolesDiffer = false; 646 RelationMember rm = visited.get(0); 647 List<OsmPrimitive> primitives = new ArrayList<>(); 648 for (int i = 1; i < visited.size(); i++) { 649 RelationMember v = visited.get(i); 650 primitives.add(rm.getMember()); 651 if (v.getRole().equals(rm.getRole()) == false) { 652 rolesDiffer = true; 653 } 654 } 655 if (rolesDiffer) 656 repeatedDiffRole.addAll(primitives); 657 else 658 repeatedSameRole.addAll(primitives); 659 } 660 addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role")); 661 addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role")); 662 } 663 664 private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) { 665 if (!repeatedMembers.isEmpty()) { 666 List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size()); 667 prims.add(r); 668 prims.addAll(repeatedMembers); 669 errors.add(TestError.builder(this, Severity.WARNING, errorCode) 670 .message(msg) 671 .primitives(prims) 672 .highlight(repeatedMembers) 673 .build()); 674 } 675 } 676 677 @Override 678 public Command fixError(TestError testError) { 679 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) { 680 ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives()); 681 if (primitives.size() >= 2) { 682 if (primitives.get(0) instanceof Relation) { 683 Relation oldRel = (Relation) primitives.get(0); 684 Relation newRel = new Relation(oldRel); 685 List<OsmPrimitive> repeatedPrims = primitives.subList(1, primitives.size()); 686 List<RelationMember> oldMembers = oldRel.getMembers(); 687 688 List<RelationMember> newMembers = new ArrayList<>(); 689 HashSet<OsmPrimitive> toRemove = new HashSet<>(repeatedPrims); 690 HashSet<OsmPrimitive> found = new HashSet<>(repeatedPrims.size()); 691 for (RelationMember rm : oldMembers) { 692 if (toRemove.contains(rm.getMember())) { 693 if (found.contains(rm.getMember()) == false) { 694 found.add(rm.getMember()); 695 newMembers.add(rm); 696 } 697 } else 698 newMembers.add(rm); 699 } 700 newRel.setMembers(newMembers); 701 return new ChangeCommand (oldRel, newRel); 702 } 703 } 704 } 705 return null; 706 } 707 708 @Override 709 public boolean isFixable(TestError testError) { 710 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) 711 return true; 712 return false; 713 } 415 714 }
