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