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

Last change on this file since 15986 was 15986, checked in by simon04, 4 years ago

see #18802 - Add Selector.getBase

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