Ticket #13165: 13165_v2.patch
File 13165_v2.patch, 17.9 KB (added by , 6 years ago) |
---|
-
src/org/openstreetmap/josm/data/validation/TestError.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.validation; 3 3 4 import java.awt.geom.Area; 5 import java.awt.geom.PathIterator; 4 6 import java.text.MessageFormat; 7 import java.util.ArrayList; 5 8 import java.util.Arrays; 6 9 import java.util.Collection; 7 10 import java.util.Collections; … … 11 14 import java.util.function.Supplier; 12 15 13 16 import org.openstreetmap.josm.command.Command; 17 import org.openstreetmap.josm.data.coor.EastNorth; 14 18 import org.openstreetmap.josm.data.osm.Node; 15 19 import org.openstreetmap.josm.data.osm.OsmPrimitive; 16 20 import org.openstreetmap.josm.data.osm.OsmUtils; … … 188 192 } 189 193 190 194 /** 195 * Sets an area to highlight when selecting this error. 196 * 197 * @param highlighted the area to highlight 198 * @return {@code this} 199 */ 200 public Builder highlight(Area highlighted) { 201 CheckParameterUtil.ensureParameterNotNull(highlighted, "highlighted"); 202 this.highlighted = Collections.singleton(highlighted); 203 return this; 204 } 205 206 /** 191 207 * Sets a supplier to obtain a command to fix the error. 192 208 * 193 209 * @param fixingCommand the fix supplier. Can be null … … 421 437 v.visit((WaySegment) o); 422 438 } else if (o instanceof List<?>) { 423 439 v.visit((List<Node>) o); 440 } else if (o instanceof Area) { 441 for (List<Node> l : getHiliteNodesForArea((Area) o)) { 442 v.visit(l); 443 } 424 444 } 425 445 } 426 446 } 427 447 428 448 /** 449 * Calculate list of node pairs describing the area. 450 * @param area the area 451 * @return list of node pairs describing the area 452 */ 453 private static List<List<Node>> getHiliteNodesForArea(Area area) { 454 List<List<Node>> hilite = new ArrayList<>(); 455 PathIterator pit = area.getPathIterator(null); 456 double[] res = new double[6]; 457 List<Node> nodes = new ArrayList<>(); 458 while (!pit.isDone()) { 459 int type = pit.currentSegment(res); 460 Node n = new Node(new EastNorth(res[0], res[1])); 461 switch (type) { 462 case PathIterator.SEG_MOVETO: 463 if (!nodes.isEmpty()) { 464 hilite.add(nodes); 465 } 466 nodes = new ArrayList<>(); 467 nodes.add(n); 468 break; 469 case PathIterator.SEG_LINETO: 470 nodes.add(n); 471 break; 472 case PathIterator.SEG_CLOSE: 473 if (!nodes.isEmpty()) { 474 nodes.add(nodes.get(0)); 475 hilite.add(nodes); 476 nodes = new ArrayList<>(); 477 } 478 break; 479 default: 480 break; 481 } 482 pit.next(); 483 } 484 if (nodes.size() > 1) { 485 hilite.add(nodes); 486 } 487 return hilite; 488 } 489 490 491 /** 429 492 * Returns the selection flag of this error 430 493 * @return true if this error is selected 431 494 * @since 5671 -
src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java
4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 6 import java.awt.Rectangle; 7 import java.awt.geom.Area; 7 8 import java.io.BufferedReader; 8 9 import java.io.IOException; 9 10 import java.io.InputStream; … … 771 772 if (fix != null) { 772 773 errorBuilder = errorBuilder.fix(() -> fix); 773 774 } 775 if (env.intersections != null) { 776 Area is = env.intersections.get(c); 777 if (is != null) { 778 errorBuilder = errorBuilder.highlight(is); 779 } 780 } 774 781 res.add(errorBuilder.primitives(p, (OsmPrimitive) c).build()); 775 782 } 776 783 } … … 906 913 if (e.getCode() == toAdd.getCode() && e.getMessage().equals(toAdd.getMessage()) 907 914 && e.getPrimitives().size() == toAdd.getPrimitives().size() 908 915 && e.getPrimitives().containsAll(toAdd.getPrimitives()) 909 && e.getHighlighted().size() == toAdd.getHighlighted().size() 910 && e.getHighlighted().containsAll(toAdd.getHighlighted())) { 916 && highlightedIsEqual(e.getHighlighted(), toAdd.getHighlighted())) { 911 917 isDup = true; 912 918 break; 913 919 } … … 917 923 errors.add(toAdd); 918 924 } 919 925 926 private static boolean highlightedIsEqual(Collection<?> highlighted, Collection<?> highlighted2) { 927 if (highlighted.size() == highlighted2.size()) { 928 if (!highlighted.isEmpty()) { 929 Object h1 = highlighted.iterator().next(); 930 Object h2 = highlighted2.iterator().next(); 931 if (h1 instanceof Area && h2 instanceof Area) { 932 return ((Area) h1).equals((Area) h2); 933 } 934 return highlighted.containsAll(highlighted2); 935 } 936 return true; 937 } 938 return false; 939 } 940 920 941 private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity, 921 942 Collection<Set<TagCheck>> checksCol) { 922 943 final List<TestError> r = new ArrayList<>(); -
src/org/openstreetmap/josm/gui/mappaint/Environment.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.gui.mappaint; 3 3 4 import java.awt.geom.Area; 5 import java.util.HashMap; 4 6 import java.util.LinkedHashSet; 7 import java.util.Map; 5 8 import java.util.Set; 6 9 7 10 import org.openstreetmap.josm.data.osm.IPrimitive; … … 68 71 public Set<IPrimitive> children; 69 72 70 73 /** 74 * Intersection areas (only filled with CrossingFinder if children is not null) 75 */ 76 public Map<IPrimitive, Area> intersections; 77 78 /** 71 79 * Creates a new uninitialized environment. 72 80 */ 73 81 public Environment() { … … 117 125 this.count = other.count; 118 126 this.context = other.getContext(); 119 127 this.children = other.children == null ? null : new LinkedHashSet<>(other.children); 128 this.intersections = other.intersections == null ? null : new HashMap<>(other.intersections); 120 129 } 121 130 122 131 /** … … 283 292 index = null; 284 293 count = null; 285 294 children = null; 295 intersections = null; 286 296 } 287 297 288 298 /** -
src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java
3 3 4 4 import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84; 5 5 6 import java.awt.geom.Area; 6 7 import java.text.MessageFormat; 7 8 import java.util.ArrayList; 8 9 import java.util.Collection; 9 10 import java.util.Collections; 11 import java.util.HashMap; 10 12 import java.util.LinkedHashSet; 11 13 import java.util.List; 12 14 import java.util.Objects; … … 29 31 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition; 30 32 import org.openstreetmap.josm.tools.CheckParameterUtil; 31 33 import org.openstreetmap.josm.tools.Geometry; 34 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 32 35 import org.openstreetmap.josm.tools.Logging; 33 36 import org.openstreetmap.josm.tools.Pair; 34 37 import org.openstreetmap.josm.tools.Utils; … … 294 297 private final class CrossingFinder extends AbstractFinder { 295 298 296 299 private final String layer; 300 private Area area; 297 301 298 302 private CrossingFinder(Environment e) { 299 303 super(e); 300 CheckParameterUtil.ensureThat( e.osm instanceof IWay, "Only ways are supported");304 CheckParameterUtil.ensureThat(isArea(e.osm), "Only areas are supported"); 301 305 layer = OsmUtils.getLayer(e.osm); 302 306 } 303 307 304 308 @Override 305 public void visit(IWay<?> w) { 306 if (Objects.equals(layer, OsmUtils.getLayer(w)) 307 && left.matches(new Environment(w).withParent(e.osm)) 308 && e.osm instanceof IWay && Geometry.PolygonIntersection.CROSSING.equals( 309 Geometry.polygonIntersection(w.getNodes(), ((IWay<?>) e.osm).getNodes()))) { 310 addToChildren(e, w); 309 public void visit(Collection<? extends IPrimitive> primitives) { 310 List<? extends IPrimitive> toIgnore; 311 if (e.osm instanceof Relation) { 312 toIgnore = ((IRelation<?>) e.osm).getMemberPrimitivesList(); 313 } else 314 toIgnore = null; 315 for (IPrimitive p : primitives) { 316 if (isPrimitiveUsable(p) && Objects.equals(layer, OsmUtils.getLayer(p)) 317 && left.matches(new Environment(p).withParent(e.osm)) && isArea(p) 318 && (toIgnore == null || !toIgnore.contains(p))) { 319 if (area == null) { 320 area = Geometry.getArea(e.osm); 321 } 322 Pair<PolygonIntersection, Area> is = Geometry.polygonIntersectionResult(Geometry.getArea(p), 323 area, Geometry.INTERSECTION_EPS_EAST_NORTH); 324 if (Geometry.PolygonIntersection.CROSSING == is.a) { 325 addToChildren(e, p); 326 // store intersection area to improve highlight and zoom to problem 327 if (e.intersections == null) { 328 e.intersections = new HashMap<>(); 329 } 330 e.intersections.put(p, is.b); 331 } 332 } 311 333 } 312 334 } 313 335 } … … 403 425 404 426 private static boolean isArea(IPrimitive p) { 405 427 return (p instanceof IWay && ((IWay<?>) p).isClosed() && ((IWay<?>) p).getNodesCount() >= 4) 406 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete()); 428 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete() 429 && !((IRelation<?>) p).hasIncompleteMembers()); 407 430 } 408 431 409 432 @Override … … 438 461 visitBBox(e, insideOrEqualFinder); 439 462 return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null; 440 463 441 } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {464 } else if (ChildOrParentSelectorType.CROSSING == type) { 442 465 e.parent = e.osm; 443 if (right instanceof OptimizedGeneralSelector 444 && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) { 466 if (e.osm.getDataSet() != null && isArea(e.osm)) { 445 467 final CrossingFinder crossingFinder = new CrossingFinder(e); 446 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); 468 visitBBox(e, crossingFinder); 469 return e.children != null; 447 470 } 448 return e.children != null;449 471 } else if (ChildOrParentSelectorType.SIBLING == type) { 450 472 if (e.osm instanceof INode) { 451 473 for (IPrimitive ref : e.osm.getReferrers(true)) { -
src/org/openstreetmap/josm/tools/Geometry.java
29 29 import org.openstreetmap.josm.data.osm.DataSet; 30 30 import org.openstreetmap.josm.data.osm.INode; 31 31 import org.openstreetmap.josm.data.osm.IPrimitive; 32 import org.openstreetmap.josm.data.osm.IRelation; 32 33 import org.openstreetmap.josm.data.osm.IWay; 33 34 import org.openstreetmap.josm.data.osm.MultipolygonBuilder; 34 35 import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon; … … 528 529 } 529 530 530 531 /** 532 * Calculate area in east/north space for given primitive. Slow for complex multipolygon relations! 533 * @param p the primitive 534 * @return the area in east/north space, might be empty if the primitive is incomplete or a node 535 * since xxx 536 */ 537 public static Area getArea(IPrimitive p) { 538 if (p instanceof Way) { 539 return getArea(((Way) p).getNodes()); 540 } 541 if (p.isMultipolygon() && !p.isIncomplete() && !((IRelation<?>) p).hasIncompleteMembers()) { 542 Multipolygon mp = new Multipolygon((Relation) p); 543 Path2D path = new Path2D.Double(); 544 path.setWindingRule(Path2D.WIND_EVEN_ODD); 545 for (Multipolygon.PolyData pd : mp.getCombinedPolygons()) { 546 path.append(pd.get(), false); 547 } 548 return new Area(path); 549 } 550 return new Area(); 551 } 552 553 /** 531 554 * Builds a path from a list of nodes 532 555 * @param polygon Nodes, forming a closed polygon 533 556 * @param path2d path to add to; can be null, then a new path is created … … 600 623 * @return intersection kind 601 624 */ 602 625 public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) { 626 return polygonIntersectionResult(a1, a2, eps).a; 627 } 603 628 629 /** 630 * Calculate intersection area and kind of intersection between two polygons. 631 * @param a1 Area of first polygon 632 * @param a2 Area of second polygon 633 * @param eps an area threshold, everything below is considered an empty intersection 634 * @return pair with intersection kind and intersection area (never null, but maybe empty) 635 * @since xxx 636 */ 637 public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) { 604 638 Area inter = new Area(a1); 605 639 inter.intersect(a2); 606 640 607 641 if (inter.isEmpty() || !checkIntersection(inter, eps)) { 608 return PolygonIntersection.OUTSIDE;642 return new Pair<>(PolygonIntersection.OUTSIDE, inter); 609 643 } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) { 610 return PolygonIntersection.FIRST_INSIDE_SECOND;644 return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter); 611 645 } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) { 612 return PolygonIntersection.SECOND_INSIDE_FIRST;646 return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter); 613 647 } else { 614 return PolygonIntersection.CROSSING;648 return new Pair<>(PolygonIntersection.CROSSING, inter); 615 649 } 616 650 } 617 651 … … 1102 1136 if (primitives.isEmpty()) 1103 1137 return res; 1104 1138 1105 final Pair<List<JoinedPolygon>, List<JoinedPolygon>> outerInner; 1106 try { 1107 outerInner = MultipolygonBuilder.joinWays(multiPolygon); 1108 } catch (MultipolygonBuilder.JoinedPolygonCreationException ex) { 1109 Logging.trace(ex); 1110 Logging.debug("Invalid multipolygon " + multiPolygon); 1111 return res; 1112 } 1139 Area mpArea = null; 1113 1140 1114 1141 Set<OsmPrimitive> members = multiPolygon.getMemberPrimitives(); 1115 1142 for (IPrimitive p : primitives) { 1116 1143 if (members.contains(p)) 1117 1144 continue; 1145 if (mpArea == null) { 1146 mpArea = getArea(multiPolygon); 1147 } 1118 1148 if (p instanceof Node) { 1119 if (isPolygonInsideMultiPolygon(Collections.singletonList((Node) p), outerInner, null)) { 1120 res.add(p); 1149 if (((Node) p).isLatLonKnown()) { 1150 EastNorth en = ((Node) p).getEastNorth(); 1151 if (mpArea.contains(en.getX(), en.getY())) { 1152 res.add(p); 1153 } 1121 1154 } 1122 1155 } else if (p instanceof Way) { 1123 if ( isPolygonInsideMultiPolygon(((Way) p).getNodes(), outerInner, null)) {1156 if (PolygonIntersection.FIRST_INSIDE_SECOND == polygonIntersection(getArea(p), mpArea)) { 1124 1157 res.add(p); 1125 1158 } 1126 1159 } else if (p.isMultipolygon()) { 1127 1160 Multipolygon mp = new Multipolygon((Relation) p); 1128 1161 boolean inside = true; 1129 // a (valid) multipolygon is inside multiPolygon if all outer rings are inside1162 // a (valid) multipolygon is inside the polygon if all outer rings are inside 1130 1163 for (PolyData outer : mp.getOuterPolygons()) { 1131 if (!isPolygonInsideMultiPolygon(outer.getNodes(), outerInner, null)) { 1164 if (PolygonIntersection.FIRST_INSIDE_SECOND != polygonIntersection(getArea(outer.getNodes()), 1165 mpArea)) { 1132 1166 inside = false; 1133 1167 break; 1134 1168 }