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

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

see #18802 - Add Selector.getConditions

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