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

Last change on this file since 15069 was 15069, checked in by GerdP, 5 years ago

fix #17695

  • add support to find multipolygon inside polygon or multipolygon with ContainsFinder
  • add some unit tests
  • performance_1: improve isPolygonInsideMultiPolygon() by using the areas calculated

in MultipolygonBuilder.joinWays() instead of calling getArea() again.

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