001    // License: GPL. For details, see LICENSE file.
002    package org.openstreetmap.josm.gui.mappaint.mapcss;
003    
004    import java.util.List;
005    
006    import org.openstreetmap.josm.data.osm.Node;
007    import org.openstreetmap.josm.data.osm.OsmPrimitive;
008    import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
009    import org.openstreetmap.josm.data.osm.Relation;
010    import org.openstreetmap.josm.data.osm.RelationMember;
011    import org.openstreetmap.josm.data.osm.Way;
012    import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
013    import org.openstreetmap.josm.gui.mappaint.Environment;
014    import org.openstreetmap.josm.gui.mappaint.Range;
015    import org.openstreetmap.josm.tools.Pair;
016    import org.openstreetmap.josm.tools.Utils;
017    
018    public interface Selector {
019    
020        /**
021         * Apply the selector to the primitive and check if it matches.
022         *
023         * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
024         * env.source is not needed. This method will set the matchingReferrers field of env as
025         * a side effect! Make sure to clear it before invoking this method.
026         * @return true, if the selector applies
027         */
028        public boolean matches(Environment env);
029    
030        public String getSubpart();
031    
032        public Range getRange();
033    
034        /**
035         * <p>Represents a child selector or a parent selector.</p>
036         * 
037         * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
038         * an "inverse" notation:</p>
039         * <pre>
040         *    selector_a > selector_b { ... }       // the standard notation (child selector)
041         *    relation[type=route] > way { ... }    // example (all ways of a route)
042         * 
043         *    selector_a < selector_b { ... }       // the inverse notation (parent selector)
044         *    node[traffic_calming] < way { ... }   // example (way that has a traffic calming node)
045         * </pre>
046         *
047         */
048        public static class ChildOrParentSelector implements Selector {
049            private final Selector left;
050            private final LinkSelector link;
051            private final Selector right;
052            /** true, if this represents a parent selector (otherwise it is a child selector)
053             */
054            private final boolean parentSelector;
055    
056            /**
057             * 
058             * @param a the first selector
059             * @param b the second selector
060             * @param parentSelector if true, this is a parent selector; otherwise a child selector
061             */
062            public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
063                this.left = a;
064                this.link = link;
065                this.right = b;
066                this.parentSelector = parentSelector;
067            }
068    
069            /**
070             * <p>Finds the first referrer matching {@link #left}</p>
071             * 
072             * <p>The visitor works on an environment and it saves the matching
073             * referrer in {@code e.parent} and its relative position in the
074             * list referrers "child list" in {@code e.index}.</p>
075             * 
076             * <p>If after execution {@code e.parent} is null, no matching
077             * referrer was found.</p>
078             *
079             */
080            private  class MatchingReferrerFinder extends AbstractVisitor{
081                private Environment e;
082    
083                /**
084                 * Constructor
085                 * @param e the environment against which we match
086                 */
087                public MatchingReferrerFinder(Environment e){
088                    this.e = e;
089                }
090    
091                @Override
092                public void visit(Node n) {
093                    // node should never be a referrer
094                    throw new AssertionError();
095                }
096    
097                @Override
098                public void visit(Way w) {
099                    /*
100                     * If e.parent is already set to the first matching referrer. We skip any following
101                     * referrer injected into the visitor.
102                     */
103                    if (e.parent != null) return;
104    
105                    if (!left.matches(e.withPrimitive(w)))
106                        return;
107                    for (int i=0; i<w.getNodesCount(); i++) {
108                        Node n = w.getNode(i);
109                        if (n.equals(e.osm)) {
110                            if (link.matches(e.withParent(w).withIndex(i).withLinkContext())) {
111                                e.parent = w;
112                                e.index = i;
113                                return;
114                            }
115                        }
116                    }
117                }
118    
119                @Override
120                public void visit(Relation r) {
121                    /*
122                     * If e.parent is already set to the first matching referrer. We skip any following
123                     * referrer injected into the visitor.
124                     */
125                    if (e.parent != null) return;
126    
127                    if (!left.matches(e.withPrimitive(r)))
128                        return;
129                    for (int i=0; i < r.getMembersCount(); i++) {
130                        RelationMember m = r.getMember(i);
131                        if (m.getMember().equals(e.osm)) {
132                            if (link.matches(e.withParent(r).withIndex(i).withLinkContext())) {
133                                e.parent = r;
134                                e.index = i;
135                                return;
136                            }
137                        }
138                    }
139                }
140            }
141    
142            @Override
143            public boolean matches(Environment e) {
144                if (!right.matches(e))
145                    return false;
146    
147                if (!parentSelector) {
148                    MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
149                    e.osm.visitReferrers(collector);
150                    if (e.parent != null)
151                        return true;
152                } else {
153                    if (e.osm instanceof Way) {
154                        List<Node> wayNodes = ((Way) e.osm).getNodes();
155                        for (int i=0; i<wayNodes.size(); i++) {
156                            Node n = wayNodes.get(i);
157                            if (left.matches(e.withPrimitive(n))) {
158                                if (link.matches(e.withChild(n).withIndex(i).withLinkContext())) {
159                                    e.child = n;
160                                    e.index = i;
161                                    return true;
162                                }
163                            }
164                        }
165                    }
166                    else if (e.osm instanceof Relation) {
167                        List<RelationMember> members = ((Relation) e.osm).getMembers();
168                        for (int i=0; i<members.size(); i++) {
169                            OsmPrimitive member = members.get(i).getMember();
170                            if (left.matches(e.withPrimitive(member))) {
171                                if (link.matches(e.withChild(member).withIndex(i).withLinkContext())) {
172                                    e.child = member;
173                                    e.index = i;
174                                    return true;
175                                }
176                            }
177                        }
178                    }
179                }
180                return false;
181            }
182    
183            @Override
184            public String getSubpart() {
185                return right.getSubpart();
186            }
187    
188            @Override
189            public Range getRange() {
190                return right.getRange();
191            }
192    
193            @Override
194            public String toString() {
195                return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
196            }
197        }
198    
199        public static class LinkSelector implements Selector {
200            protected List<Condition> conditions;
201    
202            public LinkSelector(List<Condition> conditions) {
203                this.conditions = conditions;
204            }
205    
206            @Override
207            public boolean matches(Environment env) {
208                Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
209                for (Condition c: conditions) {
210                    if (!c.applies(env)) return false;
211                }
212                return true;
213            }
214    
215            @Override
216            public String getSubpart() {
217                throw new UnsupportedOperationException("Not supported yet.");
218            }
219    
220            @Override
221            public Range getRange() {
222                throw new UnsupportedOperationException("Not supported yet.");
223            }
224    
225            @Override
226            public String toString() {
227                return "LinkSelector{" + "conditions=" + conditions + '}';
228            }
229        }
230    
231        public static class GeneralSelector implements Selector {
232            private String base;
233            public Range range;
234            private List<Condition> conds;
235            private String subpart;
236    
237            public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
238                this.base = base;
239                if (zoom != null) {
240                    int a = zoom.a == null ? 0 : zoom.a;
241                    int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
242                    if (a <= b) {
243                        range = fromLevel(a, b);
244                    }
245                }
246                if (range == null) {
247                    range = new Range();
248                }
249                if (conds == null || conds.isEmpty()) {
250                    this.conds = null;
251                } else {
252                    this.conds = conds;
253                }
254                this.subpart = subpart;
255            }
256    
257            @Override
258            public String getSubpart() {
259                return subpart;
260            }
261            @Override
262            public Range getRange() {
263                return range;
264            }
265    
266            public boolean matchesBase(Environment e){
267                if (e.osm instanceof Node) {
268                    return base.equals("node") || base.equals("*");
269                } else if (e.osm instanceof Way) {
270                    return base.equals("way") || base.equals("area") || base.equals("*");
271                } else if (e.osm instanceof Relation) {
272                    if (base.equals("area")) {
273                        return ((Relation) e.osm).isMultipolygon();
274                    } else if (base.equals("relation")) {
275                        return true;
276                    } else if (base.equals("canvas")) {
277                        return e.osm.get("#canvas") != null;
278                    }
279                }
280                return false;
281            }
282    
283            public boolean matchesConditions(Environment e){
284                if (conds == null) return true;
285                for (Condition c : conds) {
286                    if (!c.applies(e))
287                        return false;
288                }
289                return true;
290            }
291    
292            @Override
293            public boolean matches(Environment e) {
294                if (!matchesBase(e)) return false;
295                return matchesConditions(e);
296            }
297    
298            public String getBase() {
299                return base;
300            }
301    
302            public static Range fromLevel(int a, int b) {
303                if (a > b)
304                    throw new AssertionError();
305                double lower = 0;
306                double upper = Double.POSITIVE_INFINITY;
307                if (b != Integer.MAX_VALUE) {
308                    lower = level2scale(b + 1);
309                }
310                if (a != 0) {
311                    upper = level2scale(a);
312                }
313                return new Range(lower, upper);
314            }
315    
316            final static double R = 6378135;
317    
318            public static double level2scale(int lvl) {
319                if (lvl < 0)
320                    throw new IllegalArgumentException();
321                // preliminary formula - map such that mapnik imagery tiles of the same
322                // or similar level are displayed at the given scale
323                return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
324            }
325    
326            @Override
327            public String toString() {
328                return base + (range == null ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
329            }
330        }
331    }