Ticket #13307: improve_MultipolygonTest_v7.patch
File improve_MultipolygonTest_v7.patch, 28.2 KB (added by , 8 years ago) |
---|
-
src/org/openstreetmap/josm/command/RemoveRepeatedRelationMembersCommand.java
1 // License: GPL. For details, see LICENSE file. 2 package org.openstreetmap.josm.command; 3 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 6 import java.util.ArrayList; 7 import java.util.Collection; 8 import java.util.HashSet; 9 import java.util.List; 10 import java.util.Objects; 11 12 import javax.swing.Icon; 13 14 import org.openstreetmap.josm.Main; 15 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 import org.openstreetmap.josm.data.osm.Relation; 17 import org.openstreetmap.josm.data.osm.RelationMember; 18 import org.openstreetmap.josm.gui.DefaultNameFormatter; 19 import org.openstreetmap.josm.tools.ImageProvider; 20 21 /** 22 * Command that removes repeated relation members (first occurrence is kept) 23 * 24 * @author Gerd Petermann 25 */ 26 public class RemoveRepeatedRelationMembersCommand extends Command { 27 28 // The relation to be changed 29 private final Relation relation; 30 // Old value of modified 31 private Boolean oldModified; 32 private final List<OsmPrimitive> repeatedPrims; 33 private List<RelationMember> oldMembers; 34 35 36 /** 37 * @param r the relation 38 * @param membersToRemove list of members to remove 39 */ 40 public RemoveRepeatedRelationMembersCommand(Relation r, List<OsmPrimitive> membersToRemove) { 41 this.relation = r; 42 this.repeatedPrims = membersToRemove; 43 } 44 45 @Override 46 public boolean executeCommand() { 47 boolean executed = false; 48 long t1 = System.currentTimeMillis(); 49 oldMembers = relation.getMembers(); 50 51 List<RelationMember> newMembers = new ArrayList<>(); 52 HashSet<OsmPrimitive> toRemove = new HashSet<>(repeatedPrims); 53 HashSet<OsmPrimitive> found = new HashSet<>(repeatedPrims.size()); 54 for (RelationMember rm : oldMembers) { 55 if (toRemove.contains(rm.getMember())) { 56 if (found.contains(rm.getMember()) == false) { 57 found.add(rm.getMember()); 58 newMembers.add(rm); 59 } 60 } else 61 newMembers.add(rm); 62 } 63 oldModified = relation.isModified(); 64 relation.setMembers(newMembers); 65 long t2 = System.currentTimeMillis(); 66 Main.debug("remove repeated relation members action took " + (t2 - t1) + " ms, removed" 67 + (oldMembers.size() - newMembers.size()) + " members"); 68 return executed; 69 } 70 71 @Override 72 public void undoCommand() { 73 if (oldMembers != null) { 74 relation.setMembers(oldMembers); 75 if (oldModified != null) { 76 relation.setModified(oldModified); 77 } 78 } 79 } 80 81 @Override 82 public void fillModifiedData(Collection<OsmPrimitive> modified, Collection<OsmPrimitive> deleted, Collection<OsmPrimitive> added) { 83 modified.add(relation); 84 } 85 86 @Override 87 public String getDescriptionText() { 88 return tr("Remove relation member(s) from {0}", 89 relation.getDisplayName(DefaultNameFormatter.getInstance())); 90 } 91 92 @Override 93 public Icon getDescriptionIcon() { 94 return ImageProvider.get(relation.getDisplayType()); 95 } 96 97 @Override 98 public int hashCode() { 99 return Objects.hash(super.hashCode(), relation, repeatedPrims.hashCode()); 100 } 101 102 @Override 103 public boolean equals(Object obj) { 104 if (this == obj) return true; 105 if (obj == null || getClass() != obj.getClass()) return false; 106 if (!super.equals(obj)) return false; 107 RemoveRepeatedRelationMembersCommand that = (RemoveRepeatedRelationMembersCommand) obj; 108 return 109 Objects.equals(relation, that.relation) && 110 repeatedPrims.size() == that.repeatedPrims.size() && 111 repeatedPrims.containsAll(that.repeatedPrims); 112 } 113 } -
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 … … 175 176 */ 176 177 public JoinedWay(List<Node> nodes, Collection<Long> wayIds, boolean selected) { 177 178 this.nodes = new ArrayList<>(nodes); 178 this.wayIds = new ArrayList<>(wayIds);179 this.wayIds = new LinkedHashSet<>(wayIds); 179 180 this.selected = selected; 180 181 } 181 182 -
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.command.Command; 25 import org.openstreetmap.josm.command.RemoveRepeatedRelationMembersCommand; 26 import org.openstreetmap.josm.data.coor.EastNorth; 27 import org.openstreetmap.josm.data.coor.LatLon; 20 28 import org.openstreetmap.josm.data.osm.Node; 21 29 import org.openstreetmap.josm.data.osm.OsmPrimitive; 22 30 import org.openstreetmap.josm.data.osm.Relation; 23 31 import org.openstreetmap.josm.data.osm.RelationMember; 24 32 import org.openstreetmap.josm.data.osm.Way; 33 import org.openstreetmap.josm.data.osm.WaySegment; 25 34 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 26 35 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 36 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 29 37 import org.openstreetmap.josm.data.validation.OsmValidator; 30 38 import org.openstreetmap.josm.data.validation.Severity; … … 67 75 public static final int NO_STYLE_POLYGON = 1611; 68 76 /** Area style on outer way */ 69 77 public static final int OUTER_STYLE = 1613; 78 /** Multipolygon member repeated (same primitive, same role */ 79 public static final int REPEATED_MEMBER_SAME_ROLE = 1614; 80 /** Multipolygon member repeated (same primitive, different role) */ 81 public static final int REPEATED_MEMBER_DIFF_ROLE = 1615; 70 82 71 83 private static volatile ElemStyles styles; 72 84 73 85 private final Set<String> keysCheckedByAnotherTest = new HashSet<>(); 86 /** All way segments, grouped by cells */ 87 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 88 /** The already detected ways in error */ 89 private final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 74 90 75 91 /** 76 92 * Constructs a new {@code MultipolygonTest}. … … 103 119 super.endTest(); 104 120 } 105 121 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());122 private static Path2D.Double createPath(List<Node> nodes) { 123 Path2D.Double result = new Path2D.Double(); 124 result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat()); 109 125 for (int i = 1; i < nodes.size(); i++) { 110 126 Node n = nodes.get(i); 111 result.lineTo( (float) n.getCoor().lat(), (float) n.getCoor().lon());127 result.lineTo(n.getCoor().lon(), n.getCoor().lat()); 112 128 } 113 129 return result; 114 130 } 115 131 116 private static List< GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {117 List< GeneralPath> result = new ArrayList<>();132 private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) { 133 List<Path2D.Double> result = new ArrayList<>(); 118 134 for (Multipolygon.PolyData way : joinedWays) { 119 135 result.add(createPath(way.getNodes())); 120 136 } … … 121 137 return result; 122 138 } 123 139 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 140 @Override 141 141 public void visit(Way w) { 142 142 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 155 155 @Override 156 156 public void visit(Relation r) { 157 157 if (r.isMultipolygon()) { 158 crossingWays.clear(); 159 cellSegments.clear(); 160 158 161 checkMembersAndRoles(r); 162 checkRepeatedMembers(r); 159 163 checkOuterWay(r); 160 164 161 165 // Rest of checks is only for complete multipolygons … … 163 167 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r); 164 168 165 169 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. 166 checkMemberRoleCorrectness(r);170 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 167 171 checkStyleConsistency(r, polygon); 168 checkGeometry(r, polygon );172 checkGeometry(r, polygon, rolesWereChecked); 169 173 } 170 174 } 171 175 } … … 194 198 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 195 199 * </ul> 196 200 * @param r relation 201 * @return true if member roles were checked 197 202 */ 198 private voidcheckMemberRoleCorrectness(Relation r) {203 private boolean checkMemberRoleCorrectness(Relation r) { 199 204 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 200 205 if (newMP != null) { 201 206 for (RelationMember member : r.getMembers()) { … … 216 221 } 217 222 } 218 223 } 224 return newMP != null; 219 225 } 220 226 221 227 /** … … 285 291 } 286 292 } 287 293 294 private static class LatLonPolyData { 295 final PolyData pd; 296 final Path2D.Double latLonPath; 297 298 LatLonPolyData(PolyData polyData, Path2D.Double path) { 299 this.pd = polyData; 300 this.latLonPath = path; 301 } 302 } 303 288 304 /** 289 305 * Various geometry-related checks:<ul> 290 306 * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li> … … 293 309 * </ul> 294 310 * @param r relation 295 311 * @param polygon multipolygon 312 * @param rolesWereChecked might be used to skip most of the tests below 296 313 */ 297 private void checkGeometry(Relation r, Multipolygon polygon ) {314 private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) { 298 315 List<Node> openNodes = polygon.getOpenEnds(); 299 316 if (!openNodes.isEmpty()) { 300 317 List<OsmPrimitive> primitives = new LinkedList<>(); 301 318 primitives.add(r); 302 319 primitives.addAll(openNodes); 303 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 320 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, 321 primitives, openNodes)); 304 322 } 323 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 324 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 305 325 326 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 327 328 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 329 List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons); 330 List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons); 306 331 // 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); 332 for (int i = 0; i + 1 < outer.size(); i++) { 333 // report outer polygons which lie inside another outer 334 LatLonPolyData outer1 = outer.get(i); 335 for (int j = 0; j < outer.size(); j++) { 336 if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) { 337 LatLon c = outer.get(j).pd.getNodes().get(0).getCoor(); 338 if (outer1.latLonPath.contains(c.lon(), c.lat())) { 339 addError(r, 340 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 341 CROSSING_WAYS, Collections.singletonList(r), 342 Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes()))); 343 } 344 } 316 345 } 317 346 } 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); 347 for (int i = 0; i < inner.size(); i++) { 348 LatLonPolyData inner1 = inner.get(i); 349 LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor(); 350 for (int j = 0; j < inner.size(); j++) { 351 LatLonPolyData inner2 = inner.get(j); 352 if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) { 353 if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())) { 354 addError(r, 355 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 356 CROSSING_WAYS, Collections.singletonList(r), 357 Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes()))); 358 } 359 } 323 360 } 324 // Check for intersection between inner and outer members 361 362 // Find inner polygons which are not inside any outer 325 363 boolean outside = true; 326 for (int o = 0; o < outerPolygons.size(); o++) { 327 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 364 boolean crossingWithOuter = false; 365 366 for (int o = 0; o < outer.size(); o++) { 367 if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) { 368 crossingWithOuter = true; 369 break; 370 } 371 outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false; 372 if (!outside) 373 break; 328 374 } 329 if (outside ) {375 if (outside && !crossingWithOuter) { 330 376 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 331 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList( pdInner.getNodes())));377 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes()))); 332 378 } 333 379 } 334 380 } 335 381 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 } 382 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 383 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 384 if (crossingWithFirst != null) { 385 if (crossingWithFirst.contains(pd2)) 386 return true; 344 387 } 345 return intersection; 388 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 389 if (crossingWith2nd != null) { 390 if (crossingWith2nd.contains(pd1)) 391 return true; 392 } 393 return false; 346 394 } 347 395 396 private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) { 397 if (polygons == null || polygons.isEmpty()) 398 return Collections.emptyList(); 399 List<LatLonPolyData> latLonPolygons = new ArrayList<>(); 400 List<Path2D.Double> polygonsPaths = createPolygons(polygons); 401 for (int i = 0; i < polygons.size(); i++) { 402 latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i))); 403 } 404 return latLonPolygons; 405 } 406 348 407 /** 349 408 * Check for:<ul> 350 409 * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li> … … 389 448 addRelationIfNeeded(error, r); 390 449 errors.add(error); 391 450 } 451 452 /** 453 * Determine multipolygon ways which are intersecting. This is now allowed. 454 * See {@link CrossingWays} 455 * @param r the relation (for error reporting) 456 * @param innerPolygons list of inner polygons 457 * @param outerPolygons list of outer polygons 458 * @return map of crossing polygons (including polygons touching outer) 459 */ 460 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 461 List<PolyData> outerPolygons) { 462 List<Way> innerWays = new ArrayList<>(); 463 List<Way> outerWays = new ArrayList<>(); 464 for (Way w : r.getMemberPrimitives(Way.class)) { 465 for (PolyData pd : innerPolygons) { 466 if (pd.getWayIds().contains(w.getUniqueId())) { 467 innerWays.add(w); 468 break; 469 } 470 } 471 for (PolyData pd : outerPolygons) { 472 if (pd.getWayIds().contains(w.getUniqueId())) { 473 outerWays.add(w); 474 break; 475 } 476 } 477 } 478 for (Way w : innerWays) { 479 checkCrossingWay(w, r, true /* allow shared ways */); 480 } 481 for (Way w : outerWays) { 482 checkCrossingWay(w, r, false/* don't allow shared ways */); 483 } 484 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 485 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 486 List<Way> ways = entry.getKey(); 487 PolyData[] crossingPolys = new PolyData[2]; 488 for (Way w : ways) { 489 for (int j = 0; j < crossingPolys.length; j++) { 490 for (PolyData pd : innerPolygons) { 491 if (pd.getWayIds().contains(w.getUniqueId())) { 492 crossingPolys[j] = pd; 493 break; 494 } 495 } 496 if (crossingPolys[j] != null) 497 break; 498 for (PolyData pd : outerPolygons) { 499 if (pd.getWayIds().contains(w.getUniqueId())) { 500 crossingPolys[j] = pd; 501 break; 502 } 503 } 504 } 505 } 506 if (crossingPolys[0] != null && crossingPolys[1] != null) { 507 List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]); 508 if (x == null) { 509 x = new ArrayList<>(); 510 crossingPolygonsMap.put(crossingPolys[0], x); 511 } 512 x.add(crossingPolys[1]); 513 } 514 } 515 return crossingPolygonsMap; 516 } 517 518 /** 519 * 520 * @param w way that is member of the relation 521 * @param r the relation (used for error messages) 522 * @param allowSharedWaySegment false: treat similar way segment as crossing 523 */ 524 private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) { 525 526 int nodesSize = w.getNodesCount(); 527 for (int i = 0; i < nodesSize - 1; i++) { 528 final WaySegment es1 = new WaySegment(w, i); 529 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 530 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 531 if (en1 == null || en2 == null) { 532 Main.warn("Crossing ways test skipped " + es1); 533 continue; 534 } 535 for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) { 536 for (WaySegment es2 : segments) { 537 538 List<WaySegment> highlight; 539 if (es2.way == w) 540 continue; // reported by CrossingWays.SelfIntersection 541 if (!es1.intersects(es2)) { 542 if (allowSharedWaySegment || !es1.isSimilar(es2)) 543 continue; 544 } 545 546 List<Way> prims = Arrays.asList(es1.way, es2.way); 547 if ((highlight = crossingWays.get(prims)) == null) { 548 highlight = new ArrayList<>(); 549 highlight.add(es1); 550 highlight.add(es2); 551 552 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 553 CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight)); 554 crossingWays.put(prims, highlight); 555 } else { 556 highlight.add(es1); 557 highlight.add(es2); 558 } 559 } 560 segments.add(es1); 561 } 562 } 563 } 564 565 /** 566 * Check for:<ul> 567 * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li> 568 * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li> 569 * </ul> 570 * @param r relation 571 */ 572 private void checkRepeatedMembers(Relation r) { 573 boolean hasDups = false; 574 Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>(); 575 for (RelationMember rm : r.getMembers()) { 576 List<RelationMember> list = seenMemberPrimitives.get(rm.getMember()); 577 if (list == null) { 578 list = new ArrayList<>(2); 579 seenMemberPrimitives.put(rm.getMember(), list); 580 } else 581 hasDups = true; 582 list.add(rm); 583 } 584 if (!hasDups) 585 return; 586 587 List<OsmPrimitive> repeatedSameRole = new ArrayList<>(); 588 List<OsmPrimitive> repeatedDiffRole = new ArrayList<>(); 589 for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) { 590 List<RelationMember> visited = e.getValue(); 591 if (e.getValue().size() == 1) 592 continue; 593 // we found a duplicate member, check if the roles differ 594 boolean rolesDiffer = false; 595 RelationMember rm = visited.get(0); 596 List<OsmPrimitive> primitives = new ArrayList<>(); 597 for (int i = 1; i < visited.size(); i++) { 598 RelationMember v = visited.get(i); 599 primitives.add(rm.getMember()); 600 if (v.getRole().equals(rm.getRole()) == false) { 601 rolesDiffer = true; 602 } 603 } 604 if (rolesDiffer) 605 repeatedDiffRole.addAll(primitives); 606 else 607 repeatedSameRole.addAll(primitives); 608 } 609 addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role")); 610 addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role")); 611 } 612 613 private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) { 614 if (!repeatedMembers.isEmpty()) { 615 List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size()); 616 prims.add(r); 617 prims.addAll(repeatedMembers); 618 addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers)); 619 } 620 621 } 622 623 @Override 624 public Command fixError(TestError testError) { 625 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) { 626 ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives()); 627 if (primitives.size() >= 2) { 628 if (primitives.get(0) instanceof Relation) { 629 Relation r = (Relation) primitives.get(0); 630 return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size())); 631 } 632 } 633 } 634 return null; 635 } 636 637 @Override 638 public boolean isFixable(TestError testError) { 639 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) 640 return true; 641 return false; 642 } 392 643 }