Ticket #13165: 13165_v3.patch

File 13165_v3.patch, 17.1 KB (added by GerdP, 5 weeks ago)
  • src/org/openstreetmap/josm/data/osm/visitor/paint/relations/Multipolygon.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.osm.visitor.paint.relations;
    33
     4import java.awt.geom.Area;
    45import java.awt.geom.Path2D;
    56import java.awt.geom.PathIterator;
    67import java.awt.geom.Rectangle2D;
     
    514515
    515516    private boolean incomplete;
    516517
     518    private Area area;
     519
    517520    /**
    518521     * Constructs a new {@code Multipolygon} from a relation.
    519522     * @param r relation
     
    790793    public List<Node> getOpenEnds() {
    791794        return Collections.unmodifiableList(openEnds);
    792795    }
     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    }
    793817}
  • src/org/openstreetmap/josm/data/validation/TestError.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.data.validation;
    33
     4import java.awt.geom.Area;
     5import java.awt.geom.PathIterator;
    46import java.text.MessageFormat;
     7import java.util.ArrayList;
    58import java.util.Arrays;
    69import java.util.Collection;
    710import java.util.Collections;
     
    1114import java.util.function.Supplier;
    1215
    1316import org.openstreetmap.josm.command.Command;
     17import org.openstreetmap.josm.data.coor.EastNorth;
    1418import org.openstreetmap.josm.data.osm.Node;
    1519import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1620import org.openstreetmap.josm.data.osm.OsmUtils;
     
    188192        }
    189193
    190194        /**
     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        /**
    191207         * Sets a supplier to obtain a command to fix the error.
    192208         *
    193209         * @param fixingCommand the fix supplier. Can be null
     
    421437                v.visit((WaySegment) o);
    422438            } else if (o instanceof List<?>) {
    423439                v.visit((List<Node>) o);
     440            } else if (o instanceof Area) {
     441                for (List<Node> l : getHiliteNodesForArea((Area) o)) {
     442                    v.visit(l);
     443                }
    424444            }
    425445        }
    426446    }
    427447
    428448    /**
     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    /**
    429492     * Returns the selection flag of this error
    430493     * @return true if this error is selected
    431494     * @since 5671
  • src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java

     
    33
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
     6import java.awt.geom.Area;
    67import java.io.BufferedReader;
    78import java.io.IOException;
    89import java.io.InputStream;
     
    618619                            if (fix != null) {
    619620                                errorBuilder = errorBuilder.fix(() -> fix);
    620621                            }
     622                            if (env.intersections != null) {
     623                                Area is = env.intersections.get(c);
     624                                if (is != null) {
     625                                    errorBuilder = errorBuilder.highlight(is);
     626                                }
     627                            }
    621628                            res.add(errorBuilder.primitives(p, (OsmPrimitive) c).build());
    622629                        }
    623630                    }
     
    742749                if (e.getCode() == toAdd.getCode() && e.getMessage().equals(toAdd.getMessage())
    743750                        && e.getPrimitives().size() == toAdd.getPrimitives().size()
    744751                        && e.getPrimitives().containsAll(toAdd.getPrimitives())
    745                         && e.getHighlighted().size() == toAdd.getHighlighted().size()
    746                         && e.getHighlighted().containsAll(toAdd.getHighlighted())) {
     752                        && highlightedIsEqual(e.getHighlighted(), toAdd.getHighlighted())) {
    747753                    isDup = true;
    748754                    break;
    749755                }
     
    753759            errors.add(toAdd);
    754760    }
    755761
     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
    756777    private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,
    757778            Collection<Set<TagCheck>> checksCol) {
    758779        final List<TestError> r = new ArrayList<>();
  • src/org/openstreetmap/josm/gui/mappaint/Environment.java

     
    11// License: GPL. For details, see LICENSE file.
    22package org.openstreetmap.josm.gui.mappaint;
    33
     4import java.awt.geom.Area;
     5import java.util.HashMap;
    46import java.util.LinkedHashSet;
     7import java.util.Map;
    58import java.util.Set;
    69
    710import org.openstreetmap.josm.data.osm.IPrimitive;
     
    6871    public Set<IPrimitive> children;
    6972
    7073    /**
     74     * Intersection areas (only filled with CrossingFinder if children is not null)
     75     */
     76    public Map<IPrimitive, Area> intersections;
     77
     78    /**
    7179     * Creates a new uninitialized environment.
    7280     */
    7381    public Environment() {
     
    117125        this.count = other.count;
    118126        this.context = other.getContext();
    119127        this.children = other.children == null ? null : new LinkedHashSet<>(other.children);
     128        this.intersections = other.intersections == null ? null : new HashMap<>(other.intersections);
    120129    }
    121130
    122131    /**
     
    283292        index = null;
    284293        count = null;
    285294        children = null;
     295        intersections = null;
    286296    }
    287297
    288298    /**
  • src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java

     
    33
    44import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
    55
     6import java.awt.geom.Area;
    67import java.text.MessageFormat;
    78import java.util.ArrayList;
    89import java.util.Collection;
    910import java.util.Collections;
     11import java.util.HashMap;
    1012import java.util.LinkedHashSet;
    1113import java.util.List;
    1214import java.util.Objects;
     
    3032import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
    3133import org.openstreetmap.josm.tools.CheckParameterUtil;
    3234import org.openstreetmap.josm.tools.Geometry;
     35import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    3336import org.openstreetmap.josm.tools.Logging;
     37import org.openstreetmap.josm.tools.Pair;
    3438import org.openstreetmap.josm.tools.Utils;
    3539
    3640/**
     
    297301        private final class CrossingFinder extends AbstractFinder {
    298302
    299303            private final String layer;
     304            private Area area;
    300305
    301306            private CrossingFinder(Environment e) {
    302307                super(e);
    303                 CheckParameterUtil.ensureThat(e.osm instanceof IWay, "Only ways are supported");
     308                CheckParameterUtil.ensureThat(isArea(e.osm), "Only areas are supported");
    304309                layer = OsmUtils.getLayer(e.osm);
    305310            }
    306311
    307312            @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                    }
    314337                }
    315338            }
    316339        }
     
    401424
    402425        private static boolean isArea(IPrimitive p) {
    403426            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());
    405429        }
    406430
    407431        @Override
     
    436460                visitBBox(e, insideOrEqualFinder);
    437461                return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null;
    438462
    439             } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {
     463            } else if (ChildOrParentSelectorType.CROSSING == type) {
    440464                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)) {
    444466                    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;
    446469                }
    447470                return e.children != null;
    448471            } else if (ChildOrParentSelectorType.SIBLING == type) {
  • src/org/openstreetmap/josm/tools/Geometry.java

     
    2929import org.openstreetmap.josm.data.osm.DataSet;
    3030import org.openstreetmap.josm.data.osm.INode;
    3131import org.openstreetmap.josm.data.osm.IPrimitive;
     32import org.openstreetmap.josm.data.osm.IRelation;
    3233import org.openstreetmap.josm.data.osm.IWay;
    3334import org.openstreetmap.josm.data.osm.MultipolygonBuilder;
    3435import org.openstreetmap.josm.data.osm.MultipolygonBuilder.JoinedPolygon;
     
    552553    }
    553554
    554555    /**
     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    /**
    555573     * Returns the Area of a polygon, from the multipolygon relation.
    556574     * @param multipolygon the multipolygon relation
    557575     * @return Area for the multipolygon (LatLon coordinates)
     
    600618     * @return intersection kind
    601619     */
    602620    public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) {
     621        return polygonIntersectionResult(a1, a2, eps).a;
     622    }
    603623
     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) {
    604633        Area inter = new Area(a1);
    605634        inter.intersect(a2);
    606635
    607636        if (inter.isEmpty() || !checkIntersection(inter, eps)) {
    608             return PolygonIntersection.OUTSIDE;
     637            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
    609638        } 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);
    611640        } 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);
    613642        } else {
    614             return PolygonIntersection.CROSSING;
     643            return new Pair<>(PolygonIntersection.CROSSING, inter);
    615644        }
    616645    }
    617646