Speed optimizations #8

Merged
root merged 5 commits from pastk-optimize into master 2023-02-10 15:31:30 +00:00
5 changed files with 129 additions and 146 deletions

View file

@ -72,18 +72,31 @@ def query_style(args):
results = []
for zoom in range(minzoom, maxzoom + 1):
runtime_conditions_arr = []
all_runtime_conditions_arr = []
# 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))
all_runtime_conditions_arr.extend(style.get_runtime_rules(clname, "line", cltags, zoom))
all_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))
all_runtime_conditions_arr.extend(style.get_runtime_rules(clname, "node", cltags, zoom))
# If there is no any runtime conditions, do not filter style by runtime conditions
if len(runtime_conditions_arr) == 0:
runtime_conditions_arr = []
if len(all_runtime_conditions_arr) == 0:
# If there is no runtime conditions, do not filter style by runtime conditions
runtime_conditions_arr.append(None)
elif len(all_runtime_conditions_arr) == 1:
runtime_conditions_arr = all_runtime_conditions_arr
else:
# Keep unique conditions only
runtime_conditions_arr.append(all_runtime_conditions_arr.pop(0))
for new_rt_conditions in all_runtime_conditions_arr:
conditions_unique = True
for rt_conditions in runtime_conditions_arr:
if new_rt_conditions == rt_conditions:
conditions_unique = False
break
if conditions_unique:
runtime_conditions_arr.append(new_rt_conditions)
for runtime_conditions in runtime_conditions_arr:
has_icons_for_areas = False
@ -224,21 +237,24 @@ def komap_mapswithme(options):
# Parse style mapcss
global style
style = MapCSS(options.minzoom, options.maxzoom + 1)
style = MapCSS(options.minzoom, options.maxzoom)
style.parse(filename=options.filename, static_tags=mapcss_static_tags,
dynamic_tags=mapcss_dynamic_tags)
# Build optimization tree - class/type -> StyleChoosers
# Build optimization tree - class/zoom/type -> StyleChoosers
clname_cltag_unique = set()
for cl in class_order:
clname = cl if cl.find('-') == -1 else cl[:cl.find('-')]
# Get first tag of the class/type.
cltag = next(iter(classificator[cl].keys()))
style.build_choosers_tree(clname, "line", cltag)
style.build_choosers_tree(clname, "area", cltag)
style.build_choosers_tree(clname, "node", cltag)
style.restore_choosers_order("line")
style.restore_choosers_order("area")
style.restore_choosers_order("node")
clname_cltag = clname + '$' + cltag
if clname_cltag not in clname_cltag_unique:
clname_cltag_unique.add(clname_cltag)
style.build_choosers_tree(clname, "line", cltag)
style.build_choosers_tree(clname, "area", cltag)
style.build_choosers_tree(clname, "node", cltag)
style.finalize_choosers_tree()
# Get colors section from style
style_colors = {}

View file

@ -33,42 +33,44 @@ class Condition:
def test(self, tags):
"""
Test a hash against this condition
Test tags against this condition
"""
t = self.type
params = self.params
if t == 'eq': # don't compare tags against sublayers
if t == 'eq':
# Don't compare tags against sublayers
if params[0][:2] == "::":
return params[1]
try:
if t == 'eq':
return tags[params[0]] == params[1]
if t == 'ne':
return tags.get(params[0], "") != params[1]
if t == 'regex':
return bool(self.regex.match(tags[params[0]]))
if t == 'true':
return tags.get(params[0]) == 'yes'
if t == 'untrue':
return tags.get(params[0]) == 'no'
if t == 'set':
if params[0] in tags:
return tags[params[0]] != ''
return 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]))
except KeyError:
pass
return (params[0] in tags and tags[params[0]] == params[1])
if t == 'ne':
return (params[0] not in tags or tags[params[0]] != params[1])
if t == 'true':
return tags.get(params[0]) == 'yes'
if t == 'untrue':
return tags.get(params[0]) == 'no'
if t == 'set':
if params[0] in tags:
return tags[params[0]] != ''
return False
if t == 'unset':
if params[0] in tags:
return tags[params[0]] == ''
return True
if params[0] not in tags:
return False
if t == 'regex':
return bool(self.regex.match(tags[params[0]]))
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):

View file

@ -25,7 +25,7 @@ type_matches = {
class Rule():
def __init__(self, s=''):
self.runtime_conditions = []
self.runtime_conditions = None
self.conditions = []
# self.isAnd = True
self.minZoom = 0
@ -33,17 +33,12 @@ class Rule():
if s == "*":
s = ""
self.subject = s # "", "way", "node" or "relation"
self.type_matches = type_matches[s] if s in type_matches else set()
def __repr__(self):
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):
return False
if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags):
return False
def test(self, tags):
subpart = "::default"
for condition in self.conditions:
res = condition.test(tags)
@ -66,28 +61,3 @@ class Rule():
return set(["*"])
return a
def _test_feature_compatibility(f1, f2, tags={}):
"""
Checks if feature of type f1 is compatible with f2.
"""
if f2 == f1:
return True
if f2 not in ("way", "area", "line"):
return False
elif f2 == "way" and f1 == "line":
return True
elif f2 == "way" and f1 == "area":
return True
elif f2 == "area" and f1 in ("way", "area"):
# if ":area" in tags:
return True
# else:
# return False
elif f2 == "line" and f1 in ("way", "line", "area"):
return True
else:
return False
# print f1, f2, True
return True

View file

@ -87,6 +87,7 @@ class StyleChooser:
self.selzooms = None
self.compatible_types = set()
self.has_evals = False
self.has_runtime_conditions = False
self.cached_tags = None
def extract_tags(self):
@ -96,62 +97,34 @@ class StyleChooser:
for r in self.ruleChains:
a.update(r.extract_tags())
if "*" in a:
a.clear()
a.add("*")
a = set('*')
break
if self.has_evals and "*" not in a:
for s in self.styles:
for v in list(s.values()):
if type(v) == self.eval_type:
a.update(v.extract_tags())
if "*" in a or len(a) == 0:
a.clear()
a.add("*")
if len(a) == 0:
a = set('*')
self.cached_tags = a
return a
def get_runtime_conditions(self, ftype, tags, zoom):
if self.selzooms:
if zoom < self.selzooms[0] or zoom > self.selzooms[1]:
return None
def get_runtime_conditions(self, tags):
if not self.has_runtime_conditions:
return None
rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
rule_and_object_id = self.testChains(tags)
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 isCorrespondingRule(self, filter_by_runtime_conditions, rule):
# If rule can be applied according to runtime conditions, then
# function return true, else it returns false
if len(rule.runtime_conditions) == 0:
return True
if filter_by_runtime_conditions is None:
return True
if filter_by_runtime_conditions == rule.runtime_conditions:
return True
# Actually we should check rule.runtime_conditions is a subset of filter_by_runtime_conditions
for r in rule.runtime_conditions:
if r not in filter_by_runtime_conditions:
return False
return True
def updateStyles(self, sl, ftype, tags, zoom, xscale, zscale, filter_by_runtime_conditions):
def updateStyles(self, sl, tags, 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]:
return sl
#if ftype not in self.compatible_types:
#return sl
rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom)
rule_and_object_id = self.testChains(tags)
if not rule_and_object_id:
return sl
@ -159,7 +132,9 @@ class StyleChooser:
rule = rule_and_object_id[0]
object_id = rule_and_object_id[1]
if not self.isCorrespondingRule(filter_by_runtime_conditions, rule):
if (filter_by_runtime_conditions is not None
and rule.runtime_conditions is not None
and filter_by_runtime_conditions != rule.runtime_conditions):
return sl
for r in self.styles:
@ -203,12 +178,12 @@ class StyleChooser:
return sl
def testChain(self, chain, obj, tags, zoom):
def testChains(self, tags):
"""
Tests an object against a chain
"""
for r in chain:
tt = r.test(obj, tags, zoom)
for r in self.ruleChains:
tt = r.test(tags)
if tt:
return r, tt
return False
@ -249,8 +224,11 @@ class StyleChooser:
"""
adds into the current ruleChain (existing Rule)
"""
self.ruleChains[-1].runtime_conditions.append(c)
self.ruleChains[-1].runtime_conditions.sort()
if self.ruleChains[-1].runtime_conditions is None:
self.ruleChains[-1].runtime_conditions = [c]
self.has_runtime_conditions = True
else:
self.ruleChains[-1].runtime_conditions.append(c)
def addStyles(self, a):
# print "addStyle ", a

View file

@ -96,7 +96,7 @@ class MapCSS():
self.scalepair = (minscale, maxscale)
self.choosers = []
self.choosers_by_type = {}
self.choosers_by_type_and_tag = {}
self.choosers_by_type_zoom_tag = {}
self.variables = {}
self.style_loaded = False
@ -113,44 +113,61 @@ class MapCSS():
logging.error("unparsed zoom: %s" % s)
def build_choosers_tree(self, clname, type, cltag):
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 not in self.choosers_by_type_zoom_tag:
self.choosers_by_type_zoom_tag[type] = {}
for zoom in range(self.minscale, self.maxscale + 1):
if zoom not in self.choosers_by_type_zoom_tag[type]:
self.choosers_by_type_zoom_tag[type][zoom] = {}
if clname not in self.choosers_by_type_zoom_tag[type][zoom]:
self.choosers_by_type_zoom_tag[type][zoom][clname] = {'arr': [], 'set': 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 == cltag:
if chooser not in self.choosers_by_type_and_tag[type][clname]:
self.choosers_by_type_and_tag[type][clname].add(chooser)
break
chooser_tags = chooser.extract_tags()
if '*' in chooser_tags or cltag in chooser_tags:
for zoom in range(int(chooser.selzooms[0]), int(chooser.selzooms[1]) + 1):
if chooser not in self.choosers_by_type_zoom_tag[type][zoom][clname]['set']:
self.choosers_by_type_zoom_tag[type][zoom][clname]['arr'].append(chooser)
self.choosers_by_type_zoom_tag[type][zoom][clname]['set'].add(chooser)
def finalize_choosers_tree(self):
for ftype in self.choosers_by_type_zoom_tag.keys():
for zoom in self.choosers_by_type_zoom_tag[ftype].keys():
for clname in self.choosers_by_type_zoom_tag[ftype][zoom].keys():
# Discard unneeded unique set of choosers.
self.choosers_by_type_zoom_tag[ftype][zoom][clname] = self.choosers_by_type_zoom_tag[ftype][zoom][clname]['arr']
for i in range(0, len(self.choosers_by_type_zoom_tag[ftype][zoom][clname])):
chooser = self.choosers_by_type_zoom_tag[ftype][zoom][clname][i]
optimized = StyleChooser(chooser.scalepair)
optimized.styles = chooser.styles
optimized.eval_type = chooser.eval_type
optimized.has_evals = chooser.has_evals
optimized.has_runtime_conditions = chooser.has_runtime_conditions
optimized.selzooms = [zoom, zoom]
optimized.ruleChains = []
for rule in chooser.ruleChains:
# Discard chooser's rules that don't match type or zoom.
if ftype in rule.type_matches and zoom >= rule.minZoom and zoom <= rule.maxZoom:
optimized.ruleChains.append(rule)
self.choosers_by_type_zoom_tag[ftype][zoom][clname][i] = optimized
def restore_choosers_order(self, type):
ethalon_choosers = self.choosers_by_type[type]
for tag, choosers_for_tag in list(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_runtime_rules(self, clname, type, tags, zoom):
"""
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 type in self.choosers_by_type_zoom_tag:
for chooser in self.choosers_by_type_zoom_tag[type][zoom][clname]:
runtime_conditions = chooser.get_runtime_conditions(tags)
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:
for chooser in self.choosers_by_type_and_tag[type][clname]:
style = chooser.updateStyles(style, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions)
if type in self.choosers_by_type_zoom_tag:
for chooser in self.choosers_by_type_zoom_tag[type][zoom][clname]:
style = chooser.updateStyles(style, tags, 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)]: