Changeset 6607 in josm for trunk/src/org


Ignore:
Timestamp:
2014-01-03T11:12:16+01:00 (11 years ago)
Author:
simon04
Message:

see #9516 - MapCSS: add basic support for a "contains" expression

The current syntax is outer ∋ inner, e.g., way[building?] ∋ node[building?].

This implementation is known to be inefficient since, for every match on the right side, it checks all primitives of the dataset for a match.

Nevertheless, this implementation allows mappers to write specific MapCSS-based tests involving this spatial operator.

Location:
trunk/src/org/openstreetmap/josm
Files:
4 edited

Legend:

Unmodified
Added
Removed
  • trunk/src/org/openstreetmap/josm/data/validation/tests/BuildingInBuilding.java

    r6494 r6607  
    7373    }
    7474
    75     protected class MultiPolygonMembers {
    76         private final Set<Way> outers = new HashSet<Way>();
    77         private final Set<Way> inners = new HashSet<Way>();
    78         public MultiPolygonMembers(Relation multiPolygon) {
    79             for (RelationMember m : multiPolygon.getMembers()) {
    80                 if (m.getType().equals(OsmPrimitiveType.WAY)) {
    81                     if (m.getRole().equals("outer")) {
    82                         outers.add(m.getWay());
    83                     } else if (m.getRole().equals("inner")) {
    84                         inners.add(m.getWay());
    85                     }
    86                 }
    87             }
    88         }
    89     }
    90 
    9175    protected boolean sameLayers(Way w1, Way w2) {
    9276        String l1 = w1.get("layer") != null ? w1.get("layer") : "0";
    9377        String l2 = w2.get("layer") != null ? w2.get("layer") : "0";
    9478        return l1.equals(l2);
    95     }
    96 
    97     protected boolean isWayInsideMultiPolygon(Way object, Relation multiPolygon) {
    98         // Extract outer/inner members from multipolygon
    99         MultiPolygonMembers mpm = new MultiPolygonMembers(multiPolygon);
    100         // Test if object is inside an outer member
    101         for (Way out : mpm.outers) {
    102             PolygonIntersection inter = Geometry.polygonIntersection(object.getNodes(), out.getNodes());
    103             if (inter == PolygonIntersection.FIRST_INSIDE_SECOND || inter == PolygonIntersection.CROSSING) {
    104                 boolean insideInner = false;
    105                 // If inside an outer, check it is not inside an inner
    106                 for (Way in : mpm.inners) {
    107                     if (Geometry.polygonIntersection(in.getNodes(), out.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND &&
    108                         Geometry.polygonIntersection(object.getNodes(), in.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND) {
    109                         insideInner = true;
    110                         break;
    111                     }
    112                 }
    113                 // Inside outer but not inside inner -> the building appears to be inside a buiding
    114                 if (!insideInner) {
    115                     // Final check on "layer" tag. Buildings of different layers may be superposed
    116                     if (sameLayers(object, out)) {
    117                         return true;
    118                     }
    119                 }
    120             }
    121         }
    122         return false;
    12379    }
    12480
     
    13288                }
    13389
    134                 protected boolean evaluateWay(Way w, Way object) {
     90                protected boolean evaluateWay(final Way w, Way object) {
    13591                    if (w.equals(object)) return false;
    13692
     
    151107                        // Else, test if w is inside one of the multipolygons
    152108                        for (OsmPrimitive bmp : buildingMultiPolygons) {
    153                             if (bmp instanceof Relation && isWayInsideMultiPolygon(w, (Relation) bmp)) {
     109                            if (bmp instanceof Relation && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) bmp, new Predicate<Way>() {
     110                                @Override
     111                                public boolean evaluate(Way outer) {
     112                                    return sameLayers(w, outer);
     113                                }
     114                            })) {
    154115                                return true;
    155116                            }
     
    160121
    161122                protected boolean evaluateRelation(Relation r, Way object) {
    162                     MultiPolygonMembers mpm = new MultiPolygonMembers((Relation) p);
     123                    Geometry.MultiPolygonMembers mpm = new Geometry.MultiPolygonMembers((Relation) p);
    163124                    for (Way out : mpm.outers) {
    164125                        if (evaluateWay(out, object)) {
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/MapCSSParser.jj

    r6561 r6607  
    8080|   < CARET: "^" >
    8181|   < FULLSTOP: "." >
     82|   < CONTAINS: "∋" >
    8283|   < COMMENT_START: "/*" > : COMMENT
    8384|   < UNEXPECTED_CHAR : ~[] > // avoid TokenMgrErrors because they are hard to recover from
     
    262263Selector child_selector() :
    263264{
    264     boolean parentSelector = false;
     265    Selector.ChildOrParentSelectorType type = null;
    265266    Condition c;
    266267    List<Condition> conditions = new ArrayList<Condition>();
     
    272273    selLeft=selector() w()
    273274    (
    274         ( <GREATER> { parentSelector = false; } | <LESS> { parentSelector = true; } )
    275         ( ( c=condition(Context.LINK) | c=class_or_pseudoclass(Context.LINK) ) { conditions.add(c); } )*
     275        (
     276            ( <GREATER> { type = Selector.ChildOrParentSelectorType.CHILD; } | <LESS> { type = Selector.ChildOrParentSelectorType.PARENT; } )
     277            ( ( c=condition(Context.LINK) | c=class_or_pseudoclass(Context.LINK) ) { conditions.add(c); } )*
     278        |
     279            <CONTAINS> { type = Selector.ChildOrParentSelectorType.CONTAINS; }
     280        )
    276281        { selLink = new LinkSelector(conditions); }
    277282        w()
    278283        selRight=selector() w()
    279284    )?
    280     { return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, parentSelector) : selLeft; }
     285    { return selRight != null ? new ChildOrParentSelector(selLeft, selLink, selRight, type) : selLeft; }
    281286}
    282287
  • trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java

    r6561 r6607  
    1515import org.openstreetmap.josm.gui.mappaint.Environment;
    1616import org.openstreetmap.josm.gui.mappaint.Range;
     17import org.openstreetmap.josm.tools.Geometry;
    1718import org.openstreetmap.josm.tools.Pair;
    1819import org.openstreetmap.josm.tools.Utils;
     
    3334
    3435    public Range getRange();
     36
     37    public static enum ChildOrParentSelectorType {
     38        CHILD, PARENT, CONTAINS
     39    }
    3540
    3641    /**
     
    5459        /** true, if this represents a parent selector (otherwise it is a child selector)
    5560         */
    56         private final boolean parentSelector;
     61        private final ChildOrParentSelectorType type;
    5762
    5863        /**
     
    6065         * @param a the first selector
    6166         * @param b the second selector
    62          * @param parentSelector if true, this is a parent selector; otherwise a child selector
     67         * @param type the selector type
    6368         */
    64         public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
     69        public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrParentSelectorType type) {
    6570            this.left = a;
    6671            this.link = link;
    6772            this.right = b;
    68             this.parentSelector = parentSelector;
     73            this.type = type;
    6974        }
    7075
     
    142147        }
    143148
     149        private class ContainsFinder extends AbstractVisitor {
     150            private final Environment e;
     151            private final List<Node> nodes;
     152
     153            private ContainsFinder(Environment e) {
     154                this.e = e;
     155                if (e.osm instanceof Node) {
     156                    nodes = Collections.singletonList((Node) e.osm);
     157                } else if (e.osm instanceof Way) {
     158                    nodes = ((Way) e.osm).getNodes();
     159                } else {
     160                    throw new IllegalArgumentException("Relations not supported");
     161                }
     162            }
     163
     164            @Override
     165            public void visit(Node n) {
     166            }
     167
     168            @Override
     169            public void visit(Way w) {
     170                if (e.parent == null && left.matches(e.withPrimitive(w))) {
     171                    if (nodes.size() == 1
     172                            ? Geometry.nodeInsidePolygon(nodes.get(0), w.getNodes())
     173                            : Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals(Geometry.polygonIntersection(nodes, w.getNodes()))) {
     174                        e.parent = w;
     175                        e.index = 0;
     176                    }
     177                }
     178            }
     179
     180            @Override
     181            public void visit(Relation r) {
     182                if (e.parent == null && left.matches(e.withPrimitive(r))) {
     183                    if (r.isMultipolygon() && Geometry.isPolygonInsideMultiPolygon(nodes, r, null)) {
     184                        e.parent = r;
     185                        e.index = 0;
     186                    }
     187                }
     188            }
     189        }
     190
    144191        @Override
    145192        public boolean matches(Environment e) {
     
    147194                return false;
    148195
    149             if (!parentSelector) {
     196            if (ChildOrParentSelectorType.CONTAINS.equals(type)) {
     197                final OsmPrimitive rightPrimitive = e.osm;
     198                final ContainsFinder containsFinder = new ContainsFinder(e);
     199                for (final OsmPrimitive p : rightPrimitive.getDataSet().allPrimitives()) {
     200                    if (rightPrimitive.equals(p)) {
     201                        continue;
     202                    }
     203                    p.accept(containsFinder);
     204                    if (e.parent != null) {
     205                        e.osm = rightPrimitive;
     206                        return true;
     207                    }
     208                }
     209            } else if (ChildOrParentSelectorType.CHILD.equals(type)) {
    150210                MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
    151211                e.osm.visitReferrers(collector);
     
    195255        @Override
    196256        public String toString() {
    197             return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
     257            return left + " " + (ChildOrParentSelectorType.PARENT.equals(type) ? "<" : ">") + link + " " + right;
    198258        }
    199259    }
  • trunk/src/org/openstreetmap/josm/tools/Geometry.java

    r6566 r6607  
    99import java.math.MathContext;
    1010import java.util.ArrayList;
     11import java.util.Collections;
    1112import java.util.Comparator;
     13import java.util.EnumSet;
     14import java.util.HashSet;
    1215import java.util.LinkedHashSet;
    1316import java.util.List;
     
    2326import org.openstreetmap.josm.data.osm.Node;
    2427import org.openstreetmap.josm.data.osm.NodePositionComparator;
     28import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
     29import org.openstreetmap.josm.data.osm.Relation;
     30import org.openstreetmap.josm.data.osm.RelationMember;
    2531import org.openstreetmap.josm.data.osm.Way;
    2632
     
    736742        }
    737743    }
     744
     745    public static class MultiPolygonMembers {
     746        public final Set<Way> outers = new HashSet<Way>();
     747        public final Set<Way> inners = new HashSet<Way>();
     748
     749        public MultiPolygonMembers(Relation multiPolygon) {
     750            for (RelationMember m : multiPolygon.getMembers()) {
     751                if (m.getType().equals(OsmPrimitiveType.WAY)) {
     752                    if (m.getRole().equals("outer")) {
     753                        outers.add(m.getWay());
     754                    } else if (m.getRole().equals("inner")) {
     755                        inners.add(m.getWay());
     756                    }
     757                }
     758            }
     759        }
     760    }
     761
     762    /**
     763     * Tests if the {@code node} is inside the multipolygon {@code multiPolygon}. The nullable argument
     764     * {@code isOuterWayAMatch} allows to decide if the immediate {@code outer} way of the multipolygon is a match.
     765     */
     766    public static boolean isNodeInsideMultiPolygon(Node node, Relation multiPolygon, Predicate<Way> isOuterWayAMatch) {
     767        return isPolygonInsideMultiPolygon(Collections.singletonList(node), multiPolygon, isOuterWayAMatch);
     768    }
     769
     770    /**
     771     * Tests if the polygon formed by {@code nodes} is inside the multipolygon {@code multiPolygon}. The nullable argument
     772     * {@code isOuterWayAMatch} allows to decide if the immediate {@code outer} way of the multipolygon is a match.
     773     * <p/>
     774     * If {@code nodes} contains exactly one element, then it is checked whether that one node is inside the multipolygon.
     775     */
     776    public static boolean isPolygonInsideMultiPolygon(List<Node> nodes, Relation multiPolygon, Predicate<Way> isOuterWayAMatch) {
     777        // Extract outer/inner members from multipolygon
     778        MultiPolygonMembers mpm = new MultiPolygonMembers(multiPolygon);
     779        // Test if object is inside an outer member
     780        for (Way out : mpm.outers) {
     781            if (nodes.size() == 1
     782                    ? nodeInsidePolygon(nodes.get(0), out.getNodes())
     783                    : EnumSet.of(PolygonIntersection.FIRST_INSIDE_SECOND, PolygonIntersection.CROSSING).contains(polygonIntersection(nodes, out.getNodes()))) {
     784                boolean insideInner = false;
     785                // If inside an outer, check it is not inside an inner
     786                for (Way in : mpm.inners) {
     787                    if (polygonIntersection(in.getNodes(), out.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND
     788                            && (nodes.size() == 1
     789                            ? nodeInsidePolygon(nodes.get(0), in.getNodes())
     790                            : polygonIntersection(nodes, in.getNodes()) == PolygonIntersection.FIRST_INSIDE_SECOND)) {
     791                        insideInner = true;
     792                        break;
     793                    }
     794                }
     795                // Inside outer but not inside inner -> the polygon appears to be inside a the multipolygon
     796                if (!insideInner) {
     797                    // Final check using predicate
     798                    if (isOuterWayAMatch == null || isOuterWayAMatch.evaluate(out)) {
     799                        return true;
     800                    }
     801                }
     802            }
     803        }
     804        return false;
     805    }
    738806}
Note: See TracChangeset for help on using the changeset viewer.