From aa40e5c6a5ca33e958fee6a07bc8a52ab2fc68e9 Mon Sep 17 00:00:00 2001 From: Ilya Zverev Date: Mon, 21 Sep 2015 20:06:08 +0300 Subject: [PATCH] Misc updates from unknown source --- src/libkomwm.py | 270 ++++++++++++++++++++++++++++--------- src/mapcss/Condition.py | 104 +++++++++++++- src/mapcss/Eval.py | 14 +- src/mapcss/Rule.py | 9 +- src/mapcss/StyleChooser.py | 30 ++++- src/mapcss/__init__.py | 261 ++++++++++++++++++++--------------- src/timer.py | 42 ++++++ 7 files changed, 546 insertions(+), 184 deletions(-) create mode 100644 src/timer.py diff --git a/src/libkomwm.py b/src/libkomwm.py index 0fbd887..d14d60f 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -1,7 +1,10 @@ from drules_struct_pb2 import * - +from timer import * +from mapcss import MapCSS +from optparse import OptionParser import os import csv +import sys import json import mapcss.webcolors whatever_to_hex = mapcss.webcolors.webcolors.whatever_to_hex @@ -15,18 +18,17 @@ def komap_mapswithme(options, style, filename): exit() else: ddir = os.path.dirname(options.outfile) - basepath = os.path.dirname(filename) - drules = ContainerProto() 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") - textures = {} + drules = ContainerProto() classificator = {} class_order = [] class_tree = {} visibility = {} + textures = {} for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'): pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')] @@ -69,7 +71,6 @@ def komap_mapswithme(options, style, filename): bgprefix += "-" if prefix + "image" not in st: return False - # strip last ".svg" handle = st.get(prefix + "image")[:-4] return handle, handle @@ -79,43 +80,116 @@ def komap_mapswithme(options, style, filename): dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP} dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN} - for cl in class_order: - visstring = ["0"] * (options.maxzoom + 1) - dr_cont = ClassifElementProto() - dr_cont.name = cl + # atbuild = AccumulativeTimer() + # atzstyles = AccumulativeTimer() + # atdrcont = AccumulativeTimer() + # atline = AccumulativeTimer() + # atarea = AccumulativeTimer() + # atnode = AccumulativeTimer() - for zoom in xrange(options.minzoom, options.maxzoom + 1): - txclass = classificator[cl] - txclass["name"] = "name" - txclass["addr:housenumber"] = "addr:housenumber" - txclass["ref"] = "ref" - txclass["int_name"] = "int_name" - txclass["addr:flats"] = "addr:flats" + # 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() + + 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 "area" not in txclass: - zstyle = style.get_style_dict("line", txclass, zoom, olddict=zstyle, cache=False) - # for st in zstyle: - # if "fill-color" in st: - # del st["fill-color"] + if check_line: + if "area" not in txclass: + # atline.Start() + linestyle = style.get_style_dict(clname, "line", txclass, zoom, olddict=zstyle, cache=False) + 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 True: - areastyle = style.get_style_dict("area", txclass, zoom, olddict=zstyle, cache=False) + if check_area: + # atarea.Start() + areastyle = style.get_style_dict(clname, "area", txclass, zoom, olddict=zstyle, cache=False) 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, cache=False) + 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 - if "area" not in txclass: - nodestyle = style.get_style_dict("node", txclass, zoom, olddict=zstyle, cache=False) - # for st in nodestyle: - # if "fill-color" in st: - # del st["fill-color"] - zstyle = nodestyle 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() + + dr_cont = ClassifElementProto() + dr_cont.name = cl + 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] + has_lines = False - has_text = [] has_icons = False has_fills = False for st in zstyle: @@ -126,10 +200,14 @@ def komap_mapswithme(options, style, filename): has_icons = True if 'fill-color' in st: has_fills = True + + 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_lines or has_text or has_fills or has_icons: @@ -140,7 +218,7 @@ def komap_mapswithme(options, style, filename): for st in zstyle: if st.get('-x-kot-layer') == 'top': st['z-index'] = float(st.get('z-index', 0)) + 15001. - if st.get('-x-kot-layer') == 'bottom': + 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')): @@ -155,7 +233,7 @@ def komap_mapswithme(options, style, filename): 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. + # 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) @@ -167,28 +245,28 @@ def komap_mapswithme(options, style, filename): # dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN) # dr_element.lines.extend([dr_line]) - 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 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 has_icons: if st.get('icon-image'): @@ -229,7 +307,7 @@ def komap_mapswithme(options, style, filename): 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 = False + has_text = None if has_fills: if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0): @@ -247,16 +325,23 @@ def komap_mapswithme(options, style, filename): 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) - prevvis = [] - visnodes = set() + # atwrite = AccumulativeTimer() + # atwrite.Start() + drules_bin.write(drules.SerializeToString()) drules_txt.write(unicode(drules)) + visnodes = set() for k, v in visibility.iteritems(): vis = k.split("|") for i in range(1, len(vis) - 1): @@ -280,15 +365,74 @@ def komap_mapswithme(options, style, filename): for k in viskeys: offset = " " * (k.count("|") - 1) for i in range(len(oldoffset) / 4, len(offset) / 4, -1): - print >>visibility_file, " " * i + "{}" - print >>classificator_file, " " * i + "{}" + print >> visibility_file, " " * i + "{}" + print >> classificator_file, " " * i + "{}" oldoffset = offset end = "-" if k in visnodes: end = "+" - print >>visibility_file, offset + k.split("|")[-2] + " " + visibility.get(k, "0" * (options.maxzoom + 1)) + " " + end - print >>classificator_file, offset + k.split("|")[-2] + " " + end + print >> visibility_file, offset + k.split("|")[-2] + " " + visibility.get(k, "0" * (options.maxzoom + 1)) + " " + end + print >> classificator_file, offset + k.split("|")[-2] + " " + end for i in range(len(offset) / 4, 0, -1): - print >>visibility_file, " " * i + "{}" - print >>classificator_file, " " * i + "{}" + 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()) + +# 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("-l", "--locale", dest="locale", + help="language that should be used for labels (ru, en, be, uk..)", metavar="LANG") +parser.add_option("-o", "--output-file", dest="outfile", default="-", + help="output filename (defaults to stdout)", metavar="FILE") +parser.add_option("-p", "--osm2pgsql-style", dest="osm2pgsqlstyle", default="-", + help="osm2pgsql stylesheet filename", metavar="FILE") +parser.add_option("-b", "--background-only", dest="bgonly", action="store_true", default=False, + help="Skip rendering of icons and labels", metavar="BOOL") +parser.add_option("-T", "--text-scale", dest="textscale", default=1, type="float", + help="text size scale", metavar="SCALE") +parser.add_option("-c", "--config", dest="conffile", default="komap.conf", + help="config file name", metavar="FILE") + +(options, args) = parser.parse_args() + +if (options.filename is None): + parser.error("MapCSS stylesheet filename is required") + +try: + # atparse = AccumulativeTimer() + # atbuild = AccumulativeTimer() + + # atparse.Start() + style = MapCSS(options.minzoom, options.maxzoom + 1) # zoom levels + style.parse(filename = options.filename) + # atparse.Stop() + + # atbuild.Start() + komap_mapswithme(options, style, options.filename) + # atbuild.Stop() + + # print "mapcss parse, sec: %s" % (atparse.ElapsedSec()) + # print "build, sec: %s" % (atbuild.ElapsedSec()) + + exit(0) + +except Exception as e: + print >> sys.stderr, "Error\n" + str(e) + exit(-1) diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index fe009b1..a476eb9 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -24,6 +24,81 @@ for a, b in INVERSIONS.iteritems(): INVERSIONS.update(in2) del in2 +# 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): @@ -33,7 +108,6 @@ class Condition: self.params = params # e.g. ('highway','primary') if typez == "regex": self.regex = re.compile(self.params[0], re.I) - self.compiled_regex = "" def get_interesting_tags(self): @@ -41,6 +115,11 @@ class Condition: return [] return set([self.params[0]]) + def extract_tags(self): + if self.params[0][:2] == "::" or self.type == "regex": + return set(["*"]) # unknown + return set([self.params[0]]) + def get_numerics(self): if self.type in ("<", ">", ">=", "<="): return self.params[0] @@ -219,13 +298,32 @@ class Condition: return self, c2 - def Number(tt): """ Wrap float() not to produce exceptions """ - try: return float(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 diff --git a/src/mapcss/Eval.py b/src/mapcss/Eval.py index b4a47e5..3c32832 100644 --- a/src/mapcss/Eval.py +++ b/src/mapcss/Eval.py @@ -43,12 +43,12 @@ class Eval(): for t in x: q = x return 0 - tags = set([]) - # print self.expr_text + # print self.expr_text + tags = set([]) a = eval(self.expr, {}, { - "tag": lambda x: max([tags.add(x), " "]), - "prop": lambda x: "", + "tag": lambda x: max([tags.add(x), 0]), + "prop": lambda x: 0, "num": lambda x: 0, "metric": fake_compute, "zmetric": fake_compute, @@ -63,11 +63,13 @@ class Eval(): """ Compute this eval() """ + """ for k, v in tags.iteritems(): try: - tag[k] = float(v) + tags[k] = float(v) except: pass + """ try: return str(eval(self.expr, {}, { "tag": lambda x: tags.get(x, ""), @@ -165,6 +167,8 @@ def m_metric(x, t): return float(x[0:-1]) * float(t) except: return "" + + # def str(x): #""" # str() MapCSS feature diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index 2a30bd3..e2858d8 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -44,7 +44,6 @@ class Rule(): return False subpart = "::default" - for condition in self.conditions: res = condition.test(tags) if not res: @@ -72,6 +71,14 @@ class Rule(): a.update(condition.get_interesting_tags()) return a + def extract_tags(self): + a = set() + for condition in self.conditions: + a.update(condition.extract_tags()) + if "*" in a: + break + return a + def get_numerics(self): a = set() for condition in self.conditions: diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 39676e9..b2cfb72 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -20,13 +20,15 @@ from Rule import Rule from webcolors.webcolors import whatever_to_cairo as colorparser from webcolors.webcolors import cairo_to_hex from Eval import Eval +from Condition import * +TYPE_EVAL = type(Eval()) def make_nice_style(r): ra = {} for a, b in r.iteritems(): "checking and nicifying style table" - if type(b) == type(Eval()): + if type(b) == TYPE_EVAL: ra[a] = b elif "color" in a: "parsing color value to 3-tuple" @@ -80,7 +82,7 @@ class StyleChooser: def __init__(self, scalepair): self.ruleChains = [] self.styles = [] - self.eval_type = type(Eval()) + self.eval_type = TYPE_EVAL self.scalepair = scalepair self.selzooms = None self.compatible_types = set() @@ -111,6 +113,24 @@ class StyleChooser: a.update(b.extract_tags()) return a + def extract_tags(self): + a = set() + for r in self.ruleChains: + a.update(r.extract_tags()) + if "*" in a: + a.clear() + a.add("*") + break + if self.has_evals and "*" not in a: + for s in self.styles: + for v in s.values(): + if type(v) == self.eval_type: + a.update(v.extract_tags()) + if "*" in a or len(a) == 0: + a.clear() + a.add("*") + return a + def get_sql_hints(self, type, zoom): """ Returns a set of tags that were used in here in form of SQL-hints. @@ -189,6 +209,7 @@ class StyleChooser: if not hasall: allinit.update(ra) sl.append(allinit) + return sl def testChain(self, chain, obj, tags, zoom): @@ -208,6 +229,7 @@ class StyleChooser: pass def newObject(self, e=''): + # print "newRule" """ adds into the current ruleChain (starting a new Rule) """ @@ -217,6 +239,7 @@ class StyleChooser: self.ruleChains.append(rule) def addZoom(self, z): + # print "addZoom ", float(z[0]), ", ", float(z[1]) """ adds into the current ruleChain (existing Rule) """ @@ -224,12 +247,15 @@ class StyleChooser: self.ruleChains[-1].maxZoom = float(z[1]) def addCondition(self, c): + # print "addCondition ", c """ adds into the current ruleChain (existing Rule) """ + c = OptimizeCondition(c) self.ruleChains[-1].conditions.append(c) def addStyles(self, a): + # print "addStyle ", a """ adds to this.styles """ diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index f1de7f4..427c9db 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -27,7 +27,8 @@ from StyleChooser import StyleChooser from Condition import Condition -NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"]) +NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", + "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"]) WHITESPACE = re.compile(r'^ \s+ ', re.S | re.X) @@ -88,6 +89,7 @@ CENTER = re.compile(r'^center$/i') HEX = re.compile(r'^#([0-9a-f]+)$/i') VARIABLE = re.compile(r'@([a-z][\w\d]*)') + class MapCSS(): def __init__(self, minscale=0, maxscale=19): """ @@ -99,6 +101,7 @@ class MapCSS(): self.scalepair = (minscale, maxscale) self.choosers = [] self.choosers_by_type = {} + self.choosers_by_type_and_tag = {} self.variables = {} self.style_loaded = False @@ -114,7 +117,29 @@ class MapCSS(): else: logging.error("unparsed zoom: %s" % s) - def get_style(self, type, tags={}, zoom=0, scale=1, zscale=.5, cache=True): + def build_choosers_tree(self, clname, type, tags={}): + if type not in self.choosers_by_type_and_tag: + self.choosers_by_type_and_tag[type] = {} + if clname not in self.choosers_by_type_and_tag[type]: + self.choosers_by_type_and_tag[type][clname] = set() + if type in self.choosers_by_type: + for chooser in self.choosers_by_type[type]: + for tag in chooser.extract_tags(): + if tag == "*" or tag in tags: + if chooser not in self.choosers_by_type_and_tag[type][clname]: + self.choosers_by_type_and_tag[type][clname].add(chooser) + break + + def restore_choosers_order(self, type): + ethalon_choosers = self.choosers_by_type[type] + for tag, choosers_for_tag in self.choosers_by_type_and_tag[type].items(): + tmp = [] + for ec in ethalon_choosers: + if ec in choosers_for_tag: + 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, cache=True): """ Kothic styling API """ @@ -123,11 +148,11 @@ class MapCSS(): if shash in self.cache["style"]: return deepcopy(self.cache["style"][shash]) style = [] - if type in self.choosers_by_type: - for chooser in self.choosers_by_type[type]: + 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) style = [x for x in style if x["object-id"] != "::*"] - st = [] for x in style: for k, v in [('width', 0), ('casing-width', 0)]: if k in x: @@ -143,8 +168,8 @@ class MapCSS(): self.cache["style"][shash] = deepcopy(style) return style - def get_style_dict(self, type, tags={}, zoom=0, scale=1, zscale=.5, olddict={}, cache=True): - r = self.get_style(type, tags, zoom, scale, zscale, cache) + def get_style_dict(self, clname, type, tags={}, zoom=0, scale=1, zscale=.5, olddict={}, cache=True): + r = self.get_style(clname, type, tags, zoom, scale, zscale, cache) d = olddict for x in r: if x.get('object-id', '') not in d: @@ -175,17 +200,16 @@ class MapCSS(): def subst_variables(self, t): """Expects an array from parseDeclaration.""" - for k in t[0]: + for k in t[0]: t[0][k] = VARIABLE.sub(self.get_variable, t[0][k]) return t def get_variable(self, m): name = m.group()[1:] if not name in self.variables: - logging.error("Variable not found: {}".format(name)) + 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): """ Parses MapCSS given as string @@ -197,122 +221,139 @@ class MapCSS(): css = open(filename).read() if not self.style_loaded: self.choosers = [] + log = logging.getLogger('mapcss.parser') previous = 0 # what was the previous CSS word? sc = StyleChooser(self.scalepair) # currently being assembled - css_orig = css - css = css.strip() - while (css): - # Class - :motorway, :builtup, :hover - if CLASS.match(css): - if previous == oDECLARATION: - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) + stck = [] # filename, original, remained + stck.append([filename, css, css]) + try: + while (len(stck) > 0): + css = stck[-1][1].lstrip() # remained - cond = CLASS.match(css).groups()[0] - log.debug("class found: %s" % (cond)) - css = CLASS.sub("", css) + wasBroken = False + while (css): + # Class - :motorway, :builtup, :hover + if CLASS.match(css): + if previous == oDECLARATION: + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) + cond = CLASS.match(css).groups()[0] + log.debug("class found: %s" % (cond)) + css = CLASS.sub("", css) + sc.addCondition(Condition('eq', ("::class", cond))) + previous = oCONDITION - sc.addCondition(Condition('eq', ("::class", cond))) - previous = oCONDITION + ## Not class - !.motorway, !.builtup, !:hover + #elif NOT_CLASS.match(css): + #if (previous == oDECLARATION): + #self.choosers.append(sc) + #sc = StyleChooser(self.scalepair) + #cond = NOT_CLASS.match(css).groups()[0] + #log.debug("not_class found: %s" % (cond)) + #css = NOT_CLASS.sub("", css) + #sc.addCondition(Condition('ne', ("::class", cond))) + #previous = oCONDITION - ## Not class - !.motorway, !.builtup, !:hover - #elif NOT_CLASS.match(css): - #if (previous == oDECLARATION): - #self.choosers.append(sc) - #sc = StyleChooser(self.scalepair) + # 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 = oZOOM - #cond = NOT_CLASS.match(css).groups()[0] - #log.debug("not_class found: %s" % (cond)) - #css = NOT_CLASS.sub("", css) - #sc.addCondition(Condition('ne', ("::class", cond))) - #previous = oCONDITION + # Grouping - just a comma + elif GROUP.match(css): + css = GROUP.sub("", css) + sc.newGroup() + previous = oGROUP - # Zoom - elif ZOOM.match(css): - if (previous != oOBJECT & previous != oCONDITION): - sc.newObject() + # Condition - [highway=primary] + elif 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 = CONDITION.match(css).groups()[0] + log.debug("condition found: %s" % (cond)) + css = CONDITION.sub("", css) + sc.addCondition(parseCondition(cond)) + previous = oCONDITION - cond = ZOOM.match(css).groups()[0] - log.debug("zoom found: %s" % (cond)) - css = ZOOM.sub("", css) - sc.addZoom(self.parseZoom(cond)) - previous = oZOOM + # Object - way, node, relation + elif OBJECT.match(css): + if (previous == oDECLARATION): + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) + obj = OBJECT.match(css).groups()[0] + log.debug("object found: %s" % (obj)) + css = OBJECT.sub("", css) + sc.newObject(obj) + previous = oOBJECT - # Grouping - just a comma - elif GROUP.match(css): - css = GROUP.sub("", css) - sc.newGroup() - previous = oGROUP + # Declaration - {...} + elif DECLARATION.match(css): + decl = DECLARATION.match(css).groups()[0] + log.debug("declaration found: %s" % (decl)) + sc.addStyles(self.subst_variables(parseDeclaration(decl))) + css = DECLARATION.sub("", css) + previous = oDECLARATION - # Condition - [highway=primary] - elif 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 = CONDITION.match(css).groups()[0] - log.debug("condition found: %s" % (cond)) - css = CONDITION.sub("", css) - sc.addCondition(parseCondition(cond)) - previous = oCONDITION + # CSS comment + elif COMMENT.match(css): + log.debug("comment found") + css = COMMENT.sub("", css) - # Object - way, node, relation - elif OBJECT.match(css): - if (previous == oDECLARATION): - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) - obj = OBJECT.match(css).groups()[0] - log.debug("object found: %s" % (obj)) - css = OBJECT.sub("", css) - sc.newObject(obj) - previous = oOBJECT + # @import("filename.css"); + elif IMPORT.match(css): + log.debug("import found") + import_filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) + try: + css = IMPORT.sub("", css) + import_text = open(import_filename, "r").read() + stck[-1][1] = css # store remained part + stck.append([import_filename, import_text, import_text]) + wasBroken = True + break + except IOError as e: + raise Exception("Cannot import file " + import_filename + "\n" + str(e)) - # Declaration - {...} - elif DECLARATION.match(css): - decl = DECLARATION.match(css).groups()[0] - log.debug("declaration found: %s" % (decl)) - sc.addStyles(self.subst_variables(parseDeclaration(decl))) - css = DECLARATION.sub("", css) - previous = oDECLARATION + # Variables + elif VARIABLE_SET.match(css): + name = VARIABLE_SET.match(css).groups()[0] + log.debug("variable set found: %s" % name) + self.variables[name] = VARIABLE_SET.match(css).groups()[1] + css = VARIABLE_SET.sub("", css) + previous = oVARIABLE_SET - # CSS comment - elif COMMENT.match(css): - log.debug("comment found") - css = COMMENT.sub("", css) + # Unknown pattern + elif UNKNOWN.match(css): + raise Exception("Unknown construction: " + UNKNOWN.match(css).group()) - # @import("filename.css"); - elif IMPORT.match(css): - log.debug("import found") - filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) - try: - css = IMPORT.sub("", css) - import_text = open(filename, "r").read().strip() - css = import_text + css - except IOError as e: - log.warning("cannot import file %s: %s" % (filename, e)) + # Must be unreacheable + else: + raise Exception("Unexpected construction: " + css) - elif VARIABLE_SET.match(css): - name = VARIABLE_SET.match(css).groups()[0] - log.debug("variable set found: %s" % name) - self.variables[name] = VARIABLE_SET.match(css).groups()[1] - css = VARIABLE_SET.sub("", css) - previous = oVARIABLE_SET + if not wasBroken: + stck.pop() - # Unknown pattern - elif UNKNOWN.match(css): - log.warning("unknown thing found on line %s: %s" % (unicode(css_orig[:-len(unicode(css))]).count("\n") + 1, UNKNOWN.match(css).group())) - css = UNKNOWN.sub("", css) + if (previous == oDECLARATION): + self.choosers.append(sc) + sc = StyleChooser(self.scalepair) - else: - log.warning("choked on: %s" % (css)) - return + except Exception as e: + filename = stck[-1][0] # filename + css_orig = stck[-1][2] # original + css = stck[-1][1] # remained + line = unicode(css_orig[:-len(unicode(css))]).count("\n") + 1 + msg = str(e) + "\nFile: " + filename + "\nLine: " + str(line) + raise Exception(msg) - if (previous == oDECLARATION): - self.choosers.append(sc) - sc = StyleChooser(self.scalepair) try: if clamp: "clamp z-indexes, so they're tightly following integers" @@ -331,9 +372,9 @@ class MapCSS(): stylez['z-index'] = 1. * res / len(zindex) * stretch else: stylez['z-index'] = res - except TypeError: pass + for chooser in self.choosers: for t in chooser.compatible_types: if t not in self.choosers_by_type: @@ -344,10 +385,12 @@ class MapCSS(): def parseCondition(s): log = logging.getLogger('mapcss.parser.condition') + if CONDITION_TRUE.match(s): a = CONDITION_TRUE.match(s).groups() log.debug("condition true: %s" % (a[0])) return Condition('true', a) + if CONDITION_invTRUE.match(s): a = CONDITION_invTRUE.match(s).groups() log.debug("condition invtrue: %s" % (a[0])) @@ -404,16 +447,14 @@ def parseCondition(s): return Condition('eq', a) else: - log.warning("condition UNKNOWN: %s" % (s)) + raise Exception("condition UNKNOWN: " + s) def parseDeclaration(s): """ Parse declaration string into list of styles """ - styles = [] t = {} - for a in s.split(';'): # if ((o=ASSIGNMENT_EVAL.exec(a))) { t[o[1].replace(DASH,'_')]=new Eval(o[2]); } if ASSIGNMENT.match(a): diff --git a/src/timer.py b/src/timer.py new file mode 100644 index 0000000..5da95b7 --- /dev/null +++ b/src/timer.py @@ -0,0 +1,42 @@ +from timeit import default_timer + +class Timer(object): + def __init__(self): + self.timer = default_timer + self.start = self.timer() + + def Reset(self): + self.start = self.timer() + + def ElapsedMsec(self): + elapsed_secs = self.timer() - self.start + return elapsed_secs * 1000 + + def ElapsedSec(self): + elapsed_secs = self.timer() - self.start + return elapsed_secs + +class AccumulativeTimer(object): + def __init__(self): + self.timer = default_timer + self.elapsed_secs = 0 + self.start = 0 + self.count = 0 + + def Start(self): + self.start = self.timer() + + def Stop(self): + self.elapsed_secs += self.timer() - self.start + self.start = 0 + self.count += 1 + + def ElapsedMsec(self): + elapsed_msec = self.elapsed_secs * 1000 + return elapsed_msec + + def ElapsedSec(self): + return self.elapsed_secs + + def Count(self): + return self.count