diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index 22fad18..69c29b7 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -35,28 +35,27 @@ class Condition: self.regex = re.compile(self.params[0], re.I) self.compiled_regex = "" - + def get_interesting_tags(self): if self.params[0][:2] == "::": return [] return set([self.params[0]]) + def get_numerics(self): if self.type in ("<", ">", ">=", "<="): return self.params[0] else: return False + def test(self, tags): """ Test a hash against this condition """ - - - t = self.type params = self.params if t == 'eq': # don't compare tags against sublayers if params[0][:2] == "::": - return True + return params[1] try: if t == 'eq': return tags[params[0]]==params[1] @@ -65,16 +64,16 @@ class Condition: if t == 'regex': return bool(self.regex.match(tags[params[0]])) if t == 'true': - return (tags[params[0]]=='true') | (tags[params[0]]=='yes') | (tags[params[0]]=='1') - if t == 'false': - return (tags.get(params[0], "")=='false') | (tags.get(params[0], "")=='no') | (tags.get(params[0], "")=='') + return tags.get(params[0]) == 'yes' + if t == 'untrue': + return tags.get(params[0]) == 'no' if t == 'set': if params[0] in tags: - return tags[params[0]]!='' + return tags[params[0]] != '' return False if t == 'unset': if params[0] in tags: - return tags[params[0]]=='' + return tags[params[0]] == '' return True if t == '<': @@ -121,9 +120,9 @@ class Condition: if t == 'regex': return params[0], '"%s" ~ \'%s\''%(params[0],params[1].replace("'","\\'")) if t == 'true': - return params[0], '"%s" IN (\'true\', \'yes\', \'1\')'%(params[0]) + return params[0], '"%s" = \'yes\''%(params[0]) if t == 'untrue': - return params[0], '"%s" NOT IN (\'true\', \'yes\', \'1\')'%(params[0]) + return params[0], '"%s" = \'no\''%(params[0]) if t == 'set': return params[0], '"%s" IS NOT NULL'%(params[0]) if t == 'unset': @@ -190,7 +189,6 @@ class Condition: if c2.type == self.type: return (self,) - if self.type == ">=" and c2.type == "<=": # a<=2 and a>=2 --> a=2 return (Condition ("eq", self.params),) if self.type == "<=" and c2.type == ">=": diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index 691fce1..8bb56b2 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -20,33 +20,29 @@ class Rule(): def __init__(self, s=''): self.conditions = [] - self.isAnd = True - self.minZoom = 0 + #self.isAnd = True + self.minZoom = 0 self.maxZoom = 19 + if s == "*": + s = "" self.subject = s # "", "way", "node" or "relation" + def __repr__(self): - return "%s|z%s-%s %s"%(self.subject,self.minZoom,self.maxZoom, self.conditions) + return "%s|z%s-%s %s"%(self.subject, self.minZoom, self.maxZoom, self.conditions) - #public function test(obj:Entity,tags:Object):Boolean { - - - def test(self, obj, tags, zoom): - if (self.subject!='') and not _test_feature_compatibility(obj, self.subject, tags): - return False - #print _test_feature_compatibility(obj, self.subject, tags), obj, self.subject if not self.test_zoom(zoom): return False - v="a" + if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): + return False + subpart = "::default" for condition in self.conditions: - r = condition.test(tags) - if v=="a": - v = r - elif self.isAnd: - v = v & r - else: - v = v | r - return v + res = condition.test(tags) + if not res: + return False + if type(res) != bool: + subpart = res + return subpart def test_zoom(self, zoom): return (zoom >= self.minZoom) and (zoom <= self.maxZoom) @@ -55,7 +51,7 @@ class Rule(): if obj: if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, {}): return set() - + if zoom and not self.test_zoom(zoom): return set() diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 6c115ab..0b9e66c 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -100,32 +100,31 @@ class StyleChooser: a.update(p[0]) # no need to check for eval's return a,b + # // Update the current StyleList from this StyleChooser - def updateStyles(self,sl,type, tags, zoom, scale, zscale): + def updateStyles(self, sl, ftype, tags, zoom, scale, zscale): # Are any of the ruleChains fulfilled? - w = 0 + object_id = False for c in self.ruleChains: - if (self.testChain(c,type,tags,zoom)): + object_id = self.testChain(c,ftype,tags,zoom) + if object_id: break else: return sl - - ## Update StyleList - object_id = 1 - + w = 0 for r in self.styles: ra = {} for a,b in r.iteritems(): "calculating eval()'s" - if __builtins__["type"](b) == self.eval_type: + if type(b) == self.eval_type: combined_style = {} for t in sl: combined_style.update(t) for p,q in combined_style.iteritems(): if "color" in p: combined_style[p] = cairo_to_hex(q) - b = b.compute(tags,combined_style, scale, zscale) + b = b.compute(tags, combined_style, scale, zscale) ra[a] = b r = ra ra = {} @@ -150,83 +149,31 @@ class StyleChooser: pass else: ra[a]=b - - ra["layer"] = float(tags.get("layer",0))*1000+ra.get("z-index",1) # calculating z-index + ra["layer"] = float(tags.get("layer",0))*2000+ra.get("z-index",1) # calculating z-index for k,v in ra.items(): # if a value is empty, we don't need it - renderer will do as default. - if not v: - del ra[k] - if "object-id" not in ra: - ra["object-id"] = str(object_id) + if not v: + del ra[k] + ra["object-id"] = str(object_id) + hasall = False + allinit = {} for x in sl: - if x.get("object-id","1") == ra["object-id"]: - x.update(ra) - break + if x.get("object-id") == "::*": + allinit = x.copy() + if ra["object-id"] == "::*": + oid = x.get("object-id") + x.update(ra) + x["object-id"] = oid + if oid == "::*": + hasall = True + if x.get("object-id") == ra["object-id"]: + x.update(ra) + break else: - sl.append(ra) - object_id += 1 + if not hasall: + allinit.update(ra) + sl.append(allinit) + # return sl - #a = "" - #if (r is ShapeStyle) { - #a=sl.shapeStyles; - #if (ShapeStyle(r).width>sl.maxwidth && !r.evals['width']) { sl.maxwidth=ShapeStyle(r).width; } - #} else if (r is ShieldStyle) { - #a=sl.shieldStyles; - #} else if (r is TextStyle) { - #a=sl.textStyles; - #} else if (r is PointStyle) { - #a=sl.pointStyles; - #w=0; - #if (PointStyle(r).icon_width && !PointStyle(r).evals['icon_width']) { - #w=PointStyle(r).icon_width; - #} else if (PointStyle(r).icon_image && imageWidths[PointStyle(r).icon_image]) { - #w=imageWidths[PointStyle(r).icon_image]; - #} - #if (w>sl.maxwidth) { sl.maxwidth=w; } - #} else if (r is InstructionStyle) { - #if (InstructionStyle(r).breaker) { return; } - #if (InstructionStyle(r).set_tags) { - #for (var k:String in InstructionStyle(r).set_tags) { tags[k]=InstructionStyle(r).set_tags[k]; } - #} - #continue; - #} - #if (r.drawn) { tags[':drawn']='yes'; } - #tags['_width']=sl.maxwidth; - - #r.runEvals(tags); - #if (a[r.sublayer]) { - ## // If there's already a style on this sublayer, then merge them - ## // (making a deep copy if necessary to avoid altering the root style) - #if (!a[r.sublayer].merged) { a[r.sublayer]=a[r.sublayer].deepCopy(); } - #a[r.sublayer].mergeWith(r); - #} else { - ## // Otherwise, just assign it - #a[r.sublayer]=r; - #} - #} - #} - - - ## // Test a ruleChain - ## // - run a set of tests in the chain - ## // works backwards from at position "pos" in array, or -1 for the last - ## // separate tags object is required in case they've been dynamically retagged - ## // - if they fail, return false - ## // - if they succeed, and it's the last in the chain, return happily - ## // - if they succeed, and there's more in the chain, rerun this for each parent until success - - #private function testChain(chain:Array,pos:int,obj:Entity,tags:Object):Boolean { - #if (pos==-1) { pos=chain.length-1; } - - #var r:Rule=chain[pos]; - #if (!r.test(obj, tags)) { return false; } - #if (pos==0) { return true; } - - #var o:Array=obj.parentObjects; - #for each (var p:Entity in o) { - #if (testChain(chain, pos-1, p, p.getTagsHash() )) { return true; } - #} - #return false; - #} def testChain(self,chain, obj, tags, zoom): """ @@ -234,50 +181,41 @@ class StyleChooser: """ ### FIXME: total MapCSS misreading for r in chain: - if r.test(obj,tags,zoom): - return True + return r.test(obj,tags,zoom) return False - ## // --------------------------------------------------------------------------------------------- - ## // Methods to add properties (used by parsers such as MapCSS) - - def newGroup(self): """ starts a new ruleChain in this.ruleChains """ - if (len(self.ruleChains[self.rcpos])>0): - self.ruleChains.append([]) - #} - #} + if self.ruleChains[self.rcpos]: + self.ruleChains.append([]) def newObject(self,e=''): """ adds into the current ruleChain (starting a new Rule) """ - self.ruleChains[self.rcpos].append(Rule(e)) - self.ruleChains[self.rcpos][-1].minZoom=float(self.scalepair[0]) - self.ruleChains[self.rcpos][-1].maxZoom=float(self.scalepair[1]) + rule = Rule(e) + rule.minZoom=float(self.scalepair[0]) + rule.maxZoom=float(self.scalepair[1]) + self.ruleChains[self.rcpos].append(rule) - - def addZoom(self,z): """ adds into the current ruleChain (existing Rule) """ - - self.ruleChains[self.rcpos][len(self.ruleChains[self.rcpos])-1].minZoom=float(z[0]) - self.ruleChains[self.rcpos][len(self.ruleChains[self.rcpos])-1].maxZoom=float(z[1]) - + + self.ruleChains[self.rcpos][-1].minZoom=float(z[0]) + self.ruleChains[self.rcpos][-1].maxZoom=float(z[1]) def addCondition(self,c): """ adds into the current ruleChain (existing Rule) """ - self.ruleChains[self.rcpos][len(self.ruleChains[self.rcpos])-1].conditions.append(c) + self.ruleChains[self.rcpos][-1].conditions.append(c) def addStyles(self, a): diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 09e16e8..49fe45a 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -28,12 +28,12 @@ from Condition import Condition WHITESPACE = re.compile(r'^ \s+ ', re.S | re.X) COMMENT = re.compile(r'^ \/\* .+? \*\/ \s* ', re.S | re.X) -CLASS = re.compile(r'^ ([\.:]:?\w+) \s* ', re.S | re.X) +CLASS = re.compile(r'^ ([\.:]:?[*\w]+) \s* ', re.S | re.X) NOT_CLASS = re.compile(r'^ !([\.:]\w+) \s* ', re.S | re.X) ZOOM = re.compile(r'^ \| \s* z([\d\-]+) \s* ', re.I | re.S | re.X) GROUP = re.compile(r'^ , \s* ', re.I | re.S | re.X) CONDITION = re.compile(r'^ \[(.+?)\] \s* ', re.S | re.X) -OBJECT = re.compile(r'^ (\w+) \s* ', re.S | re.X) +OBJECT = re.compile(r'^ (\*|[\w]+) \s* ', re.S | re.X) DECLARATION = re.compile(r'^ \{(.+?)\} \s* ', re.S | re.X) UNKNOWN = re.compile(r'^ (\S+) \s* ', re.S | re.X) @@ -114,9 +114,9 @@ class MapCSS(): return float(ZOOM_SINGLE.match(s).groups()[0]),float(ZOOM_SINGLE.match(s).groups()[0]) else: logging.error("unparsed zoom: %s" %s) + def precompile_style (self): # TODO: styleshees precompilation - subjs = {"canvas": ("canvas",),"way": ("Polygon","LineString"), "line":("Polygon","LineString"), "area": ("Polygon",), "node": ("Point",), "*":("Point","Polygon","LineString") } mfile.write("function restyle (prop, zoom, type){") mfile.write("style = new Object;") @@ -185,10 +185,12 @@ class MapCSS(): if shash in self.cache["style"]: return self.cache["style"][shash] style = [] - - #return [{"width": 1, "color":(0,0,0), "layer": 1}, {"width": 3, "color":(1,1,1), "layer":0}] + dbg = False + if tags.get('highway') == 'footway' and zoom == 17: + dbg = True for chooser in self.choosers: style = chooser.updateStyles(style, type, tags, zoom, scale, zscale) + style = [x for x in style if x["object-id"] != "::*"] self.cache["style"][shash] = style return style @@ -210,10 +212,7 @@ class MapCSS(): p = chooser.get_sql_hints(type, zoom) if p: if p[0] and p[1]: - # print chooser.get_sql_hints(type, zoom) - hints.append(p) - #print hints return hints @@ -249,9 +248,7 @@ class MapCSS(): cond = CLASS.match(css).groups()[0] log.debug("class found: %s"% (cond)) css = CLASS.sub("", css) - - sc.addCondition(Condition('eq',("::class",cond))) previous=oCONDITION; @@ -281,11 +278,6 @@ class MapCSS(): sc.addZoom(self.parseZoom(cond)) previous=oZOOM; - #css=css.replace(ZOOM,''); - #var z:Array=parseZoom(o[1]); - #sc.addZoom(z[0],z[1]); - #previous=oZOOM; - #// Grouping - just a comma elif GROUP.match(css): css=GROUP.sub("",css) @@ -333,13 +325,9 @@ class MapCSS(): log.warning("choked on: %s"%(css)) return - #print sc if (previous==oDECLARATION): self.choosers.append(sc) sc= StyleChooser(self.scalepair) - #print self.choosers - return -#} @@ -360,13 +348,11 @@ def parseCondition(s): log.debug("condition false: %s"%(a[0])) return Condition('false' ,a) - if CONDITION_SET.match(s): a = CONDITION_SET.match(s).groups() log.debug("condition set: %s"%(a)) return Condition('set' ,a) - if CONDITION_UNSET.match(s): a = CONDITION_UNSET.match(s).groups() log.debug("condition unset: %s"%(a)) @@ -377,18 +363,22 @@ def parseCondition(s): log.debug("condition NE: %s = %s"%(a[0], a[1])) return Condition('ne' ,a) ## FIXME: convert other conditions to python + if CONDITION_LE.match(s): a = CONDITION_LE.match(s).groups() log.debug("condition LE: %s <= %s"%(a[0], a[1])) return Condition('<=' ,a) + if CONDITION_GE.match(s): a = CONDITION_GE.match(s).groups() log.debug("condition GE: %s >= %s"%(a[0], a[1])) return Condition('>=' ,a) + if CONDITION_LT.match(s): a = CONDITION_LT.match(s).groups() log.debug("condition LT: %s < %s"%(a[0], a[1])) return Condition('<' ,a) + if CONDITION_GT.match(s): a = CONDITION_GT.match(s).groups() log.debug("condition GT: %s > %s"%(a[0], a[1])) @@ -398,7 +388,6 @@ def parseCondition(s): a = CONDITION_REGEX.match(s).groups() log.debug("condition REGEX: %s = %s"%(a[0], a[1])) return Condition('regex' ,a) - #else if ((o=CONDITION_REGEX.exec(s))) { return new Condition('regex',o[1],o[2]); } if CONDITION_EQ.match(s): a = CONDITION_EQ.match(s).groups() @@ -425,71 +414,7 @@ def parseDeclaration(s): else: logging.debug("unknown %s" % (a) ) return [t] - #else if ((o=SET_TAG_EVAL.exec(a))) { xs.addSetTag(o[1],new Eval(o[2])); } - #else if ((o=SET_TAG.exec(a))) { xs.addSetTag(o[1],o[2]); } - #else if ((o=SET_TAG_TRUE.exec(a))) { xs.addSetTag(o[1],true); } - #else if ((o=EXIT.exec(a))) { xs.setPropertyFromString('breaker',true); } - #} - #// Find sublayer - #var sub:uint=5; - #if (t['z_index']) { sub=Number(t['z_index']); delete t['z_index']; } - #ss.sublayer=ps.sublayer=ts.sublayer=hs.sublayer=sub; - #xs.sublayer=10; - - #// Munge special values - #if (t['font_weight'] ) { t['font_bold' ] = t['font_weight' ].match(BOLD ) ? true : false; delete t['font_weight']; } - #if (t['font_style'] ) { t['font_italic'] = t['font_style' ].match(ITALIC) ? true : false; delete t['font_style']; } - #if (t['text_decoration']) { t['font_underline'] = t['text_decoration'].match(UNDERLINE) ? true : false; delete t['text_decoration']; } - #if (t['text_position'] ) { t['text_center'] = t['text_position' ].match(CENTER) ? true : false; delete t['text_position']; } - #if (t['text_transform']) { - #// ** needs other transformations, e.g. lower-case, sentence-case - #if (t['text_transform'].match(CAPS)) { t['font_caps']=true; } else { t['font_caps']=false; } - #delete t['text_transform']; - #} - - #// ** Do compound settings (e.g. line: 5px dotted blue;) - - #// Assign each property to the appropriate style - #for (a in t) { - #// Parse properties - #// ** also do units, e.g. px/pt - #if (a.match(COLOR)) { - #t[a] = parseCSSColor(t[a]); - #} - - #// Set in styles - #if (ss.hasOwnProperty(a)) { ss.setPropertyFromString(a,t[a]); } - #else if (ps.hasOwnProperty(a)) { ps.setPropertyFromString(a,t[a]); } - #else if (ts.hasOwnProperty(a)) { ts.setPropertyFromString(a,t[a]); } - #else if (hs.hasOwnProperty(a)) { hs.setPropertyFromString(a,t[a]); } - #} - - #// Add each style to list - #if (ss.edited) { styles.push(ss); } - #if (ps.edited) { styles.push(ps); } - #if (ts.edited) { styles.push(ts); } - #if (hs.edited) { styles.push(hs); } - #if (xs.edited) { styles.push(xs); } - #return styles; - #} - - - - - #public static function parseCSSColor(colorStr:String):uint { - #colorStr = colorStr.toLowerCase(); - #if (CSSCOLORS[colorStr]) - #return CSSCOLORS[colorStr]; - #else { - #var match:Object = HEX.exec(colorStr); - #if ( match ) - #return Number("0x"+match[1]); - #} - #return 0; - #} - #} -#} if __name__ == "__main__": logging.basicConfig(level=logging.WARNING)