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 }