initial regexp-based mapCSS parser commit.
Heavily based on RichardF's http://svn.openstreetmap.org/applications/editors/potlatch2/net/systemeD/halcyon/styleparser/
This commit is contained in:
parent
92a110834b
commit
1286f98440
4 changed files with 817 additions and 0 deletions
75
src/mapcss/Condition.py
Normal file
75
src/mapcss/Condition.py
Normal file
|
@ -0,0 +1,75 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is part of kothic, the realtime map renderer.
|
||||
|
||||
# kothic is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# kothic is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
|
||||
class Condition:
|
||||
def __init__(self, type, params):
|
||||
self.type=type # eq, regex, lt, gt etc.
|
||||
self.params=params # e.g. ('highway','primary')
|
||||
if type == "regex":
|
||||
self.regex = re.compile(self.params[0], re.I)
|
||||
|
||||
self.compiled_regex = ""
|
||||
|
||||
def test(self, params):
|
||||
"""
|
||||
Test a hash against this condition
|
||||
"""
|
||||
|
||||
|
||||
t = self.type
|
||||
if t == 'eq':
|
||||
return tags[params[0]]==params[1]
|
||||
if t == 'ne':
|
||||
return tags[params[0]]!=params[1]
|
||||
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 == 'untrue':
|
||||
return tags[params[0]]!='true' & tags[params[0]]!='yes' & tags[params[0]]!='1'
|
||||
if t == 'set':
|
||||
if params[0] in tags:
|
||||
return tags[params[0]]!=''
|
||||
return False
|
||||
if t == 'unset':
|
||||
if params[0] in tags:
|
||||
return tags[params[0]]==''
|
||||
return True
|
||||
|
||||
if t == '<':
|
||||
return (Number(tags[params[0]])< Number(params[1]))
|
||||
if t == '<=':
|
||||
return (Number(tags[params[0]])<=Number(params[1]))
|
||||
if t == '>':
|
||||
return (Number(tags[params[0]])> Number(params[1]))
|
||||
if t == '>=':
|
||||
return (Number(tags[params[0]])>=Number(params[1]))
|
||||
return False;
|
||||
|
||||
def __repr__(self):
|
||||
return "%s %s "%(self.type, repr(self.params))
|
||||
def Number(tt):
|
||||
"""
|
||||
Wrap float() not to produce exceptions
|
||||
"""
|
||||
|
||||
try:
|
||||
return float(tt)
|
||||
except ValueError:
|
||||
return 0
|
42
src/mapcss/Rule.py
Normal file
42
src/mapcss/Rule.py
Normal file
|
@ -0,0 +1,42 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is part of kothic, the realtime map renderer.
|
||||
|
||||
# kothic is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# kothic is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
class Rule():
|
||||
def __init__(self, s=''):
|
||||
self.conditions = []
|
||||
self.isAnd = True
|
||||
self.minZoom = 13 #### FIXME: take from MapCSS creation thingz
|
||||
self.maxZoom = 19
|
||||
self.subject = s # "", "way", "node" or "relation"
|
||||
def __repr__(self):
|
||||
return "%s|z%s-%s %s"%(self.subject,self.minZoom,self.maxZoom, self.conditions)
|
||||
|
||||
#public function test(obj:Entity,tags:Object):Boolean {
|
||||
#if (subject!='' && obj.getType()!=subject) { return false; }
|
||||
|
||||
#var v:Boolean=true; var i:uint=0;
|
||||
#for each (var condition:Condition in conditions) {
|
||||
#var r:Boolean=condition.test(tags);
|
||||
#if (i==0) { v=r; }
|
||||
#else if (isAnd) { v=v && r; }
|
||||
#else { v = v || r;}
|
||||
#i++;
|
||||
#}
|
||||
#return v;
|
||||
#}
|
||||
|
172
src/mapcss/StyleChooser.py
Normal file
172
src/mapcss/StyleChooser.py
Normal file
|
@ -0,0 +1,172 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is part of kothic, the realtime map renderer.
|
||||
|
||||
# kothic is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# kothic is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
|
||||
from Rule import Rule
|
||||
|
||||
|
||||
class StyleChooser:
|
||||
"""
|
||||
A StyleChooser object is equivalent to one CSS selector+declaration.
|
||||
|
||||
Its ruleChains property is an array of all the selectors, which would
|
||||
traditionally be comma-separated. For example:
|
||||
h1, h2, h3 em
|
||||
is three ruleChains.
|
||||
|
||||
Each ruleChain is itself an array of nested selectors. So the above
|
||||
example would roughly be encoded as:
|
||||
[[h1],[h2],[h3,em]]
|
||||
^^ ^^ ^^ ^^ each of these is a Rule
|
||||
|
||||
The styles property is an array of all the style objects to be drawn
|
||||
if any of the ruleChains evaluate to true.
|
||||
"""
|
||||
def __repr__(self):
|
||||
return "{(%s) : [%s] }\n"%(self.ruleChains, self.styles)
|
||||
def __init__(self):
|
||||
self.ruleChains = [[],]
|
||||
self.styles = []
|
||||
|
||||
self.rcpos=0
|
||||
self.stylepos=0
|
||||
|
||||
# // Update the current StyleList from this StyleChooser
|
||||
|
||||
def updateStyles(obj, tags, sl, imageWidths):
|
||||
# // Are any of the ruleChains fulfilled?
|
||||
# // ** needs to cope with min/max zoom
|
||||
w = 0
|
||||
fulfilled=False
|
||||
for c in self.ruleChains:
|
||||
if (testChain(c,-1,obj,tags)):
|
||||
fulfilled=True
|
||||
break
|
||||
|
||||
if (not fulfilled):
|
||||
return
|
||||
|
||||
## // Update StyleList
|
||||
#for r in self.styles:
|
||||
#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;
|
||||
#}
|
||||
|
||||
|
||||
## // ---------------------------------------------------------------------------------------------
|
||||
## // 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([])
|
||||
#}
|
||||
#}
|
||||
|
||||
|
||||
def newObject(self,e=''):
|
||||
"""
|
||||
adds into the current ruleChain (starting a new Rule)
|
||||
"""
|
||||
self.ruleChains[self.rcpos].append(Rule(e))
|
||||
|
||||
|
||||
|
||||
def addZoom(self,z):
|
||||
"""
|
||||
adds into the current ruleChain (existing Rule)
|
||||
"""
|
||||
|
||||
self.ruleChains[self.rcpos][len(self.ruleChains[self.rcpos])-1].minZoom=z[0]
|
||||
self.ruleChains[self.rcpos][len(self.ruleChains[self.rcpos])-1].maxZoom=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)
|
||||
|
||||
|
||||
def addStyles(self, a):
|
||||
"""
|
||||
adds to this.styles
|
||||
"""
|
||||
self.styles = self.styles + a
|
528
src/mapcss/__init__.py
Normal file
528
src/mapcss/__init__.py
Normal file
|
@ -0,0 +1,528 @@
|
|||
#!/usr/bin/env python
|
||||
# -*- coding: utf-8 -*-
|
||||
# This file is part of kothic, the realtime map renderer.
|
||||
|
||||
# kothic is free software: you can redistribute it and/or modify
|
||||
# it under the terms of the GNU General Public License as published by
|
||||
# the Free Software Foundation, either version 3 of the License, or
|
||||
# (at your option) any later version.
|
||||
|
||||
# kothic is distributed in the hope that it will be useful,
|
||||
# but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
# GNU General Public License for more details.
|
||||
|
||||
# You should have received a copy of the GNU General Public License
|
||||
# along with kothic. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
import re
|
||||
import logging
|
||||
|
||||
from StyleChooser import StyleChooser
|
||||
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)
|
||||
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)
|
||||
DECLARATION = re.compile(r'^ \{(.+?)\} \s* ', re.S | re.X)
|
||||
UNKNOWN = re.compile(r'^ (\S+) \s* ', re.S | re.X)
|
||||
|
||||
ZOOM_MINMAX = re.compile(r'^ (\d+)\-(\d+) $', re.S | re.X)
|
||||
ZOOM_MIN = re.compile(r'^ (\d+)\- $', re.S | re.X)
|
||||
ZOOM_MAX = re.compile(r'^ \-(\d+) $', re.S | re.X)
|
||||
ZOOM_SINGLE = re.compile(r'^ (\d+) $', re.S | re.X)
|
||||
|
||||
CONDITION_TRUE = re.compile(r'^ \s* ([:\w]+) \s* = \s* yes \s* $', re.I | re.S | re.X)
|
||||
CONDITION_FALSE = re.compile(r'^ \s* ([:\w]+) \s* = \s* no \s* $', re.I | re.S | re.X)
|
||||
CONDITION_SET = re.compile(r'^ \s* ([:\w]+) \s* $', re.S | re.X)
|
||||
CONDITION_UNSET = re.compile(r'^ \s* !([:\w]+) \s* $', re.S | re.X)
|
||||
CONDITION_EQ = re.compile(r'^ \s* ([:\w]+) \s* = \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_NE = re.compile(r'^ \s* ([:\w]+) \s* != \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_GT = re.compile(r'^ \s* ([:\w]+) \s* > \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_GE = re.compile(r'^ \s* ([:\w]+) \s* >= \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_LT = re.compile(r'^ \s* ([:\w]+) \s* < \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_LE = re.compile(r'^ \s* ([:\w]+) \s* <= \s* (.+) \s* $', re.S | re.X)
|
||||
CONDITION_REGEX = re.compile(r'^ \s* ([:\w]+) \s* =~\/ \s* (.+) \/ \s* $', re.S | re.X)
|
||||
|
||||
ASSIGNMENT_EVAL = re.compile(r"^ \s* (\S+) \s* \: \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X)
|
||||
ASSIGNMENT = re.compile(r'^ \s* (\S+) \s* \: \s* (.+?) \s* $', re.S | re.X)
|
||||
SET_TAG_EVAL = re.compile(r"^ \s* set \s+(\S+)\s* = \s* eval \s* \( \s* ' (.+?) ' \s* \) \s* $", re.I | re.S | re.X)
|
||||
SET_TAG = re.compile(r'^ \s* set \s+(\S+)\s* = \s* (.+?) \s* $', re.I | re.S | re.X)
|
||||
SET_TAG_TRUE = re.compile(r'^ \s* set \s+(\S+)\s* $', re.I | re.S | re.X)
|
||||
EXIT = re.compile(r'^ \s* exit \s* $', re.I | re.S | re.X)
|
||||
|
||||
oZOOM=2
|
||||
oGROUP=3
|
||||
oCONDITION=4
|
||||
oOBJECT=5
|
||||
oDECLARATION=6
|
||||
|
||||
DASH = re.compile(r'\-/g')
|
||||
COLOR = re.compile(r'color$/')
|
||||
BOLD = re.compile(r'^bold$/i')
|
||||
ITALIC = re.compile(r'^italic|oblique$/i')
|
||||
UNDERLINE = re.compile(r'^underline$/i')
|
||||
CAPS = re.compile(r'^uppercase$/i')
|
||||
CENTER = re.compile(r'^center$/i')
|
||||
|
||||
HEX = re.compile(r'^#([0-9a-f]+)$/i')
|
||||
|
||||
|
||||
## ** also needs to support @import rules
|
||||
|
||||
class MapCSS():
|
||||
def __init__(self,minscale,maxscale):
|
||||
"""
|
||||
"""
|
||||
self.minscale=minscale
|
||||
self.maxscale=maxscale
|
||||
self.choosers = []
|
||||
|
||||
def parseZoom(self, s):
|
||||
|
||||
if ZOOM_MINMAX.match(s):
|
||||
return ZOOM_MINMAX.match(s).groups()
|
||||
elif ZOOM_MIN.match(s):
|
||||
return ZOOM_MIN.match(s).group(), self.maxscale
|
||||
elif ZOOM_MAX.match(s):
|
||||
return self.minscale,ZOOM_MAX.match(s).group()
|
||||
elif ZOOM_SINGLE.match(s):
|
||||
return ZOOM_SINGLE.match(s).group(),ZOOM_SINGLE.match(s).group()
|
||||
else:
|
||||
logging.error("unparsed zoom: %s" %s)
|
||||
|
||||
|
||||
def parse(self, css):
|
||||
"""
|
||||
Parses MapCSS given as string
|
||||
"""
|
||||
log = logging.getLogger('mapcss.parser')
|
||||
previous = 0 # what was the previous CSS word?
|
||||
sc=StyleChooser() #currently being assembled
|
||||
choosers=[]
|
||||
|
||||
|
||||
|
||||
#o = []
|
||||
while (css):
|
||||
|
||||
# CSS comment
|
||||
if COMMENT.match(css):
|
||||
log.debug("comment found")
|
||||
css=COMMENT.sub("", css)
|
||||
|
||||
#// Whitespace (probably only at beginning of file)
|
||||
elif WHITESPACE.match(css):
|
||||
log.debug("whitespace found")
|
||||
css=WHITESPACE.sub("",css)
|
||||
|
||||
#// Class - .motorway, .builtup, :hover
|
||||
elif CLASS.match(css):
|
||||
if previous==oDECLARATION:
|
||||
self.choosers.append(sc)
|
||||
sc = StyleChooser()
|
||||
|
||||
cond = CLASS.match(css).group()
|
||||
log.debug("class found: %s"% (cond))
|
||||
css = CLASS.sub("", css)
|
||||
|
||||
|
||||
|
||||
sc.addCondition(Condition('set',cond));
|
||||
previous=oCONDITION;
|
||||
|
||||
#// Not class - !.motorway, !.builtup, !:hover
|
||||
#} else if ((o=NOT_CLASS.exec(css))) {
|
||||
#if (previous==oDECLARATION) { saveChooser(sc); sc=new StyleChooser(); }
|
||||
|
||||
#css=css.replace(NOT_CLASS,'');
|
||||
#sc.addCondition(new Condition('unset',o[1]));
|
||||
#previous=oCONDITION;
|
||||
|
||||
#// Zoom
|
||||
elif ZOOM.match(css):
|
||||
if (previous!=oOBJECT & previous!=oCONDITION):
|
||||
sc.newObject()
|
||||
|
||||
cond = ZOOM.match(css).groups()[0]
|
||||
log.debug("zoom found: %s"% (cond))
|
||||
css=ZOOM.sub("",css)
|
||||
sc.addZoom(self.parseZoom(cond))
|
||||
previous=oCONDITION;
|
||||
|
||||
#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)
|
||||
sc.newGroup()
|
||||
previous=oGROUP
|
||||
|
||||
#// Condition - [highway=primary]
|
||||
elif CONDITION.match(css):
|
||||
if (previous==oDECLARATION):
|
||||
self.choosers.append(sc)
|
||||
sc = StyleChooser()
|
||||
if (previous!=oOBJECT & previous!=oZOOM & previous!=oCONDITION):
|
||||
sc.newObject()
|
||||
cond = CONDITION.match(css).groups()[0]
|
||||
log.debug("condition found: %s"% (cond))
|
||||
css=CONDITION.sub("",css)
|
||||
sc.addCondition(parseCondition(cond))
|
||||
previous=oCONDITION;
|
||||
|
||||
#// Object - way, node, relation
|
||||
elif OBJECT.match(css):
|
||||
if (previous==oDECLARATION):
|
||||
self.choosers.append(sc)
|
||||
sc = StyleChooser()
|
||||
obj = OBJECT.match(css).group()
|
||||
log.debug("object found: %s"% (obj))
|
||||
css=OBJECT.sub("",css)
|
||||
sc.newObject(obj)
|
||||
previous=oOBJECT
|
||||
|
||||
|
||||
#// Declaration - {...}
|
||||
elif DECLARATION.match(css):
|
||||
|
||||
|
||||
decl = DECLARATION.match(css).groups()[0]
|
||||
log.debug("declaration found: %s"% (decl))
|
||||
sc.addStyles(parseDeclaration(decl))
|
||||
css=DECLARATION.sub("",css)
|
||||
previous=oDECLARATION
|
||||
|
||||
#// Unknown pattern
|
||||
elif UNKNOWN.match(css):
|
||||
log.warning("unknown thing found: %s"%(UNKNOWN.match(css).group()))
|
||||
css=UNKNOWN.sub("",css)
|
||||
|
||||
else:
|
||||
log.warning("choked on: %s"%(css))
|
||||
return
|
||||
|
||||
print sc
|
||||
if (previous==oDECLARATION):
|
||||
self.choosers.append(sc)
|
||||
sc= StyleChooser()
|
||||
print self.choosers
|
||||
return
|
||||
#}
|
||||
|
||||
|
||||
|
||||
|
||||
def parseCondition(s):
|
||||
|
||||
if CONDITION_TRUE.match(s):
|
||||
a = CONDITION_TRUE.match(s).groups()
|
||||
logging.debug("condition true: %s"%(a[0]))
|
||||
return Condition('true' ,a)
|
||||
|
||||
if CONDITION_FALSE.match(s):
|
||||
a = CONDITION_FALSE.match(s).groups()
|
||||
logging.debug("condition false: %s"%(a[0]))
|
||||
return Condition('false' ,a)
|
||||
|
||||
|
||||
if CONDITION_SET.match(s):
|
||||
a = CONDITION_SET.match(s).groups()
|
||||
logging.debug("condition set: %s"%(a))
|
||||
return Condition('set' ,a)
|
||||
|
||||
|
||||
if CONDITION_UNSET.match(s):
|
||||
a = CONDITION_UNSET.match(s).groups()
|
||||
logging.debug("condition unset: %s"%(a))
|
||||
return Condition('unset' ,a)
|
||||
|
||||
## FIXME: convert other conditions to python
|
||||
|
||||
#else if ((o=CONDITION_NE.exec(s))) { return new Condition('ne' ,o[1],o[2]); }
|
||||
#else if ((o=CONDITION_GT.exec(s))) { return new Condition('>' ,o[1],o[2]); }
|
||||
#else if ((o=CONDITION_GE.exec(s))) { return new Condition('>=' ,o[1],o[2]); }
|
||||
#else if ((o=CONDITION_LT.exec(s))) { return new Condition('<' ,o[1],o[2]); }
|
||||
#else if ((o=CONDITION_LE.exec(s))) { return new Condition('<=' ,o[1],o[2]); }
|
||||
#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()
|
||||
logging.debug("condition EQ: %s = %s"%(a[0], a[1]))
|
||||
return Condition('eq' ,a)
|
||||
|
||||
else:
|
||||
logging.warning("condition UNKNOWN: %s"%(s))
|
||||
#return null;
|
||||
#}
|
||||
|
||||
|
||||
def parseDeclaration(s):
|
||||
"""
|
||||
Parse declaration string into list of styles
|
||||
"""
|
||||
styles=[]
|
||||
t = {}
|
||||
#var t:Object=new Object();
|
||||
#var o:Object=new Object();
|
||||
#var a:String, k:String;
|
||||
|
||||
#// Create styles
|
||||
#var ss:ShapeStyle =new ShapeStyle() ;
|
||||
#var ps:PointStyle =new PointStyle() ;
|
||||
#var ts:TextStyle =new TextStyle() ;
|
||||
#var hs:ShieldStyle=new ShieldStyle();
|
||||
#var xs:InstructionStyle=new InstructionStyle();
|
||||
|
||||
for a in s.split(';'):
|
||||
#if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); }
|
||||
if ASSIGNMENT.match(a):
|
||||
tzz = ASSIGNMENT.match(a).groups()
|
||||
t[tzz[0]]=tzz[1]
|
||||
logging.debug("%s == %s" % (tzz[0],tzz[1]) )
|
||||
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)
|
||||
mc = MapCSS(0,19)
|
||||
mc.parse("""
|
||||
|
||||
/*
|
||||
|
||||
Stylesheet that mimicks, to a certain extent, potlatch 1.x
|
||||
Andy Allan, November 2009
|
||||
|
||||
Based heavily on:
|
||||
MapCSS demonstration stylesheet
|
||||
Richard Fairhurst, October 2009
|
||||
|
||||
*/
|
||||
|
||||
canvas {antialiasing: full;}
|
||||
|
||||
/* This rule applies to all areas (closed ways). Note that rules are applied in the order
|
||||
they appear in the file, so later rules may replace this one for some ways.
|
||||
This is used as a debugger for finding unstyled areas; it's obviously oversimplistic since
|
||||
it picks up closed-loop highways. */
|
||||
|
||||
way :area { color: red; width: 1; fill-color: red; fill-opacity: 0.5; }
|
||||
|
||||
/* A set of fairly standard rules.
|
||||
We use z-index to make sure high-priority roads appear above minor ones.
|
||||
The default z-index is 5. If an object matches multiple rules with the same
|
||||
z-index then the rules are "merged" (but individual properties become one or the other) */
|
||||
|
||||
way[highway=motorway],way[highway=motorway_link],
|
||||
way[highway=trunk],way[highway=trunk_link],
|
||||
way[highway=primary],way[highway=primary_link],
|
||||
way[highway=secondary],way[highway=secondary_link],
|
||||
way[highway=tertiary],way[highway=tertiary_link],
|
||||
way[highway=residential] { text: name; text-color: black; font-size: 7; text-position: line;}
|
||||
way[highway=motorway],way[highway=motorway_link] { z-index: 9; color: #809BC0; width: 7; casing-color: black; casing-width: 8; }
|
||||
way[highway=trunk],way[highway=trunk_link] { z-index: 9; color: #7FC97F; width: 7; casing-color: black; casing-width: 8; }
|
||||
way[highway=primary],way[highway=primary_link] { z-index: 8; color: #E46D71; width: 7; casing-color: black; casing-width: 8; }
|
||||
way[highway=secondary],way[highway=secondary_link] { z-index: 7; color: #FDBF6F; width: 7; casing-width: 8; }
|
||||
way[highway=tertiary],way[highway=unclassified] { z-index: 6; color: #FEFECB; width: 5; casing-width: 7; }
|
||||
way[highway=residential] { z-index: 5; color: #E8E8E8; width: 5; casing-color: gray; casing-width: 7; }
|
||||
way[highway=service] { color: white; width: 3; casing-width: 5; }
|
||||
|
||||
/* Pedestrian precincts need to be treated carefully. Only closed-loops with an explicit
|
||||
area=yes tag should be filled. The below doesn't yet work as intended. */
|
||||
way[highway=pedestrian] !:area { color: #ddddee; width: 5; casing-color: #555555; casing-width: 6; }
|
||||
way[highway=pedestrian] :area { color: #555555; width: 1; fill-color: #ddddee; fill-opacity: 0.8; }
|
||||
|
||||
way[highway=steps] { color: #FF6644; width: 2; dashes: 4, 2; }
|
||||
way[highway=footway] { color: #FF6644; width: 2; dashes: 6, 3; }
|
||||
way[highway=bridleway] { z-index:9; color: #996644; width: 2; dashes: 4, 2, 2, 2; }
|
||||
way[highway=track] { color: #996644; width: 2; dashes: 4, 2; }
|
||||
way[highway=path] { color: lightgreen; width: 2; dashes: 2, 2; }
|
||||
|
||||
way[waterway=river], way[waterway=canal] { color: blue; width: 2; text:name; text-color:blue; font-size:9; text-position: offset; text-offset: 7;}
|
||||
|
||||
way[barrier] {color: #000000; width: 1}
|
||||
|
||||
/* Fills can be solid colour or bitmap images */
|
||||
|
||||
|
||||
way[natural] :area { color: #ADD6A5; width: 1; fill-color: #ADD6A5; fill-opacity: 0.2; }
|
||||
way[landuse] :area { color: #444444; width: 2; fill-color: #444444; fill-opacity: 0.3; }
|
||||
way[amenity],way[shop] :area { color: #ADCEB5; width: 1; fill-color: #ADCEB5; fill-opacity: 0.2; }
|
||||
way[leisure],way[sport] :area { color: #8CD6B5; width: 1; fill-color: #8CD6B5; fill-opacity: 0.2; }
|
||||
way[tourism] :area { color: #F7CECE; width: 1; fill-color: #F7CECE; fill-opacity: 0.2; }
|
||||
way[historic],way[ruins] :area { color: #F7F7DE; width: 1; fill-color: #F7F7DE; fill-opacity: 0.2; }
|
||||
way[military] :area { color: #D6D6D6; width: 1; fill-color: #D6D6D6; fill-opacity: 0.2; }
|
||||
way[building] :area { color: #ff6ec7; width: 1; fill-color: #ff6ec7; fill-opacity: 0.2; }
|
||||
way[natural=water],
|
||||
way[waterway] :area { color: blue; width: 2; fill-color: blue; fill-opacity: 0.2; }
|
||||
way[landuse=forest],way[natural=wood] :area { color: green; width: 2; fill-color: green; fill-opacity: 0.2; }
|
||||
way[leisure=pitch],way[leisure=park] { color: #44ff44; width: 1; fill-color: #44ff44; fill-opacity: 0.2; }
|
||||
way[amenity=parking] :area { color: gray; width: 1; fill-color: gray; fill-opacity: 0.2; }
|
||||
way[public_transport=pay_scale_area] :area { color: gray; width: 1; fill-color: gray; fill-opacity: 0.1; }
|
||||
|
||||
/* Addressing. Nodes with addresses *and* match POIs should have a poi icon, so we put addressing first */
|
||||
|
||||
node[addr:housenumber],
|
||||
node[addr:housename] { icon-image: circle; icon-width: 4; color: #B0E0E6; casing-color:blue; casing-width: 1; }
|
||||
way[addr:interpolation] { color: #B0E0E6; width: 3; dashes: 3,3;}
|
||||
|
||||
/* POIs, too, can have bitmap icons - they can even be transparent */
|
||||
|
||||
node[amenity=pub] { icon-image: icons/pub.png; text-offset: 15; font-family: DejaVu; text: name; font-size: 9; }
|
||||
node[place] { icon-image: icons/place.png; text-offset: 17; font-family: DejaVu; text: name; font-size: 9; font-weight: bold; text-decoration: underline; }
|
||||
node[railway=station] { icon-image: icons/station.png; text-offset: 13; font-family: DejaVu; text: name; font-size: 9; font-weight: bold; }
|
||||
node[aeroway=aerodrome] { icon-image: icons/airport.png; text-offset: 13; font-family: DejaVu; text: name; font-size: 10; }
|
||||
node[amenity=atm] { icon-image: icons/atm.png; }
|
||||
node[amenity=bank] { icon-image: icons/bank.png; text-offset: 15; text: name; }
|
||||
node[highway=bus_stop] { icon-image: icons/bus_stop.png; }
|
||||
node[amenity=cafe] { icon-image: icons/cafe.png; text-offset: 15; text: name; }
|
||||
node[shop=convenience] { icon-image: icons/convenience.png; text-offset:15; text:name; }
|
||||
node[shop=supermarket] { icon-image: icons/supermarket.png; text-offset:15; text:name; }
|
||||
node[amenity=fast_food] { icon-image: icons/fast_food.png; text-offset:15; text: name; }
|
||||
node[amenity=fire_station] { icon-image: icons/fire_station.png; }
|
||||
node[amenity=hospital] { icon-image: icons/hospital.png; }
|
||||
node[tourism=hotel] { icon-image: icons/hotel.png; }
|
||||
node[amenity=parking] { icon-image: icons/parking.png; }
|
||||
node[amenity=bicycle_parking] { icon-image: icons/parking_cycle.png; text-offset: 15; text: capacity; }
|
||||
node[amenity=pharmacy] { icon-image: icons/pharmacy.png; }
|
||||
node[amenity=pharmacy][dispensing=yes] { icon-image: icons/pharmacy_dispensing.png; }
|
||||
node[amenity=police] { icon-image: icons/police.png; }
|
||||
node[amenity=post_box] { icon-image: icons/post_box.png; }
|
||||
node[amenity=recycling] { icon-image: icons/recycling.png; }
|
||||
node[amenity=restaurant] { icon-image: icons/restaurant.png; }
|
||||
node[amenity=school] { icon-image: icons/school.png; }
|
||||
node[amenity=taxi] { icon-image: icons/taxi.png; }
|
||||
node[amenity=telephone] { icon-image: icons/telephone.png; }
|
||||
way node[barrier=gate], way node[highway=gate] { icon-image: icons/gate.png; }
|
||||
way node[barrier=bollard] { icon-image: icons/bollard.png; }
|
||||
node[barrier=cattle_grid] { icon-image: icons/cattle_grid.png; }
|
||||
|
||||
/* We can stack styles at different z-index (depth) */
|
||||
|
||||
way[railway=rail]
|
||||
{ z-index: 6; color: black; width: 5; }
|
||||
{ z-index: 7; color: white; width: 3; dashes: 12,12; }
|
||||
way[railway=platform] { color:black; width: 2; }
|
||||
way[railway=subway]
|
||||
{ z-index: 6; color: #444444; width: 5; }
|
||||
{ z-index: 7; color: white; width: 3; dashes: 8,8; }
|
||||
|
||||
/* Bridge */
|
||||
way[bridge=yes], way[bridge=viaduct], way[bridge=suspension]
|
||||
{ z-index: 4; color: white; width: eval('_width+3'); }
|
||||
{ z-index: 3; color: black; width: eval('_width+6'); }
|
||||
|
||||
/* Tunnel */
|
||||
way[tunnel=yes]
|
||||
{ z-index: 4; color: white; width: eval('_width+2'); }
|
||||
{ z-index: 3; color: black; width: eval('_width+6'); dashes: 4,4; }
|
||||
|
||||
/* Oneway */
|
||||
way[oneway=yes] { z-index: 10; color: #444444; width: 3; dashes: 15,25; line-style: arrows; }
|
||||
|
||||
|
||||
/* Change the road colour based on dynamically set "highlighted" tag (see earlier) */
|
||||
|
||||
way|z1-12 .highlighted { color: pink; }
|
||||
|
||||
/* Interactive editors may choose different behaviour when a user mouses-over or selects
|
||||
an object. Potlatch 2 supports these but the stand-alone Halcyon viewer does not */
|
||||
|
||||
way :hover { z-index: 2; width: eval('_width+10'); color: #ffff99; }
|
||||
way :selected { z-index: 2; width: eval('_width+10'); color: yellow; opacity: 0.7;}
|
||||
way !:drawn { z-index:10; width: 0.5; color: gray; }
|
||||
|
||||
node :selectedway { z-index: 9; icon-image: square; icon-width: 8; color: red; }
|
||||
node :hoverway { z-index: 9; icon-image: square; icon-width: 7; color: blue; }
|
||||
node !:drawn :poi { z-index: 2; icon-image: circle; icon-width: 4; color: green; casing-color: black; casing-width: 1; }
|
||||
node :selected { z-index: 1; icon-image: square; icon-width: eval('_width+10'); color: yellow; }
|
||||
node :junction :selectedway { z-index: 8; icon-image: square; icon-width: 12; casing-color: black; casing-width: 1; }
|
||||
|
||||
/* Descendant selectors provide an easy way to style relations: this example means "any way
|
||||
which is part of a relation whose type=route". */
|
||||
|
||||
relation[type=route] way { z-index: 1; width: 17; color: blue; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=ncn] way { z-index: 1; width: 12; color: red; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=rcn] way { z-index: 1; width: 12; color: cyan; opacity: 0.3; }
|
||||
relation[type=route][route=bicycle][network=lcn] way { z-index: 1; width: 12; color: blue; opacity: 0.3; }
|
||||
relation[type=route][route=foot] way { z-index: 1; width: 10; color: #80ff80; opacity: 0.6; }
|
||||
|
||||
|
||||
|
||||
""")
|
Loading…
Add table
Reference in a new issue