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

Last change on this file since 6830 was 6830, checked in by Don-vip, 10 years ago

javadoc fixes for jdk8 compatibility

  • Property svn:eol-style set to native
File size: 17.2 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.util.Collection;
5import java.util.Collections;
6import java.util.List;
7import java.util.regex.PatternSyntaxException;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmPrimitive;
12import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
13import org.openstreetmap.josm.data.osm.Relation;
14import org.openstreetmap.josm.data.osm.RelationMember;
15import org.openstreetmap.josm.data.osm.Way;
16import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
17import org.openstreetmap.josm.gui.mappaint.Environment;
18import org.openstreetmap.josm.gui.mappaint.Range;
19import org.openstreetmap.josm.tools.CheckParameterUtil;
20import org.openstreetmap.josm.tools.Geometry;
21import org.openstreetmap.josm.tools.Pair;
22import org.openstreetmap.josm.tools.Utils;
23
24public interface Selector {
25
26 /**
27 * Apply the selector to the primitive and check if it matches.
28 *
29 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
30 * env.source is not needed. This method will set the matchingReferrers field of env as
31 * a side effect! Make sure to clear it before invoking this method.
32 * @return true, if the selector applies
33 */
34 public boolean matches(Environment env);
35
36 public String getSubpart();
37
38 public Range getRange();
39
40 public static enum ChildOrParentSelectorType {
41 CHILD, PARENT, ELEMENT_OF, CROSSING
42 }
43
44 /**
45 * <p>Represents a child selector or a parent selector.</p>
46 *
47 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
48 * an "inverse" notation:</p>
49 * <pre>
50 * selector_a &gt; selector_b { ... } // the standard notation (child selector)
51 * relation[type=route] &gt; way { ... } // example (all ways of a route)
52 *
53 * selector_a &lt; selector_b { ... } // the inverse notation (parent selector)
54 * node[traffic_calming] &lt; way { ... } // example (way that has a traffic calming node)
55 * </pre>
56 *
57 */
58 public static class ChildOrParentSelector implements Selector {
59 public final Selector left;
60 public final LinkSelector link;
61 public final Selector right;
62 public final ChildOrParentSelectorType type;
63
64 /**
65 *
66 * @param a the first selector
67 * @param b the second selector
68 * @param type the selector type
69 */
70 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrParentSelectorType type) {
71 this.left = a;
72 this.link = link;
73 this.right = b;
74 this.type = type;
75 }
76
77 /**
78 * <p>Finds the first referrer matching {@link #left}</p>
79 *
80 * <p>The visitor works on an environment and it saves the matching
81 * referrer in {@code e.parent} and its relative position in the
82 * list referrers "child list" in {@code e.index}.</p>
83 *
84 * <p>If after execution {@code e.parent} is null, no matching
85 * referrer was found.</p>
86 *
87 */
88 private class MatchingReferrerFinder extends AbstractVisitor{
89 private Environment e;
90
91 /**
92 * Constructor
93 * @param e the environment against which we match
94 */
95 public MatchingReferrerFinder(Environment e){
96 this.e = e;
97 }
98
99 @Override
100 public void visit(Node n) {
101 // node should never be a referrer
102 throw new AssertionError();
103 }
104
105 @Override
106 public void visit(Way w) {
107 /*
108 * If e.parent is already set to the first matching referrer. We skip any following
109 * referrer injected into the visitor.
110 */
111 if (e.parent != null) return;
112
113 if (!left.matches(e.withPrimitive(w)))
114 return;
115 for (int i=0; i<w.getNodesCount(); i++) {
116 Node n = w.getNode(i);
117 if (n.equals(e.osm)) {
118 if (link.matches(e.withParentAndIndexAndLinkContext(w, i))) {
119 e.parent = w;
120 e.index = i;
121 return;
122 }
123 }
124 }
125 }
126
127 @Override
128 public void visit(Relation r) {
129 /*
130 * If e.parent is already set to the first matching referrer. We skip any following
131 * referrer injected into the visitor.
132 */
133 if (e.parent != null) return;
134
135 if (!left.matches(e.withPrimitive(r)))
136 return;
137 for (int i=0; i < r.getMembersCount(); i++) {
138 RelationMember m = r.getMember(i);
139 if (m.getMember().equals(e.osm)) {
140 if (link.matches(e.withParentAndIndexAndLinkContext(r, i))) {
141 e.parent = r;
142 e.index = i;
143 return;
144 }
145 }
146 }
147 }
148 }
149
150 private abstract class AbstractFinder extends AbstractVisitor {
151 protected final Environment e;
152
153 protected AbstractFinder(Environment e) {
154 this.e = e;
155 }
156
157 @Override
158 public void visit(Node n) {
159 }
160
161 @Override
162 public void visit(Way w) {
163 }
164
165 @Override
166 public void visit(Relation r) {
167 }
168
169 public void visit(Collection<? extends OsmPrimitive> primitives) {
170 for (OsmPrimitive p : primitives) {
171 if (e.child != null) {
172 // abort if first match has been found
173 break;
174 } else if (!e.osm.equals(p) && p.isUsable()) {
175 p.accept(this);
176 }
177 }
178 }
179 }
180
181 private final class CrossingFinder extends AbstractFinder {
182 private CrossingFinder(Environment e) {
183 super(e);
184 CheckParameterUtil.ensureThat(e.osm instanceof Way, "Only ways are supported");
185 }
186
187 @Override
188 public void visit(Way w) {
189 if (e.child == null && left.matches(e.withPrimitive(w))) {
190 if (e.osm instanceof Way && Geometry.PolygonIntersection.CROSSING.equals(Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))) {
191 e.child = w;
192 }
193 }
194 }
195 }
196
197 private final class ContainsFinder extends AbstractFinder {
198 private ContainsFinder(Environment e) {
199 super(e);
200 CheckParameterUtil.ensureThat(!(e.osm instanceof Node), "Nodes not supported");
201 }
202
203 @Override
204 public void visit(Node n) {
205 if (e.child == null && left.matches(e.withPrimitive(n))) {
206 if (e.osm instanceof Way && Geometry.nodeInsidePolygon(n, ((Way) e.osm).getNodes())
207 || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isNodeInsideMultiPolygon(n, (Relation) e.osm, null)) {
208 e.child = n;
209 }
210 }
211 }
212
213 @Override
214 public void visit(Way w) {
215 if (e.child == null && left.matches(e.withPrimitive(w))) {
216 if (e.osm instanceof Way && Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals(Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))
217 || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) e.osm, null)) {
218 e.child = w;
219 }
220 }
221 }
222 }
223
224 @Override
225 public boolean matches(Environment e) {
226
227 if (!right.matches(e))
228 return false;
229
230 if (ChildOrParentSelectorType.ELEMENT_OF.equals(type)) {
231
232 if (e.osm instanceof Node || e.osm.getDataSet() == null) {
233 // nodes cannot contain elements
234 return false;
235 }
236 e.parent = e.osm;
237
238 final ContainsFinder containsFinder = new ContainsFinder(e);
239 if (right instanceof GeneralSelector) {
240 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.NODE)) {
241 containsFinder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
242 }
243 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
244 containsFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
245 }
246 } else {
247 // use slow test
248 containsFinder.visit(e.osm.getDataSet().allPrimitives());
249 }
250
251 return e.child != null;
252
253 } else if (ChildOrParentSelectorType.CROSSING.equals(type) && e.osm instanceof Way) {
254 e.parent = e.osm;
255 final CrossingFinder crossingFinder = new CrossingFinder(e);
256 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
257 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
258 }
259 return e.child != null;
260 } else if (ChildOrParentSelectorType.CHILD.equals(type)) {
261 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
262 e.osm.visitReferrers(collector);
263 if (e.parent != null)
264 return true;
265 } else if (ChildOrParentSelectorType.PARENT.equals(type)) {
266 if (e.osm instanceof Way) {
267 List<Node> wayNodes = ((Way) e.osm).getNodes();
268 for (int i=0; i<wayNodes.size(); i++) {
269 Node n = wayNodes.get(i);
270 if (left.matches(e.withPrimitive(n))) {
271 if (link.matches(e.withChildAndIndexAndLinkContext(n, i))) {
272 e.child = n;
273 e.index = i;
274 return true;
275 }
276 }
277 }
278 }
279 else if (e.osm instanceof Relation) {
280 List<RelationMember> members = ((Relation) e.osm).getMembers();
281 for (int i=0; i<members.size(); i++) {
282 OsmPrimitive member = members.get(i).getMember();
283 if (left.matches(e.withPrimitive(member))) {
284 if (link.matches(e.withChildAndIndexAndLinkContext(member, i))) {
285 e.child = member;
286 e.index = i;
287 return true;
288 }
289 }
290 }
291 }
292 }
293 return false;
294 }
295
296 @Override
297 public String getSubpart() {
298 return right.getSubpart();
299 }
300
301 @Override
302 public Range getRange() {
303 return right.getRange();
304 }
305
306 @Override
307 public String toString() {
308 return left + " " + (ChildOrParentSelectorType.PARENT.equals(type) ? "<" : ">") + link + " " + right;
309 }
310 }
311
312 /**
313 * Super class of {@link GeneralSelector} and {@link LinkSelector}
314 * @since 5841
315 */
316 public static abstract class AbstractSelector implements Selector {
317
318 protected final List<Condition> conds;
319
320 protected AbstractSelector(List<Condition> conditions) {
321 if (conditions == null || conditions.isEmpty()) {
322 this.conds = null;
323 } else {
324 this.conds = conditions;
325 }
326 }
327
328 /**
329 * Determines if all conditions match the given environment.
330 * @param env The environment to check
331 * @return {@code true} if all conditions apply, false otherwise.
332 */
333 public final boolean matchesConditions(Environment env) {
334 if (conds == null) return true;
335 for (Condition c : conds) {
336 try {
337 if (!c.applies(env)) return false;
338 } catch (PatternSyntaxException e) {
339 Main.error("PatternSyntaxException while applying condition" + c +": "+e.getMessage());
340 return false;
341 }
342 }
343 return true;
344 }
345
346 public List<Condition> getConditions() {
347 if (conds == null) {
348 return Collections.emptyList();
349 }
350 return Collections.unmodifiableList(conds);
351 }
352 }
353
354 public static class LinkSelector extends AbstractSelector {
355
356 public LinkSelector(List<Condition> conditions) {
357 super(conditions);
358 }
359
360 @Override
361 public boolean matches(Environment env) {
362 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
363 return matchesConditions(env);
364 }
365
366 @Override
367 public String getSubpart() {
368 throw new UnsupportedOperationException("Not supported yet.");
369 }
370
371 @Override
372 public Range getRange() {
373 throw new UnsupportedOperationException("Not supported yet.");
374 }
375
376 @Override
377 public String toString() {
378 return "LinkSelector{" + "conditions=" + conds + '}';
379 }
380 }
381
382 public static class GeneralSelector extends AbstractSelector {
383 public final String base;
384 public final Range range;
385 public final String subpart;
386
387 public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
388 super(conds);
389 this.base = base;
390 if (zoom != null) {
391 int a = zoom.a == null ? 0 : zoom.a;
392 int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
393 if (a <= b) {
394 range = fromLevel(a, b);
395 } else {
396 range = Range.ZERO_TO_INFINITY;
397 }
398 } else {
399 range = Range.ZERO_TO_INFINITY;
400 }
401 this.subpart = subpart;
402 }
403
404 @Override
405 public String getSubpart() {
406 return subpart;
407 }
408 @Override
409 public Range getRange() {
410 return range;
411 }
412
413 public boolean matchesBase(OsmPrimitiveType type) {
414 if (base.equals("*")) {
415 return true;
416 } else if (OsmPrimitiveType.NODE.equals(type)) {
417 return base.equals("node");
418 } else if (OsmPrimitiveType.WAY.equals(type)) {
419 return base.equals("way") || base.equals("area");
420 } else if (OsmPrimitiveType.RELATION.equals(type)) {
421 return base.equals("area") || base.equals("relation") || base.equals("canvas");
422 }
423 return false;
424 }
425
426 public boolean matchesBase(OsmPrimitive p) {
427 if (!matchesBase(p.getType())) {
428 return false;
429 } else {
430 if (p instanceof Relation) {
431 if (base.equals("area")) {
432 return ((Relation) p).isMultipolygon();
433 } else if (base.equals("canvas")) {
434 return p.get("#canvas") != null;
435 }
436 }
437 return true;
438 }
439 }
440
441 public boolean matchesBase(Environment e) {
442 return matchesBase(e.osm);
443 }
444
445 @Override
446 public boolean matches(Environment e) {
447 return matchesBase(e) && matchesConditions(e);
448 }
449
450 public String getBase() {
451 return base;
452 }
453
454 public static Range fromLevel(int a, int b) {
455 if (a > b)
456 throw new AssertionError();
457 double lower = 0;
458 double upper = Double.POSITIVE_INFINITY;
459 if (b != Integer.MAX_VALUE) {
460 lower = level2scale(b + 1);
461 }
462 if (a != 0) {
463 upper = level2scale(a);
464 }
465 return new Range(lower, upper);
466 }
467
468 final static double R = 6378135;
469
470 public static double level2scale(int lvl) {
471 if (lvl < 0)
472 throw new IllegalArgumentException();
473 // preliminary formula - map such that mapnik imagery tiles of the same
474 // or similar level are displayed at the given scale
475 return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
476 }
477
478 @Override
479 public String toString() {
480 return base + (Range.ZERO_TO_INFINITY.equals(range) ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
481 }
482 }
483}
Note: See TracBrowser for help on using the repository browser.