Ticket #13307: improve_MultipolygonTest_v9.patch
| File improve_MultipolygonTest_v9.patch, 32.5 KB (added by , 10 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/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; 16 import java.util.Iterator; 14 17 import java.util.LinkedList; 15 18 import java.util.List; 19 import java.util.Map; 20 import java.util.Map.Entry; 16 21 import java.util.Set; 17 22 18 23 import org.openstreetmap.josm.Main; 19 24 import org.openstreetmap.josm.actions.CreateMultipolygonAction; 25 import org.openstreetmap.josm.command.Command; 26 import org.openstreetmap.josm.command.RemoveRepeatedRelationMembersCommand; 27 import org.openstreetmap.josm.data.coor.EastNorth; 28 import org.openstreetmap.josm.data.coor.LatLon; 20 29 import org.openstreetmap.josm.data.osm.Node; 21 30 import org.openstreetmap.josm.data.osm.OsmPrimitive; 22 31 import org.openstreetmap.josm.data.osm.Relation; 23 32 import org.openstreetmap.josm.data.osm.RelationMember; 24 33 import org.openstreetmap.josm.data.osm.Way; 34 import org.openstreetmap.josm.data.osm.WaySegment; 25 35 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon; 36 import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.JoinedWay; 26 37 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 38 import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache; 29 39 import org.openstreetmap.josm.data.validation.OsmValidator; 30 40 import org.openstreetmap.josm.data.validation.Severity; … … 67 77 public static final int NO_STYLE_POLYGON = 1611; 68 78 /** Area style on outer way */ 69 79 public static final int OUTER_STYLE = 1613; 80 /** Multipolygon member repeated (same primitive, same role */ 81 public static final int REPEATED_MEMBER_SAME_ROLE = 1614; 82 /** Multipolygon member repeated (same primitive, different role) */ 83 public static final int REPEATED_MEMBER_DIFF_ROLE = 1615; 70 84 71 85 private static volatile ElemStyles styles; 72 86 … … 103 117 super.endTest(); 104 118 } 105 119 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());120 private static Path2D.Double createPath(List<Node> nodes) { 121 Path2D.Double result = new Path2D.Double(); 122 result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat()); 109 123 for (int i = 1; i < nodes.size(); i++) { 110 124 Node n = nodes.get(i); 111 result.lineTo( (float) n.getCoor().lat(), (float) n.getCoor().lon());125 result.lineTo(n.getCoor().lon(), n.getCoor().lat()); 112 126 } 113 127 return result; 114 128 } 115 129 116 private static List< GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {117 List< GeneralPath> result = new ArrayList<>();130 private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) { 131 List<Path2D.Double> result = new ArrayList<>(); 118 132 for (Multipolygon.PolyData way : joinedWays) { 119 133 result.add(createPath(way.getNodes())); 120 134 } … … 121 135 return result; 122 136 } 123 137 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 138 @Override 141 139 public void visit(Way w) { 142 140 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 156 154 public void visit(Relation r) { 157 155 if (r.isMultipolygon()) { 158 156 checkMembersAndRoles(r); 157 checkRepeatedMembers(r); 159 158 checkOuterWay(r); 159 List<Node> intersectionNodes = checkIntersectionAtNodes(r); 160 160 161 161 // Rest of checks is only for complete multipolygons 162 162 if (!r.hasIncompleteMembers()) { 163 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 163 164 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);167 165 checkStyleConsistency(r, polygon); 168 checkGeometry(r, polygon );166 checkGeometry(r, polygon, intersectionNodes, rolesWereChecked); 169 167 } 170 168 } 171 169 } … … 190 188 } 191 189 192 190 /** 193 * Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match:<ul> 191 * If simple joining of ways doesn't work, create new multipolygon using the logics from 192 * CreateMultipolygonAction and see if roles match:<ul> 194 193 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 195 194 * </ul> 196 195 * @param r relation 196 * @return true if member roles were checked 197 197 */ 198 private voidcheckMemberRoleCorrectness(Relation r) {198 private boolean checkMemberRoleCorrectness(Relation r) { 199 199 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 200 200 201 if (newMP != null) { 201 202 for (RelationMember member : r.getMembers()) { 202 203 final Collection<RelationMember> memberInNewMP = newMP.b.getMembersFor(Collections.singleton(member.getMember())); … … 216 217 } 217 218 } 218 219 } 220 return newMP != null; 219 221 } 220 222 221 223 /** … … 285 287 } 286 288 } 287 289 290 private static class LatLonPolyData { 291 final PolyData pd; 292 final Path2D.Double latLonPath; 293 294 LatLonPolyData(PolyData polyData, Path2D.Double path) { 295 this.pd = polyData; 296 this.latLonPath = path; 297 } 298 } 299 288 300 /** 289 301 * Various geometry-related checks:<ul> 290 302 * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li> … … 293 305 * </ul> 294 306 * @param r relation 295 307 * @param polygon multipolygon 308 * @param intersectionNodes known nodes where ways of this multipolygon intersect or touch 309 * @param rolesWereChecked might be used to skip most of the tests below 296 310 */ 297 private void checkGeometry(Relation r, Multipolygon polygon) { 298 List<Node> openNodes = polygon.getOpenEnds(); 311 private void checkGeometry(Relation r, Multipolygon polygon, List<Node> intersectionNodes, boolean rolesWereChecked) { 312 Collection<JoinedWay> allPolygons = Multipolygon.joinWays(r.getMemberPrimitives(Way.class)); 313 List<Node> openNodes = new ArrayList<>(); 314 Iterator<JoinedWay> iter = allPolygons.iterator(); 315 while (iter.hasNext()) { 316 JoinedWay jw = iter.next(); 317 if (jw.isClosed()) 318 continue; 319 openNodes.add(jw.getFirstNode()); 320 openNodes.add(jw.getLastNode()); 321 iter.remove(); 322 } 299 323 if (!openNodes.isEmpty()) { 300 324 List<OsmPrimitive> primitives = new LinkedList<>(); 301 325 primitives.add(r); 302 326 primitives.addAll(openNodes); 303 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 327 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, 328 primitives, openNodes)); 304 329 } 305 330 306 // For painting is used Polygon class which works with ints only. For validation we need more precision307 331 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 308 332 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); 333 334 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 335 336 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 337 List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons); 338 List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons); 339 for (int i = 0; i < outer.size(); i++) { 340 // report outer polygons which lie inside another outer 341 LatLonPolyData outer1 = outer.get(i); 342 for (int j = 0; j < outer.size(); j++) { 343 if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) { 344 LatLon c = null; 345 // find node which is not a intersection of multipolygon ways 346 for (int k = 0; k < outer.get(j).pd.getNodes().size(); k++) { 347 Node nc = outer.get(j).pd.getNodes().get(k); 348 if (intersectionNodes.contains(nc) == false) 349 c = nc.getCoor(); 350 } 351 if (c != null && outer1.latLonPath.contains(c.lon(), c.lat())) { 352 // List<OsmPrimitive> l = new ArrayList<>(); 353 // l.add(r); 354 // l.add(member.getMember()); 355 // addError(r, new TestError(this, Severity.WARNING, RelationChecker.ROLE_VERIF_PROBLEM_MSG, 356 // tr("Role for ''{0}'' should be ''{1}''", 357 // member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP), 358 // MessageFormat.format("Role for ''{0}'' should be ''{1}''", 359 // member.getMember().getDisplayName(DefaultNameFormatter.getInstance()), roleInNewMP), 360 // WRONG_MEMBER_ROLE, l, Collections.singleton(member.getMember()))); 361 addError(r, 362 new TestError(this, Severity.WARNING, tr("Outer inside outer"), 363 CROSSING_WAYS, Collections.singletonList(r), 364 Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes()))); 365 } 366 } 316 367 } 317 368 } 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); 369 for (int i = 0; i < inner.size(); i++) { 370 LatLonPolyData inner1 = inner.get(i); 371 for (int j = 0; j < inner.size(); j++) { 372 LatLonPolyData inner2 = inner.get(j); 373 if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) { 374 // we must allow that inner polygons share some nodes 375 boolean allInside = true; 376 for (Node innerPoint: inner2.pd.getNodes()) { 377 if (!inner1.latLonPath.contains(innerPoint.getCoor().lon(), innerPoint.getCoor().lat())) { 378 allInside = false; 379 break; 380 } 381 } 382 if (allInside) { 383 addError(r, 384 new TestError(this, Severity.WARNING, tr("Inner inside inner"), 385 CROSSING_WAYS, Collections.singletonList(r), 386 Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes()))); 387 } 388 } 323 389 } 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; 390 391 // Find inner polygons which are not inside any outer 392 if (true || !rolesWereChecked) { 393 boolean outside = true; 394 boolean crossingWithOuter = false; 395 LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor(); 396 for (int o = 0; o < outer.size(); o++) { 397 if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) { 398 crossingWithOuter = true; 399 break; 400 } 401 outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false; 402 if (!outside) 403 break; 404 } 405 if (outside && !crossingWithOuter) { 406 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 407 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes()))); 408 } 328 409 } 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 410 } 334 411 } 335 412 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 } 413 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 414 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 415 if (crossingWithFirst != null) { 416 if (crossingWithFirst.contains(pd2)) 417 return true; 344 418 } 345 return intersection; 419 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 420 if (crossingWith2nd != null) { 421 if (crossingWith2nd.contains(pd1)) 422 return true; 423 } 424 return false; 346 425 } 347 426 427 private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) { 428 if (polygons == null || polygons.isEmpty()) 429 return Collections.emptyList(); 430 List<LatLonPolyData> latLonPolygons = new ArrayList<>(); 431 List<Path2D.Double> polygonsPaths = createPolygons(polygons); 432 for (int i = 0; i < polygons.size(); i++) { 433 latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i))); 434 } 435 return latLonPolygons; 436 } 437 348 438 /** 349 439 * Check for:<ul> 350 440 * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li> … … 389 479 addRelationIfNeeded(error, r); 390 480 errors.add(error); 391 481 } 482 483 /** 484 * Determine multipolygon ways which are intersecting (crossing without a common node). This is not allowed. 485 * See {@link CrossingWays} 486 * @param r the relation (for error reporting) 487 * @param innerPolygons list of inner polygons 488 * @param outerPolygons list of outer polygons 489 * @return map of crossing polygons (including polygons touching outer) 490 */ 491 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 492 List<PolyData> outerPolygons) { 493 /** All way segments, grouped by cells */ 494 final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 495 /** The already detected ways in error */ 496 final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 497 498 for (Way w : r.getMemberPrimitives(Way.class)) { 499 checkCrossingWay(w, r, cellSegments, crossingWays); 500 } 501 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 502 if (!crossingWays.isEmpty()) { 503 List<PolyData> allPolygons = new ArrayList<>(innerPolygons.size() + outerPolygons.size()); 504 allPolygons.addAll(innerPolygons); 505 allPolygons.addAll(outerPolygons); 506 507 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 508 List<Way> ways = entry.getKey(); 509 if (ways.size() != 2) 510 continue; 511 PolyData[] crossingPolys = new PolyData[2]; 512 for (int i = 0; i < 2; i++) { 513 Way w = ways.get(i); 514 for (PolyData pd : allPolygons) { 515 if (pd.getWayIds().contains(w.getUniqueId())) { 516 crossingPolys[i] = pd; 517 break; 518 } 519 } 520 } 521 if (crossingPolys[0] != null && crossingPolys[1] != null) { 522 List<PolyData> crossingPolygons = crossingPolygonsMap.get(crossingPolys[0]); 523 if (crossingPolygons == null) { 524 crossingPolygons = new ArrayList<>(); 525 crossingPolygonsMap.put(crossingPolys[0], crossingPolygons); 526 } 527 crossingPolygons.add(crossingPolys[1]); 528 } 529 } 530 } 531 return crossingPolygonsMap; 532 } 533 534 /** 535 * Find ways which are crossing without sharing a node. 536 * @param w way that is member of the relation 537 * @param r the relation (used for error messages) 538 * @param crossingWays 539 * @param cellSegments 540 */ 541 private void checkCrossingWay(Way w, Relation r, Map<Point2D, List<WaySegment>> cellSegments, Map<List<Way>, List<WaySegment>> crossingWays) { 542 int nodesSize = w.getNodesCount(); 543 for (int i = 0; i < nodesSize - 1; i++) { 544 final WaySegment es1 = new WaySegment(w, i); 545 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 546 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 547 if (en1 == null || en2 == null) { 548 Main.warn("Crossing ways test skipped " + es1); 549 continue; 550 } 551 for (List<WaySegment> segments : CrossingWays.getSegments(cellSegments, en1, en2)) { 552 for (WaySegment es2 : segments) { 553 554 List<WaySegment> highlight; 555 if (es2.way == w) 556 continue; // reported by CrossingWays.SelfIntersection 557 if (!es1.intersects(es2)) 558 continue; 559 560 List<Way> prims = Arrays.asList(es1.way, es2.way); 561 if ((highlight = crossingWays.get(prims)) == null) { 562 highlight = new ArrayList<>(); 563 highlight.add(es1); 564 highlight.add(es2); 565 addError(r, new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 566 CROSSING_WAYS, Arrays.asList(r, es1.way, es2.way), highlight)); 567 crossingWays.put(prims, highlight); 568 } else { 569 highlight.add(es1); 570 highlight.add(es2); 571 } 572 } 573 segments.add(es1); 574 } 575 } 576 } 577 578 /** 579 * Detect intersections of multipolygon ways at nodes. If any way node is used by more than two ways 580 * or two times in one way and at least once in another way we found an intersection. 581 * @param r the relation 582 * @return List of nodes were ways intersect 583 */ 584 private List<Node> checkIntersectionAtNodes(Relation r) { 585 List<Node> intersectionNodes = new ArrayList<>(); 586 List<Pair<Set<Way>,List<Node>>> intersectionErrors = new ArrayList<>(); 587 HashMap<Way, String> wayRoleMap = new HashMap<>(); 588 for (RelationMember rm : r.getMembers()) { 589 if (rm.isWay()) 590 wayRoleMap.put(rm.getWay(), rm.getRole()); 591 } 592 Map<Node, List<Way>> nodeMap = new HashMap<>(); 593 for (RelationMember rm : r.getMembers()) { 594 if (!rm.isWay()) 595 continue; 596 int numNodes = rm.getWay().getNodesCount(); 597 for (int i = 0; i < numNodes; i++) { 598 Node n = rm.getWay().getNode(i); 599 if (n.getReferrers().size() <= 1) { 600 continue; // cannot be a problem node 601 } 602 List<Way> ways = nodeMap.get(n); 603 if (ways == null) { 604 ways = new ArrayList<>(); 605 nodeMap.put(n, ways); 606 } 607 ways.add(rm.getWay()); 608 if (ways.size() > 2 || (ways.size() == 2 && i != 0 && i + 1 != numNodes)) { 609 intersectionNodes.add(n); 610 boolean allInner = true; 611 for (Way w : ways) { 612 String role = wayRoleMap.get(w); 613 if (!"inner".equals(role)) 614 allInner = false; 615 } 616 if (!allInner) { 617 Set<Way> errorWays = new HashSet<>(ways); 618 boolean addNew = true; 619 for (Pair<Set<Way>, List<Node>> pair : intersectionErrors) { 620 if (pair.a.size() == errorWays.size() && pair.a.containsAll(errorWays)) { 621 pair.b.add(n); 622 addNew = false; 623 break; 624 } 625 } 626 if (addNew) { 627 List<Node> errNodes = new ArrayList<>(); 628 errNodes.add(n); 629 intersectionErrors.add(new Pair<>(errorWays, errNodes)); 630 } 631 } 632 } 633 } 634 } 635 for (Pair<Set<Way>,List<Node>> pair : intersectionErrors) { 636 errors.add(new TestError(this, Severity.WARNING, tr("Multipolygon ways share node(s)"), 637 CROSSING_WAYS, pair.a, pair.b)); 638 } 639 return intersectionNodes; 640 } 641 642 /** 643 * Check for:<ul> 644 * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li> 645 * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li> 646 * </ul> 647 * @param r relation 648 */ 649 private void checkRepeatedMembers(Relation r) { 650 boolean hasDups = false; 651 Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>(); 652 for (RelationMember rm : r.getMembers()) { 653 List<RelationMember> list = seenMemberPrimitives.get(rm.getMember()); 654 if (list == null) { 655 list = new ArrayList<>(2); 656 seenMemberPrimitives.put(rm.getMember(), list); 657 } else 658 hasDups = true; 659 list.add(rm); 660 } 661 if (!hasDups) 662 return; 663 664 List<OsmPrimitive> repeatedSameRole = new ArrayList<>(); 665 List<OsmPrimitive> repeatedDiffRole = new ArrayList<>(); 666 for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) { 667 List<RelationMember> visited = e.getValue(); 668 if (e.getValue().size() == 1) 669 continue; 670 // we found a duplicate member, check if the roles differ 671 boolean rolesDiffer = false; 672 RelationMember rm = visited.get(0); 673 List<OsmPrimitive> primitives = new ArrayList<>(); 674 for (int i = 1; i < visited.size(); i++) { 675 RelationMember v = visited.get(i); 676 primitives.add(rm.getMember()); 677 if (v.getRole().equals(rm.getRole()) == false) { 678 rolesDiffer = true; 679 } 680 } 681 if (rolesDiffer) 682 repeatedDiffRole.addAll(primitives); 683 else 684 repeatedSameRole.addAll(primitives); 685 } 686 addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role")); 687 addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role")); 688 } 689 690 private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) { 691 if (!repeatedMembers.isEmpty()) { 692 List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size()); 693 prims.add(r); 694 prims.addAll(repeatedMembers); 695 addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers)); 696 } 697 698 } 699 700 @Override 701 public Command fixError(TestError testError) { 702 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) { 703 ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives()); 704 if (primitives.size() >= 2) { 705 if (primitives.get(0) instanceof Relation) { 706 Relation r = (Relation) primitives.get(0); 707 return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size())); 708 } 709 } 710 } 711 return null; 712 } 713 714 @Override 715 public boolean isFixable(TestError testError) { 716 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) 717 return true; 718 return false; 719 } 720 392 721 }
