Ticket #13165: 13165_v2.patch

File 13165_v2.patch, 17.9 KB (added by GerdP, 11 months ago)
  • 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

     
    44import static org.openstreetmap.josm.tools.I18n.tr;
    55
    66import java.awt.Rectangle;
     7import java.awt.geom.Area;
    78import java.io.BufferedReader;
    89import java.io.IOException;
    910import java.io.InputStream;
     
    771772                            if (fix != null) {
    772773                                errorBuilder = errorBuilder.fix(() -> fix);
    773774                            }
     775                            if (env.intersections != null) {
     776                                Area is = env.intersections.get(c);
     777                                if (is != null) {
     778                                    errorBuilder = errorBuilder.highlight(is);
     779                                }
     780                            }
    774781                            res.add(errorBuilder.primitives(p, (OsmPrimitive) c).build());
    775782                        }
    776783                    }
     
    906913                if (e.getCode() == toAdd.getCode() && e.getMessage().equals(toAdd.getMessage())
    907914                        && e.getPrimitives().size() == toAdd.getPrimitives().size()
    908915                        && e.getPrimitives().containsAll(toAdd.getPrimitives())
    909                         && e.getHighlighted().size() == toAdd.getHighlighted().size()
    910                         && e.getHighlighted().containsAll(toAdd.getHighlighted())) {
     916                        && highlightedIsEqual(e.getHighlighted(), toAdd.getHighlighted())) {
    911917                    isDup = true;
    912918                    break;
    913919                }
     
    917923            errors.add(toAdd);
    918924    }
    919925
     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
    920941    private static Collection<TestError> getErrorsForPrimitive(OsmPrimitive p, boolean includeOtherSeverity,
    921942            Collection<Set<TagCheck>> checksCol) {
    922943        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;
     
    2931import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
    3032import org.openstreetmap.josm.tools.CheckParameterUtil;
    3133import org.openstreetmap.josm.tools.Geometry;
     34import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    3235import org.openstreetmap.josm.tools.Logging;
    3336import org.openstreetmap.josm.tools.Pair;
    3437import org.openstreetmap.josm.tools.Utils;
     
    294297        private final class CrossingFinder extends AbstractFinder {
    295298
    296299            private final String layer;
     300            private Area area;
    297301
    298302            private CrossingFinder(Environment e) {
    299303                super(e);
    300                 CheckParameterUtil.ensureThat(e.osm instanceof IWay, "Only ways are supported");
     304                CheckParameterUtil.ensureThat(isArea(e.osm), "Only areas are supported");
    301305                layer = OsmUtils.getLayer(e.osm);
    302306            }
    303307
    304308            @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                    }
    311333                }
    312334            }
    313335        }
     
    403425
    404426        private static boolean isArea(IPrimitive p) {
    405427            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());
    407430        }
    408431
    409432        @Override
     
    438461                visitBBox(e, insideOrEqualFinder);
    439462                return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null;
    440463
    441             } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {
     464            } else if (ChildOrParentSelectorType.CROSSING == type) {
    442465                e.parent = e.osm;
    443                 if (right instanceof OptimizedGeneralSelector
    444                         && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
     466                if (e.osm.getDataSet() != null && isArea(e.osm)) {
    445467                    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;
    447470                }
    448                 return e.children != null;
    449471            } else if (ChildOrParentSelectorType.SIBLING == type) {
    450472                if (e.osm instanceof INode) {
    451473                    for (IPrimitive ref : e.osm.getReferrers(true)) {
  • 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;
     
    528529    }
    529530
    530531    /**
     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    /**
    531554     * Builds a path from a list of nodes
    532555     * @param polygon Nodes, forming a closed polygon
    533556     * @param path2d path to add to; can be null, then a new path is created
     
    600623     * @return intersection kind
    601624     */
    602625    public static PolygonIntersection polygonIntersection(Area a1, Area a2, double eps) {
     626        return polygonIntersectionResult(a1, a2, eps).a;
     627    }
    603628
     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) {
    604638        Area inter = new Area(a1);
    605639        inter.intersect(a2);
    606640
    607641        if (inter.isEmpty() || !checkIntersection(inter, eps)) {
    608             return PolygonIntersection.OUTSIDE;
     642            return new Pair<>(PolygonIntersection.OUTSIDE, inter);
    609643        } 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);
    611645        } 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);
    613647        } else {
    614             return PolygonIntersection.CROSSING;
     648            return new Pair<>(PolygonIntersection.CROSSING, inter);
    615649        }
    616650    }
    617651
     
    11021136        if (primitives.isEmpty())
    11031137            return res;
    11041138
    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;
    11131140
    11141141        Set<OsmPrimitive> members = multiPolygon.getMemberPrimitives();
    11151142        for (IPrimitive p : primitives) {
    11161143            if (members.contains(p))
    11171144                continue;
     1145            if (mpArea == null) {
     1146                mpArea = getArea(multiPolygon);
     1147            }
    11181148            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                    }
    11211154                }
    11221155            } else if (p instanceof Way) {
    1123                 if (isPolygonInsideMultiPolygon(((Way) p).getNodes(), outerInner, null)) {
     1156                if (PolygonIntersection.FIRST_INSIDE_SECOND == polygonIntersection(getArea(p), mpArea)) {
    11241157                    res.add(p);
    11251158                }
    11261159            } else if (p.isMultipolygon()) {
    11271160                Multipolygon mp = new Multipolygon((Relation) p);
    11281161                boolean inside = true;
    1129                 // a (valid) multipolygon is inside multiPolygon if all outer rings are inside
     1162                // a (valid) multipolygon is inside the polygon if all outer rings are inside
    11301163                for (PolyData outer : mp.getOuterPolygons()) {
    1131                     if (!isPolygonInsideMultiPolygon(outer.getNodes(), outerInner, null)) {
     1164                    if (PolygonIntersection.FIRST_INSIDE_SECOND != polygonIntersection(getArea(outer.getNodes()),
     1165                            mpArea)) {
    11321166                        inside = false;
    11331167                        break;
    11341168                    }