Kothic: implemented runtime selectors

This commit is contained in:
Constantin Shalnev 2015-09-21 12:19:46 +03:00
parent d028412ec2
commit b29c52d807
5 changed files with 364 additions and 423 deletions

View file

@ -1,5 +1,4 @@
from drules_struct_pb2 import *
from timer import *
from mapcss import MapCSS
from optparse import OptionParser
import os
@ -13,24 +12,37 @@ whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo
WIDTH_SCALE = 1.0
def komap_mapswithme(options, style):
if options.outfile == "-":
print "Please specify base output path."
exit()
else:
ddir = os.path.dirname(options.outfile)
def mwm_encode_color(st, prefix='', default='black'):
if prefix:
prefix += "-"
opacity = hex(255 - int(255 * float(st.get(prefix + "opacity", 1))))
color = whatever_to_hex(st.get(prefix + 'color', default))
color = color[1] + color[1] + color[3] + color[3] + color[5] + color[5]
return int(opacity + color, 16)
types_file = open(os.path.join(ddir, 'types.txt'), "w")
drules_bin = open(os.path.join(options.outfile + '.bin'), "wb")
drules_txt = open(os.path.join(options.outfile + '.txt'), "wb")
def mwm_encode_image(st, prefix='icon', bgprefix='symbol'):
if prefix:
prefix += "-"
if bgprefix:
bgprefix += "-"
if prefix + "image" not in st:
return False
# strip last ".svg"
handle = st.get(prefix + "image")[:-4]
return handle, handle
def komap_mapswithme(options):
ddir = os.path.dirname(options.outfile)
classificator = {}
class_order = []
class_tree = {}
visibility = {}
textures = {}
# Build classificator tree from mapcss-mapping.csv file
types_file = open(os.path.join(ddir, 'types.txt'), "w")
for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'):
cl = row[0].replace("|", "-")
pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')]
kv = {}
for i in pairs:
@ -42,9 +54,9 @@ def komap_mapswithme(options, style):
kv[i[0].strip('?')] = "yes"
else:
kv[i[0]] = i[1]
classificator[row[0].replace("|", "-")] = kv
classificator[cl] = kv
if row[2] != "x":
class_order.append(row[0].replace("|", "-"))
class_order.append(cl)
print >> types_file, row[0]
else:
# compatibility mode
@ -52,306 +64,262 @@ def komap_mapswithme(options, style):
print >> types_file, row[6]
else:
print >> types_file, "mapswithme"
class_tree[row[0].replace("|", "-")] = row[0]
class_tree[cl] = row[0]
class_order.sort()
types_file.close()
def mwm_encode_color(st, prefix='', default='black'):
if prefix:
prefix += "-"
opacity = hex(255 - int(255 * float(st.get(prefix + "opacity", 1))))
color = whatever_to_hex(st.get(prefix + 'color', default))
color = color[1] + color[1] + color[3] + color[3] + color[5] + color[5]
return int(opacity + color, 16)
# Get all mapcss tags which are used in mapcss-mapping.csv
mapcss_mapping_tags = set()
for v in classificator.values():
for t in v.keys():
mapcss_mapping_tags.add(t)
def mwm_encode_image(st, prefix='icon', bgprefix='symbol'):
if prefix:
prefix += "-"
if bgprefix:
bgprefix += "-"
if prefix + "image" not in st:
return False
# strip last ".svg"
handle = st.get(prefix + "image")[:-4]
return handle, handle
# Parse style mapcss
style = MapCSS(options.minzoom, options.maxzoom + 1)
style.parse(filename = options.filename, mapcss_tags = mapcss_mapping_tags)
# Build optimization tree - class/type -> StyleChoosers
for cl in class_order:
clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
cltags = classificator[cl]
style.build_choosers_tree(clname, "line", cltags)
style.build_choosers_tree(clname, "area", cltags)
style.build_choosers_tree(clname, "node", cltags)
style.restore_choosers_order("line")
style.restore_choosers_order("area")
style.restore_choosers_order("node")
visibility = {}
bgpos = 0
dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP}
dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN}
# atbuild = AccumulativeTimer()
# atzstyles = AccumulativeTimer()
# atdrcont = AccumulativeTimer()
# atline = AccumulativeTimer()
# atarea = AccumulativeTimer()
# atnode = AccumulativeTimer()
# atbuild.Start()
for cl in class_order:
clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
# clname = cl
style.build_choosers_tree(clname, "line", classificator[cl])
style.build_choosers_tree(clname, "area", classificator[cl])
style.build_choosers_tree(clname, "node", classificator[cl])
style.restore_choosers_order("line")
style.restore_choosers_order("area")
style.restore_choosers_order("node")
# atbuild.Stop()
# Build drules tree
drules = ContainerProto()
for cl in class_order:
visstring = ["0"] * (options.maxzoom - options.minzoom + 1)
clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
# clname = cl
txclass = classificator[cl]
txclass["name"] = "name"
txclass["addr:housenumber"] = "addr:housenumber"
txclass["ref"] = "ref"
txclass["int_name"] = "int_name"
txclass["addr:flats"] = "addr:flats"
prev_area_len = -1
prev_node_len = -1
prev_line_len = -1
check_area = True
check_node = True
check_line = True
# atzstyles.Start()
zstyles_arr = [None] * (options.maxzoom - options.minzoom + 1)
has_icons_for_areas_arr = [False] * (options.maxzoom - options.minzoom + 1)
for zoom in xrange(options.maxzoom, options.minzoom - 1, -1):
has_icons_for_areas = False
zstyle = {}
if check_line:
if "area" not in txclass:
# atline.Start()
linestyle = style.get_style_dict(clname, "line", txclass, zoom, olddict=zstyle)
if prev_line_len == -1:
prev_line_len = len(linestyle)
if len(linestyle) == 0:
if prev_line_len != 0:
check_line = False
zstyle = linestyle
# atline.Stop()
if check_area:
# atarea.Start()
areastyle = style.get_style_dict(clname, "area", txclass, zoom, olddict=zstyle)
for st in areastyle.values():
if "icon-image" in st or 'symbol-shape' in st or 'symbol-image' in st:
has_icons_for_areas = True
break
if prev_area_len == -1:
prev_area_len = len(areastyle)
if len(areastyle) == 0:
if prev_area_len != 0:
check_area = False
zstyle = areastyle
# atarea.Stop()
if check_node:
if "area" not in txclass:
# atnode.Start()
nodestyle = style.get_style_dict(clname, "node", txclass, zoom, olddict=zstyle)
if prev_node_len == -1:
prev_node_len = len(nodestyle)
if len(nodestyle) == 0:
if prev_node_len != 0:
check_node = False
zstyle = nodestyle
# atnode.Stop()
if not check_line and not check_area and not check_node:
break
zstyle = zstyle.values()
zstyles_arr[zoom - options.minzoom] = zstyle
has_icons_for_areas_arr[zoom - options.minzoom]= has_icons_for_areas
# atzstyles.Stop()
# atdrcont.Start()
cltags = classificator[cl]
cltags["name"] = "name"
cltags["addr:housenumber"] = "addr:housenumber"
cltags["ref"] = "ref"
cltags["int_name"] = "int_name"
cltags["addr:flats"] = "addr:flats"
dr_cont = ClassifElementProto()
dr_cont.name = cl
visstring = ["0"] * (options.maxzoom - options.minzoom + 1)
for zoom in xrange(options.minzoom, options.maxzoom + 1):
zstyle = zstyles_arr[zoom - options.minzoom]
if zstyle is None or len(zstyle) == 0:
continue
has_icons_for_areas = has_icons_for_areas_arr[zoom - options.minzoom]
runtime_conditions_arr = []
has_lines = False
has_icons = False
has_fills = False
for st in zstyle:
st = dict([(k, v) for k, v in st.iteritems() if str(v).strip(" 0.")])
if 'width' in st or 'pattern-image' in st:
has_lines = True
if 'icon-image' in st or 'symbol-shape' in st or 'symbol-image' in st:
has_icons = True
if 'fill-color' in st:
has_fills = True
# Get runtime conditions which are used for class 'cl' on zoom 'zoom'
if "area" not in cltags:
runtime_conditions_arr.extend( style.get_runtime_rules(clname, "line", cltags, zoom) )
runtime_conditions_arr.extend( style.get_runtime_rules(clname, "area", cltags, zoom) )
if "area" not in cltags:
runtime_conditions_arr.extend( style.get_runtime_rules(clname, "node", cltags, zoom) )
has_text = None
txfmt = []
for st in zstyle:
if st.get('text') and not st.get('text') in txfmt:
txfmt.append(st.get('text'))
if has_text is None:
has_text = []
has_text.append(st)
# If there is no any runtime conditions, do not filter style by runtime conditions
if len(runtime_conditions_arr) == 0:
runtime_conditions_arr.append(None)
if (not has_lines) and (not has_text) and (not has_fills) and (not has_icons):
continue
for runtime_conditions in runtime_conditions_arr:
visstring[zoom] = "1"
dr_element = DrawElementProto()
dr_element.scale = zoom
has_icons_for_areas = False
zstyle = {}
for st in zstyle:
if st.get('-x-kot-layer') == 'top':
st['z-index'] = float(st.get('z-index', 0)) + 15001.
elif st.get('-x-kot-layer') == 'bottom':
st['z-index'] = float(st.get('z-index', 0)) - 15001.
# Get style for class 'cl' on zoom 'zoom' with corresponding runtime conditions
if "area" not in cltags:
linestyle = style.get_style_dict(clname, "line", cltags, zoom, olddict=zstyle, filter_by_runtime_conditions=runtime_conditions)
zstyle = linestyle
areastyle = style.get_style_dict(clname, "area", cltags, zoom, olddict=zstyle, filter_by_runtime_conditions=runtime_conditions)
for st in areastyle.values():
if "icon-image" in st or 'symbol-shape' in st or 'symbol-image' in st:
has_icons_for_areas = True
break
zstyle = areastyle
if "area" not in cltags:
nodestyle = style.get_style_dict(clname, "node", cltags, zoom, olddict=zstyle, filter_by_runtime_conditions=runtime_conditions)
zstyle = nodestyle
if st.get('casing-width') not in (None, 0): # and (st.get('width') or st.get('fill-color')):
if st.get('casing-linecap', 'butt') == 'butt':
dr_line = LineRuleProto()
dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
dr_line.color = mwm_encode_color(st, "casing")
dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000)
dashes = st.get('casing-dashes', st.get('dashes', []))
dr_line.dashdot.dd.extend(dashes)
dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP)
dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
dr_element.lines.extend([dr_line])
zstyle = zstyle.values()
# Let's try without this additional line style overhead. Needed only for casing in road endings.
# if st.get('casing-linecap', st.get('linecap', 'round')) != 'butt':
# dr_line = LineRuleProto()
# dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
# dr_line.color = mwm_encode_color(st, "casing")
# dr_line.priority = -15000
# dashes = st.get('casing-dashes', st.get('dashes', []))
# dr_line.dashdot.dd.extend(dashes)
# dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'round'), ROUNDCAP)
# dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
# dr_element.lines.extend([dr_line])
if len(zstyle) == 0:
continue
if has_lines:
if st.get('width'):
dr_line = LineRuleProto()
dr_line.width = (st.get('width', 0) * WIDTH_SCALE)
dr_line.color = mwm_encode_color(st)
for i in st.get('dashes', []):
dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE])
dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP)
dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN)
dr_line.priority = min((int(st.get('z-index', 0)) + 1000), 20000)
dr_element.lines.extend([dr_line])
if st.get('pattern-image'):
dr_line = LineRuleProto()
dr_line.width = 0
dr_line.color = 0
icon = mwm_encode_image(st, prefix='pattern')
dr_line.pathsym.name = icon[0]
dr_line.pathsym.step = float(st.get('pattern-spacing', 0)) - 16
dr_line.pathsym.offset = st.get('pattern-offset', 0)
dr_line.priority = int(st.get('z-index', 0)) + 1000
dr_element.lines.extend([dr_line])
textures[icon[0]] = icon[1]
if st.get('shield-font-size'):
dr_element.shield.height = int(st.get('shield-font-size', 10))
dr_element.shield.color = mwm_encode_color(st, "shield-text")
if st.get('shield-text-halo-radius', 0) != 0:
dr_element.shield.stroke_color = mwm_encode_color(st, "shield-text-halo", "white")
dr_element.shield.priority = min(19100, (16000 + int(st.get('z-index', 0))))
has_lines = False
has_icons = False
has_fills = False
for st in zstyle:
st = dict([(k, v) for k, v in st.iteritems() if str(v).strip(" 0.")])
if 'width' in st or 'pattern-image' in st:
has_lines = True
if 'icon-image' in st or 'symbol-shape' in st or 'symbol-image' in st:
has_icons = True
if 'fill-color' in st:
has_fills = True
if has_icons:
if st.get('icon-image'):
if not has_icons_for_areas:
dr_element.symbol.apply_for_type = 1
icon = mwm_encode_image(st)
dr_element.symbol.name = icon[0]
dr_element.symbol.priority = min(19100, (16000 + int(st.get('z-index', 0))))
textures[icon[0]] = icon[1]
has_icons = False
if st.get('symbol-shape'):
dr_element.circle.radius = float(st.get('symbol-size'))
dr_element.circle.color = mwm_encode_color(st, 'symbol-fill')
dr_element.circle.priority = min(19000, (14000 + int(st.get('z-index', 0))))
has_icons = False
has_text = None
txfmt = []
for st in zstyle:
if st.get('text') and not st.get('text') in txfmt:
txfmt.append(st.get('text'))
if has_text is None:
has_text = []
has_text.append(st)
if has_text and st.get('text'):
has_text = has_text[:2]
has_text.reverse()
dr_text = dr_element.path_text
base_z = 15000
if st.get('text-position', 'center') == 'line':
if (not has_lines) and (not has_text) and (not has_fills) and (not has_icons):
continue
visstring[zoom] = "1"
dr_element = DrawElementProto()
dr_element.scale = zoom
if runtime_conditions:
for rc in runtime_conditions:
dr_element.apply_if.append(str(rc))
for st in zstyle:
if st.get('-x-kot-layer') == 'top':
st['z-index'] = float(st.get('z-index', 0)) + 15001.
elif st.get('-x-kot-layer') == 'bottom':
st['z-index'] = float(st.get('z-index', 0)) - 15001.
if st.get('casing-width') not in (None, 0): # and (st.get('width') or st.get('fill-color')):
if st.get('casing-linecap', 'butt') == 'butt':
dr_line = LineRuleProto()
dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
dr_line.color = mwm_encode_color(st, "casing")
dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000)
dashes = st.get('casing-dashes', st.get('dashes', []))
dr_line.dashdot.dd.extend(dashes)
dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP)
dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
dr_element.lines.extend([dr_line])
# Let's try without this additional line style overhead. Needed only for casing in road endings.
# if st.get('casing-linecap', st.get('linecap', 'round')) != 'butt':
# dr_line = LineRuleProto()
# dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2)
# dr_line.color = mwm_encode_color(st, "casing")
# dr_line.priority = -15000
# dashes = st.get('casing-dashes', st.get('dashes', []))
# dr_line.dashdot.dd.extend(dashes)
# dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'round'), ROUNDCAP)
# dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN)
# dr_element.lines.extend([dr_line])
if has_lines:
if st.get('width'):
dr_line = LineRuleProto()
dr_line.width = (st.get('width', 0) * WIDTH_SCALE)
dr_line.color = mwm_encode_color(st)
for i in st.get('dashes', []):
dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE])
dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP)
dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN)
dr_line.priority = min((int(st.get('z-index', 0)) + 1000), 20000)
dr_element.lines.extend([dr_line])
if st.get('pattern-image'):
dr_line = LineRuleProto()
dr_line.width = 0
dr_line.color = 0
icon = mwm_encode_image(st, prefix='pattern')
dr_line.pathsym.name = icon[0]
dr_line.pathsym.step = float(st.get('pattern-spacing', 0)) - 16
dr_line.pathsym.offset = st.get('pattern-offset', 0)
dr_line.priority = int(st.get('z-index', 0)) + 1000
dr_element.lines.extend([dr_line])
if st.get('shield-font-size'):
dr_element.shield.height = int(st.get('shield-font-size', 10))
dr_element.shield.color = mwm_encode_color(st, "shield-text")
if st.get('shield-text-halo-radius', 0) != 0:
dr_element.shield.stroke_color = mwm_encode_color(st, "shield-text-halo", "white")
dr_element.shield.priority = min(19100, (16000 + int(st.get('z-index', 0))))
if has_icons:
if st.get('icon-image'):
if not has_icons_for_areas:
dr_element.symbol.apply_for_type = 1
icon = mwm_encode_image(st)
dr_element.symbol.name = icon[0]
dr_element.symbol.priority = min(19100, (16000 + int(st.get('z-index', 0))))
has_icons = False
if st.get('symbol-shape'):
dr_element.circle.radius = float(st.get('symbol-size'))
dr_element.circle.color = mwm_encode_color(st, 'symbol-fill')
dr_element.circle.priority = min(19000, (14000 + int(st.get('z-index', 0))))
has_icons = False
if has_text and st.get('text'):
has_text = has_text[:2]
has_text.reverse()
dr_text = dr_element.path_text
base_z = 16000
else:
dr_text = dr_element.caption
for sp in has_text[:]:
dr_cur_subtext = dr_text.primary
if len(has_text) == 2:
dr_cur_subtext = dr_text.secondary
dr_cur_subtext.height = int(float(sp.get('font-size', "10").split(",")[0]))
dr_cur_subtext.color = mwm_encode_color(sp, "text")
if st.get('text-halo-radius', 0) != 0:
dr_cur_subtext.stroke_color = mwm_encode_color(sp, "text-halo", "white")
if 'text-offset' in sp or 'text-offset-y' in sp:
dr_cur_subtext.offset_y = int(sp.get('text-offset-y', sp.get('text-offset', 0)))
if 'text-offset-x' in sp:
dr_cur_subtext.offset_x = int(sp.get('text-offset-x', 0))
has_text.pop()
dr_text.priority = min(19000, (base_z + int(st.get('z-index', 0))))
has_text = None
if has_fills:
if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0):
dr_element.area.color = mwm_encode_color(st, "fill")
if st.get('fill-position', 'foreground') == 'background':
if 'z-index' not in st:
bgpos -= 1
dr_element.area.priority = bgpos - 16000
else:
zzz = int(st.get('z-index', 0))
if zzz > 0:
dr_element.area.priority = zzz - 16000
else:
dr_element.area.priority = zzz - 16700
base_z = 15000
if st.get('text-position', 'center') == 'line':
dr_text = dr_element.path_text
base_z = 16000
else:
dr_element.area.priority = (int(st.get('z-index', 0)) + 1 + 1000)
has_fills = False
dr_text = dr_element.caption
for sp in has_text[:]:
dr_cur_subtext = dr_text.primary
if len(has_text) == 2:
dr_cur_subtext = dr_text.secondary
dr_cur_subtext.height = int(float(sp.get('font-size', "10").split(",")[0]))
dr_cur_subtext.color = mwm_encode_color(sp, "text")
if st.get('text-halo-radius', 0) != 0:
dr_cur_subtext.stroke_color = mwm_encode_color(sp, "text-halo", "white")
if 'text-offset' in sp or 'text-offset-y' in sp:
dr_cur_subtext.offset_y = int(sp.get('text-offset-y', sp.get('text-offset', 0)))
if 'text-offset-x' in sp:
dr_cur_subtext.offset_x = int(sp.get('text-offset-x', 0))
has_text.pop()
dr_text.priority = min(19000, (base_z + int(st.get('z-index', 0))))
has_text = None
dr_cont.element.extend([dr_element])
if has_fills:
if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0):
dr_element.area.color = mwm_encode_color(st, "fill")
if st.get('fill-position', 'foreground') == 'background':
if 'z-index' not in st:
bgpos -= 1
dr_element.area.priority = bgpos - 16000
else:
zzz = int(st.get('z-index', 0))
if zzz > 0:
dr_element.area.priority = zzz - 16000
else:
dr_element.area.priority = zzz - 16700
else:
dr_element.area.priority = (int(st.get('z-index', 0)) + 1 + 1000)
has_fills = False
dr_cont.element.extend([dr_element])
if dr_cont.element:
drules.cont.extend([dr_cont])
# atdrcont.Stop()
visibility["world|" + class_tree[cl] + "|"] = "".join(visstring)
# atwrite = AccumulativeTimer()
# atwrite.Start()
# Write drules_proto.bin and drules_proto.txt files
drules_bin = open(os.path.join(options.outfile + '.bin'), "wb")
drules_txt = open(os.path.join(options.outfile + '.txt'), "wb")
drules_bin.write(drules.SerializeToString())
drules_txt.write(unicode(drules))
drules_bin.close()
drules_txt.close()
# Write classificator.txt and visibility.txt files
visnodes = set()
for k, v in visibility.iteritems():
@ -379,7 +347,6 @@ def komap_mapswithme(options, style):
for i in range(len(oldoffset) / 4, len(offset) / 4, -1):
print >> visibility_file, " " * i + "{}"
print >> classificator_file, " " * i + "{}"
oldoffset = offset
end = "-"
if k in visnodes:
@ -390,48 +357,31 @@ def komap_mapswithme(options, style):
print >> visibility_file, " " * i + "{}"
print >> classificator_file, " " * i + "{}"
# atwrite.Stop()
# print "build, sec: %s" % (atbuild.ElapsedSec())
# print "zstyle %s times, sec: %s" % (atzstyles.Count(), atzstyles.ElapsedSec())
# print "drcont %s times, sec: %s" % (atdrcont.Count(), atdrcont.ElapsedSec())
# print "line %s times, sec: %s" % (atline.Count(), atline.ElapsedSec())
# print "area %s times, sec: %s" % (atarea.Count(), atarea.ElapsedSec())
# print "node %s times, sec: %s" % (atnode.Count(), atnode.ElapsedSec())
# print "writing files, sec: %s" % (atwrite.ElapsedSec())
visibility_file.close()
classificator_file.close()
# Main
parser = OptionParser()
parser.add_option("-s", "--stylesheet", dest="filename",
help="read MapCSS stylesheet from FILE", metavar="FILE")
parser.add_option("-f", "--minzoom", dest="minzoom", default=0, type="int",
help="minimal available zoom level", metavar="ZOOM")
parser.add_option("-t", "--maxzoom", dest="maxzoom", default=19, type="int",
help="maximal available zoom level", metavar="ZOOM")
parser.add_option("-o", "--output-file", dest="outfile", default="-",
help="output filename (defaults to stdout)", metavar="FILE")
(options, args) = parser.parse_args()
if (options.filename is None):
parser.error("MapCSS stylesheet filename is required")
try:
# atparse = AccumulativeTimer()
# atbuild = AccumulativeTimer()
parser = OptionParser()
parser.add_option("-s", "--stylesheet", dest="filename",
help="read MapCSS stylesheet from FILE", metavar="FILE")
parser.add_option("-f", "--minzoom", dest="minzoom", default=0, type="int",
help="minimal available zoom level", metavar="ZOOM")
parser.add_option("-t", "--maxzoom", dest="maxzoom", default=19, type="int",
help="maximal available zoom level", metavar="ZOOM")
parser.add_option("-o", "--output-file", dest="outfile", default="-",
help="output filename (defaults to stdout)", metavar="FILE")
# atparse.Start()
style = MapCSS(options.minzoom, options.maxzoom + 1) # zoom levels
style.parse(filename = options.filename)
# atparse.Stop()
(options, args) = parser.parse_args()
# atbuild.Start()
komap_mapswithme(options, style)
# atbuild.Stop()
if (options.filename is None):
parser.error("MapCSS stylesheet filename is required")
# print "mapcss parse, sec: %s" % (atparse.ElapsedSec())
# print "build, sec: %s" % (atbuild.ElapsedSec())
if options.outfile == "-":
parser.error("Please specify base output path.")
komap_mapswithme(options)
exit(0)

View file

@ -17,95 +17,19 @@
import re
# Fast conditions
class EqConditionDD:
def __init__(self, params):
self.value = params[1]
def extract_tags(self):
return set(["*"])
def test(self, tags):
return self.value
class EqCondition:
def __init__(self, params):
self.tag = params[0]
self.value = params[1]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] == self.value
else:
return False
class NotEqCondition:
def __init__(self, params):
self.tag = params[0]
self.value = params[1]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] != self.value
else:
return False
class SetCondition:
def __init__(self, params):
self.tag = params[0]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] != ''
return False
class UnsetCondition:
def __init__(self, params):
self.tag = params[0]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] == ''
return True
class TrueCondition:
def __init__(self, params):
self.tag = params[0]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] == 'yes'
return False
class UntrueCondition:
def __init__(self, params):
self.tag = params[0]
def extract_tags(self):
return set([self.tag])
def test(self, tags):
if self.tag in tags:
return tags[self.tag] == 'no'
return False
# Slow condition
class Condition:
def __init__(self, typez, params):
self.type = typez # eq, regex, lt, gt etc.
if type(params) == type(str()):
params = (params,)
self.params = params # e.g. ('highway','primary')
self.params = params # e.g. ('highway','primary')
if typez == "regex":
self.regex = re.compile(self.params[0], re.I)
def extract_tags(self):
def extract_tag(self):
if self.params[0][:2] == "::" or self.type == "regex":
return set(["*"]) # unknown
return set([self.params[0]])
return "*" # unknown
return self.params[0]
def test(self, tags):
"""
@ -148,6 +72,32 @@ class Condition:
return False
def __repr__(self):
t = self.type
params = self.params
if t == 'eq' and params[0][:2] == "::":
return "::%s" % (params[1])
if t == 'eq':
return "%s=%s" % (params[0], params[1])
if t == 'ne':
return "%s=%s" % (params[0], params[1])
if t == 'regex':
return "%s=~/%s/" % (params[0], params[1]);
if t == 'true':
return "%s?" % (params[0])
if t == 'untrue':
return "!%s?" % (params[0])
if t == 'set':
return "%s" % (params[0])
if t == 'unset':
return "!%s" % (params[0])
if t == '<':
return "%s<%s" % (params[0], params[1])
if t == '<=':
return "%s<=%s" % (params[0], params[1])
if t == '>':
return "%s>%s" % (params[0], params[1])
if t == '>=':
return "%s>=%s" % (params[0], params[1])
return "%s %s " % (self.type, repr(self.params))
def __eq__(self, a):
@ -162,23 +112,3 @@ def Number(tt):
except ValueError:
return 0
# Some conditions we can optimize by using "python polymorthism"
def OptimizeCondition(condition):
if (condition.type == "eq"):
if (condition.params[0][:2] == "::"):
return EqConditionDD(condition.params)
else:
return EqCondition(condition.params)
elif (condition.type == "ne"):
return NotEqCondition(condition.params)
elif (condition.type == "set"):
return SetCondition(condition.params)
elif (condition.type == "unset"):
return UnsetCondition(condition.params)
elif (condition.type == "true"):
return TrueCondition(condition.params)
elif (condition.type == "untrue"):
return UntrueCondition(condition.params)
else:
return condition

View file

@ -25,6 +25,7 @@ type_matches = {
class Rule():
def __init__(self, s=''):
self.runtime_conditions = []
self.conditions = []
# self.isAnd = True
self.minZoom = 0
@ -34,7 +35,7 @@ class Rule():
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 %s" % (self.subject, self.minZoom, self.maxZoom, self.conditions, self.runtime_conditions)
def test(self, obj, tags, zoom):
if (zoom < self.minZoom) or (zoom > self.maxZoom):
@ -58,8 +59,9 @@ class Rule():
def extract_tags(self):
a = set()
for condition in self.conditions:
a.update(condition.extract_tags())
a.add(condition.extract_tag())
if "*" in a:
a = set(["*"])
break
return a

View file

@ -106,7 +106,24 @@ class StyleChooser:
a.add("*")
return a
def updateStyles(self, sl, ftype, tags, zoom, scale, zscale):
def get_runtime_conditions(self, ftype, tags, zoom):
if self.selzooms:
if zoom < self.selzooms[0] or zoom > self.selzooms[1]:
return None
rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
if not rule_and_object_id:
return None
rule = rule_and_object_id[0]
if (len(rule.runtime_conditions) == 0):
return None
return rule.runtime_conditions
def updateStyles(self, sl, ftype, tags, zoom, xscale, zscale, filter_by_runtime_conditions):
# Are any of the ruleChains fulfilled?
if self.selzooms:
if zoom < self.selzooms[0] or zoom > self.selzooms[1]:
@ -115,9 +132,15 @@ class StyleChooser:
#if ftype not in self.compatible_types:
#return sl
object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
if not object_id:
if not rule_and_object_id:
return sl
rule = rule_and_object_id[0]
object_id = rule_and_object_id[1]
if filter_by_runtime_conditions and (filter_by_runtime_conditions != rule.runtime_conditions):
return sl
for r in self.styles:
@ -132,7 +155,7 @@ class StyleChooser:
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, xscale, zscale)
ra[a] = b
ra = make_nice_style(ra)
else:
@ -168,7 +191,7 @@ class StyleChooser:
for r in chain:
tt = r.test(obj, tags, zoom)
if tt:
return tt
return r, tt
return False
def newGroup(self):
@ -200,9 +223,16 @@ class StyleChooser:
"""
adds into the current ruleChain (existing Rule)
"""
c = OptimizeCondition(c)
self.ruleChains[-1].conditions.append(c)
def addRuntimeCondition(self, c):
# print "addRuntimeCondition ", c
"""
adds into the current ruleChain (existing Rule)
"""
self.ruleChains[-1].runtime_conditions.append(c)
self.ruleChains[-1].runtime_conditions.sort()
def addStyles(self, a):
# print "addStyle ", a
"""

View file

@ -33,6 +33,7 @@ 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)
RUNTIME_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)
IMPORT = re.compile(r'^@import\("(.+?)"\); \s* ', re.S | re.X)
@ -133,15 +134,23 @@ class MapCSS():
tmp.append(ec)
self.choosers_by_type_and_tag[type][tag] = tmp
def get_style(self, clname, type, tags={}, zoom=0, scale=1, zscale=.5):
def get_runtime_rules(self, clname, type, tags, zoom):
"""
Kothic styling API
Returns array of runtime_conditions which are used for clname/type/tags/zoom
"""
runtime_rules = []
if type in self.choosers_by_type_and_tag:
for chooser in self.choosers_by_type_and_tag[type][clname]:
runtime_conditions = chooser.get_runtime_conditions(type, tags, zoom)
if runtime_conditions:
runtime_rules.append(runtime_conditions)
return runtime_rules
def get_style(self, clname, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions):
style = []
if type in self.choosers_by_type_and_tag:
choosers = self.choosers_by_type_and_tag[type][clname]
for chooser in choosers:
style = chooser.updateStyles(style, type, tags, zoom, scale, zscale)
for chooser in self.choosers_by_type_and_tag[type][clname]:
style = chooser.updateStyles(style, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions)
style = [x for x in style if x["object-id"] != "::*"]
for x in style:
for k, v in [('width', 0), ('casing-width', 0)]:
@ -155,8 +164,11 @@ class MapCSS():
style = st
return style
def get_style_dict(self, clname, type, tags={}, zoom=0, scale=1, zscale=.5, olddict={}):
r = self.get_style(clname, type, tags, zoom, scale, zscale)
def get_style_dict(self, clname, type, tags={}, zoom=0, xscale=1, zscale=.5, olddict={}, filter_by_runtime_conditions=None):
"""
Kothic styling API
"""
r = self.get_style(clname, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions)
d = olddict
for x in r:
if x.get('object-id', '') not in d:
@ -165,7 +177,7 @@ class MapCSS():
return d
def subst_variables(self, t):
"""Expects an array from parseDeclaration."""
""" Expects an array from parseDeclaration. """
for k in t[0]:
t[0][k] = VARIABLE.sub(self.get_variable, t[0][k])
return t
@ -176,7 +188,7 @@ class MapCSS():
raise Exception("Variable not found: " + str(format(name)))
return self.variables[name] if name in self.variables else m.group()
def parse(self, css=None, clamp=True, stretch=1000, filename=None):
def parse(self, css=None, clamp=True, stretch=1000, filename=None, mapcss_tags=None):
"""
Parses MapCSS given as string
"""
@ -238,6 +250,19 @@ class MapCSS():
sc.newGroup()
previous = oGROUP
# RuntimeCondition - (population>=10000)
elif RUNTIME_CONDITION.match(css):
if (previous == oDECLARATION):
self.choosers.append(sc)
sc = StyleChooser(self.scalepair)
if (previous != oOBJECT) and (previous != oZOOM) and (previous != oCONDITION):
sc.newObject()
cond = RUNTIME_CONDITION.match(css).groups()[0]
log.debug("runtime condition found: %s" % (cond))
css = RUNTIME_CONDITION.sub("", css)
sc.addRuntimeCondition(parseCondition(cond))
previous = oCONDITION
# Condition - [highway=primary]
elif CONDITION.match(css):
if (previous == oDECLARATION):
@ -247,8 +272,12 @@ class MapCSS():
sc.newObject()
cond = CONDITION.match(css).groups()[0]
log.debug("condition found: %s" % (cond))
c = parseCondition(cond)
tag = c.extract_tag()
if (tag != "*") and (mapcss_tags != None) and (tag not in mapcss_tags):
raise Exception("Unknown tag '" + tag + "' in condition " + cond)
css = CONDITION.sub("", css)
sc.addCondition(parseCondition(cond))
sc.addCondition(c)
previous = oCONDITION
# Object - way, node, relation