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

Last change on this file since 15716 was 15289, checked in by Don-vip, 5 years ago

see #10435 - Enable grouping of similar settings for enabling/disabling several settings at once

  • Property svn:eol-style set to native
File size: 30.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import static org.openstreetmap.josm.data.projection.Ellipsoid.WGS84;
5
6import java.text.MessageFormat;
7import java.util.ArrayList;
8import java.util.Collection;
9import java.util.Collections;
10import java.util.LinkedHashSet;
11import java.util.List;
12import java.util.Objects;
13import java.util.function.IntFunction;
14import java.util.function.IntSupplier;
15import java.util.regex.PatternSyntaxException;
16
17import org.openstreetmap.josm.data.osm.INode;
18import org.openstreetmap.josm.data.osm.IPrimitive;
19import org.openstreetmap.josm.data.osm.IRelation;
20import org.openstreetmap.josm.data.osm.IRelationMember;
21import org.openstreetmap.josm.data.osm.IWay;
22import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
23import org.openstreetmap.josm.data.osm.OsmUtils;
24import org.openstreetmap.josm.data.osm.Relation;
25import org.openstreetmap.josm.data.osm.visitor.PrimitiveVisitor;
26import org.openstreetmap.josm.data.osm.visitor.paint.relations.MultipolygonCache;
27import org.openstreetmap.josm.gui.mappaint.Environment;
28import org.openstreetmap.josm.gui.mappaint.Range;
29import org.openstreetmap.josm.gui.mappaint.mapcss.ConditionFactory.OpenEndPseudoClassCondition;
30import org.openstreetmap.josm.tools.CheckParameterUtil;
31import org.openstreetmap.josm.tools.Geometry;
32import org.openstreetmap.josm.tools.Logging;
33import org.openstreetmap.josm.tools.Pair;
34import org.openstreetmap.josm.tools.Utils;
35
36/**
37 * MapCSS selector.
38 *
39 * A rule has two parts, a selector and a declaration block
40 * e.g.
41 * <pre>
42 * way[highway=residential]
43 * { width: 10; color: blue; }
44 * </pre>
45 *
46 * The selector decides, if the declaration block gets applied or not.
47 *
48 * All implementing classes of Selector are immutable.
49 */
50public interface Selector {
51
52 /** selector base that matches anything. */
53 String BASE_ANY = "*";
54
55 /** selector base that matches on OSM object node. */
56 String BASE_NODE = "node";
57
58 /** selector base that matches on OSM object way. */
59 String BASE_WAY = "way";
60
61 /** selector base that matches on OSM object relation. */
62 String BASE_RELATION = "relation";
63
64 /** selector base that matches with any area regardless of whether the area border is only modelled with a single way or with
65 * a set of ways glued together with a relation.*/
66 String BASE_AREA = "area";
67
68 /** selector base for special rules containing meta information. */
69 String BASE_META = "meta";
70
71 /** selector base for style information not specific to nodes, ways or relations. */
72 String BASE_CANVAS = "canvas";
73
74 /** selector base for artificial bases created to use preferences. */
75 String BASE_SETTING = "setting";
76
77 /** selector base for grouping settings. */
78 String BASE_SETTINGS = "settings";
79
80 /**
81 * Apply the selector to the primitive and check if it matches.
82 *
83 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
84 * env.source is not needed. This method will set the matchingReferrers field of env as
85 * a side effect! Make sure to clear it before invoking this method.
86 * @return true, if the selector applies
87 */
88 boolean matches(Environment env);
89
90 /**
91 * Returns the subpart, if supported. A subpart identifies different rendering layers (<code>::subpart</code> syntax).
92 * @return the subpart, if supported
93 * @throws UnsupportedOperationException if not supported
94 */
95 Subpart getSubpart();
96
97 /**
98 * Returns the scale range, an interval of the form "lower &lt; x &lt;= upper" where 0 &lt;= lower &lt; upper.
99 * @return the scale range, if supported
100 * @throws UnsupportedOperationException if not supported
101 */
102 Range getRange();
103
104 /**
105 * Create an "optimized" copy of this selector that omits the base check.
106 *
107 * For the style source, the list of rules is preprocessed, such that
108 * there is a separate list of rules for nodes, ways, ...
109 *
110 * This means that the base check does not have to be performed
111 * for each rule, but only once for each primitive.
112 *
113 * @return a selector that is identical to this object, except the base of the
114 * "rightmost" selector is not checked
115 */
116 Selector optimizedBaseCheck();
117
118 /**
119 * The type of child of parent selector.
120 * @see ChildOrParentSelector
121 */
122 enum ChildOrParentSelectorType {
123 CHILD, PARENT, SUBSET_OR_EQUAL, NOT_SUBSET_OR_EQUAL, SUPERSET_OR_EQUAL, NOT_SUPERSET_OR_EQUAL, CROSSING, SIBLING,
124 }
125
126 /**
127 * <p>Represents a child selector or a parent selector.</p>
128 *
129 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
130 * an "inverse" notation:</p>
131 * <pre>
132 * selector_a &gt; selector_b { ... } // the standard notation (child selector)
133 * relation[type=route] &gt; way { ... } // example (all ways of a route)
134 *
135 * selector_a &lt; selector_b { ... } // the inverse notation (parent selector)
136 * node[traffic_calming] &lt; way { ... } // example (way that has a traffic calming node)
137 * </pre>
138 * <p>Child: see <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Childselector">wiki</a>
139 * <br>Parent: see <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Parentselector">wiki</a></p>
140 */
141 class ChildOrParentSelector implements Selector {
142 public final Selector left;
143 public final LinkSelector link;
144 public final Selector right;
145 public final ChildOrParentSelectorType type;
146
147 /**
148 * Constructs a new {@code ChildOrParentSelector}.
149 * @param a the first selector
150 * @param link link
151 * @param b the second selector
152 * @param type the selector type
153 */
154 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrParentSelectorType type) {
155 CheckParameterUtil.ensureParameterNotNull(a, "a");
156 CheckParameterUtil.ensureParameterNotNull(b, "b");
157 CheckParameterUtil.ensureParameterNotNull(link, "link");
158 CheckParameterUtil.ensureParameterNotNull(type, "type");
159 this.left = a;
160 this.link = link;
161 this.right = b;
162 this.type = type;
163 }
164
165 /**
166 * <p>Finds the first referrer matching {@link #left}</p>
167 *
168 * <p>The visitor works on an environment and it saves the matching
169 * referrer in {@code e.parent} and its relative position in the
170 * list referrers "child list" in {@code e.index}.</p>
171 *
172 * <p>If after execution {@code e.parent} is null, no matching
173 * referrer was found.</p>
174 *
175 */
176 private class MatchingReferrerFinder implements PrimitiveVisitor {
177 private final Environment e;
178
179 /**
180 * Constructor
181 * @param e the environment against which we match
182 */
183 MatchingReferrerFinder(Environment e) {
184 this.e = e;
185 }
186
187 @Override
188 public void visit(INode n) {
189 // node should never be a referrer
190 throw new AssertionError();
191 }
192
193 private <T extends IPrimitive> void doVisit(T parent, IntSupplier counter, IntFunction<IPrimitive> getter) {
194 // If e.parent is already set to the first matching referrer.
195 // We skip any following referrer injected into the visitor.
196 if (e.parent != null) return;
197
198 if (!left.matches(e.withPrimitive(parent)))
199 return;
200 int count = counter.getAsInt();
201 if (link.conds == null) {
202 // index is not needed, we can avoid the sequential search below
203 e.parent = parent;
204 e.count = count;
205 return;
206 }
207 for (int i = 0; i < count; i++) {
208 if (getter.apply(i).equals(e.osm) && link.matches(e.withParentAndIndexAndLinkContext(parent, i, count))) {
209 e.parent = parent;
210 e.index = i;
211 e.count = count;
212 return;
213 }
214 }
215 }
216
217 @Override
218 public void visit(IWay<?> w) {
219 doVisit(w, w::getNodesCount, w::getNode);
220 }
221
222 @Override
223 public void visit(IRelation<?> r) {
224 doVisit(r, r::getMembersCount, i -> r.getMember(i).getMember());
225 }
226 }
227
228 private abstract static class AbstractFinder implements PrimitiveVisitor {
229 protected final Environment e;
230
231 protected AbstractFinder(Environment e) {
232 this.e = e;
233 }
234
235 @Override
236 public void visit(INode n) {
237 }
238
239 @Override
240 public void visit(IWay<?> w) {
241 }
242
243 @Override
244 public void visit(IRelation<?> r) {
245 }
246
247 public void visit(Collection<? extends IPrimitive> primitives) {
248 for (IPrimitive p : primitives) {
249 if (e.child != null) {
250 // abort if first match has been found
251 break;
252 } else if (isPrimitiveUsable(p)) {
253 p.accept(this);
254 }
255 }
256 }
257
258 public boolean isPrimitiveUsable(IPrimitive p) {
259 return !e.osm.equals(p) && p.isUsable();
260 }
261
262 protected void addToChildren(Environment e, IPrimitive p) {
263 if (e.children == null) {
264 e.children = new LinkedHashSet<>();
265 }
266 e.children.add(p);
267 }
268 }
269
270 private class MultipolygonOpenEndFinder extends AbstractFinder {
271
272 @Override
273 public void visit(IWay<?> w) {
274 w.visitReferrers(innerVisitor);
275 }
276
277 MultipolygonOpenEndFinder(Environment e) {
278 super(e);
279 }
280
281 private final PrimitiveVisitor innerVisitor = new AbstractFinder(e) {
282 @Override
283 public void visit(IRelation<?> r) {
284 if (r instanceof Relation && left.matches(e.withPrimitive(r))) {
285 final List<?> openEnds = MultipolygonCache.getInstance().get((Relation) r).getOpenEnds();
286 final int openEndIndex = openEnds.indexOf(e.osm);
287 if (openEndIndex >= 0) {
288 e.parent = r;
289 e.index = openEndIndex;
290 e.count = openEnds.size();
291 }
292 }
293 }
294 };
295 }
296
297 private final class CrossingFinder extends AbstractFinder {
298
299 private final String layer;
300
301 private CrossingFinder(Environment e) {
302 super(e);
303 CheckParameterUtil.ensureThat(e.osm instanceof IWay, "Only ways are supported");
304 layer = OsmUtils.getLayer(e.osm);
305 }
306
307 @Override
308 public void visit(IWay<?> w) {
309 if (Objects.equals(layer, OsmUtils.getLayer(w))
310 && left.matches(new Environment(w).withParent(e.osm))
311 && e.osm instanceof IWay && Geometry.PolygonIntersection.CROSSING.equals(
312 Geometry.polygonIntersection(w.getNodes(), ((IWay<?>) e.osm).getNodes()))) {
313 addToChildren(e, w);
314 }
315 }
316 }
317
318 /**
319 * Finds elements which are inside the right element, collects those in {@code children}
320 */
321 private class ContainsFinder extends AbstractFinder {
322 protected List<IPrimitive> toCheck;
323
324 protected ContainsFinder(Environment e) {
325 super(e);
326 CheckParameterUtil.ensureThat(!(e.osm instanceof INode), "Nodes not supported");
327 }
328
329 @Override
330 public void visit(Collection<? extends IPrimitive> primitives) {
331 for (IPrimitive p : primitives) {
332 if (p != e.osm && isPrimitiveUsable(p) && left.matches(new Environment(p).withParent(e.osm))) {
333 if (toCheck == null) {
334 toCheck = new ArrayList<>();
335 }
336 toCheck.add(p);
337 }
338 }
339 }
340
341 void execGeometryTests() {
342 if (toCheck == null || toCheck.isEmpty())
343 return;
344
345 if (e.osm instanceof IWay) {
346 for (IPrimitive p : Geometry.filterInsidePolygon(toCheck, (IWay<?>) e.osm)) {
347 addToChildren(e, p);
348 }
349 } else if (e.osm instanceof Relation && e.osm.isMultipolygon()) {
350 for (IPrimitive p : Geometry.filterInsideMultipolygon(toCheck, (Relation) e.osm)) {
351 addToChildren(e, p);
352 }
353 }
354 }
355 }
356
357 /**
358 * Finds elements which are inside the left element, or in other words, it finds elements enclosing e.osm.
359 * The found enclosing elements are collected in {@code e.children}.
360 */
361 private class InsideOrEqualFinder extends AbstractFinder {
362
363 protected InsideOrEqualFinder(Environment e) {
364 super(e);
365 }
366
367 @Override
368 public void visit(IWay<?> w) {
369 if (left.matches(new Environment(w).withParent(e.osm))
370 && w.getBBox().bounds(e.osm.getBBox())
371 && !Geometry.filterInsidePolygon(Collections.singletonList(e.osm), w).isEmpty()) {
372 addToChildren(e, w);
373 }
374 }
375
376 @Override
377 public void visit(IRelation<?> r) {
378 if (r instanceof Relation && r.isMultipolygon() && r.getBBox().bounds(e.osm.getBBox())
379 && left.matches(new Environment(r).withParent(e.osm))
380 && !Geometry.filterInsideMultipolygon(Collections.singletonList(e.osm), (Relation) r).isEmpty()) {
381 addToChildren(e, r);
382 }
383 }
384 }
385
386 private void visitBBox(Environment e, AbstractFinder finder) {
387 boolean withNodes = finder instanceof ContainsFinder;
388 if (left instanceof OptimizedGeneralSelector) {
389 if (withNodes && ((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.NODE)) {
390 finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
391 }
392 if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.WAY)) {
393 finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
394 }
395 if (((OptimizedGeneralSelector) left).matchesBase(OsmPrimitiveType.RELATION)) {
396 finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox()));
397 }
398 } else {
399 if (withNodes) {
400 finder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
401 }
402 finder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
403 finder.visit(e.osm.getDataSet().searchRelations(e.osm.getBBox()));
404 }
405 }
406
407 private static boolean isArea(IPrimitive p) {
408 return (p instanceof IWay && ((IWay<?>) p).isClosed() && ((IWay<?>) p).getNodesCount() >= 4)
409 || (p instanceof IRelation && p.isMultipolygon() && !p.isIncomplete());
410 }
411
412 @Override
413 public boolean matches(Environment e) {
414
415 if (!right.matches(e))
416 return false;
417
418 if (ChildOrParentSelectorType.SUBSET_OR_EQUAL == type || ChildOrParentSelectorType.NOT_SUBSET_OR_EQUAL == type) {
419
420 if (e.osm.getDataSet() == null || !isArea(e.osm)) {
421 // only areas can contain elements
422 return ChildOrParentSelectorType.NOT_SUBSET_OR_EQUAL == type;
423 }
424 ContainsFinder containsFinder = new ContainsFinder(e);
425 e.parent = e.osm;
426
427 visitBBox(e, containsFinder);
428 containsFinder.execGeometryTests();
429 return ChildOrParentSelectorType.SUBSET_OR_EQUAL == type ? e.children != null : e.children == null;
430
431 } else if (ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type || ChildOrParentSelectorType.NOT_SUPERSET_OR_EQUAL == type) {
432
433 if (e.osm.getDataSet() == null || (e.osm instanceof INode && ((INode) e.osm).getCoor() == null)
434 || (!(e.osm instanceof INode) && !isArea(e.osm))) {
435 return ChildOrParentSelectorType.NOT_SUPERSET_OR_EQUAL == type;
436 }
437
438 InsideOrEqualFinder insideOrEqualFinder = new InsideOrEqualFinder(e);
439 e.parent = e.osm;
440
441 visitBBox(e, insideOrEqualFinder);
442 return ChildOrParentSelectorType.SUPERSET_OR_EQUAL == type ? e.children != null : e.children == null;
443
444 } else if (ChildOrParentSelectorType.CROSSING == type && e.osm instanceof IWay) {
445 e.parent = e.osm;
446 if (right instanceof OptimizedGeneralSelector
447 && ((OptimizedGeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
448 final CrossingFinder crossingFinder = new CrossingFinder(e);
449 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
450 }
451 return e.children != null;
452 } else if (ChildOrParentSelectorType.SIBLING == type) {
453 if (e.osm instanceof INode) {
454 for (IPrimitive ref : e.osm.getReferrers(true)) {
455 if (ref instanceof IWay) {
456 IWay<?> w = (IWay<?>) ref;
457 final int i = w.getNodes().indexOf(e.osm);
458 if (i - 1 >= 0) {
459 final INode n = w.getNode(i - 1);
460 final Environment e2 = e.withPrimitive(n).withParent(w).withChild(e.osm);
461 if (left.matches(e2) && link.matches(e2.withLinkContext())) {
462 e.child = n;
463 e.index = i;
464 e.count = w.getNodesCount();
465 e.parent = w;
466 return true;
467 }
468 }
469 }
470 }
471 }
472 } else if (ChildOrParentSelectorType.CHILD == type
473 && link.conds != null && !link.conds.isEmpty()
474 && link.conds.get(0) instanceof OpenEndPseudoClassCondition) {
475 if (e.osm instanceof INode) {
476 e.osm.visitReferrers(new MultipolygonOpenEndFinder(e));
477 return e.parent != null;
478 }
479 } else if (ChildOrParentSelectorType.CHILD == type) {
480 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
481 e.osm.visitReferrers(collector);
482 if (e.parent != null)
483 return true;
484 } else if (ChildOrParentSelectorType.PARENT == type) {
485 if (e.osm instanceof IWay) {
486 List<? extends INode> wayNodes = ((IWay<?>) e.osm).getNodes();
487 for (int i = 0; i < wayNodes.size(); i++) {
488 INode n = wayNodes.get(i);
489 if (left.matches(e.withPrimitive(n))
490 && link.matches(e.withChildAndIndexAndLinkContext(n, i, wayNodes.size()))) {
491 e.child = n;
492 e.index = i;
493 e.count = wayNodes.size();
494 return true;
495 }
496 }
497 } else if (e.osm instanceof IRelation) {
498 List<? extends IRelationMember<?>> members = ((IRelation<?>) e.osm).getMembers();
499 for (int i = 0; i < members.size(); i++) {
500 IPrimitive member = members.get(i).getMember();
501 if (left.matches(e.withPrimitive(member))
502 && link.matches(e.withChildAndIndexAndLinkContext(member, i, members.size()))) {
503 e.child = member;
504 e.index = i;
505 e.count = members.size();
506 return true;
507 }
508 }
509 }
510 }
511 return false;
512 }
513
514 @Override
515 public Subpart getSubpart() {
516 return right.getSubpart();
517 }
518
519 @Override
520 public Range getRange() {
521 return right.getRange();
522 }
523
524 @Override
525 public Selector optimizedBaseCheck() {
526 return new ChildOrParentSelector(left, link, right.optimizedBaseCheck(), type);
527 }
528
529 @Override
530 public String toString() {
531 return left.toString() + ' ' + (ChildOrParentSelectorType.PARENT == type ? '<' : '>') + link + ' ' + right;
532 }
533 }
534
535 /**
536 * Super class of {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.GeneralSelector} and
537 * {@link org.openstreetmap.josm.gui.mappaint.mapcss.Selector.LinkSelector}.
538 * @since 5841
539 */
540 abstract class AbstractSelector implements Selector {
541
542 protected final List<Condition> conds;
543
544 protected AbstractSelector(List<Condition> conditions) {
545 if (conditions == null || conditions.isEmpty()) {
546 this.conds = null;
547 } else {
548 this.conds = conditions;
549 }
550 }
551
552 /**
553 * Determines if all conditions match the given environment.
554 * @param env The environment to check
555 * @return {@code true} if all conditions apply, false otherwise.
556 */
557 @Override
558 public boolean matches(Environment env) {
559 CheckParameterUtil.ensureParameterNotNull(env, "env");
560 if (conds == null) return true;
561 for (Condition c : conds) {
562 try {
563 if (!c.applies(env)) return false;
564 } catch (PatternSyntaxException e) {
565 Logging.log(Logging.LEVEL_ERROR, "PatternSyntaxException while applying condition" + c + ':', e);
566 return false;
567 }
568 }
569 return true;
570 }
571
572 /**
573 * Returns the list of conditions.
574 * @return the list of conditions
575 */
576 public List<Condition> getConditions() {
577 if (conds == null) {
578 return Collections.emptyList();
579 }
580 return Collections.unmodifiableList(conds);
581 }
582 }
583
584 /**
585 * In a child selector, conditions on the link between a parent and a child object.
586 * See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Linkselector">wiki</a>
587 */
588 class LinkSelector extends AbstractSelector {
589
590 public LinkSelector(List<Condition> conditions) {
591 super(conditions);
592 }
593
594 @Override
595 public boolean matches(Environment env) {
596 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
597 return super.matches(env);
598 }
599
600 @Override
601 public Subpart getSubpart() {
602 throw new UnsupportedOperationException("Not supported yet.");
603 }
604
605 @Override
606 public Range getRange() {
607 throw new UnsupportedOperationException("Not supported yet.");
608 }
609
610 @Override
611 public Selector optimizedBaseCheck() {
612 throw new UnsupportedOperationException();
613 }
614
615 @Override
616 public String toString() {
617 return "LinkSelector{conditions=" + conds + '}';
618 }
619 }
620
621 /**
622 * General selector. See <a href="https://josm.openstreetmap.de/wiki/Help/Styles/MapCSSImplementation#Selectors">wiki</a>
623 */
624 class GeneralSelector extends OptimizedGeneralSelector {
625
626 public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, Subpart subpart) {
627 super(base, zoom, conds, subpart);
628 }
629
630 public boolean matchesConditions(Environment e) {
631 return super.matches(e);
632 }
633
634 @Override
635 public Selector optimizedBaseCheck() {
636 return new OptimizedGeneralSelector(this);
637 }
638
639 @Override
640 public boolean matches(Environment e) {
641 return matchesBase(e) && super.matches(e);
642 }
643 }
644
645 /**
646 * Superclass of {@link GeneralSelector}. Used to create an "optimized" copy of this selector that omits the base check.
647 * @see Selector#optimizedBaseCheck
648 */
649 class OptimizedGeneralSelector extends AbstractSelector {
650 public final String base;
651 public final Range range;
652 public final Subpart subpart;
653
654 public OptimizedGeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, Subpart subpart) {
655 super(conds);
656 this.base = checkBase(base);
657 if (zoom != null) {
658 int a = zoom.a == null ? 0 : zoom.a;
659 int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
660 if (a <= b) {
661 range = fromLevel(a, b);
662 } else {
663 range = Range.ZERO_TO_INFINITY;
664 }
665 } else {
666 range = Range.ZERO_TO_INFINITY;
667 }
668 this.subpart = subpart != null ? subpart : Subpart.DEFAULT_SUBPART;
669 }
670
671 public OptimizedGeneralSelector(String base, Range range, List<Condition> conds, Subpart subpart) {
672 super(conds);
673 this.base = checkBase(base);
674 this.range = range;
675 this.subpart = subpart != null ? subpart : Subpart.DEFAULT_SUBPART;
676 }
677
678 public OptimizedGeneralSelector(GeneralSelector s) {
679 this(s.base, s.range, s.conds, s.subpart);
680 }
681
682 @Override
683 public Subpart getSubpart() {
684 return subpart;
685 }
686
687 @Override
688 public Range getRange() {
689 return range;
690 }
691
692 /**
693 * Set base and check if this is a known value.
694 * @param base value for base
695 * @return the matching String constant for a known value
696 * @throws IllegalArgumentException if value is not knwon
697 */
698 private static String checkBase(String base) {
699 switch(base) {
700 case "*": return BASE_ANY;
701 case "node": return BASE_NODE;
702 case "way": return BASE_WAY;
703 case "relation": return BASE_RELATION;
704 case "area": return BASE_AREA;
705 case "meta": return BASE_META;
706 case "canvas": return BASE_CANVAS;
707 case "setting": return BASE_SETTING;
708 case "settings": return BASE_SETTINGS;
709 default:
710 throw new IllegalArgumentException(MessageFormat.format("Unknown MapCSS base selector {0}", base));
711 }
712 }
713
714 public String getBase() {
715 return base;
716 }
717
718 public boolean matchesBase(OsmPrimitiveType type) {
719 if (BASE_ANY.equals(base)) {
720 return true;
721 } else if (OsmPrimitiveType.NODE == type) {
722 return BASE_NODE.equals(base);
723 } else if (OsmPrimitiveType.WAY == type) {
724 return BASE_WAY.equals(base) || BASE_AREA.equals(base);
725 } else if (OsmPrimitiveType.RELATION == type) {
726 return BASE_AREA.equals(base) || BASE_RELATION.equals(base) || BASE_CANVAS.equals(base);
727 }
728 return false;
729 }
730
731 public boolean matchesBase(IPrimitive p) {
732 if (!matchesBase(p.getType())) {
733 return false;
734 } else {
735 if (p instanceof IRelation) {
736 if (BASE_AREA.equals(base)) {
737 return ((IRelation<?>) p).isMultipolygon();
738 } else if (BASE_CANVAS.equals(base)) {
739 return p.get("#canvas") != null;
740 }
741 }
742 return true;
743 }
744 }
745
746 public boolean matchesBase(Environment e) {
747 return matchesBase(e.osm);
748 }
749
750 @Override
751 public Selector optimizedBaseCheck() {
752 throw new UnsupportedOperationException();
753 }
754
755 public static Range fromLevel(int a, int b) {
756 if (a > b)
757 throw new AssertionError();
758 double lower = 0;
759 double upper = Double.POSITIVE_INFINITY;
760 if (b != Integer.MAX_VALUE) {
761 lower = level2scale(b + 1);
762 }
763 if (a != 0) {
764 upper = level2scale(a);
765 }
766 return new Range(lower, upper);
767 }
768
769 public static double level2scale(int lvl) {
770 if (lvl < 0)
771 throw new IllegalArgumentException("lvl must be >= 0 but is "+lvl);
772 // preliminary formula - map such that mapnik imagery tiles of the same
773 // or similar level are displayed at the given scale
774 return 2.0 * Math.PI * WGS84.a / Math.pow(2.0, lvl) / 2.56;
775 }
776
777 public static int scale2level(double scale) {
778 if (scale < 0)
779 throw new IllegalArgumentException("scale must be >= 0 but is "+scale);
780 return (int) Math.floor(Math.log(2 * Math.PI * WGS84.a / 2.56 / scale) / Math.log(2));
781 }
782
783 @Override
784 public String toString() {
785 return base + (Range.ZERO_TO_INFINITY.equals(range) ? "" : range) + (conds != null ? Utils.join("", conds) : "")
786 + (subpart != null && subpart != Subpart.DEFAULT_SUBPART ? ("::" + subpart) : "");
787 }
788 }
789}
Note: See TracBrowser for help on using the repository browser.