Ticket #13165: 13165_v3.patch
File 13165_v3.patch, 17.1 KB (added by , 4 years ago) |
---|
-
src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java
1 1 // License: GPL. For details, see LICENSE file. 2 2 package org.openstreetmap.josm.data.osm.visitor.paint.relations; 3 3 4 import java.awt.geom.Area; 4 5 import java.awt.geom.Path2D; 5 6 import java.awt.geom.PathIterator; 6 7 import java.awt.geom.Rectangle2D; … … 514 515 515 516 private boolean incomplete; 516 517 518 private Area area; 519 517 520 /** 518 521 * Constructs a new {@code Multipolygon} from a relation. 519 522 * @param r relation … … 790 793 public List<Node> getOpenEnds() { 791 794 return Collections.unmodifiableList(openEnds); 792 795 } 796 797 /** 798 * Calculate area in east/north space. Slow for complex multipolygon relations, so the result is stored. 799 * @return the area in east/north space, might be empty if the multipolygon is incomplete or invalid 800 * since xxx 801 */ 802 public Area getAreaEastNorth() { 803 if (area == null) { 804 if (isIncomplete()) { 805 area = new Area(); 806 } else { 807 Path2D path = new Path2D.Double(); 808 path.setWindingRule(Path2D.WIND_EVEN_ODD); 809 for (PolyData pd : getCombinedPolygons()) { 810 path.append(pd.get(), false); 811 } 812 area = new Area(path); 813 } 814 } 815 return area; 816 } 793 817 } -
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
3 3 4 4 import static org.openstreetmap.josm.tools.I18n.tr; 5 5 6 import java.awt.geom.Area; 6 7 import java.io.BufferedReader; 7 8 import java.io.IOException; 8 9 import java.io.InputStream; … … 618 619 if (fix != null) { 619 620 errorBuilder = errorBuilder.fix(() -> fix); 620 621 } 622 if (env.intersections != null) { 623 Area is = env.intersections.get(c); 624 if (is != null) { 625 errorBuilder = errorBuilder.highlight(is); 626 } 627 } 621 628 res.add(errorBuilder.primitives(p, (OsmPrimitive) c).build()); 622 629 } 623 630 } … … 742 749 if (e.getCode() == toAdd.getCode() && e.getMessage().equals(toAdd.getMessage()) 743 750 && e.getPrimitives().size() == toAdd.getPrimitives().size() 744 751 && e.getPrimitives().containsAll(toAdd.getPrimitives()) 745 && e.getHighlighted().size() == toAdd.getHighlighted().size() 746 && e.getHighlighted().containsAll(toAdd.getHighlighted())) { 752 && highlightedIsEqual(e.getHighlighted(), toAdd.getHighlighted())) { 747 753 isDup = true; 748 754 break; 749 755 } … … 753 759 errors.add(toAdd); 754 760 } 755 761 762 private static boolean highlightedIsEqual(Collection<?> highlighted, Collection<?> highlighted2) { 763 if (highlighted.size() == highlighted2.size()) { 764 if (!highlighted.isEmpty()) { 765 Object h1 = highlighted.iterator().next(); 766 Object h2 = highlighted2.iterator().next(); 767 if (h1 instanceof Area && h2 instanceof Area) { 768 return ((Area) h1).equals((Area) h2); 769 } 770 return highlighted.containsAll(highlighted2); 771 } 772 return true; 773 } 774 return false; 775 } 776 756 777 private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity, 757 778 Collection<Set<TagCheck>> checksCol) { 758 779 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; … … 30 32 import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition; 31 33 import org.openstreetmap.josm.tools.CheckParameterUtil; 32 34 import org.openstreetmap.josm.tools.Geometry; 35 import org.openstreetmap.josm.tools.Geometry.PolygonIntersection; 33 36 import org.openstreetmap.josm.tools.Logging; 37 import org.openstreetmap.josm.tools.Pair; 34 38 import org.openstreetmap.josm.tools.Utils; 35 39 36 40 /** … … 297 301 private final class CrossingFinder extends AbstractFinder { 298 302 299 303 private final String layer; 304 private Area area; 300 305 301 306 private CrossingFinder(Environment e) { 302 307 super(e); 303 CheckParameterUtil.ensureThat( e.osm instanceof IWay, "Only ways are supported");308 CheckParameterUtil.ensureThat(isArea(e.osm), "Only areas are supported"); 304 309 layer = OsmUtils.getLayer(e.osm); 305 310 } 306 311 307 312 @Override 308 public void visit(IWay<?> w) { 309 if (Objects.equals(layer, OsmUtils.getLayer(w)) 310 && left.matches(new Environment(w).withParent(e.osm)) 311 && e.osm instanceof IWay && Geometry.PolygonIntersection.CROSSING.equals( 312 Geometry.polygonIntersection(w.getNodes(), ((IWay<?>) e.osm).getNodes()))) { 313 addToChildren(e, w); 313 public void visit(Collection<? extends IPrimitive> primitives) { 314 List<? extends IPrimitive> toIgnore; 315 if (e.osm instanceof Relation) { 316 toIgnore = ((IRelation<?>) e.osm).getMemberPrimitivesList(); 317 } else 318 toIgnore = null; 319 for (IPrimitive p : primitives) { 320 if (isPrimitiveUsable(p) && Objects.equals(layer, OsmUtils.getLayer(p)) 321 && left.matches(new Environment(p).withParent(e.osm)) && isArea(p) 322 && (toIgnore == null || !toIgnore.contains(p))) { 323 if (area == null) { 324 area = Geometry.getAreaEastNorth(e.osm); 325 } 326 Pair<PolygonIntersection, Area> is = Geometry.polygonIntersectionResult(Geometry.getAreaEastNorth(p), 327 area, Geometry.INTERSECTION_EPS_EAST_NORTH); 328 if (Geometry.PolygonIntersection.CROSSING == is.a) { 329 addToChildren(e, p); 330 // store intersection area to improve highlight and zoom to problem 331 if (e.intersections == null) { 332 e.intersections = new HashMap<>(); 333 } 334 e.intersections.put(p, is.b); 335 } 336 } 314 337 } 315 338 } 316 339 } … … 401 424 402 425 private static boolean isArea(IPrimitive p) { 403 426 return (p instanceof IWay && ((IWay<?>) p).isClosed() && ((IWay<?>) p).getNodesCount() >= 4) 404 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete()); 427 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete() 428 && !((IRelation<?>) p).hasIncompleteMembers()); 405 429 } 406 430 407 431 @Override … … 436 460 visitBBox(e, insideOrEqualFinder); 437 461 return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null; 438 462 439 } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {463 } else if (ChildOrParentSelectorType.CROSSING == type) { 440 464 e.parent = e.osm; 441 if (right instanceof OptimizedGeneralSelector 442 && e.osm.getDataSet() != null 443 && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) { 465 if (e.osm.getDataSet() != null && isArea(e.osm)) { 444 466 final CrossingFinder crossingFinder = new CrossingFinder(e); 445 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox())); 467 visitBBox(e, crossingFinder); 468 return e.children != null; 446 469 } 447 470 return e.children != null; 448 471 } else if (ChildOrParentSelectorType.SIBLING == type) { -
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; … … 552 553 } 553 554 554 555 /** 556 * Calculate area in east/north space for given primitive. Uses {@link MultipolygonCache} for multipolygon relations. 557 * @param p the primitive 558 * @return the area in east/north space, might be empty if the primitive is incomplete or not closed or a node 559 * since xxx 560 */ 561 public static Area getAreaEastNorth(IPrimitive p) { 562 if (p instanceof Way && ((Way) p).isClosed()) { 563 return Geometry.getArea(((Way) p).getNodes()); 564 } 565 if (p.isMultipolygon() && !p.isIncomplete() && !((IRelation<?>) p).hasIncompleteMembers()) { 566 Multipolygon mp = MultipolygonCache.getInstance().get((Relation) p); 567 return mp.getAreaEastNorth(); 568 } 569 return new Area(); 570 } 571 572 /** 555 573 * Returns the Area of a polygon, from the multipolygon relation. 556 574 * @param multipolygon the multipolygon relation 557 575 * @return Area for the multipolygon (LatLon coordinates) … … 600 618 * @return intersection kind 601 619 */ 602 620 public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) { 621 return polygonIntersectionResult(a1, a2, eps).a; 622 } 603 623 624 /** 625 * Calculate intersection area and kind of intersection between two polygons. 626 * @param a1 Area of first polygon 627 * @param a2 Area of second polygon 628 * @param eps an area threshold, everything below is considered an empty intersection 629 * @return pair with intersection kind and intersection area (never null, but maybe empty) 630 * @since xxx 631 */ 632 public static Pair<PolygonIntersection, Area> polygonIntersectionResult(Area a1, Area a2, double eps) { 604 633 Area inter = new Area(a1); 605 634 inter.intersect(a2); 606 635 607 636 if (inter.isEmpty() || !checkIntersection(inter, eps)) { 608 return PolygonIntersection.OUTSIDE;637 return new Pair<>(PolygonIntersection.OUTSIDE, inter); 609 638 } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) { 610 return PolygonIntersection.FIRST_INSIDE_SECOND;639 return new Pair<>(PolygonIntersection.FIRST_INSIDE_SECOND, inter); 611 640 } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) { 612 return PolygonIntersection.SECOND_INSIDE_FIRST;641 return new Pair<>(PolygonIntersection.SECOND_INSIDE_FIRST, inter); 613 642 } else { 614 return PolygonIntersection.CROSSING;643 return new Pair<>(PolygonIntersection.CROSSING, inter); 615 644 } 616 645 } 617 646