Ticket #13307: improve_MultipolygonTest_v10.patch
File improve_MultipolygonTest_v10.patch, 31.6 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 … … 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/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
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.Point2D; 8 8 import java.text.MessageFormat; 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.LinkedList; 15 16 import java.util.List; 17 import java.util.Map; 18 import java.util.Map.Entry; 16 19 import java.util.Set; 17 20 18 21 import org.openstreetmap.josm.Main; 19 22 import org.openstreetmap.josm.actions.CreateMultipolygonAction; 23 import org.openstreetmap.josm.command.Command; 24 import org.openstreetmap.josm.command.RemoveRepeatedRelationMembersCommand; 25 import org.openstreetmap.josm.data.coor.EastNorth; 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 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;29 34 import org.openstreetmap.josm.data.validation.OsmValidator; 30 35 import org.openstreetmap.josm.data.validation.Severity; 31 36 import org.openstreetmap.josm.data.validation.Test; … … 67 72 public static final int NO_STYLE_POLYGON = 1611; 68 73 /** Area style on outer way */ 69 74 public static final int OUTER_STYLE = 1613; 75 /** Multipolygon member repeated (same primitive, same role */ 76 public static final int REPEATED_MEMBER_SAME_ROLE = 1614; 77 /** Multipolygon member repeated (same primitive, different role) */ 78 public static final int REPEATED_MEMBER_DIFF_ROLE = 1615; 70 79 71 80 private static volatile ElemStyles styles; 72 81 … … 103 112 super.endTest(); 104 113 } 105 114 106 private static GeneralPath createPath(List<Node> nodes) {107 GeneralPath result = new GeneralPath();108 result.moveTo((float) nodes.get(0).getCoor().lat(), (float) nodes.get(0).getCoor().lon());109 for (int i = 1; i < nodes.size(); i++) {110 Node n = nodes.get(i);111 result.lineTo((float) n.getCoor().lat(), (float) n.getCoor().lon());112 }113 return result;114 }115 116 private static List<GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {117 List<GeneralPath> result = new ArrayList<>();118 for (Multipolygon.PolyData way : joinedWays) {119 result.add(createPath(way.getNodes()));120 }121 return result;122 }123 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 115 @Override 141 116 public void visit(Way w) { 142 117 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 156 131 public void visit(Relation r) { 157 132 if (r.isMultipolygon()) { 158 133 checkMembersAndRoles(r); 134 checkRepeatedWayMembers(r); 159 135 checkOuterWay(r); 136 List<Node> intersectionNodes = checkIntersectionAtNodes(r); 160 137 161 138 // Rest of checks is only for complete multipolygons 162 139 if (!r.hasIncompleteMembers()) { 163 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r); 164 165 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. 166 checkMemberRoleCorrectness(r); 140 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 141 Multipolygon polygon = new Multipolygon(r); 167 142 checkStyleConsistency(r, polygon); 168 checkGeometry(r, polygon );143 checkGeometry(r, polygon, intersectionNodes, rolesWereChecked); 169 144 } 170 145 } 171 146 } … … 190 165 } 191 166 192 167 /** 193 * Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match:<ul> 168 * If simple joining of ways doesn't work, create new multipolygon using the logics from 169 * CreateMultipolygonAction and see if roles match:<ul> 194 170 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 195 171 * </ul> 196 172 * @param r relation 173 * @return true if member roles were checked 197 174 */ 198 private voidcheckMemberRoleCorrectness(Relation r) {175 private boolean checkMemberRoleCorrectness(Relation r) { 199 176 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 177 200 178 if (newMP != null) { 201 179 for (RelationMember member : r.getMembers()) { 202 180 final Collection<RelationMember> memberInNewMP = newMP.b.getMembersFor(Collections.singleton(member.getMember())); … … 216 194 } 217 195 } 218 196 } 197 return newMP != null; 219 198 } 220 199 221 200 /** … … 293 272 * </ul> 294 273 * @param r relation 295 274 * @param polygon multipolygon 275 * @param intersectionNodes known nodes where ways of this multipolygon intersect or touch 276 * @param rolesWereChecked might be used to skip most of the tests below 296 277 */ 297 private void checkGeometry(Relation r, Multipolygon polygon ) {278 private void checkGeometry(Relation r, Multipolygon polygon, List<Node> intersectionNodes, boolean rolesWereChecked) { 298 279 List<Node> openNodes = polygon.getOpenEnds(); 299 280 if (!openNodes.isEmpty()) { 300 281 List<OsmPrimitive> primitives = new LinkedList<>(); … … 303 284 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 304 285 } 305 286 306 // For painting is used Polygon class which works with ints only. For validation we need more precision307 287 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 308 288 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 309 List<GeneralPath> innerPolygonsPaths = innerPolygons.isEmpty() ? Collections.<GeneralPath>emptyList() : createPolygons(innerPolygons); 310 List<GeneralPath> outerPolygonsPaths = createPolygons(outerPolygons); 289 290 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 291 292 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 311 293 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); 294 // report outer polygons which lie inside another outer 295 PolyData outer1 = outerPolygons.get(i); 296 for (int j = 0; j < outerPolygons.size(); j++) { 297 if (i != j && !crossing(crossingPolyMap, outer1, outerPolygons.get(j))) { 298 EastNorth en1 = null; 299 // find node which is not a intersection of multipolygon ways 300 for (int k = 0; k < outerPolygons.get(j).getNodes().size(); k++) { 301 Node n = outerPolygons.get(j).getNodes().get(k); 302 if (intersectionNodes.contains(n) == false) 303 en1 = n.getEastNorth(); 304 } 305 if (en1 != null && outer1.get().contains(en1.getX(), en1.getY())) { 306 addError(r, 307 new TestError(this, Severity.WARNING, tr("Outer inside outer"), 308 CROSSING_WAYS, Collections.singletonList(r), 309 Arrays.asList(outer1.getNodes(), outerPolygons.get(j).getNodes()))); 310 } 311 } 316 312 } 317 313 } 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); 314 for (PolyData inner1 : innerPolygons) { 315 for (PolyData inner2 : innerPolygons) { 316 if (inner1 != inner2 && !crossing(crossingPolyMap, inner1, inner2)) { 317 // we must allow that inner polygons share some nodes 318 boolean allInside = true; 319 for (Node innerPoint : inner2.getNodes()) { 320 EastNorth en = innerPoint.getEastNorth(); 321 if (!inner1.get().contains(en.getX(), en.getY())) { 322 allInside = false; 323 break; 324 } 325 } 326 if (allInside) { 327 addError(r, 328 new TestError(this, Severity.WARNING, tr("Inner inside inner"), 329 CROSSING_WAYS, Collections.singletonList(r), 330 Arrays.asList(inner1.getNodes(), inner2.getNodes()))); 331 } 332 } 323 333 } 324 // Check for intersection between inner and outer members 325 boolean outside = true; 326 for (int o = 0; o < outerPolygons.size(); o++) { 327 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 334 335 // Find inner polygons which are not inside any outer 336 // TODO enable the following line to reduce number of warnings for same problem (and adapt multipolygon.oms) 337 // if (!rolesWereChecked) { 338 boolean outside = true; 339 boolean crossingWithOuter = false; 340 EastNorth innerPoint = inner1.getNodes().get(0).getEastNorth(); 341 for (PolyData outer : outerPolygons) { 342 if (crossing(crossingPolyMap, inner1, outer)) { 343 crossingWithOuter = true; 344 break; 345 } 346 outside &= !outer.get().contains(innerPoint.getX(), innerPoint.getY()); 347 if (!outside) 348 break; 349 } 350 if (outside && !crossingWithOuter) { 351 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 352 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.getNodes()))); 353 } 328 354 } 329 if (outside) { 330 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 331 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(pdInner.getNodes()))); 332 } 333 } 355 // } 334 356 } 335 357 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 } 358 /** 359 * Check if crossing map contains combination of two given polygons. 360 * @param crossingPolyMap the map 361 * @param pd1 1st polygon 362 * @param pd2 2nd polygon 363 * @return true if the polygons are crossing without sharing a node 364 */ 365 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 366 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 367 if (crossingWithFirst != null) { 368 if (crossingWithFirst.contains(pd2)) 369 return true; 344 370 } 345 return intersection; 371 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 372 if (crossingWith2nd != null) { 373 if (crossingWith2nd.contains(pd1)) 374 return true; 375 } 376 return false; 346 377 } 347 378 348 379 /** … … 389 420 addRelationIfNeeded(error, r); 390 421 errors.add(error); 391 422 } 423 424 /** 425 * Determine multipolygon ways which are intersecting (crossing without a common node). This is not allowed. 426 * See {@link CrossingWays} 427 * @param r the relation (for error reporting) 428 * @param innerPolygons list of inner polygons 429 * @param outerPolygons list of outer polygons 430 * @return map of crossing polygons (including polygons touching outer) 431 */ 432 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 433 List<PolyData> outerPolygons) { 434 /** All way segments, grouped by cells */ 435 final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 436 /** The already detected ways in error */ 437 final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 438 439 for (Way w : r.getMemberPrimitives(Way.class)) { 440 checkCrossingWay(w, r, cellSegments, crossingWays); 441 } 442 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 443 if (!crossingWays.isEmpty()) { 444 List<PolyData> allPolygons = new ArrayList<>(innerPolygons.size() + outerPolygons.size()); 445 allPolygons.addAll(innerPolygons); 446 allPolygons.addAll(outerPolygons); 447 448 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 449 List<Way> ways = entry.getKey(); 450 if (ways.size() != 2) 451 continue; 452 PolyData[] crossingPolys = new PolyData[2]; 453 for (int i = 0; i < 2; i++) { 454 Way w = ways.get(i); 455 for (PolyData pd : allPolygons) { 456 if (pd.getWayIds().contains(w.getUniqueId())) { 457 crossingPolys[i] = pd; 458 break; 459 } 460 } 461 } 462 if (crossingPolys[0] != null && crossingPolys[1] != null) { 463 List<PolyData> crossingPolygons = crossingPolygonsMap.get(crossingPolys[0]); 464 if (crossingPolygons == null) { 465 crossingPolygons = new ArrayList<>(); 466 crossingPolygonsMap.put(crossingPolys[0], crossingPolygons); 467 } 468 crossingPolygons.add(crossingPolys[1]); 469 } 470 } 471 } 472 return crossingPolygonsMap; 473 } 474 475 /** 476 * Find ways which are crossing without sharing a node. 477 * @param w way that is member of the relation 478 * @param r the relation (used for error messages) 479 * @param crossingWays 480 * @param cellSegments 481 */ 482 private void checkCrossingWay(Way w, Relation r, Map<Point2D, List<WaySegment>> cellSegments, Map<List<Way>, List<WaySegment>> crossingWays) { 483 int nodesSize = w.getNodesCount(); 484 for (int i = 0; i < nodesSize - 1; i++) { 485 final WaySegment es1 = new WaySegment(w, i); 486 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 487 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 488 if (en1 == null || en2 == null) { 489 Main.warn("Crossing ways test skipped " + es1); 490 continue; 491 } 492 for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) { 493 for (WaySegment es2 : segments) { 494 495 List<WaySegment> highlight; 496 if (es2.way == w) 497 continue; // reported by CrossingWays.SelfIntersection 498 if (!es1.intersects(es2)) 499 continue; 500 501 List<Way> prims = Arrays.asList(es1.way, es2.way); 502 if ((highlight = crossingWays.get(prims)) == null) { 503 highlight = new ArrayList<>(); 504 highlight.add(es1); 505 highlight.add(es2); 506 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 507 CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight)); 508 crossingWays.put(prims, highlight); 509 } else { 510 highlight.add(es1); 511 highlight.add(es2); 512 } 513 } 514 segments.add(es1); 515 } 516 } 517 } 518 519 /** 520 * Detect intersections of multipolygon ways at nodes. If any way node is used by more than two ways 521 * or two times in one way and at least once in another way we found an intersection. 522 * @param r the relation 523 * @return List of nodes were ways intersect 524 */ 525 private List<Node> checkIntersectionAtNodes(Relation r) { 526 List<Node> intersectionNodes = new ArrayList<>(); 527 List<Pair<Set<Way>,List<Node>>> intersectionErrors = new ArrayList<>(); 528 HashMap<Way, String> wayRoleMap = new HashMap<>(); 529 for (RelationMember rm : r.getMembers()) { 530 if (rm.isWay()) 531 wayRoleMap.put(rm.getWay(), rm.getRole()); 532 } 533 Map<Node, List<Way>> nodeMap = new HashMap<>(); 534 for (RelationMember rm : r.getMembers()) { 535 if (!rm.isWay()) 536 continue; 537 int numNodes = rm.getWay().getNodesCount(); 538 for (int i = 0; i < numNodes; i++) { 539 Node n = rm.getWay().getNode(i); 540 if (n.getReferrers().size() <= 1) { 541 continue; // cannot be a problem node 542 } 543 List<Way> ways = nodeMap.get(n); 544 if (ways == null) { 545 ways = new ArrayList<>(); 546 nodeMap.put(n, ways); 547 } 548 ways.add(rm.getWay()); 549 if (ways.size() > 2 || (ways.size() == 2 && i != 0 && i + 1 != numNodes)) { 550 intersectionNodes.add(n); 551 boolean allInner = true; 552 for (Way w : ways) { 553 String role = wayRoleMap.get(w); 554 if (!"inner".equals(role)) 555 allInner = false; 556 } 557 if (!allInner) { 558 Set<Way> errorWays = new HashSet<>(ways); 559 boolean addNew = true; 560 for (Pair<Set<Way>, List<Node>> pair : intersectionErrors) { 561 if (pair.a.size() == errorWays.size() && pair.a.containsAll(errorWays)) { 562 pair.b.add(n); 563 addNew = false; 564 break; 565 } 566 } 567 if (addNew) { 568 List<Node> errNodes = new ArrayList<>(); 569 errNodes.add(n); 570 intersectionErrors.add(new Pair<>(errorWays, errNodes)); 571 } 572 } 573 } 574 } 575 } 576 for (Pair<Set<Way>,List<Node>> pair : intersectionErrors) { 577 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon ways share node(s)"), 578 CROSSING_WAYS, pair.a, pair.b)); 579 } 580 return intersectionNodes; 581 } 582 583 /** 584 * Check for:<ul> 585 * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li> 586 * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li> 587 * </ul> 588 * @param r relation 589 */ 590 private void checkRepeatedWayMembers(Relation r) { 591 boolean hasDups = false; 592 Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>(); 593 for (RelationMember rm : r.getMembers()) { 594 List<RelationMember> list = seenMemberPrimitives.get(rm.getMember()); 595 if (list == null) { 596 list = new ArrayList<>(2); 597 seenMemberPrimitives.put(rm.getMember(), list); 598 } else { 599 hasDups = true; 600 } 601 list.add(rm); 602 } 603 if (!hasDups) 604 return; 605 606 List<OsmPrimitive> repeatedSameRole = new ArrayList<>(); 607 List<OsmPrimitive> repeatedDiffRole = new ArrayList<>(); 608 for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) { 609 List<RelationMember> visited = e.getValue(); 610 if (e.getValue().size() == 1) 611 continue; 612 // we found a duplicate member, check if the roles differ 613 boolean rolesDiffer = false; 614 RelationMember rm = visited.get(0); 615 List<OsmPrimitive> primitives = new ArrayList<>(); 616 for (int i = 1; i < visited.size(); i++) { 617 RelationMember v = visited.get(i); 618 primitives.add(rm.getMember()); 619 if (v.getRole().equals(rm.getRole()) == false) { 620 rolesDiffer = true; 621 } 622 } 623 if (rolesDiffer) 624 repeatedDiffRole.addAll(primitives); 625 else 626 repeatedSameRole.addAll(primitives); 627 } 628 addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role")); 629 addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role")); 630 } 631 632 private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) { 633 if (!repeatedMembers.isEmpty()) { 634 List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size()); 635 prims.add(r); 636 prims.addAll(repeatedMembers); 637 addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers)); 638 } 639 640 } 641 642 @Override 643 public Command fixError(TestError testError) { 644 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) { 645 ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives()); 646 if (primitives.size() >= 2) { 647 if (primitives.get(0) instanceof Relation) { 648 Relation r = (Relation) primitives.get(0); 649 return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size())); 650 } 651 } 652 } 653 return null; 654 } 655 656 @Override 657 public boolean isFixable(TestError testError) { 658 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) 659 return true; 660 return false; 661 } 662 392 663 }