001 // License: GPL. For details, see LICENSE file.
002 package org.openstreetmap.josm.gui.mappaint.mapcss;
003
004 import java.util.List;
005
006 import org.openstreetmap.josm.data.osm.Node;
007 import org.openstreetmap.josm.data.osm.OsmPrimitive;
008 import org.openstreetmap.josm.data.osm.OsmPrimitiveType;
009 import org.openstreetmap.josm.data.osm.Relation;
010 import org.openstreetmap.josm.data.osm.RelationMember;
011 import org.openstreetmap.josm.data.osm.Way;
012 import org.openstreetmap.josm.data.osm.visitor.AbstractVisitor;
013 import org.openstreetmap.josm.gui.mappaint.Environment;
014 import org.openstreetmap.josm.gui.mappaint.Range;
015 import org.openstreetmap.josm.tools.Pair;
016 import org.openstreetmap.josm.tools.Utils;
017
018 public interface Selector {
019
020 /**
021 * Apply the selector to the primitive and check if it matches.
022 *
023 * @param env the Environment. env.mc and env.layer are read-only when matching a selector.
024 * env.source is not needed. This method will set the matchingReferrers field of env as
025 * a side effect! Make sure to clear it before invoking this method.
026 * @return true, if the selector applies
027 */
028 public boolean matches(Environment env);
029
030 public String getSubpart();
031
032 public Range getRange();
033
034 /**
035 * <p>Represents a child selector or a parent selector.</p>
036 *
037 * <p>In addition to the standard CSS notation for child selectors, JOSM also supports
038 * an "inverse" notation:</p>
039 * <pre>
040 * selector_a > selector_b { ... } // the standard notation (child selector)
041 * relation[type=route] > way { ... } // example (all ways of a route)
042 *
043 * selector_a < selector_b { ... } // the inverse notation (parent selector)
044 * node[traffic_calming] < way { ... } // example (way that has a traffic calming node)
045 * </pre>
046 *
047 */
048 public static class ChildOrParentSelector implements Selector {
049 private final Selector left;
050 private final LinkSelector link;
051 private final Selector right;
052 /** true, if this represents a parent selector (otherwise it is a child selector)
053 */
054 private final boolean parentSelector;
055
056 /**
057 *
058 * @param a the first selector
059 * @param b the second selector
060 * @param parentSelector if true, this is a parent selector; otherwise a child selector
061 */
062 public ChildOrParentSelector(Selector a, LinkSelector link, Selector b, boolean parentSelector) {
063 this.left = a;
064 this.link = link;
065 this.right = b;
066 this.parentSelector = parentSelector;
067 }
068
069 /**
070 * <p>Finds the first referrer matching {@link #left}</p>
071 *
072 * <p>The visitor works on an environment and it saves the matching
073 * referrer in {@code e.parent} and its relative position in the
074 * list referrers "child list" in {@code e.index}.</p>
075 *
076 * <p>If after execution {@code e.parent} is null, no matching
077 * referrer was found.</p>
078 *
079 */
080 private class MatchingReferrerFinder extends AbstractVisitor{
081 private Environment e;
082
083 /**
084 * Constructor
085 * @param e the environment against which we match
086 */
087 public MatchingReferrerFinder(Environment e){
088 this.e = e;
089 }
090
091 @Override
092 public void visit(Node n) {
093 // node should never be a referrer
094 throw new AssertionError();
095 }
096
097 @Override
098 public void visit(Way w) {
099 /*
100 * If e.parent is already set to the first matching referrer. We skip any following
101 * referrer injected into the visitor.
102 */
103 if (e.parent != null) return;
104
105 if (!left.matches(e.withPrimitive(w)))
106 return;
107 for (int i=0; i<w.getNodesCount(); i++) {
108 Node n = w.getNode(i);
109 if (n.equals(e.osm)) {
110 if (link.matches(e.withParent(w).withIndex(i).withLinkContext())) {
111 e.parent = w;
112 e.index = i;
113 return;
114 }
115 }
116 }
117 }
118
119 @Override
120 public void visit(Relation r) {
121 /*
122 * If e.parent is already set to the first matching referrer. We skip any following
123 * referrer injected into the visitor.
124 */
125 if (e.parent != null) return;
126
127 if (!left.matches(e.withPrimitive(r)))
128 return;
129 for (int i=0; i < r.getMembersCount(); i++) {
130 RelationMember m = r.getMember(i);
131 if (m.getMember().equals(e.osm)) {
132 if (link.matches(e.withParent(r).withIndex(i).withLinkContext())) {
133 e.parent = r;
134 e.index = i;
135 return;
136 }
137 }
138 }
139 }
140 }
141
142 @Override
143 public boolean matches(Environment e) {
144 if (!right.matches(e))
145 return false;
146
147 if (!parentSelector) {
148 MatchingReferrerFinder collector = new MatchingReferrerFinder(e);
149 e.osm.visitReferrers(collector);
150 if (e.parent != null)
151 return true;
152 } else {
153 if (e.osm instanceof Way) {
154 List<Node> wayNodes = ((Way) e.osm).getNodes();
155 for (int i=0; i<wayNodes.size(); i++) {
156 Node n = wayNodes.get(i);
157 if (left.matches(e.withPrimitive(n))) {
158 if (link.matches(e.withChild(n).withIndex(i).withLinkContext())) {
159 e.child = n;
160 e.index = i;
161 return true;
162 }
163 }
164 }
165 }
166 else if (e.osm instanceof Relation) {
167 List<RelationMember> members = ((Relation) e.osm).getMembers();
168 for (int i=0; i<members.size(); i++) {
169 OsmPrimitive member = members.get(i).getMember();
170 if (left.matches(e.withPrimitive(member))) {
171 if (link.matches(e.withChild(member).withIndex(i).withLinkContext())) {
172 e.child = member;
173 e.index = i;
174 return true;
175 }
176 }
177 }
178 }
179 }
180 return false;
181 }
182
183 @Override
184 public String getSubpart() {
185 return right.getSubpart();
186 }
187
188 @Override
189 public Range getRange() {
190 return right.getRange();
191 }
192
193 @Override
194 public String toString() {
195 return left +" "+ (parentSelector? "<" : ">")+link+" " +right;
196 }
197 }
198
199 public static class LinkSelector implements Selector {
200 protected List<Condition> conditions;
201
202 public LinkSelector(List<Condition> conditions) {
203 this.conditions = conditions;
204 }
205
206 @Override
207 public boolean matches(Environment env) {
208 Utils.ensure(env.isLinkContext(), "Requires LINK context in environment, got ''{0}''", env.getContext());
209 for (Condition c: conditions) {
210 if (!c.applies(env)) return false;
211 }
212 return true;
213 }
214
215 @Override
216 public String getSubpart() {
217 throw new UnsupportedOperationException("Not supported yet.");
218 }
219
220 @Override
221 public Range getRange() {
222 throw new UnsupportedOperationException("Not supported yet.");
223 }
224
225 @Override
226 public String toString() {
227 return "LinkSelector{" + "conditions=" + conditions + '}';
228 }
229 }
230
231 public static class GeneralSelector implements Selector {
232 private String base;
233 public Range range;
234 private List<Condition> conds;
235 private String subpart;
236
237 public GeneralSelector(String base, Pair<Integer, Integer> zoom, List<Condition> conds, String subpart) {
238 this.base = base;
239 if (zoom != null) {
240 int a = zoom.a == null ? 0 : zoom.a;
241 int b = zoom.b == null ? Integer.MAX_VALUE : zoom.b;
242 if (a <= b) {
243 range = fromLevel(a, b);
244 }
245 }
246 if (range == null) {
247 range = new Range();
248 }
249 if (conds == null || conds.isEmpty()) {
250 this.conds = null;
251 } else {
252 this.conds = conds;
253 }
254 this.subpart = subpart;
255 }
256
257 @Override
258 public String getSubpart() {
259 return subpart;
260 }
261 @Override
262 public Range getRange() {
263 return range;
264 }
265
266 public boolean matchesBase(Environment e){
267 if (e.osm instanceof Node) {
268 return base.equals("node") || base.equals("*");
269 } else if (e.osm instanceof Way) {
270 return base.equals("way") || base.equals("area") || base.equals("*");
271 } else if (e.osm instanceof Relation) {
272 if (base.equals("area")) {
273 return ((Relation) e.osm).isMultipolygon();
274 } else if (base.equals("relation")) {
275 return true;
276 } else if (base.equals("canvas")) {
277 return e.osm.get("#canvas") != null;
278 }
279 }
280 return false;
281 }
282
283 public boolean matchesConditions(Environment e){
284 if (conds == null) return true;
285 for (Condition c : conds) {
286 if (!c.applies(e))
287 return false;
288 }
289 return true;
290 }
291
292 @Override
293 public boolean matches(Environment e) {
294 if (!matchesBase(e)) return false;
295 return matchesConditions(e);
296 }
297
298 public String getBase() {
299 return base;
300 }
301
302 public static Range fromLevel(int a, int b) {
303 if (a > b)
304 throw new AssertionError();
305 double lower = 0;
306 double upper = Double.POSITIVE_INFINITY;
307 if (b != Integer.MAX_VALUE) {
308 lower = level2scale(b + 1);
309 }
310 if (a != 0) {
311 upper = level2scale(a);
312 }
313 return new Range(lower, upper);
314 }
315
316 final static double R = 6378135;
317
318 public static double level2scale(int lvl) {
319 if (lvl < 0)
320 throw new IllegalArgumentException();
321 // preliminary formula - map such that mapnik imagery tiles of the same
322 // or similar level are displayed at the given scale
323 return 2.0 * Math.PI * R / Math.pow(2.0, lvl) / 2.56;
324 }
325
326 @Override
327 public String toString() {
328 return base + (range == null ? "" : range) + Utils.join("", conds) + (subpart != null ? ("::" + subpart) : "");
329 }
330 }
331 }