Ticket #13307: improve_MultipolygonTest_v6.patch
File improve_MultipolygonTest_v6.patch, 29.1 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/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/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; 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; 31 39 import org.openstreetmap.josm.data.validation.Test; 32 40 import org.openstreetmap.josm.data.validation.TestError; 41 import org.openstreetmap.josm.data.validation.util.ValUtil; 33 42 import org.openstreetmap.josm.gui.DefaultNameFormatter; 34 43 import org.openstreetmap.josm.gui.mappaint.ElemStyles; 35 44 import org.openstreetmap.josm.gui.mappaint.MapPaintStyles; … … 67 76 public static final int NO_STYLE_POLYGON = 1611; 68 77 /** Area style on outer way */ 69 78 public static final int OUTER_STYLE = 1613; 79 /** Multipolygon member repeated (same primitive, same role */ 80 public static final int REPEATED_MEMBER_SAME_ROLE = 1614; 81 /** Multipolygon member repeated (same primitive, different role) */ 82 public static final int REPEATED_MEMBER_DIFF_ROLE = 1615; 70 83 71 84 private static volatile ElemStyles styles; 72 85 73 86 private final Set<String> keysCheckedByAnotherTest = new HashSet<>(); 87 /** All way segments, grouped by cells */ 88 private final Map<Point2D, List<WaySegment>> cellSegments = new HashMap<>(1000); 89 /** The already detected ways in error */ 90 private final Map<List<Way>, List<WaySegment>> crossingWays = new HashMap<>(50); 74 91 75 92 /** 76 93 * Constructs a new {@code MultipolygonTest}. … … 103 120 super.endTest(); 104 121 } 105 122 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());123 private static Path2D.Double createPath(List<Node> nodes) { 124 Path2D.Double result = new Path2D.Double(); 125 result.moveTo(nodes.get(0).getCoor().lon(), nodes.get(0).getCoor().lat()); 109 126 for (int i = 1; i < nodes.size(); i++) { 110 127 Node n = nodes.get(i); 111 result.lineTo( (float) n.getCoor().lat(), (float) n.getCoor().lon());128 result.lineTo(n.getCoor().lon(), n.getCoor().lat()); 112 129 } 113 130 return result; 114 131 } 115 132 116 private static List< GeneralPath> createPolygons(List<Multipolygon.PolyData> joinedWays) {117 List< GeneralPath> result = new ArrayList<>();133 private static List<Path2D.Double> createPolygons(List<Multipolygon.PolyData> joinedWays) { 134 List<Path2D.Double> result = new ArrayList<>(); 118 135 for (Multipolygon.PolyData way : joinedWays) { 119 136 result.add(createPath(way.getNodes())); 120 137 } … … 121 138 return result; 122 139 } 123 140 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 141 @Override 141 142 public void visit(Way w) { 142 143 if (!w.isArea() && ElemStyles.hasOnlyAreaElemStyle(w)) { … … 155 156 @Override 156 157 public void visit(Relation r) { 157 158 if (r.isMultipolygon()) { 159 crossingWays.clear(); 160 cellSegments.clear(); 161 158 162 checkMembersAndRoles(r); 163 checkRepeatedMembers(r); 159 164 checkOuterWay(r); 160 165 161 166 // Rest of checks is only for complete multipolygons … … 163 168 Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, r); 164 169 165 170 // Create new multipolygon using the logics from CreateMultipolygonAction and see if roles match. 166 checkMemberRoleCorrectness(r);171 boolean rolesWereChecked = checkMemberRoleCorrectness(r); 167 172 checkStyleConsistency(r, polygon); 168 checkGeometry(r, polygon );173 checkGeometry(r, polygon, rolesWereChecked); 169 174 } 170 175 } 171 176 } … … 194 199 * <li>{@link #WRONG_MEMBER_ROLE}: Role for ''{0}'' should be ''{1}''</li> 195 200 * </ul> 196 201 * @param r relation 202 * @return true if member roles were checked 197 203 */ 198 private voidcheckMemberRoleCorrectness(Relation r) {204 private boolean checkMemberRoleCorrectness(Relation r) { 199 205 final Pair<Relation, Relation> newMP = CreateMultipolygonAction.createMultipolygonRelation(r.getMemberPrimitives(Way.class), false); 200 206 if (newMP != null) { 201 207 for (RelationMember member : r.getMembers()) { … … 216 222 } 217 223 } 218 224 } 225 return newMP != null; 219 226 } 220 227 221 228 /** … … 285 292 } 286 293 } 287 294 295 private static class LatLonPolyData { 296 final PolyData pd; 297 final Path2D.Double latLonPath; 298 299 LatLonPolyData(PolyData polyData, Path2D.Double path) { 300 this.pd = polyData; 301 this.latLonPath = path; 302 } 303 } 304 288 305 /** 289 306 * Various geometry-related checks:<ul> 290 307 * <li>{@link #NON_CLOSED_WAY}: Multipolygon is not closed</li> … … 293 310 * </ul> 294 311 * @param r relation 295 312 * @param polygon multipolygon 313 * @param rolesWereChecked might be used to skip most of the tests below 296 314 */ 297 private void checkGeometry(Relation r, Multipolygon polygon ) {315 private void checkGeometry(Relation r, Multipolygon polygon, boolean rolesWereChecked) { 298 316 List<Node> openNodes = polygon.getOpenEnds(); 299 317 if (!openNodes.isEmpty()) { 300 318 List<OsmPrimitive> primitives = new LinkedList<>(); 301 319 primitives.add(r); 302 320 primitives.addAll(openNodes); 303 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, primitives, openNodes)); 321 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon is not closed"), NON_CLOSED_WAY, 322 primitives, openNodes)); 304 323 } 324 List<PolyData> innerPolygons = polygon.getInnerPolygons(); 325 List<PolyData> outerPolygons = polygon.getOuterPolygons(); 305 326 327 HashMap<PolyData, List<PolyData>> crossingPolyMap = checkCrossingWays(r, innerPolygons, outerPolygons); 328 329 //Polygons may intersect without crossing ways when one polygon lies completely inside the other 330 List<LatLonPolyData> inner = calcLatLonPolygons(innerPolygons); 331 List<LatLonPolyData> outer = calcLatLonPolygons(outerPolygons); 306 332 // 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); 333 for (int i = 0; i + 1 < outer.size(); i++) { 334 // report outer polygons which lie inside another outer 335 LatLonPolyData outer1 = outer.get(i); 336 for (int j = 0; j < outer.size(); j++) { 337 if (i != j && !crossing(crossingPolyMap, outer1.pd, outer.get(j).pd)) { 338 LatLon c = outer.get(j).pd.getNodes().get(0).getCoor(); 339 if (outer1.latLonPath.contains(c.lon(), c.lat())) { 340 addError(r, 341 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 342 CROSSING_WAYS, Collections.singletonList(r), 343 Arrays.asList(outer1.pd.getNodes(), outer.get(j).pd.getNodes()))); 344 } 345 } 316 346 } 317 347 } 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); 348 for (int i = 0; i < inner.size(); i++) { 349 LatLonPolyData inner1 = inner.get(i); 350 LatLon innerPoint = inner1.pd.getNodes().get(0).getCoor(); 351 for (int j = 0; j < inner.size(); j++) { 352 LatLonPolyData inner2 = inner.get(j); 353 if (i != j && !crossing(crossingPolyMap, inner1.pd, inner2.pd)) { 354 if (inner.get(j).latLonPath.contains(innerPoint.lon(), innerPoint.lat())) { 355 addError(r, 356 new TestError(this, Severity.WARNING, tr("Intersection between multipolygon ways"), 357 CROSSING_WAYS, Collections.singletonList(r), 358 Arrays.asList(inner1.pd.getNodes(), inner2.pd.getNodes()))); 359 } 360 } 323 361 } 324 // Check for intersection between inner and outer members 362 363 // Find inner polygons which are not inside any outer 325 364 boolean outside = true; 326 for (int o = 0; o < outerPolygons.size(); o++) { 327 outside &= checkCrossingWays(r, outerPolygons, outerPolygonsPaths, pdInner, o) == Intersection.OUTSIDE; 365 boolean crossingWithOuter = false; 366 367 for (int o = 0; o < outer.size(); o++) { 368 if (crossing(crossingPolyMap, inner1.pd, outer.get(o).pd)) { 369 crossingWithOuter = true; 370 break; 371 } 372 outside &= outer.get(o).latLonPath.contains(innerPoint.lon(), innerPoint.lat()) == false; 373 if (!outside) 374 break; 328 375 } 329 if (outside ) {376 if (outside && !crossingWithOuter) { 330 377 addError(r, new TestError(this, Severity.WARNING, tr("Multipolygon inner way is outside"), 331 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList( pdInner.getNodes())));378 INNER_WAY_OUTSIDE, Collections.singletonList(r), Arrays.asList(inner1.pd.getNodes()))); 332 379 } 333 380 } 334 381 } 335 382 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 } 383 private boolean crossing(HashMap<PolyData, List<PolyData>> crossingPolyMap, PolyData pd1, PolyData pd2) { 384 List<PolyData> crossingWithFirst = crossingPolyMap.get(pd1); 385 if (crossingWithFirst != null) { 386 if (crossingWithFirst.contains(pd2)) 387 return true; 344 388 } 345 return intersection; 389 List<PolyData> crossingWith2nd = crossingPolyMap.get(pd2); 390 if (crossingWith2nd != null) { 391 if (crossingWith2nd.contains(pd1)) 392 return true; 393 } 394 return false; 346 395 } 347 396 397 private List<LatLonPolyData> calcLatLonPolygons(List<PolyData> polygons) { 398 if (polygons == null || polygons.isEmpty()) 399 return Collections.emptyList(); 400 List<LatLonPolyData> latLonPolygons = new ArrayList<>(); 401 List<Path2D.Double> polygonsPaths = createPolygons(polygons); 402 for (int i = 0; i < polygons.size(); i++) { 403 latLonPolygons.add(new LatLonPolyData(polygons.get(i), polygonsPaths.get(i))); 404 } 405 return latLonPolygons; 406 } 407 348 408 /** 349 409 * Check for:<ul> 350 410 * <li>{@link #WRONG_MEMBER_ROLE}: No useful role for multipolygon member</li> … … 389 449 addRelationIfNeeded(error, r); 390 450 errors.add(error); 391 451 } 452 453 /** 454 * Determine multipolygon ways which are intersecting. This is now allowed. 455 * See {@link CrossingWays} 456 * @param r the relation (for error reporting) 457 * @param innerPolygons list of inner polygons 458 * @param outerPolygons list of outer polygons 459 * @return map of crossing polygons (including polygons touching outer) 460 */ 461 private HashMap<PolyData, List<PolyData>> checkCrossingWays(Relation r, List<PolyData> innerPolygons, 462 List<PolyData> outerPolygons) { 463 List<Way> innerWays = new ArrayList<>(); 464 List<Way> outerWays = new ArrayList<>(); 465 for (Way w : r.getMemberPrimitives(Way.class)) { 466 for (PolyData pd : innerPolygons) { 467 if (pd.getWayIds().contains(w.getUniqueId())) { 468 innerWays.add(w); 469 break; 470 } 471 } 472 for (PolyData pd : outerPolygons) { 473 if (pd.getWayIds().contains(w.getUniqueId())) { 474 outerWays.add(w); 475 break; 476 } 477 } 478 } 479 for (Way w : innerWays) { 480 checkCrossingWay(w, r, true /* allow shared ways */); 481 } 482 for (Way w : outerWays) { 483 checkCrossingWay(w, r, false/* don't allow shared ways */); 484 } 485 HashMap<PolyData, List<PolyData>> crossingPolygonsMap = new HashMap<>(); 486 for (Entry<List<Way>, List<WaySegment>> entry : crossingWays.entrySet()) { 487 List<Way> ways = entry.getKey(); 488 PolyData[] crossingPolys = new PolyData[2]; 489 for (Way w : ways) { 490 for (int j = 0; j < crossingPolys.length; j++) { 491 for (PolyData pd : innerPolygons) { 492 if (pd.getWayIds().contains(w.getUniqueId())) { 493 crossingPolys[j] = pd; 494 break; 495 } 496 } 497 if (crossingPolys[j] != null) 498 break; 499 for (PolyData pd : outerPolygons) { 500 if (pd.getWayIds().contains(w.getUniqueId())) { 501 crossingPolys[j] = pd; 502 break; 503 } 504 } 505 } 506 } 507 if (crossingPolys[0] != null && crossingPolys[1] != null) { 508 List<PolyData> x = crossingPolygonsMap.get(crossingPolys[0]); 509 if (x == null) { 510 x = new ArrayList<>(); 511 crossingPolygonsMap.put(crossingPolys[0], x); 512 } 513 x.add(crossingPolys[1]); 514 } 515 } 516 return crossingPolygonsMap; 517 } 518 519 /** 520 * 521 * @param w way that is member of the relation 522 * @param r the relation (used for error messages) 523 * @param allowSharedWaySegment false: treat similar way segment as crossing 524 */ 525 private void checkCrossingWay(Way w, Relation r, boolean allowSharedWaySegment) { 526 527 int nodesSize = w.getNodesCount(); 528 for (int i = 0; i < nodesSize - 1; i++) { 529 final WaySegment es1 = new WaySegment(w, i); 530 final EastNorth en1 = es1.getFirstNode().getEastNorth(); 531 final EastNorth en2 = es1.getSecondNode().getEastNorth(); 532 if (en1 == null || en2 == null) { 533 Main.warn("Crossing ways test skipped " + es1); 534 continue; 535 } 536 for (List<WaySegment> segments : getSegments(en1, en2)) { 537 for (WaySegment es2 : segments) { 538 539 List<WaySegment> highlight; 540 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 * Returns all the cells this segment crosses. Each cell contains the list 567 * of segments already processed 568 * 569 * @param n1 The first EastNorth 570 * @param n2 The second EastNorth 571 * @return A list with all the cells the segment crosses 572 */ 573 private List<List<WaySegment>> getSegments(EastNorth n1, EastNorth n2) { 574 575 List<List<WaySegment>> cells = new ArrayList<>(); 576 for (Point2D cell : ValUtil.getSegmentCells(n1, n2, OsmValidator.griddetail)) { 577 List<WaySegment> segments = cellSegments.get(cell); 578 if (segments == null) { 579 segments = new ArrayList<>(); 580 cellSegments.put(cell, segments); 581 } 582 cells.add(segments); 583 } 584 return cells; 585 } 586 587 /** 588 * Check for:<ul> 589 * <li>{@link #REPEATED_MEMBER_DIFF_ROLE}: Multipolygon member(s) repeated with different role</li> 590 * <li>{@link #REPEATED_MEMBER_SAME_ROLE}: Multipolygon member(s) repeated with same role</li> 591 * </ul> 592 * @param r relation 593 */ 594 private void checkRepeatedMembers(Relation r) { 595 boolean hasDups = false; 596 Map<OsmPrimitive, List<RelationMember>> seenMemberPrimitives = new HashMap<>(); 597 for (RelationMember rm : r.getMembers()) { 598 List<RelationMember> list = seenMemberPrimitives.get(rm.getMember()); 599 if (list == null) { 600 list = new ArrayList<>(2); 601 seenMemberPrimitives.put(rm.getMember(), list); 602 } else 603 hasDups = true; 604 list.add(rm); 605 } 606 if (!hasDups) 607 return; 608 609 List<OsmPrimitive> repeatedSameRole = new ArrayList<>(); 610 List<OsmPrimitive> repeatedDiffRole = new ArrayList<>(); 611 for (Entry<OsmPrimitive, List<RelationMember>> e : seenMemberPrimitives.entrySet()) { 612 List<RelationMember> visited = e.getValue(); 613 if (e.getValue().size() == 1) 614 continue; 615 // we found a duplicate member, check if the roles differ 616 boolean rolesDiffer = false; 617 RelationMember rm = visited.get(0); 618 List<OsmPrimitive> primitives = new ArrayList<>(); 619 for (int i = 1; i < visited.size(); i++) { 620 RelationMember v = visited.get(i); 621 primitives.add(rm.getMember()); 622 if (v.getRole().equals(rm.getRole()) == false) { 623 rolesDiffer = true; 624 } 625 } 626 if (rolesDiffer) 627 repeatedDiffRole.addAll(primitives); 628 else 629 repeatedSameRole.addAll(primitives); 630 } 631 addRepeatedMemberError(r, repeatedDiffRole, REPEATED_MEMBER_DIFF_ROLE, tr("Multipolygon member(s) repeated with different role")); 632 addRepeatedMemberError(r, repeatedSameRole, REPEATED_MEMBER_SAME_ROLE, tr("Multipolygon member(s) repeated with same role")); 633 } 634 635 private void addRepeatedMemberError(Relation r, List<OsmPrimitive> repeatedMembers, int errorCode, String msg) { 636 if (!repeatedMembers.isEmpty()) { 637 List<OsmPrimitive> prims = new ArrayList<>(1 + repeatedMembers.size()); 638 prims.add(r); 639 prims.addAll(repeatedMembers); 640 addError(r, new TestError(this, Severity.WARNING, msg, errorCode, prims, repeatedMembers)); 641 } 642 643 } 644 645 @Override 646 public Command fixError(TestError testError) { 647 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) { 648 ArrayList<OsmPrimitive> primitives = new ArrayList<>(testError.getPrimitives()); 649 if (primitives.size() >= 2) { 650 if (primitives.get(0) instanceof Relation) { 651 Relation r = (Relation) primitives.get(0); 652 return new RemoveRepeatedRelationMembersCommand(r, primitives.subList(1, primitives.size())); 653 } 654 } 655 } 656 return null; 657 } 658 659 @Override 660 public boolean isFixable(TestError testError) { 661 if (testError.getCode() == REPEATED_MEMBER_SAME_ROLE) 662 return true; 663 return false; 664 } 392 665 }