Ticket #13165: 13165_v0.patch

File 13165_v0.patch, 16.0 KB (added by GerdP, 4 years ago)
  • src/org/openstreetmap/josm/data/validation/tests/MapCSSTagChecker.java

     
    577577                final String description1 = group == null ? description : group;
    578578                final String description2 = group == null ? null : description;
    579579                final List<OsmPrimitive> primitives;
     580                final Collection<OsmPrimitive> hilite;
    580581                if (env.child != null) {
    581582                    primitives = Arrays.asList(p, env.child);
    582583                } else {
    583584                    primitives = Collections.singletonList(p);
    584585                }
     586                if (env.hilite != null && !env.hilite.isEmpty()) {
     587                    hilite = env.hilite;
     588                } else
     589                    hilite = primitives;
     590
    585591                if (fix != null) {
    586592                    return new FixableTestError(null, getSeverity(), description1, description2, matchingSelector.toString(), 3000,
    587                             primitives, fix);
     593                            primitives, hilite, fix);
    588594                } else {
    589                     return new TestError(null, getSeverity(), description1, description2, matchingSelector.toString(), 3000, primitives);
     595                    return new TestError(null, getSeverity(), description1, description2, matchingSelector.toString(), 3000, primitives, hilite);
    590596                }
    591597            } else {
    592598                return null;
  • 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.util.Collection;
     5
    46import org.openstreetmap.josm.data.osm.OsmPrimitive;
    57import org.openstreetmap.josm.data.osm.Relation;
    68import org.openstreetmap.josm.gui.mappaint.mapcss.Condition.Context;
     
    2123    public StyleSource source;
    2224    private Context context = Context.PRIMITIVE;
    2325    public static final String DEFAULT_LAYER = "default";
     26    public Collection<OsmPrimitive> hilite;
    2427
    2528    /**
    2629     * If not null, this is the matching parent object if a condition or an expression
     
    9093        this.index = other.index;
    9194        this.count = other.count;
    9295        this.context = other.getContext();
     96        this.hilite = other.hilite;
    9397    }
    9498
    9599    /**
     
    243247        child = null;
    244248        index = null;
    245249        count = null;
     250        hilite = null;
    246251    }
    247252
    248253    public Cascade getCascade(String layer) {
  • src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java

     
    33
    44import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
    55
     6import java.awt.Rectangle;
     7import java.awt.geom.Area;
     8import java.util.ArrayList;
    69import java.util.Collection;
    710import java.util.Collections;
     11import java.util.HashSet;
    812import java.util.List;
    913import java.util.NoSuchElementException;
    1014import java.util.function.IntFunction;
     
    1216import java.util.regex.PatternSyntaxException;
    1317
    1418import org.openstreetmap.josm.Main;
     19import org.openstreetmap.josm.data.coor.EastNorth;
     20import org.openstreetmap.josm.data.coor.LatLon;
     21import org.openstreetmap.josm.data.osm.BBox;
    1522import org.openstreetmap.josm.data.osm.Node;
    1623import org.openstreetmap.josm.data.osm.OsmPrimitive;
    1724import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     25import org.openstreetmap.josm.data.osm.PrimitiveId;
    1826import org.openstreetmap.josm.data.osm.Relation;
    1927import org.openstreetmap.josm.data.osm.RelationMember;
    2028import org.openstreetmap.josm.data.osm.Way;
    2129import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
     30import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon;
     31import org.openstreetmap.josm.data.osm.visitor.paint.relations.Multipolygon.PolyData;
    2232import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
     33import org.openstreetmap.josm.data.projection.Ellipsoid;
    2334import org.openstreetmap.josm.gui.mappaint.Environment;
    2435import org.openstreetmap.josm.gui.mappaint.Range;
    2536import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
    2637import org.openstreetmap.josm.tools.CheckParameterUtil;
    2738import org.openstreetmap.josm.tools.Geometry;
     39import org.openstreetmap.josm.tools.Geometry.PolygonIntersection;
    2840import org.openstreetmap.josm.tools.Pair;
    2941import org.openstreetmap.josm.tools.SubclassFilteredCollection;
    3042import org.openstreetmap.josm.tools.Utils;
     
    241253        }
    242254
    243255        private final class CrossingFinder extends AbstractFinder {
     256            HashSet<PrimitiveId> checkedPrimitives = new HashSet<>();
     257
    244258            private CrossingFinder(Environment e) {
    245259                super(e);
    246260                CheckParameterUtil.ensureThat(e.osm instanceof Way, "Only ways are supported");
    247261            }
    248262
     263            /**
     264             * Check two areas for intersection, if found, find elements to highlight
     265             * @param a1 1st area
     266             * @param a2 2nd area
     267             * @param p2 primitive that is or contains area a2
     268             * @param nodes1 List of nodes used to calculate 1st area
     269             * @param nodes2 List of nodes used to calculate 2nd area
     270             */
     271            void checkIntersection (Area a1, Area a2, OsmPrimitive p2, List<Node> nodes1, List<Node> nodes2 ) {
     272                if (!a1.getBounds2D().intersects(a2.getBounds2D()))
     273                    return;
     274                Area inter = new Area (a1);
     275                inter.intersect(a2);
     276                Rectangle bounds = inter.getBounds();
     277                PolygonIntersection is;
     278                if (inter.isEmpty() || bounds.getHeight()*bounds.getWidth() <= 1.0) {
     279                    is = PolygonIntersection.OUTSIDE;
     280                } else if (a2.getBounds2D().contains(a1.getBounds2D()) && inter.equals(a1)) {
     281                    is = PolygonIntersection.FIRST_INSIDE_SECOND;
     282                } else if (a1.getBounds2D().contains(a2.getBounds2D()) && inter.equals(a2)) {
     283                    is = PolygonIntersection.SECOND_INSIDE_FIRST;
     284                } else {
     285                    is = PolygonIntersection.CROSSING;
     286                }
     287
     288                if (is == PolygonIntersection.CROSSING) {
     289                    e.child = p2;
     290                    // either one area is contained in the other or they are crossing
     291                    if (e.parent.isMultipolygon() || p2.isMultipolygon() || (nodes1.size() > 10 && nodes2.size() > 10)) {
     292                        // calculate bounding box of intersection plus 0.1 m to catch nodes for better highlighting
     293                        EastNorth en1 = new EastNorth(bounds.getMinX(), bounds.getMinY());
     294                        EastNorth en2 = new EastNorth(bounds.getMaxX(), bounds.getMaxY());
     295                        LatLon ll1 = Main.getProjection().getBaseProjection().eastNorth2latlon(en1);
     296                        LatLon ll2 = Main.getProjection().getBaseProjection().eastNorth2latlon(en2);
     297                        double inc = 0.1 * (360.0d / (Ellipsoid.WGS84.a * 2 * Math.PI));
     298                        BBox searchBox = new BBox (ll1.getX() - inc, ll1.getY() - inc ,ll2.getX() + inc, ll2.getY() + inc);
     299                        List<Node> nearNodes = e.osm.getDataSet().searchNodes(searchBox);
     300                        HashSet<Node> nodes = new HashSet<>();
     301                        nodes.addAll(nodes1);
     302                        nodes.addAll(nodes2);
     303                        nearNodes.retainAll(nodes);
     304                        e.hilite = new ArrayList<>(nearNodes);
     305                        if (p2.isMultipolygon() || e.parent.isMultipolygon()) {
     306                            // try to find ways which are involved
     307                            List<Way> nearWays = e.osm.getDataSet().searchWays(new BBox(ll1.getX(), ll1.getY(), ll2.getX(), ll2.getY()));
     308                            for (Way w1 : nearWays) {
     309                                if (w1.getNodesCount() > 100)
     310                                    continue;
     311                                for (OsmPrimitive ref : w1.getReferrers()) {
     312                                    if (ref == p2 || ref == e.parent)
     313                                        e.hilite.add(w1);
     314                                }
     315                            }
     316                        }
     317                    }
     318                }
     319
     320            }
    249321            @Override
    250322            public void visit(Way w) {
    251323                if (e.child == null && left.matches(new Environment(w).withParent(e.osm))) {
    252                     if (e.osm instanceof Way && Geometry.PolygonIntersection.CROSSING.equals(
    253                             Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))) {
    254                         e.child = w;
     324                    // check way-way intersection
     325                    Area a = Geometry.getArea(w.getNodes());
     326                    Area b = Geometry.getArea(((Way) e.osm).getNodes());
     327                    checkIntersection(a, b, w, w.getNodes(), ((Way) e.osm).getNodes());
     328                    return;
     329                }
     330                if (e.parent.isMultipolygon()) {
     331                    // check mp-way intersection
     332                    if (e.child == null && left.matches(new Environment(w).withParent(e.parent))) {
     333                        if (checkedPrimitives.contains(w))
     334                            return;
     335                        checkedPrimitives.add(w);
     336                        for (OsmPrimitive p : w.getReferrers()) {
     337                            if (p == e.parent)
     338                                return;
     339                        }
     340                        Multipolygon polygon = MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) e.parent);
     341                        if (polygon == null)
     342                            return;
     343                        Area a1 = Geometry.getArea(w.getNodes());
     344                        for (PolyData pd : polygon.getCombinedPolygons()) {
     345                            Area a2 = new Area(pd.get());
     346                            checkIntersection(a1, a2, w, w.getNodes(), pd.getNodes());
     347                            if (e.child != null)
     348                                return;
     349                        }
    255350                    }
     351                    if (e.child == null) {
     352                        // is way member of a matching mp relation ?
     353                        for (OsmPrimitive otherParent : w.getReferrers()) {
     354                            if (otherParent.isMultipolygon() && !otherParent.isIncomplete() && e.parent != otherParent) {
     355                                if (left.matches(new Environment(otherParent).withParent(otherParent))) {
     356                                    if (checkedPrimitives.contains(otherParent))
     357                                        return;
     358                                    checkedPrimitives.add(otherParent);
     359                                    if (((Relation) otherParent).hasIncompleteMembers() )
     360                                        return;
     361                                    // okay, we have two different complete multipolygons, check if they are overlapping
     362                                    Multipolygon p1 = MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) e.parent);
     363                                    if (p1 == null)
     364                                        return;
     365                                    Multipolygon p2 = MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) otherParent);
     366                                    if (p2 == null)
     367                                        return;
     368                                    for (PolyData pd1 : p1.getCombinedPolygons()) {
     369                                        Area a1 = new Area(pd1.get());
     370                                        for (PolyData pd2 : p2.getCombinedPolygons()) {
     371                                            Area a2 = new Area(pd2.get());
     372                                            checkIntersection(a1, a2, otherParent, pd1.getNodes(), pd2.getNodes());
     373                                            if (e.child != null)
     374                                                return;
     375                                        }
     376                                    }
     377                                }
     378                            }
     379                        }
     380                    }
     381//                } else {
     382//                    for (OsmPrimitive otherParent : w.getReferrers()) {
     383//                        if (otherParent.isMultipolygon() && ((Relation) otherParent).hasIncompleteMembers() == false) {
     384//                            if (left.matches(new Environment(otherParent).withParent(otherParent))) {
     385//                                checkedPrimitives.add(otherParent);
     386//                                if (e.parent == otherParent || ((Relation) otherParent).hasIncompleteMembers() )
     387//                                    return;
     388//                                // we have a matching way that might cross a multipolygon, check if they are overlapping
     389//                                Area a1 = Geometry.getArea(((Way) e.osm).getNodes());
     390//
     391//                                Multipolygon p2 = MultipolygonCache.getInstance().get(Main.map.mapView, (Relation) otherParent);
     392//                                if (p2 == null)
     393//                                    return;
     394//                                for (PolyData pd2 : p2.getCombinedPolygons()) {
     395//                                    Area a2 = new Area(pd2.get());
     396//                                    checkIntersection(a1, a2, otherParent, ((Way) e.osm).getNodes(), pd2.getNodes());
     397//                                    if (e.child != null)
     398//                                        return;
     399//                                }
     400//                            }
     401//                        }
     402//                    }
    256403                }
    257404            }
    258405        }
     
    348495                    crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
    349496                }
    350497                return e.child != null;
     498            } else if (ChildOrParentSelectorType.CROSSING.equals(type) && e.osm.isMultipolygon()) {
     499                Relation r = (Relation) e.osm;
     500                if (r.hasIncompleteMembers())
     501                    return false;
     502                List<RelationMember> members = r.getMembers();
     503                for (int i = 0; i < members.size(); i++) {
     504                    OsmPrimitive member = members.get(i).getMember();
     505                    if (member instanceof Way) {
     506                        Environment ew = new Environment(member);
     507                        ew.parent = e.osm;
     508                        final CrossingFinder crossingFinder = new CrossingFinder(ew);
     509                        if (right instanceof OptimizedGeneralSelector
     510                                && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
     511                            List<Way> nearWays = ew.osm.getDataSet().searchWays(ew.osm.getBBox());
     512                            crossingFinder.visit(nearWays);
     513                            if (ew.child != null) {
     514                                e.child = ew.child;
     515                                e.hilite = ew.hilite;
     516                                return true;
     517                            }
     518                        }
     519                    }
     520                }
     521                return e.child != null;
    351522            } else if (ChildOrParentSelectorType.SIBLING.equals(type)) {
    352523                if (e.osm instanceof Node) {
    353524                    for (Way w : Utils.filteredCollection(e.osm.getReferrers(true), Way.class)) {