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

Last change on this file since 6623 was 6623, checked in by Don-vip, 10 years ago

fix Sonar issues

  • Property svn:eol-style set to native
File size: 17.1 KB
Line 
1// License: GPL. For details, see LICENSE file.
2package org.openstreetmap.josm.gui.mappaint.mapcss;
3
4import java.util.Collection;
5import java.util.Collections;
6import java.util.List;
7import java.util.regex.PatternSyntaxException;
8
9import org.openstreetmap.josm.Main;
10import org.openstreetmap.josm.data.osm.Node;
11import org.openstreetmap.josm.data.osm.OsmPrimitive;
12import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
13import org.openstreetmap.josm.data.osm.Relation;
14import org.openstreetmap.josm.data.osm.RelationMember;
15import org.openstreetmap.josm.data.osm.Way;
16import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
17import org.openstreetmap.josm.gui.mappaint.Environment;
18import org.openstreetmap.josm.gui.mappaint.Range;
19import org.openstreetmap.josm.tools.CheckParameterUtil;
20import org.openstreetmap.josm.tools.Geometry;
21import org.openstreetmap.josm.tools.Pair;
22import org.openstreetmap.josm.tools.Utils;
23
24public interface Selector {
25
26 /**
27 * Apply the selector to the primitive and check if it matches.
28 *
29 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
30 * env.source is not needed. This method will set the matchingReferrers field of env as
31 * a side effect! Make sure to clear it before invoking this method.
32 * @return true, if the selector applies
33 */
34 public boolean matches(Environment env);
35
36 public String getSubpart();
37
38 public Range getRange();
39
40 public static enum ChildOrParentSelectorType {
41 CHILD, PARENT, ELEMENT_OF, CROSSING
42 }
43
44 /**
45 * <p>Represents a child selector or a parent selector.</p>
46 *
47 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
48 * an "inverse" notation:</p>
49 * <pre>
50 * selector_a > selector_b { ... } // the standard notation (child selector)
51 * relation[type=route] > way { ... } // example (all ways of a route)
52 *
53 * selector_a < selector_b { ... } // the inverse notation (parent selector)
54 * node[traffic_calming] < way { ... } // example (way that has a traffic calming node)
55 * </pre>
56 *
57 */
58 public static class ChildOrParentSelector implements Selector {
59 private final Selector left;
60 private final LinkSelector link;
61 private final Selector right;
62 /** true, if this represents a parent selector (otherwise it is a child selector)
63 */
64 private final ChildOrParentSelectorType type;
65
66 /**
67 *
68 * @param a the first selector
69 * @param b the second selector
70 * @param type the selector type
71 */
72 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, ChildOrParentSelectorType type) {
73 this.left = a;
74 this.link = link;
75 this.right = b;
76 this.type = type;
77 }
78
79 /**
80 * <p>Finds the first referrer matching {@link #left}</p>
81 *
82 * <p>The visitor works on an environment and it saves the matching
83 * referrer in {@code e.parent} and its relative position in the
84 * list referrers "child list" in {@code e.index}.</p>
85 *
86 * <p>If after execution {@code e.parent} is null, no matching
87 * referrer was found.</p>
88 *
89 */
90 private class MatchingReferrerFinder extends AbstractVisitor{
91 private Environment e;
92
93 /**
94 * Constructor
95 * @param e the environment against which we match
96 */
97 public MatchingReferrerFinder(Environment e){
98 this.e = e;
99 }
100
101 @Override
102 public void visit(Node n) {
103 // node should never be a referrer
104 throw new AssertionError();
105 }
106
107 @Override
108 public void visit(Way w) {
109 /*
110 * If e.parent is already set to the first matching referrer. We skip any following
111 * referrer injected into the visitor.
112 */
113 if (e.parent != null) return;
114
115 if (!left.matches(e.withPrimitive(w)))
116 return;
117 for (int i=0; i<w.getNodesCount(); i++) {
118 Node n = w.getNode(i);
119 if (n.equals(e.osm)) {
120 if (link.matches(e.withParentAndIndexAndLinkContext(w, i))) {
121 e.parent = w;
122 e.index = i;
123 return;
124 }
125 }
126 }
127 }
128
129 @Override
130 public void visit(Relation r) {
131 /*
132 * If e.parent is already set to the first matching referrer. We skip any following
133 * referrer injected into the visitor.
134 */
135 if (e.parent != null) return;
136
137 if (!left.matches(e.withPrimitive(r)))
138 return;
139 for (int i=0; i < r.getMembersCount(); i++) {
140 RelationMember m = r.getMember(i);
141 if (m.getMember().equals(e.osm)) {
142 if (link.matches(e.withParentAndIndexAndLinkContext(r, i))) {
143 e.parent = r;
144 e.index = i;
145 return;
146 }
147 }
148 }
149 }
150 }
151
152 private abstract class AbstractFinder extends AbstractVisitor {
153 protected final Environment e;
154
155 protected AbstractFinder(Environment e) {
156 this.e = e;
157 }
158
159 @Override
160 public void visit(Node n) {
161 }
162
163 @Override
164 public void visit(Way w) {
165 }
166
167 @Override
168 public void visit(Relation r) {
169 }
170
171 public void visit(Collection<? extends OsmPrimitive> primitives) {
172 for (OsmPrimitive p : primitives) {
173 if (e.child != null) {
174 // abort if first match has been found
175 break;
176 } else if (!e.osm.equals(p)) {
177 p.accept(this);
178 }
179 }
180 }
181 }
182
183 private final class CrossingFinder extends AbstractFinder {
184 private CrossingFinder(Environment e) {
185 super(e);
186 CheckParameterUtil.ensureThat(e.osm instanceof Way, "Only ways are supported");
187 }
188
189 @Override
190 public void visit(Way w) {
191 if (e.child == null && left.matches(e.withPrimitive(w))) {
192 if (e.osm instanceof Way && Geometry.PolygonIntersection.CROSSING.equals(Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))) {
193 e.child = w;
194 }
195 }
196 }
197 }
198
199 private final class ContainsFinder extends AbstractFinder {
200 private ContainsFinder(Environment e) {
201 super(e);
202 CheckParameterUtil.ensureThat(!(e.osm instanceof Node), "Nodes not supported");
203 }
204
205 @Override
206 public void visit(Node n) {
207 if (e.child == null && left.matches(e.withPrimitive(n))) {
208 if (e.osm instanceof Way && Geometry.nodeInsidePolygon(n, ((Way) e.osm).getNodes())
209 || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isNodeInsideMultiPolygon(n, (Relation) e.osm, null)) {
210 e.child = n;
211 }
212 }
213 }
214
215 @Override
216 public void visit(Way w) {
217 if (e.child == null && left.matches(e.withPrimitive(w))) {
218 if (e.osm instanceof Way && Geometry.PolygonIntersection.FIRST_INSIDE_SECOND.equals(Geometry.polygonIntersection(w.getNodes(), ((Way) e.osm).getNodes()))
219 || e.osm instanceof Relation && ((Relation) e.osm).isMultipolygon() && Geometry.isPolygonInsideMultiPolygon(w.getNodes(), (Relation) e.osm, null)) {
220 e.child = w;
221 }
222 }
223 }
224 }
225
226 @Override
227 public boolean matches(Environment e) {
228
229 if (!right.matches(e))
230 return false;
231
232 if (ChildOrParentSelectorType.ELEMENT_OF.equals(type)) {
233
234 if (e.osm instanceof Node) {
235 // nodes cannot contain elements
236 return false;
237 }
238 e.parent = e.osm;
239
240 final ContainsFinder containsFinder = new ContainsFinder(e);
241 if (right instanceof GeneralSelector) {
242 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.NODE)) {
243 containsFinder.visit(e.osm.getDataSet().searchNodes(e.osm.getBBox()));
244 }
245 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
246 containsFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
247 }
248 } else {
249 // use slow test
250 containsFinder.visit(e.osm.getDataSet().allPrimitives());
251 }
252
253 return e.child != null;
254
255 } else if (ChildOrParentSelectorType.CROSSING.equals(type) && e.osm instanceof Way) {
256 final CrossingFinder crossingFinder = new CrossingFinder(e);
257 if (((GeneralSelector) right).matchesBase(OsmPrimitiveType.WAY)) {
258 crossingFinder.visit(e.osm.getDataSet().searchWays(e.osm.getBBox()));
259 }
260 return e.child != null;
261 } else if (ChildOrParentSelectorType.CHILD.equals(type)) {
262 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
263 e.osm.visitReferrers(collector);
264 if (e.parent != null)
265 return true;
266 } else {
267 if (e.osm instanceof Way) {
268 List<Node> wayNodes = ((Way) e.osm).getNodes();
269 for (int i=0; i<wayNodes.size(); i++) {
270 Node n = wayNodes.get(i);
271 if (left.matches(e.withPrimitive(n))) {
272 if (link.matches(e.withChildAndIndexAndLinkContext(n, i))) {
273 e.child = n;
274 e.index = i;
275 return true;
276 }
277 }
278 }
279 }
280 else if (e.osm instanceof Relation) {
281 List<RelationMember> members = ((Relation) e.osm).getMembers();
282 for (int i=0; i<members.size(); i++) {
283 OsmPrimitive member = members.get(i).getMember();
284 if (left.matches(e.withPrimitive(member))) {
285 if (link.matches(e.withChildAndIndexAndLinkContext(member, i))) {
286 e.child = member;
287 e.index = i;
288 return true;
289 }
290 }
291 }
292 }
293 }
294 return false;
295 }
296
297 @Override
298 public String getSubpart() {
299 return right.getSubpart();
300 }
301
302 @Override
303 public Range getRange() {
304 return right.getRange();
305 }
306
307 @Override
308 public String toString() {
309 return left + " " + (ChildOrParentSelectorType.PARENT.equals(type) ? "<" : ">") + link + " " + right;
310 }
311 }
312
313 /**
314 * Super class of {@link GeneralSelector} and {@link LinkSelector}
315 * @since 5841
316 */
317 public static abstract class AbstractSelector implements Selector {
318
319 protected final List<Condition> conds;
320
321 protected AbstractSelector(List<Condition> conditions) {
322 if (conditions == null || conditions.isEmpty()) {
323 this.conds = null;
324 } else {
325 this.conds = conditions;
326 }
327 }
328
329 /**
330 * Determines if all conditions match the given environment.
331 * @param env The environment to check
332 * @return {@code true} if all conditions apply, false otherwise.
333 */
334 public final boolean matchesConditions(Environment env) {
335 if (conds == null) return true;
336 for (Condition c : conds) {
337 try {
338 if (!c.applies(env)) return false;
339 } catch (PatternSyntaxException e) {
340 Main.error("PatternSyntaxException while applying condition" + c +": "+e.getMessage());
341 return false;
342 }
343 }
344 return true;
345 }
346
347 public List<Condition> getConditions() {
348 return Collections.unmodifiableList(conds);
349 }
350 }
351
352 public static class LinkSelector extends AbstractSelector {
353
354 public LinkSelector(List<Condition> conditions) {
355 super(conditions);
356 }
357
358 @Override
359 public boolean matches(Environment env) {
360 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
361 return matchesConditions(env);
362 }
363
364 @Override
365 public String getSubpart() {
366 throw new UnsupportedOperationException("Not supported yet.");
367 }
368
369 @Override
370 public Range getRange() {
371 throw new UnsupportedOperationException("Not supported yet.");
372 }
373
374 @Override
375 public String toString() {
376 return "LinkSelector{" + "conditions=" + conds + '}';
377 }
378 }
379
380 public static class GeneralSelector extends AbstractSelector {
381 public final String base;
382 public final Range range;
383 public final String subpart;
384
385 public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
386 super(conds);
387 this.base = base;
388 if (zoom != null) {
389 int a = zoom.a == null ? 0 : zoom.a;
390 int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
391 if (a <= b) {
392 range = fromLevel(a, b);
393 } else {
394 range = Range.ZERO_TO_INFINITY;
395 }
396 } else {
397 range = Range.ZERO_TO_INFINITY;
398 }
399 this.subpart = subpart;
400 }
401
402 @Override
403 public String getSubpart() {
404 return subpart;
405 }
406 @Override
407 public Range getRange() {
408 return range;
409 }
410
411 public boolean matchesBase(OsmPrimitiveType type) {
412 if (base.equals("*")) {
413 return true;
414 } else if (OsmPrimitiveType.NODE.equals(type)) {
415 return base.equals("node");
416 } else if (OsmPrimitiveType.WAY.equals(type)) {
417 return base.equals("way") || base.equals("area");
418 } else if (OsmPrimitiveType.RELATION.equals(type)) {
419 return base.equals("area") || base.equals("relation") || base.equals("canvas");
420 }
421 return false;
422 }
423
424 public boolean matchesBase(OsmPrimitive p) {
425 if (!matchesBase(p.getType())) {
426 return false;
427 } else {
428 if (p instanceof Relation) {
429 if (base.equals("area")) {
430 return ((Relation) p).isMultipolygon();
431 } else if (base.equals("canvas")) {
432 return p.get("#canvas") != null;
433 }
434 }
435 return true;
436 }
437 }
438
439 public boolean matchesBase(Environment e) {
440 return matchesBase(e.osm);
441 }
442
443 @Override
444 public boolean matches(Environment e) {
445 return matchesBase(e) && matchesConditions(e);
446 }
447
448 public String getBase() {
449 return base;
450 }
451
452 public static Range fromLevel(int a, int b) {
453 if (a > b)
454 throw new AssertionError();
455 double lower = 0;
456 double upper = Double.POSITIVE_INFINITY;
457 if (b != Integer.MAX_VALUE) {
458 lower = level2scale(b + 1);
459 }
460 if (a != 0) {
461 upper = level2scale(a);
462 }
463 return new Range(lower, upper);
464 }
465
466 final static double R = 6378135;
467
468 public static double level2scale(int lvl) {
469 if (lvl < 0)
470 throw new IllegalArgumentException();
471 // preliminary formula - map such that mapnik imagery tiles of the same
472 // or similar level are displayed at the given scale
473 return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
474 }
475
476 @Override
477 public String toString() {
478 return base + (Range.ZERO_TO_INFINITY.equals(range) ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
479 }
480 }
481}
Note: See TracBrowser for help on using the repository browser.