source: josm/trunk/src/org/openstreetmap/josm/gui/mappaint/mapcss/Selector.java@ 6538

Last change on this file since 6538 was 6538, checked in by simon04, 10 years ago

see #9414 - MapCSS-based tagchecker: provide {i.key}, {i.value}, {i.tag} variables to messages/fixes which refer to the corresponding match condition

  • Property svn:eol-style set to native
File size: 12.0 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.util.Collections;
5import java.util.List;
6import java.util.regex.PatternSyntaxException;
7
8import org.openstreetmap.josm.Main;
9import org.openstreetmap.josm.data.osm.Node;
10import org.openstreetmap.josm.data.osm.OsmPrimitive;
11import org.openstreetmap.josm.data.osm.Relation;
12import org.openstreetmap.josm.data.osm.RelationMember;
13import org.openstreetmap.josm.data.osm.Way;
14import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
15import org.openstreetmap.josm.gui.mappaint.Environment;
16import org.openstreetmap.josm.gui.mappaint.Range;
17import org.openstreetmap.josm.tools.Pair;
18import org.openstreetmap.josm.tools.Utils;
19
20public interface Selector {
21
22 /**
23 * Apply the selector to the primitive and check if it matches.
24 *
25 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
26 * env.source is not needed. This method will set the matchingReferrers field of env as
27 * a side effect! Make sure to clear it before invoking this method.
28 * @return true, if the selector applies
29 */
30 public boolean matches(Environment env);
31
32 public String getSubpart();
33
34 public Range getRange();
35
36 /**
37 * <p>Represents a child selector or a parent selector.</p>
38 *
39 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
40 * an "inverse" notation:</p>
41 * <pre>
42 * selector_a > selector_b { ... } // the standard notation (child selector)
43 * relation[type=route] > way { ... } // example (all ways of a route)
44 *
45 * selector_a < selector_b { ... } // the inverse notation (parent selector)
46 * node[traffic_calming] < way { ... } // example (way that has a traffic calming node)
47 * </pre>
48 *
49 */
50 public static class ChildOrParentSelector implements Selector {
51 private final Selector left;
52 private final LinkSelector link;
53 private final Selector right;
54 /** true, if this represents a parent selector (otherwise it is a child selector)
55 */
56 private final boolean parentSelector;
57
58 /**
59 *
60 * @param a the first selector
61 * @param b the second selector
62 * @param parentSelector if true, this is a parent selector; otherwise a child selector
63 */
64 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
65 this.left = a;
66 this.link = link;
67 this.right = b;
68 this.parentSelector = parentSelector;
69 }
70
71 /**
72 * <p>Finds the first referrer matching {@link #left}</p>
73 *
74 * <p>The visitor works on an environment and it saves the matching
75 * referrer in {@code e.parent} and its relative position in the
76 * list referrers "child list" in {@code e.index}.</p>
77 *
78 * <p>If after execution {@code e.parent} is null, no matching
79 * referrer was found.</p>
80 *
81 */
82 private class MatchingReferrerFinder extends AbstractVisitor{
83 private Environment e;
84
85 /**
86 * Constructor
87 * @param e the environment against which we match
88 */
89 public MatchingReferrerFinder(Environment e){
90 this.e = e;
91 }
92
93 @Override
94 public void visit(Node n) {
95 // node should never be a referrer
96 throw new AssertionError();
97 }
98
99 @Override
100 public void visit(Way w) {
101 /*
102 * If e.parent is already set to the first matching referrer. We skip any following
103 * referrer injected into the visitor.
104 */
105 if (e.parent != null) return;
106
107 if (!left.matches(e.withPrimitive(w)))
108 return;
109 for (int i=0; i<w.getNodesCount(); i++) {
110 Node n = w.getNode(i);
111 if (n.equals(e.osm)) {
112 if (link.matches(e.withParentAndIndexAndLinkContext(w, i))) {
113 e.parent = w;
114 e.index = i;
115 return;
116 }
117 }
118 }
119 }
120
121 @Override
122 public void visit(Relation r) {
123 /*
124 * If e.parent is already set to the first matching referrer. We skip any following
125 * referrer injected into the visitor.
126 */
127 if (e.parent != null) return;
128
129 if (!left.matches(e.withPrimitive(r)))
130 return;
131 for (int i=0; i < r.getMembersCount(); i++) {
132 RelationMember m = r.getMember(i);
133 if (m.getMember().equals(e.osm)) {
134 if (link.matches(e.withParentAndIndexAndLinkContext(r, i))) {
135 e.parent = r;
136 e.index = i;
137 return;
138 }
139 }
140 }
141 }
142 }
143
144 @Override
145 public boolean matches(Environment e) {
146 if (!right.matches(e))
147 return false;
148
149 if (!parentSelector) {
150 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
151 e.osm.visitReferrers(collector);
152 if (e.parent != null)
153 return true;
154 } else {
155 if (e.osm instanceof Way) {
156 List<Node> wayNodes = ((Way) e.osm).getNodes();
157 for (int i=0; i<wayNodes.size(); i++) {
158 Node n = wayNodes.get(i);
159 if (left.matches(e.withPrimitive(n))) {
160 if (link.matches(e.withChildAndIndexAndLinkContext(n, i))) {
161 e.child = n;
162 e.index = i;
163 return true;
164 }
165 }
166 }
167 }
168 else if (e.osm instanceof Relation) {
169 List<RelationMember> members = ((Relation) e.osm).getMembers();
170 for (int i=0; i<members.size(); i++) {
171 OsmPrimitive member = members.get(i).getMember();
172 if (left.matches(e.withPrimitive(member))) {
173 if (link.matches(e.withChildAndIndexAndLinkContext(member, i))) {
174 e.child = member;
175 e.index = i;
176 return true;
177 }
178 }
179 }
180 }
181 }
182 return false;
183 }
184
185 @Override
186 public String getSubpart() {
187 return right.getSubpart();
188 }
189
190 @Override
191 public Range getRange() {
192 return right.getRange();
193 }
194
195 @Override
196 public String toString() {
197 return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
198 }
199 }
200
201 /**
202 * Super class of {@link GeneralSelector} and {@link LinkSelector}
203 * @since 5841
204 */
205 public static abstract class AbstractSelector implements Selector {
206
207 protected final List<Condition> conds;
208
209 protected AbstractSelector(List<Condition> conditions) {
210 if (conditions == null || conditions.isEmpty()) {
211 this.conds = null;
212 } else {
213 this.conds = conditions;
214 }
215 }
216
217 /**
218 * Determines if all conditions match the given environment.
219 * @param env The environment to check
220 * @return {@code true} if all conditions apply, false otherwise.
221 */
222 public final boolean matchesConditions(Environment env) {
223 if (conds == null) return true;
224 for (Condition c : conds) {
225 try {
226 if (!c.applies(env)) return false;
227 } catch (PatternSyntaxException e) {
228 Main.error("PatternSyntaxException while applying condition" + c +": "+e.getMessage());
229 return false;
230 }
231 }
232 return true;
233 }
234
235 public List<Condition> getConditions() {
236 return Collections.unmodifiableList(conds);
237 }
238 }
239
240 public static class LinkSelector extends AbstractSelector {
241
242 public LinkSelector(List<Condition> conditions) {
243 super(conditions);
244 }
245
246 @Override
247 public boolean matches(Environment env) {
248 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
249 return matchesConditions(env);
250 }
251
252 @Override
253 public String getSubpart() {
254 throw new UnsupportedOperationException("Not supported yet.");
255 }
256
257 @Override
258 public Range getRange() {
259 throw new UnsupportedOperationException("Not supported yet.");
260 }
261
262 @Override
263 public String toString() {
264 return "LinkSelector{" + "conditions=" + conds + '}';
265 }
266 }
267
268 public static class GeneralSelector extends AbstractSelector {
269 private String base;
270 public Range range;
271 private String subpart;
272
273 public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
274 super(conds);
275 this.base = base;
276 if (zoom != null) {
277 int a = zoom.a == null ? 0 : zoom.a;
278 int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
279 if (a <= b) {
280 range = fromLevel(a, b);
281 }
282 }
283 if (range == null) {
284 range = new Range();
285 }
286 this.subpart = subpart;
287 }
288
289 @Override
290 public String getSubpart() {
291 return subpart;
292 }
293 @Override
294 public Range getRange() {
295 return range;
296 }
297
298 public boolean matchesBase(Environment e){
299 if (e.osm instanceof Node) {
300 return base.equals("node") || base.equals("*");
301 } else if (e.osm instanceof Way) {
302 return base.equals("way") || base.equals("area") || base.equals("*");
303 } else if (e.osm instanceof Relation) {
304 if (base.equals("area")) {
305 return ((Relation) e.osm).isMultipolygon();
306 } else if (base.equals("relation")) {
307 return true;
308 } else if (base.equals("canvas")) {
309 return e.osm.get("#canvas") != null;
310 }
311 }
312 return false;
313 }
314
315 @Override
316 public boolean matches(Environment e) {
317 return matchesBase(e) && matchesConditions(e);
318 }
319
320 public String getBase() {
321 return base;
322 }
323
324 public static Range fromLevel(int a, int b) {
325 if (a > b)
326 throw new AssertionError();
327 double lower = 0;
328 double upper = Double.POSITIVE_INFINITY;
329 if (b != Integer.MAX_VALUE) {
330 lower = level2scale(b + 1);
331 }
332 if (a != 0) {
333 upper = level2scale(a);
334 }
335 return new Range(lower, upper);
336 }
337
338 final static double R = 6378135;
339
340 public static double level2scale(int lvl) {
341 if (lvl < 0)
342 throw new IllegalArgumentException();
343 // preliminary formula - map such that mapnik imagery tiles of the same
344 // or similar level are displayed at the given scale
345 return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
346 }
347
348 @Override
349 public String toString() {
350 return base + (range == null ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
351 }
352 }
353}
Note: See TracBrowser for help on using the repository browser.