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

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

see #19251 - Java 8: use Stream

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