From 7c1cf3e99344c6e50165b62637fe422bb8eb54d9 Mon Sep 17 00:00:00 2001 From: Harry Bond Date: Wed, 18 Jan 2023 13:39:51 +0000 Subject: [PATCH 01/46] explicitly state encoding for windows --- src/libkomwm.py | 18 +++++++++--------- src/mapcss/__init__.py | 4 ++-- 2 files changed, 11 insertions(+), 11 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 01bdefc..eb66880 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -120,7 +120,7 @@ def komap_mapswithme(options): colors_file_name = os.path.join(ddir, 'colors.txt') colors = set() if os.path.exists(colors_file_name): - colors_in_file = open(colors_file_name, "r") + colors_in_file = open(colors_file_name, "r", encoding="utf-8") for colorLine in colors_in_file: colors.add(int(colorLine)) colors_in_file.close() @@ -132,13 +132,13 @@ def komap_mapswithme(options): patterns_file_name = os.path.join(ddir, 'patterns.txt') if os.path.exists(patterns_file_name): - patterns_in_file = open(patterns_file_name, "r") + patterns_in_file = open(patterns_file_name, "r", encoding="utf-8") for patternsLine in patterns_in_file: addPattern([float(x) for x in patternsLine.split()]) patterns_in_file.close() # Build classificator tree from mapcss-mapping.csv file - types_file = open(os.path.join(ddir, 'types.txt'), "w") + types_file = open(os.path.join(ddir, 'types.txt'), "w", encoding="utf-8") # Mapcss-mapping format # @@ -163,7 +163,7 @@ def komap_mapswithme(options): cnt = 1 unique_types_check = set() - for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'): + for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv'), encoding="utf-8"), delimiter=';'): if len(row) <= 1: # Allow for empty lines and comments that do not contain ';' symbol continue @@ -220,7 +220,7 @@ def komap_mapswithme(options): mapcss_static_tags[t] = mapcss_static_tags.get(t, True) and i == 0 # Get all mapcss dynamic tags from mapcss-dynamic.txt - mapcss_dynamic_tags = set([line.rstrip() for line in open(os.path.join(ddir, 'mapcss-dynamic.txt'))]) + mapcss_dynamic_tags = set([line.rstrip() for line in open(os.path.join(ddir, 'mapcss-dynamic.txt'), encoding="utf-8")]) # Parse style mapcss global style @@ -557,8 +557,8 @@ def komap_mapswithme(options): return -1 viskeys.sort(key=functools.cmp_to_key(cmprepl)) - visibility_file = open(os.path.join(ddir, 'visibility.txt'), "w") - classificator_file = open(os.path.join(ddir, 'classificator.txt'), "w") + visibility_file = open(os.path.join(ddir, 'visibility.txt'), "w", encoding="utf-8") + classificator_file = open(os.path.join(ddir, 'classificator.txt'), "w", encoding="utf-8") oldoffset = "" for k in viskeys: @@ -579,12 +579,12 @@ def komap_mapswithme(options): visibility_file.close() classificator_file.close() - colors_file = open(colors_file_name, "w") + colors_file = open(colors_file_name, "w", encoding="utf-8") for c in sorted(colors): colors_file.write("%d\n" % (c)) colors_file.close() - patterns_file = open(patterns_file_name, "w") + patterns_file = open(patterns_file_name, "w", encoding="utf-8") for p in patterns: patterns_file.write("%s\n" % (' '.join(str(elem) for elem in p))) patterns_file.close() diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index a5a84a3..b102260 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -202,7 +202,7 @@ class MapCSS(): if filename: basepath = os.path.dirname(filename) if not css: - css = open(filename).read() + css = open(filename, encoding="utf-8").read() if not self.style_loaded: self.choosers = [] @@ -322,7 +322,7 @@ class MapCSS(): import_filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) try: css = IMPORT.sub("", css, 1) - import_text = open(import_filename, "r").read() + import_text = open(import_filename, "r", encoding="utf-8").read() stck[-1][1] = css # store remained part stck.append([import_filename, import_text, import_text]) wasBroken = True -- 2.45.3 From 32cc5aafb63874ed5961412e947594870fe1eaa8 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 17 Jan 2023 21:26:31 +0000 Subject: [PATCH 02/46] Add casing-width-add support Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 18 +++++++++++++++--- src/mapcss/Condition.py | 2 +- src/mapcss/StyleChooser.py | 3 --- src/mapcss/__init__.py | 2 +- 4 files changed, 17 insertions(+), 8 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 01bdefc..41dcf73 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -296,7 +296,7 @@ def komap_mapswithme(options): zstyle.sort(key = rule_sort_key) # For debug purpose. - # if str(cl) == 'entrance' and int(zoom) == 19: + # if str(cl) == 'highway-path' and int(zoom) == 19: # print(cl) # print(zstyle) @@ -354,11 +354,23 @@ def komap_mapswithme(options): 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-width') not in (None, 0) or st.get('casing-width-add') is not None: # and (st.get('width') or st.get('fill-color')): is_area_st = 'fill-color' in st if has_lines and not is_area_st and 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) + + # 'casing-width' has precedence over 'casing-width-add'. + if st.get('casing-width-add') is not None and st.get('casing-width') in (None, 0): + # Look for base width in other style objects (usually ::default). + base_width = 0 + for wst in zstyle: + if wst.get('width') not in (None, 0): + # Rail bridge styles use width from ::dash object instead of ::default. + if base_width == 0 or wst.get('object-id') != '::default': + base_width = wst.get('width', 0) + st['casing-width'] = base_width + st.get('casing-width-add') + + dr_line.width = round((st.get('width', 0) + st.get('casing-width') * 2) * WIDTH_SCALE, 2) dr_line.color = mwm_encode_color(colors, st, "casing") if '-x-me-casing-line-priority' in st: dr_line.priority = int(st.get('-x-me-casing-line-priority')) diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index 1612480..9bed7d4 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -75,7 +75,7 @@ class Condition: t = self.type params = self.params if t == 'eq' and params[0][:2] == "::": - return "::%s" % (params[1]) + return "%s" % (params[1]) if t == 'eq': return "%s=%s" % (params[0], params[1]) if t == 'ne': diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 9291e6b..2444152 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -277,9 +277,6 @@ class StyleChooser: b = str(float(b) / 2) except: pass - if "text" == a[-4:]: - if b[:5] != "eval(": - b = "eval(tag(\"" + b + "\"))" if b[:5] == "eval(": b = Eval(b) self.has_evals = True diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index a5a84a3..d2bcfb1 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -22,7 +22,7 @@ from .StyleChooser import StyleChooser from .Condition import Condition -NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", +NEEDED_KEYS = set(["width", "casing-width", "casing-width-add", "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) -- 2.45.3 From 471abbd75447624fd40880f2888c731bf0a612b0 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 20 Jan 2023 13:31:47 +0000 Subject: [PATCH 03/46] Look for missing base_width to use with casing-width Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 41dcf73..b7b0641 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -359,18 +359,19 @@ def komap_mapswithme(options): if has_lines and not is_area_st and st.get('casing-linecap', 'butt') == 'butt': dr_line = LineRuleProto() - # 'casing-width' has precedence over 'casing-width-add'. - if st.get('casing-width-add') is not None and st.get('casing-width') in (None, 0): - # Look for base width in other style objects (usually ::default). - base_width = 0 + base_width = st.get('width', 0) + if base_width == 0: for wst in zstyle: if wst.get('width') not in (None, 0): # Rail bridge styles use width from ::dash object instead of ::default. if base_width == 0 or wst.get('object-id') != '::default': base_width = wst.get('width', 0) - st['casing-width'] = base_width + st.get('casing-width-add') + # 'casing-width' has precedence over 'casing-width-add'. + if st.get('casing-width') in (None, 0): + st['casing-width'] = base_width + st.get('casing-width-add') + base_width = 0 - dr_line.width = round((st.get('width', 0) + st.get('casing-width') * 2) * WIDTH_SCALE, 2) + dr_line.width = round((base_width + st.get('casing-width') * 2) * WIDTH_SCALE, 2) dr_line.color = mwm_encode_color(colors, st, "casing") if '-x-me-casing-line-priority' in st: dr_line.priority = int(st.get('-x-me-casing-line-priority')) -- 2.45.3 From a6daa7121b55658346ce155e712adaa9737aa0b6 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 20 Jan 2023 22:52:09 +0000 Subject: [PATCH 04/46] Make per-tag sets of selectors more precise and small Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 9 +++++---- src/mapcss/Rule.py | 10 ++++++---- src/mapcss/__init__.py | 4 ++-- 3 files changed, 13 insertions(+), 10 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index b7b0641..3b93298 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -231,10 +231,11 @@ def komap_mapswithme(options): # 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) + # 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") diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index a652f30..ede2543 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -59,10 +59,12 @@ class Rule(): def extract_tags(self): a = set() for condition in self.conditions: - a.add(condition.extract_tag()) - if "*" in a: - a = set(["*"]) - break + tag = condition.extract_tag() + if tag != '*': + a.add(tag) + elif len(a) == 0: + return set(["*"]) + return a diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index d2bcfb1..13bfd13 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -112,7 +112,7 @@ class MapCSS(): else: logging.error("unparsed zoom: %s" % s) - def build_choosers_tree(self, clname, type, tags={}): + 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]: @@ -120,7 +120,7 @@ class MapCSS(): 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 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 -- 2.45.3 From 4e298922829add7313f3f7f09f96e3c8eea69763 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 22 Jan 2023 16:32:37 +0000 Subject: [PATCH 05/46] Optimize looking for runtime conditions in selectors Signed-off-by: Konstantin Pastbin --- src/mapcss/StyleChooser.py | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 2444152..091bf13 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -111,6 +111,14 @@ class StyleChooser: return a def get_runtime_conditions(self, ftype, tags, zoom): + has_rt_conds = False + for rule in self.ruleChains: + if (len(rule.runtime_conditions) > 0): + has_rt_conds = True + break + if not has_rt_conds: + return None + if self.selzooms: if zoom < self.selzooms[0] or zoom > self.selzooms[1]: return None @@ -143,7 +151,6 @@ class StyleChooser: return True 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]: return sl @@ -151,6 +158,7 @@ class StyleChooser: #if ftype not in self.compatible_types: #return sl + # Are any of the ruleChains fulfilled? rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom) if not rule_and_object_id: -- 2.45.3 From 41498a6ec5fa7c8d8f1f1e8626a25ceb778c1d29 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 22 Jan 2023 21:24:45 +0000 Subject: [PATCH 06/46] Process unique runtime conditions once only Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 27 ++++++++++++++++++++------- 1 file changed, 20 insertions(+), 7 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 3b93298..52b1274 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -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 -- 2.45.3 From 70fa78f39bafef271a3dba964d676fd420ac836b Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 25 Jan 2023 18:56:56 +0000 Subject: [PATCH 07/46] Optimize frequently called functions Signed-off-by: Konstantin Pastbin --- src/mapcss/Condition.py | 64 ++++++++++++++++++++------------------ src/mapcss/Rule.py | 30 ++---------------- src/mapcss/StyleChooser.py | 55 ++++++++++---------------------- 3 files changed, 53 insertions(+), 96 deletions(-) diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index 9bed7d4..a3b84cb 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -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): diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index ede2543..0966310 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -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,6 +33,7 @@ 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) @@ -41,7 +42,7 @@ class Rule(): if (zoom < self.minZoom) or (zoom > self.maxZoom): return False - if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): + if (obj not in self.type_matches): return False subpart = "::default" @@ -66,28 +67,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 diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 091bf13..6af83c1 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -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,32 +97,24 @@ 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): - has_rt_conds = False - for rule in self.ruleChains: - if (len(rule.runtime_conditions) > 0): - has_rt_conds = True - break - if not has_rt_conds: + if not self.has_runtime_conditions: return None - if self.selzooms: - if zoom < self.selzooms[0] or zoom > self.selzooms[1]: - return None + if zoom < self.selzooms[0] or zoom > self.selzooms[1]: + return None rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom) @@ -130,30 +123,11 @@ class StyleChooser: 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): - if self.selzooms: - if zoom < self.selzooms[0] or zoom > self.selzooms[1]: - return sl + if zoom < self.selzooms[0] or zoom > self.selzooms[1]: + return sl #if ftype not in self.compatible_types: #return sl @@ -167,7 +141,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: @@ -257,8 +233,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 -- 2.45.3 From 27b41b5e3fb4102d13ca1039146db5338c27a3c8 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 30 Jan 2023 18:48:52 +0000 Subject: [PATCH 08/46] Add zoom into choosers optimization tree Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 19 +++++++++------- src/mapcss/StyleChooser.py | 6 ----- src/mapcss/__init__.py | 46 ++++++++++++++++++++------------------ 3 files changed, 35 insertions(+), 36 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 52b1274..53eafe8 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -237,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 = {} diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 6af83c1..b27b550 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -126,12 +126,6 @@ class StyleChooser: return rule.runtime_conditions def updateStyles(self, sl, ftype, tags, zoom, xscale, zscale, filter_by_runtime_conditions): - if zoom < self.selzooms[0] or zoom > self.selzooms[1]: - return sl - - #if ftype not in self.compatible_types: - #return sl - # Are any of the ruleChains fulfilled? rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom) diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 13bfd13..feff12a 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -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,34 +113,36 @@ 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 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 finalize_choosers_tree(self): + # Remove unneeded unique sets of choosers + 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(): + self.choosers_by_type_zoom_tag[ftype][zoom][clname] = self.choosers_by_type_zoom_tag[ftype][zoom][clname]['arr'] 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]: + 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(type, tags, zoom) if runtime_conditions: runtime_rules.append(runtime_conditions) @@ -148,8 +150,8 @@ class MapCSS(): 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]: + 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, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions) style = [x for x in style if x["object-id"] != "::*"] for x in style: -- 2.45.3 From 9b38690e69a9f195688121feebff1078dfa83ae4 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 31 Jan 2023 18:07:26 +0000 Subject: [PATCH 09/46] Optimize choosers by discarding non-matching rules Signed-off-by: Konstantin Pastbin --- src/mapcss/Rule.py | 8 +------- src/mapcss/StyleChooser.py | 17 +++++++---------- src/mapcss/__init__.py | 21 ++++++++++++++++++--- 3 files changed, 26 insertions(+), 20 deletions(-) diff --git a/src/mapcss/Rule.py b/src/mapcss/Rule.py index 0966310..db516c1 100644 --- a/src/mapcss/Rule.py +++ b/src/mapcss/Rule.py @@ -38,13 +38,7 @@ class Rule(): 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 (obj not in self.type_matches): - return False - + def test(self, tags): subpart = "::default" for condition in self.conditions: res = condition.test(tags) diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index b27b550..a386599 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -109,14 +109,11 @@ class StyleChooser: self.cached_tags = a return a - def get_runtime_conditions(self, ftype, tags, zoom): + def get_runtime_conditions(self, tags): if not self.has_runtime_conditions: return None - if zoom < self.selzooms[0] or zoom > self.selzooms[1]: - 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 @@ -125,9 +122,9 @@ class StyleChooser: return rule.runtime_conditions - 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? - 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 @@ -181,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 diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index feff12a..d52e8a5 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -130,11 +130,26 @@ class MapCSS(): self.choosers_by_type_zoom_tag[type][zoom][clname]['set'].add(chooser) def finalize_choosers_tree(self): - # Remove unneeded unique sets of choosers 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 get_runtime_rules(self, clname, type, tags, zoom): """ @@ -143,7 +158,7 @@ class MapCSS(): runtime_rules = [] 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(type, tags, zoom) + runtime_conditions = chooser.get_runtime_conditions(tags) if runtime_conditions: runtime_rules.append(runtime_conditions) return runtime_rules @@ -152,7 +167,7 @@ class MapCSS(): style = [] 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, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions) + 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)]: -- 2.45.3 From 5b160e185faf5bc4f6c85e197e45ce520709cf94 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 14 Feb 2023 18:48:47 +0000 Subject: [PATCH 10/46] Remove apply_for_type symbol attribute Signed-off-by: Konstantin Pastbin --- README | 5 +---- src/libkomwm.py | 11 ++--------- 2 files changed, 3 insertions(+), 13 deletions(-) diff --git a/README b/README index 6e6e675..3b13e84 100644 --- a/README +++ b/README @@ -1,4 +1 @@ -Kothic Map Renderer, patched for MWM use. - -python src/komap.py -r mapswithme -s [path to repo]/data/styles/normal.mapcss -o [path to repo]/data/drules_proto - +Kothic Mapcss parser/processor tailored for Organic Maps use. diff --git a/src/libkomwm.py b/src/libkomwm.py index 53eafe8..418684e 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -99,7 +99,6 @@ def query_style(args): runtime_conditions_arr.append(new_rt_conditions) for runtime_conditions in runtime_conditions_arr: - has_icons_for_areas = False zstyle = {} # Get style for class 'cl' on zoom 'zoom' with corresponding runtime conditions @@ -107,16 +106,12 @@ def query_style(args): 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 list(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 - results.append((cl, zoom, has_icons_for_areas, runtime_conditions, list(zstyle.values()))) + results.append((cl, zoom, runtime_conditions, list(zstyle.values()))) return results @@ -297,7 +292,7 @@ def komap_mapswithme(options): for results in imapfunc(query_style, ((cl, classificator[cl], options.minzoom, options.maxzoom) for cl in class_order)): for result in results: - cl, zoom, has_icons_for_areas, runtime_conditions, zstyle = result + cl, zoom, runtime_conditions, zstyle = result # First, sort rules by 'object-id' in captions (primary, secondary, none ..); # Then by 'z-index' in ascending order. @@ -462,8 +457,6 @@ def komap_mapswithme(options): 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] if '-x-me-icon-priority' in st: -- 2.45.3 From d580b748f254e13813985d5e40bcd6ddb715b305 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 24 Mar 2023 22:16:10 +0200 Subject: [PATCH 11/46] Make captions optional by default Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libkomwm.py b/src/libkomwm.py index 418684e..d741ca1 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -503,6 +503,9 @@ def komap_mapswithme(options): dr_cur_subtext.is_optional = value else: dr_cur_subtext.is_optional = True + elif st.get('text-position', 'center') == 'center' and dr_element.symbol.priority: + # On by default for all captions (not path texts) with icons. + dr_cur_subtext.is_optional = True dr_cur_subtext = dr_text.secondary # Priority is assigned from the first (primary) rule. -- 2.45.3 From dbba1c41e0237ad37be0905cf7502e609fca58ab Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 20 Jun 2023 18:18:40 +0300 Subject: [PATCH 12/46] Allow comments in mapcss-mapping.csv Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 26 +++----------------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index d741ca1..03e12d8 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -148,32 +148,12 @@ def komap_mapswithme(options): # Build classificator tree from mapcss-mapping.csv file types_file = open(os.path.join(ddir, 'types.txt'), "w") - # Mapcss-mapping format - # - # A CSV table mapping tags to types. Some types can be deemed obsolete, either completely or replaced with a different type. - # - # Example row: highway|bus_stop;[highway=bus_stop];;name;int_name;22; (mind the last semicolon!) - # It contains: - # - type name: "highway|bus_stop" ('|' is converted to '-' internally) - # - mapcss selector for tags: "[highway=bus_stop]" (you can group selectors and use e.g. [oneway?]) - # - "x" for an obsolete type or an empty cell otherwise - # - primary title tag (usually "name") - # - secondary title tag (usually "int_name") - # - type id, sequential starting from 1 - # - replacement type for an obsolete tag, if exists - # - # A shorter format for above example: highway|bus_stop;22; - # It leaves only columns 1, 6 and 7. For obsolete types with no replacement put "x" into the last column. - # Obviously it works only for simple types that are produced from tags replacing '=' with '|'. - # - # An example of type with replacement: - # highway|unsurfaced|disused;[highway=unsurfaced][disused?];x;name;int_name;838;highway|unclassified - + # The mapcss-mapping.csv format is described inside the file itself. cnt = 1 unique_types_check = set() for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'): - if len(row) <= 1: - # Allow for empty lines and comments that do not contain ';' symbol + if len(row) <= 1 or row[0].startswith('#'): + # Allow for empty lines and comment lines starting with '#'. continue if len(row) == 3: # Short format: type name, type id, x / replacement type name -- 2.45.3 From 2f311e7504986111d4fd0cca7f305238ba611c6f Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 18 Jun 2023 12:39:04 +0300 Subject: [PATCH 13/46] Add a bg-top priorities range Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 61 +++++++++++++++++++++++------------------- src/mapcss/__init__.py | 5 ++-- 2 files changed, 36 insertions(+), 30 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 03e12d8..90ffd35 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -27,6 +27,23 @@ from drules_struct_pb2 import * WIDTH_SCALE = 1.0 +# z-index values defined in mapcss files are first +# compressed into a 0-1000 integer range +# and then adjusted to divide into following "priorities ranges": +# (15000; 17000) : overlays (icons, captions...) +# [0; 1000) : FG - foreground areas and lines +# [-1000; 0) : BG-top - water, linear and areal, rendered just on top of landcover +# (-2000; -1000) : BG-by-size - landcover areas, later in core sorted by their bbox size +# The core renderer then re-adjusts those ranges as necessary to accomodate +# for special behavior and features' layer=* values. +# See drape_frontend/stylist.cpp for the details of layering logic. + +PRIORITY_RANGE = 1000 # All z-indexes are compressed into this range. + +# Drules are arranged into following ranges depending on "fill-position: *". +BASE_PRIORITY_FG = 0 +BASE_PRIORITY_BG_TOP = -1000 +BASE_PRIORITY_BG_BY_SIZE = -2000 def to_boolean(s): s = s.lower() @@ -213,7 +230,8 @@ def komap_mapswithme(options): # Parse style mapcss global style style = MapCSS(options.minzoom, options.maxzoom) - style.parse(filename=options.filename, static_tags=mapcss_static_tags, + style.parse(clamp=True, stretch=PRIORITY_RANGE, + filename=options.filename, static_tags=mapcss_static_tags, dynamic_tags=mapcss_dynamic_tags) # Build optimization tree - class/zoom/type -> StyleChoosers @@ -243,8 +261,6 @@ def komap_mapswithme(options): visibility = {} - bgpos = 0 - dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP} dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN} @@ -365,10 +381,11 @@ def komap_mapswithme(options): dr_line.width = round((base_width + st.get('casing-width') * 2) * WIDTH_SCALE, 2) dr_line.color = mwm_encode_color(colors, st, "casing") - if '-x-me-casing-line-priority' in st: - dr_line.priority = int(st.get('-x-me-casing-line-priority')) + # Casing line should be rendered below the "main" line, hence priority -1. + if st.get('fill-position', 'foreground') == 'background-top': + dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP - 1, BASE_PRIORITY_BG_TOP) else: - dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000) + dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_FG - 1, BASE_PRIORITY_FG) dashes = st.get('casing-dashes', st.get('dashes', [])) dr_line.dashdot.dd.extend(dashes) addPattern(dr_line.dashdot.dd) @@ -402,10 +419,10 @@ def komap_mapswithme(options): addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) - if '-x-me-line-priority' in st: - dr_line.priority = int(st.get('-x-me-line-priority')) + if st.get('fill-position', 'foreground') == 'background-top': + dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP else: - dr_line.priority = min((int(st.get('z-index', 0)) + 1000), 20000) + dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_FG dr_element.lines.extend([dr_line]) if st.get('pattern-image'): dr_line = LineRuleProto() @@ -415,10 +432,10 @@ def komap_mapswithme(options): 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) - if '-x-me-line-priority' in st: - dr_line.priority = int(st.get('-x-me-line-priority')) + if st.get('fill-position', 'foreground') == 'background-top': + dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP else: - dr_line.priority = int(st.get('z-index', 0)) + 1000 + dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_FG dr_element.lines.extend([dr_line]) if st.get('shield-font-size'): dr_element.shield.height = int(st.get('shield-font-size', 10)) @@ -503,23 +520,13 @@ def komap_mapswithme(options): if has_fills: if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0): dr_element.area.color = mwm_encode_color(colors, st, "fill") - priority = 0 + dr_element.area.priority = int(st.get('z-index', 0)) if st.get('fill-position', 'foreground') == 'background': - if 'z-index' not in st: - bgpos -= 1 - priority = bgpos - 16000 - else: - zzz = int(st.get('z-index', 0)) - if zzz > 0: - priority = zzz - 16000 - else: - priority = zzz - 16700 + dr_element.area.priority += BASE_PRIORITY_BG_BY_SIZE + elif (st.get('fill-position', 'foreground') == 'background-top'): + dr_element.area.priority += BASE_PRIORITY_BG_TOP else: - priority = (int(st.get('z-index', 0)) + 1 + 1000) - if '-x-me-area-priority' in st: - dr_element.area.priority = int(st.get('-x-me-area-priority')) - else: - dr_element.area.priority = priority + dr_element.area.priority += BASE_PRIORITY_FG has_fills = False str_dr_element = dr_cont.name + "/" + str(dr_element) diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index d52e8a5..1a1368a 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -389,13 +389,12 @@ class MapCSS(): zindex.add(float(stylez.get('z-index', 0))) zindex = list(zindex) zindex.sort() - zoffset = len([x for x in zindex if x < 0]) for chooser in self.choosers: for stylez in chooser.styles: if 'z-index' in stylez: - res = zindex.index(float(stylez.get('z-index', 0))) - zoffset + res = zindex.index(float(stylez.get('z-index', 0))) if stretch: - stylez['z-index'] = 1. * res / len(zindex) * stretch + stylez['z-index'] = stretch * res / len(zindex) else: stylez['z-index'] = res except TypeError: -- 2.45.3 From ebe1ced85c8bea376879bf43f0c853c7f4741d98 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sat, 8 Jul 2023 19:24:25 +0300 Subject: [PATCH 14/46] Remove empty casing dashdot definitions Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 90ffd35..0af8d99 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -386,8 +386,8 @@ def komap_mapswithme(options): dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP - 1, BASE_PRIORITY_BG_TOP) else: dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_FG - 1, BASE_PRIORITY_FG) - dashes = st.get('casing-dashes', st.get('dashes', [])) - dr_line.dashdot.dd.extend(dashes) + for i in st.get('casing-dashes', st.get('dashes', [])): + dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN) -- 2.45.3 From 8e8f17bd2958c655d6c1e1b359caee4fe1cce1bd Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 26 Feb 2023 18:15:07 +0000 Subject: [PATCH 15/46] Remove unused zoom 0 from drules output Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/libkomwm.py b/src/libkomwm.py index 0af8d99..66160ef 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -349,6 +349,9 @@ def komap_mapswithme(options): visstring[zoom] = "1" + if zoom == 0: + continue + dr_element = DrawElementProto() dr_element.scale = zoom -- 2.45.3 From 492906e130b53f82deb62ef6c8ece9227a24ce27 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 26 Feb 2023 18:16:14 +0000 Subject: [PATCH 16/46] Prepend minVisibleScale to priority values Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/src/libkomwm.py b/src/libkomwm.py index 66160ef..d370afb 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -4,6 +4,7 @@ import os import csv import sys import functools +from itertools import chain from multiprocessing import Pool, set_start_method from collections import OrderedDict import mapcss.webcolors @@ -131,6 +132,21 @@ def query_style(args): results.append((cl, zoom, runtime_conditions, list(zstyle.values()))) return results +def apply_min_visible_scale(dr_zooms, maxzoom): + # Inverse minVisibleScale (lower scale means higher priority). + min_visible_scale_adjustment = maxzoom - dr_zooms[0].scale + # Overlays priorities range is [15000, 17000), make it [0, 2000) + # and add 10000 per zoom level, the final range is [0, 182000). + min_visible_scale_adjustment = -15000 + min_visible_scale_adjustment * 10000 + for drz in dr_zooms: + for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.circle)): + if dr.priority: + if dr.priority < 15000 or dr.priority >= 17000: + print("Overlay priority out of range: ", dr.priority) + dr.priority += min_visible_scale_adjustment + if drz.shield.priority: + # TODO: minVisibleScale for shields is hardcoded to 10 for now. + drz.shield.priority += -15000 + (maxzoom - 10) * 10000 def komap_mapswithme(options): if options.data and os.path.isdir(options.data): @@ -310,6 +326,7 @@ def komap_mapswithme(options): if dr_cont is not None and dr_cont.name != cl: if dr_cont.element: + apply_min_visible_scale(dr_cont.element, options.maxzoom) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[dr_cont.name] + "|"] = "".join(visstring) dr_cont = None @@ -539,6 +556,7 @@ def komap_mapswithme(options): if dr_cont is not None: if dr_cont.element: + apply_min_visible_scale(dr_cont.element, options.maxzoom) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[cl] + "|"] = "".join(visstring) -- 2.45.3 From b34963026a0eebc45b6dacb821ddc3793a467463 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 5 Mar 2023 16:47:16 +0000 Subject: [PATCH 17/46] Calculate minVisibleScale for overlays only Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index d370afb..7a9f6e7 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -133,20 +133,29 @@ def query_style(args): return results def apply_min_visible_scale(dr_zooms, maxzoom): + # Determine minVisibleScale for overlays only, i.e. disregarding lines and areas visibility. + def get_overlays_min_visible_scale(dr_zooms): + for drz in dr_zooms: + for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): + if dr.priority: + return drz.scale + return None + + overlays_min_visible_scale = get_overlays_min_visible_scale(dr_zooms) + if overlays_min_visible_scale is None: + return + # Inverse minVisibleScale (lower scale means higher priority). - min_visible_scale_adjustment = maxzoom - dr_zooms[0].scale + min_visible_scale_adjustment = maxzoom - overlays_min_visible_scale # Overlays priorities range is [15000, 17000), make it [0, 2000) # and add 10000 per zoom level, the final range is [0, 182000). min_visible_scale_adjustment = -15000 + min_visible_scale_adjustment * 10000 for drz in dr_zooms: - for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.circle)): + for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): if dr.priority: if dr.priority < 15000 or dr.priority >= 17000: print("Overlay priority out of range: ", dr.priority) dr.priority += min_visible_scale_adjustment - if drz.shield.priority: - # TODO: minVisibleScale for shields is hardcoded to 10 for now. - drz.shield.priority += -15000 + (maxzoom - 10) * 10000 def komap_mapswithme(options): if options.data and os.path.isdir(options.data): -- 2.45.3 From 0850cde77237972c618e8da3478c6bc783313166 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 24 Mar 2023 20:52:11 +0200 Subject: [PATCH 18/46] Dump priorities into separate *.prio.txt files Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 158 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 144 insertions(+), 14 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 7a9f6e7..7035157 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -41,10 +41,60 @@ WIDTH_SCALE = 1.0 PRIORITY_RANGE = 1000 # All z-indexes are compressed into this range. -# Drules are arranged into following ranges depending on "fill-position: *". -BASE_PRIORITY_FG = 0 -BASE_PRIORITY_BG_TOP = -1000 -BASE_PRIORITY_BG_BY_SIZE = -2000 +# Drules are arranged into following ranges. +PRIO_OVERLAYS = 'overlays' +PRIO_FG = 'FG' +PRIO_BG_TOP = 'BG-top' +PRIO_BG_BY_SIZE = 'BG-by-size' + +prio_ranges = { + PRIO_OVERLAYS: {'pos': 4, 'base': 0, 'priorities': {}}, + PRIO_FG: {'pos': 3, 'base': 0, 'priorities': {}}, + PRIO_BG_TOP: {'pos': 2, 'base': -1000, 'priorities': {}}, + PRIO_BG_BY_SIZE: {'pos': 1, 'base': -2000, 'priorities': {}}, +} + +prio_ranges[PRIO_OVERLAYS]['comment'] = ''' +Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). +Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. +''' +prio_ranges[PRIO_FG]['comment'] = ''' +FG geometry: foreground lines and areas (e.g. buildings) are rendered always below overlays +and always on top of background geometry (BG-top & BG-by-size) even if a foreground feature +is layer=-10 (as tunnels should be visibile over landcover and water). +''' +prio_ranges[PRIO_BG_TOP]['comment'] = ''' +BG-top geometry: background lines and areas that should be always below foreground ones +(including e.g. layer=-10 underwater tunnels), but above background areas sorted by size (BG-by-size), +because ordering by size doesn't always work with e.g. water mapped over a forest, +so water should be on top of other landcover always, but linear waterways should be hidden beneath it. +Still, e.g. a layer=-1 BG-top feature will be rendered under a layer=0 BG-by-size feature +(so areal water tunnels are hidden beneath other landcover area) and a layer=1 landcover areas +are displayed above layer=0 BG-top. +''' +prio_ranges[PRIO_BG_BY_SIZE]['comment'] = ''' +BG-by-size geometry: background areas rendered below BG-top and everything else. +Smaller areas are rendered above larger ones (area's size is estimated as the size of its' bounding box). +So effectively priority values of BG-by-size areas are not used at the moment. +But we might use them later for some special cases, e.g. to determine a main area type of a multi-type feature. +Keep them in a logical importance order please. +''' + +COMMENT_AUTOFORMAT = '''This file is automatically re-formatted and re-sorted in priorities descending order +when generate_drules.sh is run. Custom formatting and comments are not preserved. +''' + +COMMENT_RANGES_OVERVIEW = ''' +Priorities ranges' rendering order overview: +- overlays (icons, captions...) +- FG: foreground areas and lines +- BG-top: water (linear and areal) +- BG-by-size: landcover areas sorted by their size +''' + +OVERLAYS_MAX_PRIORITY = 200000 + +CASING_OBJECT_ID = '_casing' def to_boolean(s): s = s.lower() @@ -154,15 +204,79 @@ def apply_min_visible_scale(dr_zooms, maxzoom): for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): if dr.priority: if dr.priority < 15000 or dr.priority >= 17000: - print("Overlay priority out of range: ", dr.priority) + print("WARNING: overlay priority out of range: ", dr.priority) dr.priority += min_visible_scale_adjustment +def get_priorities_filename(prio_range, path): + return os.path.join(path, f'priorities_{prio_ranges[prio_range]["pos"]}_{prio_range}.prio.txt') + +def dump_priorities(prio_range, path): + outfile = open(get_priorities_filename(prio_range, path), 'w') + comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW + for s in comment.splitlines(): + outfile.write(f'# {s}'.strip() + '\n') + outfile.write('\n') + for dr_id in sorted(prio_ranges[prio_range]['priorities'].keys(), key = lambda k: (OVERLAYS_MAX_PRIORITY - k[0], k[1], k[2], k[3])): + # (priority, dr_cont.name, dr_object_id, dr_type_name) + priority_key = dr_id[3] + if priority_key == 'symbol': + priority_key = 'icon' + # TODO: add zoom ranges support? + output = f'{dr_id[1]}\t{priority_key}{dr_id[2]}\t{dr_id[0]}\n' + outfile.write(output) + outfile.close() + +dr_unique = set() +def store_priorities(dr_cont, dr_lines_objects): + for drz in dr_cont.element: + for dr in chain(drz.lines, (drz.area, drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): + if dr.priority: + dr_type_name = dr.DESCRIPTOR.name + dr_type_name = dr_type_name[:dr_type_name.find('RuleProto')].lower() + + priority = dr.priority + dr_object_id = '' + prio_range = PRIO_OVERLAYS + priority_max = OVERLAYS_MAX_PRIORITY + if dr_type_name == 'line' or dr_type_name == 'area': + priority_max = PRIORITY_RANGE + if dr_type_name == 'line': + dr_object_id = dr_lines_objects[priority] + if dr_object_id == CASING_OBJECT_ID: + priority += 1 + dr_object_id = '' + if priority >= prio_ranges[PRIO_FG]['base']: + prio_range = PRIO_FG + elif priority >= prio_ranges[PRIO_BG_TOP]['base']: + prio_range = PRIO_BG_TOP + else: + prio_range = PRIO_BG_BY_SIZE + priority -= prio_ranges[prio_range]['base'] + + dr_id = (priority, dr_cont.name, dr_object_id, dr_type_name) + if priority < 0 or priority > priority_max: + print('WARNING: priority {} for drule "{}{}" ({}) out of range'.format(*dr_id)) + + if dr_id in prio_ranges[prio_range]['priorities']: + prio_ranges[prio_range]['priorities'][dr_id].add(drz.scale) + else: + prio_ranges[prio_range]['priorities'][dr_id] = set([drz.scale]) + + # TODO: add zoom ranges support? + dr_id_unique = dr_id[1:] + if dr_id_unique in dr_unique: + print('WARNING: multiple priorities for drule "{}{}" ({})'.format(*dr_id_unique)) + else: + dr_unique.add(dr_id_unique) + def komap_mapswithme(options): if options.data and os.path.isdir(options.data): ddir = options.data else: ddir = os.path.dirname(options.outfile) + is_dump_priorities = options.priorities_path and os.path.isdir(options.priorities_path) + classificator = {} class_order = [] class_tree = {} @@ -336,6 +450,8 @@ def komap_mapswithme(options): if dr_cont is not None and dr_cont.name != cl: if dr_cont.element: apply_min_visible_scale(dr_cont.element, options.maxzoom) + if is_dump_priorities: + store_priorities(dr_cont, dr_lines_objects) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[dr_cont.name] + "|"] = "".join(visstring) dr_cont = None @@ -343,6 +459,7 @@ def komap_mapswithme(options): if dr_cont is None: dr_cont = ClassifElementProto() dr_cont.name = cl + dr_lines_objects = {} visstring = ["0"] * (options.maxzoom - options.minzoom + 1) @@ -412,15 +529,16 @@ def komap_mapswithme(options): dr_line.color = mwm_encode_color(colors, st, "casing") # Casing line should be rendered below the "main" line, hence priority -1. if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP - 1, BASE_PRIORITY_BG_TOP) + dr_line.priority = max(int(st.get('z-index', 0)) - 1 + prio_ranges[PRIO_BG_TOP]['base'], prio_ranges[PRIO_BG_TOP]['base']) else: - dr_line.priority = max(int(st.get('z-index', 0)) + BASE_PRIORITY_FG - 1, BASE_PRIORITY_FG) + dr_line.priority = max(int(st.get('z-index', 0)) - 1 + prio_ranges[PRIO_FG]['base'], prio_ranges[PRIO_FG]['base']) for i in st.get('casing-dashes', st.get('dashes', [])): dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) 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]) + dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else CASING_OBJECT_ID if has_fills and is_area_st and float(st.get('fill-opacity', 1)) > 0: dr_element.area.border.color = mwm_encode_color(colors, st, "casing") @@ -449,10 +567,11 @@ def komap_mapswithme(options): dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP + dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_BG_TOP]['base'] else: - dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_FG + dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_FG]['base'] dr_element.lines.extend([dr_line]) + dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else '' if st.get('pattern-image'): dr_line = LineRuleProto() dr_line.width = 0 @@ -462,10 +581,11 @@ def komap_mapswithme(options): dr_line.pathsym.step = float(st.get('pattern-spacing', 0)) - 16 dr_line.pathsym.offset = st.get('pattern-offset', 0) if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_BG_TOP + dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_BG_TOP]['base'] else: - dr_line.priority = int(st.get('z-index', 0)) + BASE_PRIORITY_FG + dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_FG]['base'] dr_element.lines.extend([dr_line]) + dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else '' if st.get('shield-font-size'): dr_element.shield.height = int(st.get('shield-font-size', 10)) dr_element.shield.text_color = mwm_encode_color(colors, st, "shield-text") @@ -551,11 +671,11 @@ def komap_mapswithme(options): dr_element.area.color = mwm_encode_color(colors, st, "fill") dr_element.area.priority = int(st.get('z-index', 0)) if st.get('fill-position', 'foreground') == 'background': - dr_element.area.priority += BASE_PRIORITY_BG_BY_SIZE + dr_element.area.priority += prio_ranges[PRIO_BG_BY_SIZE]['base'] elif (st.get('fill-position', 'foreground') == 'background-top'): - dr_element.area.priority += BASE_PRIORITY_BG_TOP + dr_element.area.priority += prio_ranges[PRIO_BG_TOP]['base'] else: - dr_element.area.priority += BASE_PRIORITY_FG + dr_element.area.priority += prio_ranges[PRIO_FG]['base'] has_fills = False str_dr_element = dr_cont.name + "/" + str(dr_element) @@ -566,10 +686,18 @@ def komap_mapswithme(options): if dr_cont is not None: if dr_cont.element: apply_min_visible_scale(dr_cont.element, options.maxzoom) + if is_dump_priorities: + store_priorities(dr_cont, dr_lines_objects) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[cl] + "|"] = "".join(visstring) + if is_dump_priorities: + dump_priorities(PRIO_OVERLAYS, options.priorities_path) + dump_priorities(PRIO_FG, options.priorities_path) + dump_priorities(PRIO_BG_TOP, options.priorities_path) + dump_priorities(PRIO_BG_BY_SIZE, options.priorities_path) + # Write drules_proto.bin and drules_proto.txt files drules_bin = open(os.path.join(options.outfile + '.bin'), "wb") @@ -645,6 +773,8 @@ def main(): help="output filename", metavar="FILE") parser.add_option("-x", "--txt", dest="txt", action="store_true", help="create a text file for output", default=False) + parser.add_option("-p", "--priorities-out-path", dest="priorities_path", + help="path to write priorities files to", metavar="PATH") parser.add_option("-d", "--data-path", dest="data", help="path to mapcss-mapping.csv and other files", metavar="PATH") -- 2.45.3 From 58dcaaba5118200a627d811112f81e4dfe54ea0f Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Thu, 20 Jul 2023 13:36:09 +0300 Subject: [PATCH 19/46] Load priorities from *.prio.txt files Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 237 +++++++++++++++---------------------- src/mapcss/StyleChooser.py | 2 +- 2 files changed, 97 insertions(+), 142 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 7035157..0444086 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -182,92 +182,67 @@ def query_style(args): results.append((cl, zoom, runtime_conditions, list(zstyle.values()))) return results -def apply_min_visible_scale(dr_zooms, maxzoom): - # Determine minVisibleScale for overlays only, i.e. disregarding lines and areas visibility. - def get_overlays_min_visible_scale(dr_zooms): - for drz in dr_zooms: - for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): - if dr.priority: - return drz.scale - return None - - overlays_min_visible_scale = get_overlays_min_visible_scale(dr_zooms) - if overlays_min_visible_scale is None: - return - - # Inverse minVisibleScale (lower scale means higher priority). - min_visible_scale_adjustment = maxzoom - overlays_min_visible_scale - # Overlays priorities range is [15000, 17000), make it [0, 2000) - # and add 10000 per zoom level, the final range is [0, 182000). - min_visible_scale_adjustment = -15000 + min_visible_scale_adjustment * 10000 - for drz in dr_zooms: - for dr in chain((drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): - if dr.priority: - if dr.priority < 15000 or dr.priority >= 17000: - print("WARNING: overlay priority out of range: ", dr.priority) - dr.priority += min_visible_scale_adjustment - def get_priorities_filename(prio_range, path): return os.path.join(path, f'priorities_{prio_ranges[prio_range]["pos"]}_{prio_range}.prio.txt') -def dump_priorities(prio_range, path): - outfile = open(get_priorities_filename(prio_range, path), 'w') - comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW - for s in comment.splitlines(): - outfile.write(f'# {s}'.strip() + '\n') - outfile.write('\n') - for dr_id in sorted(prio_ranges[prio_range]['priorities'].keys(), key = lambda k: (OVERLAYS_MAX_PRIORITY - k[0], k[1], k[2], k[3])): - # (priority, dr_cont.name, dr_object_id, dr_type_name) - priority_key = dr_id[3] - if priority_key == 'symbol': - priority_key = 'icon' - # TODO: add zoom ranges support? - output = f'{dr_id[1]}\t{priority_key}{dr_id[2]}\t{dr_id[0]}\n' - outfile.write(output) - outfile.close() +def load_priorities(prio_range, path, classif): + def print_warning(msg): + print(f'WARNING: {msg} in {fname}:\n\t{line}') -dr_unique = set() -def store_priorities(dr_cont, dr_lines_objects): - for drz in dr_cont.element: - for dr in chain(drz.lines, (drz.area, drz.caption, drz.symbol, drz.path_text, drz.shield, drz.circle)): - if dr.priority: - dr_type_name = dr.DESCRIPTOR.name - dr_type_name = dr_type_name[:dr_type_name.find('RuleProto')].lower() - - priority = dr.priority - dr_object_id = '' - prio_range = PRIO_OVERLAYS - priority_max = OVERLAYS_MAX_PRIORITY - if dr_type_name == 'line' or dr_type_name == 'area': - priority_max = PRIORITY_RANGE - if dr_type_name == 'line': - dr_object_id = dr_lines_objects[priority] - if dr_object_id == CASING_OBJECT_ID: - priority += 1 - dr_object_id = '' - if priority >= prio_ranges[PRIO_FG]['base']: - prio_range = PRIO_FG - elif priority >= prio_ranges[PRIO_BG_TOP]['base']: - prio_range = PRIO_BG_TOP - else: - prio_range = PRIO_BG_BY_SIZE - priority -= prio_ranges[prio_range]['base'] - - dr_id = (priority, dr_cont.name, dr_object_id, dr_type_name) - if priority < 0 or priority > priority_max: - print('WARNING: priority {} for drule "{}{}" ({}) out of range'.format(*dr_id)) - - if dr_id in prio_ranges[prio_range]['priorities']: - prio_ranges[prio_range]['priorities'][dr_id].add(drz.scale) + priority_max = OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else PRIORITY_RANGE + fname = get_priorities_filename(prio_range, path) + with open(fname, 'r') as f: + for line in f: + line = line.strip() + if not line or line.startswith('#'): + continue + tokens = line.split() + if len(tokens) != 3: + print_warning('skipping malformed line') + continue + if tokens[0] not in classif: + print_warning('unknown classificator type') + if tokens[1].split('::')[0] not in ('icon', 'caption', 'pathtext', 'shield', 'line', 'area'): + print_warning('unknown drule type') + key = (tokens[0], tokens[1]) + if key in prio_ranges[prio_range]['priorities']: + print_warning(f'duplicate priority (previous value {prio_ranges[prio_range]["priorities"][key]})') + try: + priority = int(tokens[2]) + except ValueError: + print_warning('skipping invalid priority value') + else: + if priority >= 0 and priority < priority_max: + prio_ranges[prio_range]['priorities'][key] = priority else: - prio_ranges[prio_range]['priorities'][dr_id] = set([drz.scale]) + print_warning(f'skipping out of [0;{priority_max}) range priority value') - # TODO: add zoom ranges support? - dr_id_unique = dr_id[1:] - if dr_id_unique in dr_unique: - print('WARNING: multiple priorities for drule "{}{}" ({})'.format(*dr_id_unique)) - else: - dr_unique.add(dr_id_unique) +def dump_priorities(prio_range, path): + # TODO : clamp overlays prios? + with open(get_priorities_filename(prio_range, path), 'w') as outfile: + comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW + for s in comment.splitlines(): + outfile.write(f'# {s}'.strip() + '\n') + outfile.write('\n') + + for dr in sorted(prio_ranges[prio_range]['priorities'].items(), key = lambda item: (OVERLAYS_MAX_PRIORITY - item[1], item[0][0], item[0][1])): + # TODO: add zoom ranges support? + outfile.write(f'{dr[0][0]}\t{dr[0][1]}\t{dr[1]}\n') + +def get_drape_priority(cl, dr_type, object_id): + if object_id == '::default': + object_id = '' + prio_id = (cl, dr_type + object_id) + ranges_to_check = (PRIO_OVERLAYS, ) + if dr_type == 'line': + ranges_to_check = (PRIO_FG, PRIO_BG_TOP) + elif dr_type == 'area': + ranges_to_check = (PRIO_BG_BY_SIZE, PRIO_BG_TOP, PRIO_FG) + for r in ranges_to_check: + if prio_id in prio_ranges[r]['priorities']: + return prio_ranges[r]['priorities'][prio_id] + prio_ranges[r]['base'] + print(f'WARNING: priority is not set for {prio_id}') + return 0 def komap_mapswithme(options): if options.data and os.path.isdir(options.data): @@ -275,8 +250,6 @@ def komap_mapswithme(options): else: ddir = os.path.dirname(options.outfile) - is_dump_priorities = options.priorities_path and os.path.isdir(options.priorities_path) - classificator = {} class_order = [] class_tree = {} @@ -356,6 +329,14 @@ def komap_mapswithme(options): class_order.sort() types_file.close() + output = '' + for prio_range in prio_ranges.keys(): + load_priorities(prio_range, options.priorities_path, unique_types_check) + output += f'{"" if not output else ", "}{len(prio_ranges[prio_range]["priorities"])} {prio_range}' + print(f'Loaded priorities: {output}.') + + del unique_types_check + # Get all mapcss static tags which are used in mapcss-mapping.csv # This is a dict with main_tag flags (True = appears first in types) mapcss_static_tags = {} @@ -369,7 +350,7 @@ def komap_mapswithme(options): # Parse style mapcss global style style = MapCSS(options.minzoom, options.maxzoom) - style.parse(clamp=True, stretch=PRIORITY_RANGE, + style.parse(clamp=False, stretch=PRIORITY_RANGE, filename=options.filename, static_tags=mapcss_static_tags, dynamic_tags=mapcss_dynamic_tags) @@ -438,7 +419,7 @@ def komap_mapswithme(options): first = 1 if str(dict_.get('text')) == 'none': first = 2 - return (first, int(dict_.get('z-index', 0))) + return (first, dict_.get('object-id')) zstyle.sort(key = rule_sort_key) @@ -449,9 +430,6 @@ def komap_mapswithme(options): if dr_cont is not None and dr_cont.name != cl: if dr_cont.element: - apply_min_visible_scale(dr_cont.element, options.maxzoom) - if is_dump_priorities: - store_priorities(dr_cont, dr_lines_objects) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[dr_cont.name] + "|"] = "".join(visstring) dr_cont = None @@ -503,10 +481,12 @@ def komap_mapswithme(options): 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) or st.get('casing-width-add') is not None: # and (st.get('width') or st.get('fill-color')): is_area_st = 'fill-color' in st @@ -527,18 +507,18 @@ def komap_mapswithme(options): dr_line.width = round((base_width + st.get('casing-width') * 2) * WIDTH_SCALE, 2) dr_line.color = mwm_encode_color(colors, st, "casing") - # Casing line should be rendered below the "main" line, hence priority -1. - if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = max(int(st.get('z-index', 0)) - 1 + prio_ranges[PRIO_BG_TOP]['base'], prio_ranges[PRIO_BG_TOP]['base']) - else: - dr_line.priority = max(int(st.get('z-index', 0)) - 1 + prio_ranges[PRIO_FG]['base'], prio_ranges[PRIO_FG]['base']) + dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) + # Casing line should be rendered below the "main" line, hence priority -1 (but not less than base). + if (st.get('object-id') == '::default' and dr_line.priority != 0 and + dr_line.priority != prio_ranges[PRIO_FG]['base'] and + dr_line.priority != prio_ranges[PRIO_BG_TOP]['base']): + dr_line.priority -= 1 for i in st.get('casing-dashes', st.get('dashes', [])): dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) 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]) - dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else CASING_OBJECT_ID if has_fills and is_area_st and float(st.get('fill-opacity', 1)) > 0: dr_element.area.border.color = mwm_encode_color(colors, st, "casing") @@ -566,12 +546,8 @@ def komap_mapswithme(options): addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) - if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_BG_TOP]['base'] - else: - dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_FG]['base'] + dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) dr_element.lines.extend([dr_line]) - dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else '' if st.get('pattern-image'): dr_line = LineRuleProto() dr_line.width = 0 @@ -580,12 +556,9 @@ def komap_mapswithme(options): 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) - if st.get('fill-position', 'foreground') == 'background-top': - dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_BG_TOP]['base'] - else: - dr_line.priority = int(st.get('z-index', 0)) + prio_ranges[PRIO_FG]['base'] + dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) dr_element.lines.extend([dr_line]) - dr_lines_objects[dr_line.priority] = st.get('object-id') if st.get('object-id') != '::default' else '' + if st.get('shield-font-size'): dr_element.shield.height = int(st.get('shield-font-size', 10)) dr_element.shield.text_color = mwm_encode_color(colors, st, "shield-text") @@ -594,10 +567,7 @@ def komap_mapswithme(options): dr_element.shield.color = mwm_encode_color(colors, st, "shield") if st.get('shield-outline-radius', 0) != 0: dr_element.shield.stroke_color = mwm_encode_color(colors, st, "shield-outline", "white") - if '-x-me-shield-priority' in st: - dr_element.shield.priority = int(st.get('-x-me-shield-priority')) - else: - dr_element.shield.priority = min(19100, (16000 + int(st.get('z-index', 0)))) + dr_element.shield.priority = get_drape_priority(cl, 'shield', st.get('object-id')) if st.get('shield-min-distance', 0) != 0: dr_element.shield.min_distance = int(st.get('shield-min-distance', 0)) @@ -605,20 +575,15 @@ def komap_mapswithme(options): if st.get('icon-image'): icon = mwm_encode_image(st) dr_element.symbol.name = icon[0] - if '-x-me-icon-priority' in st: - dr_element.symbol.priority = int(st.get('-x-me-icon-priority')) - else: - dr_element.symbol.priority = min(19100, (16000 + int(st.get('z-index', 0)))) + dr_element.symbol.priority = get_drape_priority(cl, 'icon', st.get('object-id')) if 'icon-min-distance' in st: dr_element.symbol.min_distance = int(st.get('icon-min-distance', 0)) has_icons = False if st.get('symbol-shape'): + # TODO: not used in current styles; do "circles" work in drape at all? dr_element.circle.radius = float(st.get('symbol-size')) dr_element.circle.color = mwm_encode_color(colors, st, 'symbol-fill') - if '-x-me-symbol-priority' in st: - dr_element.circle.priority = int(st.get('-x-me-symbol-priority')) - else: - dr_element.circle.priority = min(19000, (14000 + int(st.get('z-index', 0)))) + dr_element.circle.priority = get_drape_priority(cl, 'circle', st.get('object-id')) has_icons = False if has_text and st.get('text') and st.get('text') != 'none': @@ -626,10 +591,10 @@ def komap_mapswithme(options): has_text = has_text[:2] dr_text = dr_element.caption - base_z = 15000 + text_priority_key = 'caption' if st.get('text-position', 'center') == 'line': dr_text = dr_element.path_text - base_z = 16000 + text_priority_key = 'pathtext' dr_cur_subtext = dr_text.primary for sp in has_text: @@ -655,13 +620,7 @@ def komap_mapswithme(options): dr_cur_subtext = dr_text.secondary # Priority is assigned from the first (primary) rule. - if '-x-me-text-priority' in st: - dr_text.priority = int(st.get('-x-me-text-priority')) - else: - dr_text.priority = min(19000, (base_z + int(st.get('z-index', 0)))) - if '-x-me-min-text-priority' in st: - min_priority = int(st.get('-x-me-min-text-priority')) - dr_text.priority = max(min_priority, dr_text.priority) + dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) # Process captions block once. has_text = None @@ -669,13 +628,7 @@ def komap_mapswithme(options): if has_fills: if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0): dr_element.area.color = mwm_encode_color(colors, st, "fill") - dr_element.area.priority = int(st.get('z-index', 0)) - if st.get('fill-position', 'foreground') == 'background': - dr_element.area.priority += prio_ranges[PRIO_BG_BY_SIZE]['base'] - elif (st.get('fill-position', 'foreground') == 'background-top'): - dr_element.area.priority += prio_ranges[PRIO_BG_TOP]['base'] - else: - dr_element.area.priority += prio_ranges[PRIO_FG]['base'] + dr_element.area.priority = get_drape_priority(cl, 'area', st.get('object-id')) has_fills = False str_dr_element = dr_cont.name + "/" + str(dr_element) @@ -685,18 +638,16 @@ def komap_mapswithme(options): if dr_cont is not None: if dr_cont.element: - apply_min_visible_scale(dr_cont.element, options.maxzoom) - if is_dump_priorities: - store_priorities(dr_cont, dr_lines_objects) drules.cont.extend([dr_cont]) visibility["world|" + class_tree[cl] + "|"] = "".join(visstring) - if is_dump_priorities: - dump_priorities(PRIO_OVERLAYS, options.priorities_path) - dump_priorities(PRIO_FG, options.priorities_path) - dump_priorities(PRIO_BG_TOP, options.priorities_path) - dump_priorities(PRIO_BG_BY_SIZE, options.priorities_path) + + output = '' + for prio_range in prio_ranges.keys(): + dump_priorities(prio_range, options.priorities_path) + output += f'{"" if not output else ", "}{len(prio_ranges[prio_range]["priorities"])} {prio_range}' + print(f'Re-formated priorities files: {output}.') # Write drules_proto.bin and drules_proto.txt files @@ -773,8 +724,8 @@ def main(): help="output filename", metavar="FILE") parser.add_option("-x", "--txt", dest="txt", action="store_true", help="create a text file for output", default=False) - parser.add_option("-p", "--priorities-out-path", dest="priorities_path", - help="path to write priorities files to", metavar="PATH") + parser.add_option("-p", "--priorities-path", dest="priorities_path", + help="path to priorities *.prio.txt files", metavar="PATH") parser.add_option("-d", "--data-path", dest="data", help="path to mapcss-mapping.csv and other files", metavar="PATH") @@ -786,6 +737,10 @@ def main(): if options.outfile == "-": parser.error("Please specify base output path.") + if (options.priorities_path is None or not os.path.isdir(options.priorities_path)): + parser.error("A path to priorities *.prio.txt files is required.") + options.priorities_path = os.path.normpath(options.priorities_path) + komap_mapswithme(options) if __name__ == '__main__': diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index a386599..e307971 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -40,7 +40,7 @@ def make_nice_style(r): ra[a] = colorparser(b) elif b: ra[a] = b - elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")): + elif any(x in a for x in ("width", "opacity", "offset", "radius", "extrude")): "these things are float's or not in table at all" try: ra[a] = float(b) -- 2.45.3 From 9c914f097b4c78b663f732e488859741d881415e Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 21 Jul 2023 19:54:21 +0300 Subject: [PATCH 20/46] Compress / re-space priorities evenly Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 0444086..75e6e39 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -185,7 +185,7 @@ def query_style(args): def get_priorities_filename(prio_range, path): return os.path.join(path, f'priorities_{prio_ranges[prio_range]["pos"]}_{prio_range}.prio.txt') -def load_priorities(prio_range, path, classif): +def load_priorities(prio_range, path, classif, compress = False): def print_warning(msg): print(f'WARNING: {msg} in {fname}:\n\t{line}') @@ -217,8 +217,24 @@ def load_priorities(prio_range, path, classif): else: print_warning(f'skipping out of [0;{priority_max}) range priority value') + if compress: + print(f'Compressing {prio_range} priorities into a (0;{priority_max}) range:') + unique_prios = set(prio_ranges[prio_range]['priorities'].values()) + print(f'\tunique priorities values: {len(unique_prios)}') + # Keep gaps at the range borders. + base_idx = 1 + if 0 not in unique_prios: + base_idx = 0 + unique_prios.add(0) + unique_prios.add(priority_max) + step = min(priority_max / len(unique_prios), 10) + print(f'\tnew step between priorities: {step}') + unique_prios = sorted(unique_prios) + for prio_id in prio_ranges[prio_range]['priorities'].keys(): + idx = unique_prios.index(prio_ranges[prio_range]['priorities'][prio_id]) + prio_ranges[prio_range]['priorities'][prio_id] = int(step * (base_idx + idx)) + def dump_priorities(prio_range, path): - # TODO : clamp overlays prios? with open(get_priorities_filename(prio_range, path), 'w') as outfile: comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW for s in comment.splitlines(): @@ -331,7 +347,7 @@ def komap_mapswithme(options): output = '' for prio_range in prio_ranges.keys(): - load_priorities(prio_range, options.priorities_path, unique_types_check) + load_priorities(prio_range, options.priorities_path, unique_types_check, compress = True) output += f'{"" if not output else ", "}{len(prio_ranges[prio_range]["priorities"])} {prio_range}' print(f'Loaded priorities: {output}.') -- 2.45.3 From 8c5e15262106600f4668d1d10ec91a56f56b9e73 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sat, 22 Jul 2023 00:57:30 +0300 Subject: [PATCH 21/46] Disable priorities compression by default Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 75e6e39..ab37ee5 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -92,9 +92,7 @@ Priorities ranges' rendering order overview: - BG-by-size: landcover areas sorted by their size ''' -OVERLAYS_MAX_PRIORITY = 200000 - -CASING_OBJECT_ID = '_casing' +OVERLAYS_MAX_PRIORITY = 100000 def to_boolean(s): s = s.lower() @@ -347,7 +345,7 @@ def komap_mapswithme(options): output = '' for prio_range in prio_ranges.keys(): - load_priorities(prio_range, options.priorities_path, unique_types_check, compress = True) + load_priorities(prio_range, options.priorities_path, unique_types_check, compress = False) output += f'{"" if not output else ", "}{len(prio_ranges[prio_range]["priorities"])} {prio_range}' print(f'Loaded priorities: {output}.') -- 2.45.3 From 98755e5d6a94bc596350e18f7f5c97424752ff1e Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 28 Jul 2023 17:52:39 +0300 Subject: [PATCH 22/46] Add auto-fixing of captions/pathtexts priorities being higher than icon/shield Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/libkomwm.py b/src/libkomwm.py index ab37ee5..575b263 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -215,6 +215,18 @@ def load_priorities(prio_range, path, classif, compress = False): else: print_warning(f'skipping out of [0;{priority_max}) range priority value') + for key in prio_ranges[PRIO_OVERLAYS]['priorities'].keys(): + main_prio_id = None + if key[1].startswith('caption'): + main_prio_id = (key[0], key[1].replace('caption', 'icon')) + if key[1].startswith('pathtext'): + main_prio_id = (key[0], key[1].replace('pathtext', 'shield')) + if main_prio_id is not None and main_prio_id in prio_ranges[PRIO_OVERLAYS]['priorities']: + main_prio = prio_ranges[PRIO_OVERLAYS]['priorities'][main_prio_id] + if prio_ranges[PRIO_OVERLAYS]['priorities'][key] > main_prio: + print(f'WARNING: {key} priority is higher than {main_prio_id}, making it equal') + prio_ranges[PRIO_OVERLAYS]['priorities'][key] = main_prio + if compress: print(f'Compressing {prio_range} priorities into a (0;{priority_max}) range:') unique_prios = set(prio_ranges[prio_range]['priorities'].values()) -- 2.45.3 From 41e9860567c81bf1605ea1fb763356f0585c3fb8 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 31 Jul 2023 19:05:58 +0300 Subject: [PATCH 23/46] Group types with same priorities Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 575b263..b745e18 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -251,9 +251,19 @@ def dump_priorities(prio_range, path): outfile.write(f'# {s}'.strip() + '\n') outfile.write('\n') - for dr in sorted(prio_ranges[prio_range]['priorities'].items(), key = lambda item: (OVERLAYS_MAX_PRIORITY - item[1], item[0][0], item[0][1])): - # TODO: add zoom ranges support? - outfile.write(f'{dr[0][0]}\t{dr[0][1]}\t{dr[1]}\n') + if len(prio_ranges[prio_range]['priorities']): + prios = sorted(prio_ranges[prio_range]['priorities'].items(), + key = lambda item: (OVERLAYS_MAX_PRIORITY - item[1], item[0][0], item[0][1])) + group_prio = prios[0][1] + group = '' + for p in prios: + if p[1] != group_prio: + outfile.write(f'{group}=== {group_prio}\n\n') + group_prio = p[1] + group = '' + # TODO: add zoom ranges support? + group += f'{p[0][0]}\t{p[0][1]}\n' + outfile.write(f'{group}=== {group_prio}\n') def get_drape_priority(cl, dr_type, object_id): if object_id == '::default': -- 2.45.3 From b30c3bee845cfa7839a193e17b83de0a2e69d22d Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 31 Jul 2023 19:06:49 +0300 Subject: [PATCH 24/46] Load grouped priorities Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 45 +++++++++++++++++++++++++++++---------------- 1 file changed, 29 insertions(+), 16 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index b745e18..c53daa8 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -190,30 +190,43 @@ def load_priorities(prio_range, path, classif, compress = False): priority_max = OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else PRIORITY_RANGE fname = get_priorities_filename(prio_range, path) with open(fname, 'r') as f: + group = [] for line in f: line = line.strip() if not line or line.startswith('#'): continue tokens = line.split() - if len(tokens) != 3: + if len(tokens) != 2: print_warning('skipping malformed line') continue - if tokens[0] not in classif: - print_warning('unknown classificator type') - if tokens[1].split('::')[0] not in ('icon', 'caption', 'pathtext', 'shield', 'line', 'area'): - print_warning('unknown drule type') - key = (tokens[0], tokens[1]) - if key in prio_ranges[prio_range]['priorities']: - print_warning(f'duplicate priority (previous value {prio_ranges[prio_range]["priorities"][key]})') - try: - priority = int(tokens[2]) - except ValueError: - print_warning('skipping invalid priority value') - else: - if priority >= 0 and priority < priority_max: - prio_ranges[prio_range]['priorities'][key] = priority + if tokens[0] == "===": + try: + priority = int(tokens[1]) + except ValueError: + print_warning('skipping invalid priority value') else: - print_warning(f'skipping out of [0;{priority_max}) range priority value') + if priority >= 0 and priority < priority_max: + if len(group): + for key in group: + prio_ranges[prio_range]['priorities'][key] = priority + else: + print_warning('skipping empty priority group') + else: + print_warning(f'skipping out of [0;{priority_max}) range priority value') + group = [] + else: + if tokens[0] not in classif: + print_warning('unknown classificator type') + if tokens[1].split('::')[0] not in ('icon', 'caption', 'pathtext', 'shield', 'line', 'area'): + print_warning('unknown drule type') + key = (tokens[0], tokens[1]) + if key in prio_ranges[prio_range]['priorities']: + print_warning(f'duplicate priority (previous value {prio_ranges[prio_range]["priorities"][key]})') + group.append(key) + + if len(group): + line = group + print_warning(f'skipping last types groups with no priority set') for key in prio_ranges[PRIO_OVERLAYS]['priorities'].keys(): main_prio_id = None -- 2.45.3 From 2d5c553af8a5e9ca0bd61091f8266baa56aae388 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 1 Aug 2023 20:39:06 +0300 Subject: [PATCH 25/46] Add comments with visibility range and other drules info Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 85 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 74 insertions(+), 11 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index c53daa8..f79d8c3 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -54,6 +54,8 @@ prio_ranges = { PRIO_BG_BY_SIZE: {'pos': 1, 'base': -2000, 'priorities': {}}, } +visibilities = {} + prio_ranges[PRIO_OVERLAYS]['comment'] = ''' Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. @@ -193,7 +195,9 @@ def load_priorities(prio_range, path, classif, compress = False): group = [] for line in f: line = line.strip() - if not line or line.startswith('#'): + # Strip comments. + line = line.split('#', 1)[0].strip() + if not line: continue tokens = line.split() if len(tokens) != 2: @@ -257,7 +261,47 @@ def load_priorities(prio_range, path, classif, compress = False): idx = unique_prios.index(prio_ranges[prio_range]['priorities'][prio_id]) prio_ranges[prio_range]['priorities'][prio_id] = int(step * (base_idx + idx)) -def dump_priorities(prio_range, path): + +def store_visibility(cl, dr_type, object_id, zoom): + dr_id = dr_type if object_id == '::default' else dr_type + object_id + if cl not in visibilities: + visibilities[cl] = {} + if dr_id not in visibilities[cl]: + visibilities[cl][dr_id] = set() + visibilities[cl][dr_id].add(zoom) + + +def dump_priorities(prio_range, path, maxzoom): + + def prettify_zooms(zooms, maxzoom): + + def add_zrange(first, prev, result, maxzoom): + first = str(first) + prev = str(prev) + if first == prev: + zrange = first + elif prev == str(maxzoom): + zrange = first + '-' + else: + zrange = first + '-' + prev + if result != '': + result += ',' + result += zrange + return result + + zooms = sorted(zooms) + first = zooms.pop(0) + prev = first + result = '' + for zoom in zooms: + if zoom == prev + 1: + prev = zoom + else: + result = add_zrange(first, prev, result, maxzoom) + first = zoom + prev = zoom + return 'z' + add_zrange(first, prev, result, maxzoom) + with open(get_priorities_filename(prio_range, path), 'w') as outfile: comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW for s in comment.splitlines(): @@ -274,8 +318,24 @@ def dump_priorities(prio_range, path): outfile.write(f'{group}=== {group_prio}\n\n') group_prio = p[1] group = '' + drule_zooms = '' + other_drules = '' + if p[0][0] in visibilities: + for dr_id in sorted(visibilities[p[0][0]].keys()): + zoom_range = prettify_zooms(visibilities[p[0][0]][dr_id], maxzoom) + if dr_id == p[0][1]: + drule_zooms = zoom_range + else: + if other_drules: + other_drules += ', ' + other_drules += dr_id + ' ' + zoom_range + info = '' + if drule_zooms or other_drules: + info = f' # {drule_zooms}' + if other_drules: + info += f' (also has {other_drules})' # TODO: add zoom ranges support? - group += f'{p[0][0]}\t{p[0][1]}\n' + group += f'{p[0][0]:40} {p[0][1]:10}{info}\n' outfile.write(f'{group}=== {group_prio}\n') def get_drape_priority(cl, dr_type, object_id): @@ -530,13 +590,6 @@ def komap_mapswithme(options): 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) or st.get('casing-width-add') is not None: # and (st.get('width') or st.get('fill-color')): is_area_st = 'fill-color' in st if has_lines and not is_area_st and st.get('casing-linecap', 'butt') == 'butt': @@ -561,7 +614,10 @@ def komap_mapswithme(options): if (st.get('object-id') == '::default' and dr_line.priority != 0 and dr_line.priority != prio_ranges[PRIO_FG]['base'] and dr_line.priority != prio_ranges[PRIO_BG_TOP]['base']): + store_visibility(cl, 'line', '(casing)', zoom) dr_line.priority -= 1 + else: + store_visibility(cl, 'line', st.get('object-id'), zoom) for i in st.get('casing-dashes', st.get('dashes', [])): dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) @@ -596,6 +652,7 @@ def komap_mapswithme(options): 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 = get_drape_priority(cl, 'line', st.get('object-id')) + store_visibility(cl, 'line', st.get('object-id'), zoom) dr_element.lines.extend([dr_line]) if st.get('pattern-image'): dr_line = LineRuleProto() @@ -606,6 +663,7 @@ def komap_mapswithme(options): dr_line.pathsym.step = float(st.get('pattern-spacing', 0)) - 16 dr_line.pathsym.offset = st.get('pattern-offset', 0) dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) + store_visibility(cl, 'line', st.get('object-id'), zoom) dr_element.lines.extend([dr_line]) if st.get('shield-font-size'): @@ -617,6 +675,7 @@ def komap_mapswithme(options): if st.get('shield-outline-radius', 0) != 0: dr_element.shield.stroke_color = mwm_encode_color(colors, st, "shield-outline", "white") dr_element.shield.priority = get_drape_priority(cl, 'shield', st.get('object-id')) + store_visibility(cl, 'shield', st.get('object-id'), zoom) if st.get('shield-min-distance', 0) != 0: dr_element.shield.min_distance = int(st.get('shield-min-distance', 0)) @@ -625,6 +684,7 @@ def komap_mapswithme(options): icon = mwm_encode_image(st) dr_element.symbol.name = icon[0] dr_element.symbol.priority = get_drape_priority(cl, 'icon', st.get('object-id')) + store_visibility(cl, 'icon', st.get('object-id'), zoom) if 'icon-min-distance' in st: dr_element.symbol.min_distance = int(st.get('icon-min-distance', 0)) has_icons = False @@ -633,6 +693,7 @@ def komap_mapswithme(options): dr_element.circle.radius = float(st.get('symbol-size')) dr_element.circle.color = mwm_encode_color(colors, st, 'symbol-fill') dr_element.circle.priority = get_drape_priority(cl, 'circle', st.get('object-id')) + store_visibility(cl, 'circle', st.get('object-id'), zoom) has_icons = False if has_text and st.get('text') and st.get('text') != 'none': @@ -670,6 +731,7 @@ def komap_mapswithme(options): # Priority is assigned from the first (primary) rule. dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) + store_visibility(cl, text_priority_key, st.get('object-id'), zoom) # Process captions block once. has_text = None @@ -678,6 +740,7 @@ def komap_mapswithme(options): if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0): dr_element.area.color = mwm_encode_color(colors, st, "fill") dr_element.area.priority = get_drape_priority(cl, 'area', st.get('object-id')) + store_visibility(cl, 'area', st.get('object-id'), zoom) has_fills = False str_dr_element = dr_cont.name + "/" + str(dr_element) @@ -694,7 +757,7 @@ def komap_mapswithme(options): output = '' for prio_range in prio_ranges.keys(): - dump_priorities(prio_range, options.priorities_path) + dump_priorities(prio_range, options.priorities_path, options.maxzoom) output += f'{"" if not output else ", "}{len(prio_ranges[prio_range]["priorities"])} {prio_range}' print(f'Re-formated priorities files: {output}.') -- 2.45.3 From fbecca40c3ea56a80bd2145efaa57416e6eaa066 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 30 Jul 2023 14:14:58 +0300 Subject: [PATCH 26/46] Make optional captions below other overlays --- src/libkomwm.py | 45 ++++++++++++++++++++++++++++++--------------- 1 file changed, 30 insertions(+), 15 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index f79d8c3..b4be691 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -94,7 +94,7 @@ Priorities ranges' rendering order overview: - BG-by-size: landcover areas sorted by their size ''' -OVERLAYS_MAX_PRIORITY = 100000 +OVERLAYS_MAX_PRIORITY = 10000 def to_boolean(s): s = s.lower() @@ -232,17 +232,18 @@ def load_priorities(prio_range, path, classif, compress = False): line = group print_warning(f'skipping last types groups with no priority set') - for key in prio_ranges[PRIO_OVERLAYS]['priorities'].keys(): - main_prio_id = None - if key[1].startswith('caption'): - main_prio_id = (key[0], key[1].replace('caption', 'icon')) - if key[1].startswith('pathtext'): - main_prio_id = (key[0], key[1].replace('pathtext', 'shield')) - if main_prio_id is not None and main_prio_id in prio_ranges[PRIO_OVERLAYS]['priorities']: - main_prio = prio_ranges[PRIO_OVERLAYS]['priorities'][main_prio_id] - if prio_ranges[PRIO_OVERLAYS]['priorities'][key] > main_prio: - print(f'WARNING: {key} priority is higher than {main_prio_id}, making it equal') - prio_ranges[PRIO_OVERLAYS]['priorities'][key] = main_prio + if prio_range == PRIO_OVERLAYS: + for key in prio_ranges[PRIO_OVERLAYS]['priorities'].keys(): + main_prio_id = None + if key[1].startswith('caption'): + main_prio_id = (key[0], key[1].replace('caption', 'icon')) + if key[1].startswith('pathtext'): + main_prio_id = (key[0], key[1].replace('pathtext', 'shield')) + if main_prio_id is not None and main_prio_id in prio_ranges[PRIO_OVERLAYS]['priorities']: + main_prio = prio_ranges[PRIO_OVERLAYS]['priorities'][main_prio_id] + if prio_ranges[PRIO_OVERLAYS]['priorities'][key] > main_prio: + print(f'WARNING: {key} priority is higher than {main_prio_id}, making it equal') + prio_ranges[PRIO_OVERLAYS]['priorities'][key] = main_prio if compress: print(f'Compressing {prio_range} priorities into a (0;{priority_max}) range:') @@ -342,6 +343,7 @@ def get_drape_priority(cl, dr_type, object_id): if object_id == '::default': object_id = '' prio_id = (cl, dr_type + object_id) + ranges_to_check = (PRIO_OVERLAYS, ) if dr_type == 'line': ranges_to_check = (PRIO_FG, PRIO_BG_TOP) @@ -350,6 +352,7 @@ def get_drape_priority(cl, dr_type, object_id): for r in ranges_to_check: if prio_id in prio_ranges[r]['priorities']: return prio_ranges[r]['priorities'][prio_id] + prio_ranges[r]['base'] + print(f'WARNING: priority is not set for {prio_id}') return 0 @@ -724,13 +727,25 @@ def komap_mapswithme(options): dr_cur_subtext.is_optional = value else: dr_cur_subtext.is_optional = True - elif st.get('text-position', 'center') == 'center' and dr_element.symbol.priority: + elif text_priority_key == 'caption' and dr_element.symbol.priority: # On by default for all captions (not path texts) with icons. dr_cur_subtext.is_optional = True dr_cur_subtext = dr_text.secondary - # Priority is assigned from the first (primary) rule. - dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) + if text_priority_key == 'caption' and dr_element.symbol.priority: + # Mandatory captions with icons use icon's priority. + dr_text.priority = get_drape_priority(cl, 'icon', st.get('object-id')) + # Optional captions (with icons) are automatically placed below all other overlays. + if dr_text.primary.is_optional: + dr_text.priority -= OVERLAYS_MAX_PRIORITY + + oid = '' if st.get('object-id') == '::default' else st.get('object-id') + cap_prio_id = (cl, 'caption' + oid) + if cap_prio_id in prio_ranges[PRIO_OVERLAYS]['priorities']: + del prio_ranges[PRIO_OVERLAYS]['priorities'][cap_prio_id] + else: + dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) + store_visibility(cl, text_priority_key, st.get('object-id'), zoom) # Process captions block once. -- 2.45.3 From 0e23a0e798ff732ce74dd205b72b7eb5dad75451 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 2 Aug 2023 14:06:14 +0300 Subject: [PATCH 27/46] Remove drule types from prio.txt files Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index b4be691..cd66bad 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -335,8 +335,12 @@ def dump_priorities(prio_range, path, maxzoom): info = f' # {drule_zooms}' if other_drules: info += f' (also has {other_drules})' + cl = p[0][0] + tokens = p[0][1].split('::') + if len(tokens) > 1: + cl += f'::{tokens[1]}' # TODO: add zoom ranges support? - group += f'{p[0][0]:40} {p[0][1]:10}{info}\n' + group += f'{cl:50} {info}\n' outfile.write(f'{group}=== {group_prio}\n') def get_drape_priority(cl, dr_type, object_id): -- 2.45.3 From a03d74f588f69170a81231213166aeb9d92720a1 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 2 Aug 2023 17:49:53 +0300 Subject: [PATCH 28/46] Load priorities from files without explicit drule types specified Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 80 +++++++++++++++++++++++++++++-------------------- 1 file changed, 47 insertions(+), 33 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index cd66bad..9400cfe 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -200,7 +200,7 @@ def load_priorities(prio_range, path, classif, compress = False): if not line: continue tokens = line.split() - if len(tokens) != 2: + if len(tokens) > 2: print_warning('skipping malformed line') continue if tokens[0] == "===": @@ -219,13 +219,17 @@ def load_priorities(prio_range, path, classif, compress = False): print_warning(f'skipping out of [0;{priority_max}) range priority value') group = [] else: - if tokens[0] not in classif: + cl = tokens[0] + object_id = '' + oid_pos = cl.find('::') + if oid_pos != -1: + object_id = cl[oid_pos:] + cl = cl[0:oid_pos] + if cl not in classif: print_warning('unknown classificator type') - if tokens[1].split('::')[0] not in ('icon', 'caption', 'pathtext', 'shield', 'line', 'area'): - print_warning('unknown drule type') - key = (tokens[0], tokens[1]) + key = (cl, object_id) if key in prio_ranges[prio_range]['priorities']: - print_warning(f'duplicate priority (previous value {prio_ranges[prio_range]["priorities"][key]})') + print_warning(f'overriding previously set priority value {prio_ranges[prio_range]["priorities"][key]}') group.append(key) if len(group): @@ -264,12 +268,15 @@ def load_priorities(prio_range, path, classif, compress = False): def store_visibility(cl, dr_type, object_id, zoom): - dr_id = dr_type if object_id == '::default' else dr_type + object_id + if object_id == '::default': + object_id = '' if cl not in visibilities: visibilities[cl] = {} - if dr_id not in visibilities[cl]: - visibilities[cl][dr_id] = set() - visibilities[cl][dr_id].add(zoom) + if dr_type not in visibilities[cl]: + visibilities[cl][dr_type] = {} + if object_id not in visibilities[cl][dr_type]: + visibilities[cl][dr_type][object_id] = set() + visibilities[cl][dr_type][object_id].add(zoom) def dump_priorities(prio_range, path, maxzoom): @@ -319,34 +326,46 @@ def dump_priorities(prio_range, path, maxzoom): outfile.write(f'{group}=== {group_prio}\n\n') group_prio = p[1] group = '' - drule_zooms = '' + cl = p[0][0] + object_id = p[0][1] + + line_drules = '' other_drules = '' - if p[0][0] in visibilities: - for dr_id in sorted(visibilities[p[0][0]].keys()): - zoom_range = prettify_zooms(visibilities[p[0][0]][dr_id], maxzoom) - if dr_id == p[0][1]: - drule_zooms = zoom_range - else: - if other_drules: - other_drules += ', ' - other_drules += dr_id + ' ' + zoom_range + if cl in visibilities: + for dr_type in sorted(visibilities[cl].keys()): + for oid in sorted(visibilities[cl][dr_type].keys()): + dr_zoom = dr_type + oid if oid else dr_type + dr_zoom += ' ' + prettify_zooms(visibilities[cl][dr_type][oid], maxzoom) + # Drules matching this prio_range and object_id. + if (oid == object_id and + ((prio_range == PRIO_OVERLAYS and dr_type in ('icon', 'caption', 'shield', 'pathtext')) or + (prio_range in (PRIO_FG, PRIO_BG_TOP) and dr_type in ('line', 'area')) or + (prio_range == PRIO_BG_BY_SIZE and dr_type == 'area'))): + if line_drules: + line_drules += ' and ' + line_drules += dr_zoom + else: + # Drules from other prio_ranges or with other object_ids. + if other_drules: + other_drules += ', ' + other_drules += dr_zoom + if object_id: + cl += object_id + if not line_drules: + print(f'WARNING: priority is defined, but no drules for {cl}') + info = '' - if drule_zooms or other_drules: - info = f' # {drule_zooms}' + if line_drules or other_drules: + info = f' # {line_drules}' if other_drules: info += f' (also has {other_drules})' - cl = p[0][0] - tokens = p[0][1].split('::') - if len(tokens) > 1: - cl += f'::{tokens[1]}' - # TODO: add zoom ranges support? group += f'{cl:50} {info}\n' outfile.write(f'{group}=== {group_prio}\n') def get_drape_priority(cl, dr_type, object_id): if object_id == '::default': object_id = '' - prio_id = (cl, dr_type + object_id) + prio_id = (cl, object_id) ranges_to_check = (PRIO_OVERLAYS, ) if dr_type == 'line': @@ -742,11 +761,6 @@ def komap_mapswithme(options): # Optional captions (with icons) are automatically placed below all other overlays. if dr_text.primary.is_optional: dr_text.priority -= OVERLAYS_MAX_PRIORITY - - oid = '' if st.get('object-id') == '::default' else st.get('object-id') - cap_prio_id = (cl, 'caption' + oid) - if cap_prio_id in prio_ranges[PRIO_OVERLAYS]['priorities']: - del prio_ranges[PRIO_OVERLAYS]['priorities'][cap_prio_id] else: dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) -- 2.45.3 From 024810fb85721b1ecc9e8e05d01113b54984d584 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 2 Aug 2023 23:26:17 +0300 Subject: [PATCH 29/46] Validate visibilities Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 80 +++++++++++++++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 32 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 9400cfe..d187534 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -279,37 +279,52 @@ def store_visibility(cl, dr_type, object_id, zoom): visibilities[cl][dr_type][object_id].add(zoom) +def prettify_zooms(zooms, maxzoom): + + def add_zrange(first, prev, result, maxzoom): + first = str(first) + prev = str(prev) + if first == prev: + zrange = first + elif prev == str(maxzoom): + zrange = first + '-' + else: + zrange = first + '-' + prev + if result != '': + result += ',' + result += zrange + return result + + zooms = sorted(zooms) + first = zooms.pop(0) + prev = first + result = '' + for zoom in zooms: + if zoom == prev + 1: + prev = zoom + else: + result = add_zrange(first, prev, result, maxzoom) + first = zoom + prev = zoom + return 'z' + add_zrange(first, prev, result, maxzoom) + + +def validate_visibilities(maxzoom): + for cl, dr_types in visibilities.items(): + for dr_type, object_ids in dr_types.items(): + for object_id, zooms in object_ids.items(): + zoom_range = prettify_zooms(zooms, maxzoom) + if zoom_range.find(',') != -1: + print(f'WARNING: non-contiguous visibility range {zoom_range} for {cl} {dr_type}{object_id}') + + if dr_type == 'caption' and 'icon' in dr_types and object_id in dr_types['icon']: + icon_zooms = sorted(dr_types['icon'][object_id]) + if sorted(zooms)[0] < icon_zooms[0]: + print(f'WARNING: caption {zoom_range} appears before icon {prettify_zooms(icon_zooms, maxzoom)}' + f' for {cl}{object_id}') + + def dump_priorities(prio_range, path, maxzoom): - - def prettify_zooms(zooms, maxzoom): - - def add_zrange(first, prev, result, maxzoom): - first = str(first) - prev = str(prev) - if first == prev: - zrange = first - elif prev == str(maxzoom): - zrange = first + '-' - else: - zrange = first + '-' + prev - if result != '': - result += ',' - result += zrange - return result - - zooms = sorted(zooms) - first = zooms.pop(0) - prev = first - result = '' - for zoom in zooms: - if zoom == prev + 1: - prev = zoom - else: - result = add_zrange(first, prev, result, maxzoom) - first = zoom - prev = zoom - return 'z' + add_zrange(first, prev, result, maxzoom) - with open(get_priorities_filename(prio_range, path), 'w') as outfile: comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW for s in comment.splitlines(): @@ -545,8 +560,8 @@ def komap_mapswithme(options): for result in results: cl, zoom, runtime_conditions, zstyle = result - # First, sort rules by 'object-id' in captions (primary, secondary, none ..); - # Then by 'z-index' in ascending order. + # First, sort rules by ::object-id in captions (primary, secondary, none ..) + # then by other ::object-id in ascending order. def rule_sort_key(dict_): first = 0 if dict_.get('text'): @@ -787,6 +802,7 @@ def komap_mapswithme(options): visibility["world|" + class_tree[cl] + "|"] = "".join(visstring) + validate_visibilities(options.maxzoom) output = '' for prio_range in prio_ranges.keys(): -- 2.45.3 From 24b8586c2300275677a422371f11a82a2e86fdfd Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Mon, 7 Aug 2023 20:51:55 +0300 Subject: [PATCH 30/46] Update comments Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index d187534..e493e27 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -28,10 +28,9 @@ from drules_struct_pb2 import * WIDTH_SCALE = 1.0 -# z-index values defined in mapcss files are first -# compressed into a 0-1000 integer range -# and then adjusted to divide into following "priorities ranges": -# (15000; 17000) : overlays (icons, captions...) +# Priority values defined in *.prio.txt files are adjusted +# to fit into the following "priorities ranges": +# [-10000; 10000): overlays (icons, captions...) # [0; 1000) : FG - foreground areas and lines # [-1000; 0) : BG-top - water, linear and areal, rendered just on top of landcover # (-2000; -1000) : BG-by-size - landcover areas, later in core sorted by their bbox size @@ -39,7 +38,11 @@ WIDTH_SCALE = 1.0 # for special behavior and features' layer=* values. # See drape_frontend/stylist.cpp for the details of layering logic. -PRIORITY_RANGE = 1000 # All z-indexes are compressed into this range. +# Priority range for area and line drules. Should be same as drule::kLayerPriorityRange. +LAYER_PRIORITY_RANGE = 1000 +# Should be same as drule::kOverlaysMaxPriority. The overlays range is [-kOverlaysMaxPriority; kOverlaysMaxPriority), +# negative values are used for optional captions which are below all other overlays. +OVERLAYS_MAX_PRIORITY = 10000 # Drules are arranged into following ranges. PRIO_OVERLAYS = 'overlays' @@ -59,7 +62,18 @@ visibilities = {} prio_ranges[PRIO_OVERLAYS]['comment'] = ''' Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. +Optional captions (which have an icon) are displayed only if there are no other overlays in their way +(technically their priorities are automatically put below all other overlays in the same order as corresponding icons). +House numbers are hardcoded to have the lowest priority. ''' + +prio_ranges[PRIO_OVERLAYS]['comment'] = f''' +Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). +Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. +Optional captions (which have an icon) are displayed only if there are no other overlays in their way +(technically, max overlays priority value ({OVERLAYS_MAX_PRIORITY}) is subtracted from their priorities automatically). +''' + prio_ranges[PRIO_FG]['comment'] = ''' FG geometry: foreground lines and areas (e.g. buildings) are rendered always below overlays and always on top of background geometry (BG-top & BG-by-size) even if a foreground feature @@ -94,8 +108,6 @@ Priorities ranges' rendering order overview: - BG-by-size: landcover areas sorted by their size ''' -OVERLAYS_MAX_PRIORITY = 10000 - def to_boolean(s): s = s.lower() if s == "true" or s == "yes": @@ -189,7 +201,7 @@ def load_priorities(prio_range, path, classif, compress = False): def print_warning(msg): print(f'WARNING: {msg} in {fname}:\n\t{line}') - priority_max = OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else PRIORITY_RANGE + priority_max = OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else LAYER_PRIORITY_RANGE fname = get_priorities_filename(prio_range, path) with open(fname, 'r') as f: group = [] @@ -500,7 +512,7 @@ def komap_mapswithme(options): # Parse style mapcss global style style = MapCSS(options.minzoom, options.maxzoom) - style.parse(clamp=False, stretch=PRIORITY_RANGE, + style.parse(clamp=False, stretch=LAYER_PRIORITY_RANGE, filename=options.filename, static_tags=mapcss_static_tags, dynamic_tags=mapcss_dynamic_tags) -- 2.45.3 From 04f5f4413702faa4b24ca7f3216e6930748f7c25 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sun, 13 Aug 2023 15:35:40 +0300 Subject: [PATCH 31/46] Allow setting negative overlays priorities to e.g. put icons below automatic optional captions Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index e493e27..6df76d2 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -202,6 +202,7 @@ def load_priorities(prio_range, path, classif, compress = False): print(f'WARNING: {msg} in {fname}:\n\t{line}') priority_max = OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else LAYER_PRIORITY_RANGE + priority_min = -OVERLAYS_MAX_PRIORITY if prio_range == PRIO_OVERLAYS else 0 fname = get_priorities_filename(prio_range, path) with open(fname, 'r') as f: group = [] @@ -221,14 +222,14 @@ def load_priorities(prio_range, path, classif, compress = False): except ValueError: print_warning('skipping invalid priority value') else: - if priority >= 0 and priority < priority_max: + if priority >= priority_min and priority < priority_max: if len(group): for key in group: prio_ranges[prio_range]['priorities'][key] = priority else: print_warning('skipping empty priority group') else: - print_warning(f'skipping out of [0;{priority_max}) range priority value') + print_warning(f'skipping out of [{priority_min};{priority_max}) range priority value') group = [] else: cl = tokens[0] @@ -379,7 +380,8 @@ def dump_priorities(prio_range, path, maxzoom): if object_id: cl += object_id if not line_drules: - print(f'WARNING: priority is defined, but no drules for {cl}') + line_drules = "WARNING: no style defined (the type will be not included into map data)" + print(f'{line_drules} for {cl}') info = '' if line_drules or other_drules: @@ -403,7 +405,7 @@ def get_drape_priority(cl, dr_type, object_id): if prio_id in prio_ranges[r]['priorities']: return prio_ranges[r]['priorities'][prio_id] + prio_ranges[r]['base'] - print(f'WARNING: priority is not set for {prio_id}') + print(f'WARNING: priority is not set for {dr_type} {cl}{object_id}') return 0 def komap_mapswithme(options): @@ -787,7 +789,7 @@ def komap_mapswithme(options): dr_text.priority = get_drape_priority(cl, 'icon', st.get('object-id')) # Optional captions (with icons) are automatically placed below all other overlays. if dr_text.primary.is_optional: - dr_text.priority -= OVERLAYS_MAX_PRIORITY + dr_text.priority = max(dr_text.priority - OVERLAYS_MAX_PRIORITY, -OVERLAYS_MAX_PRIORITY) else: dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) -- 2.45.3 From dea0efb9ee2564fcb0df47a168b5549048548340 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 15 Aug 2023 16:31:23 +0300 Subject: [PATCH 32/46] Sort drule types in comments in logical order And some other minor changes. Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 22 ++++++++++++++-------- 1 file changed, 14 insertions(+), 8 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 6df76d2..3202120 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -97,7 +97,8 @@ Keep them in a logical importance order please. ''' COMMENT_AUTOFORMAT = '''This file is automatically re-formatted and re-sorted in priorities descending order -when generate_drules.sh is run. Custom formatting and comments are not preserved. +when generate_drules.sh is run. All comments (drule types visibilities, etc.) are generated automatically +for information only. Custom formatting and comments are not preserved. ''' COMMENT_RANGES_OVERVIEW = ''' @@ -262,6 +263,7 @@ def load_priorities(prio_range, path, classif, compress = False): print(f'WARNING: {key} priority is higher than {main_prio_id}, making it equal') prio_ranges[PRIO_OVERLAYS]['priorities'][key] = main_prio + # TODO: update compression logic to handle icons put inbetween automatic optional captions priorities. if compress: print(f'Compressing {prio_range} priorities into a (0;{priority_max}) range:') unique_prios = set(prio_ranges[prio_range]['priorities'].values()) @@ -294,15 +296,15 @@ def store_visibility(cl, dr_type, object_id, zoom): def prettify_zooms(zooms, maxzoom): - def add_zrange(first, prev, result, maxzoom): + def add_zrange(first, last, result, maxzoom): first = str(first) - prev = str(prev) - if first == prev: - zrange = first - elif prev == str(maxzoom): + last = str(last) + if last == str(maxzoom): zrange = first + '-' + elif first == last: + zrange = first else: - zrange = first + '-' + prev + zrange = first + '-' + last if result != '': result += ',' result += zrange @@ -345,6 +347,9 @@ def dump_priorities(prio_range, path, maxzoom): outfile.write('\n') if len(prio_ranges[prio_range]['priorities']): + dr_types_order = (('icon', 'caption', 'pathtext', 'shield', 'line', 'area') if prio_range == PRIO_OVERLAYS + else ('line', 'area', 'icon', 'caption', 'pathtext', 'shield')) + prios = sorted(prio_ranges[prio_range]['priorities'].items(), key = lambda item: (OVERLAYS_MAX_PRIORITY - item[1], item[0][0], item[0][1])) group_prio = prios[0][1] @@ -360,7 +365,7 @@ def dump_priorities(prio_range, path, maxzoom): line_drules = '' other_drules = '' if cl in visibilities: - for dr_type in sorted(visibilities[cl].keys()): + for dr_type in sorted(visibilities[cl].keys(), key = lambda drt: dr_types_order.index(drt)): for oid in sorted(visibilities[cl][dr_type].keys()): dr_zoom = dr_type + oid if oid else dr_type dr_zoom += ' ' + prettify_zooms(visibilities[cl][dr_type][oid], maxzoom) @@ -380,6 +385,7 @@ def dump_priorities(prio_range, path, maxzoom): if object_id: cl += object_id if not line_drules: + # TODO: produce similar warnings for all types in mapcss-mapping.csv but without a style. line_drules = "WARNING: no style defined (the type will be not included into map data)" print(f'{line_drules} for {cl}') -- 2.45.3 From e256a119ab3117add38c6ed85e97e6edb45e13de Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 16 Aug 2023 17:12:00 +0300 Subject: [PATCH 33/46] Display drules with automatic priorities Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 149 ++++++++++++++++++++++++++++++------------------ 1 file changed, 93 insertions(+), 56 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 3202120..af8feb8 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -41,7 +41,7 @@ WIDTH_SCALE = 1.0 # Priority range for area and line drules. Should be same as drule::kLayerPriorityRange. LAYER_PRIORITY_RANGE = 1000 # Should be same as drule::kOverlaysMaxPriority. The overlays range is [-kOverlaysMaxPriority; kOverlaysMaxPriority), -# negative values are used for optional captions which are below all other overlays. +# negative values are used for optional captions which are below most other overlays. OVERLAYS_MAX_PRIORITY = 10000 # Drules are arranged into following ranges. @@ -59,18 +59,10 @@ prio_ranges = { visibilities = {} -prio_ranges[PRIO_OVERLAYS]['comment'] = ''' -Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). -Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. -Optional captions (which have an icon) are displayed only if there are no other overlays in their way -(technically their priorities are automatically put below all other overlays in the same order as corresponding icons). -House numbers are hardcoded to have the lowest priority. -''' - prio_ranges[PRIO_OVERLAYS]['comment'] = f''' Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. -Optional captions (which have an icon) are displayed only if there are no other overlays in their way +Optional captions (which have an icon) are usually displayed only if there are no other overlays in their way (technically, max overlays priority value ({OVERLAYS_MAX_PRIORITY}) is subtracted from their priorities automatically). ''' @@ -97,8 +89,8 @@ Keep them in a logical importance order please. ''' COMMENT_AUTOFORMAT = '''This file is automatically re-formatted and re-sorted in priorities descending order -when generate_drules.sh is run. All comments (drule types visibilities, etc.) are generated automatically -for information only. Custom formatting and comments are not preserved. +when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.) +are generated automatically for information only. Custom formatting and comments are not preserved. ''' COMMENT_RANGES_OVERVIEW = ''' @@ -282,16 +274,17 @@ def load_priorities(prio_range, path, classif, compress = False): prio_ranges[prio_range]['priorities'][prio_id] = int(step * (base_idx + idx)) -def store_visibility(cl, dr_type, object_id, zoom): +def store_visibility(cl, dr_type, object_id, zoom, auto_comment = None): if object_id == '::default': object_id = '' + dr_type_comment = (dr_type, auto_comment) if cl not in visibilities: visibilities[cl] = {} - if dr_type not in visibilities[cl]: - visibilities[cl][dr_type] = {} - if object_id not in visibilities[cl][dr_type]: - visibilities[cl][dr_type][object_id] = set() - visibilities[cl][dr_type][object_id].add(zoom) + if dr_type_comment not in visibilities[cl]: + visibilities[cl][dr_type_comment] = {} + if object_id not in visibilities[cl][dr_type_comment]: + visibilities[cl][dr_type_comment][object_id] = set() + visibilities[cl][dr_type_comment][object_id].add(zoom) def prettify_zooms(zooms, maxzoom): @@ -343,40 +336,65 @@ def dump_priorities(prio_range, path, maxzoom): with open(get_priorities_filename(prio_range, path), 'w') as outfile: comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW for s in comment.splitlines(): - outfile.write(f'# {s}'.strip() + '\n') + outfile.write(f'# {s}'.rstrip() + '\n') outfile.write('\n') if len(prio_ranges[prio_range]['priorities']): dr_types_order = (('icon', 'caption', 'pathtext', 'shield', 'line', 'area') if prio_range == PRIO_OVERLAYS else ('line', 'area', 'icon', 'caption', 'pathtext', 'shield')) + comment_auto_captions = ''' + All automatic optional captions priorities are below 0. + They follow the order of their correspoding icons. + ''' prios = sorted(prio_ranges[prio_range]['priorities'].items(), key = lambda item: (OVERLAYS_MAX_PRIORITY - item[1], item[0][0], item[0][1])) group_prio = prios[0][1] group = '' + group_comment = '# ' for p in prios: if p[1] != group_prio: - outfile.write(f'{group}=== {group_prio}\n\n') + if prio_range == PRIO_OVERLAYS and comment_auto_captions and group_prio < 0: + for s in comment_auto_captions.splitlines(): + outfile.write(f'# {s.strip()}'.rstrip() + '\n') + outfile.write('\n') + comment_auto_captions = None + outfile.write(f'{group}{group_comment}=== {group_prio}\n\n') group_prio = p[1] group = '' + group_comment = '# ' + cl = p[0][0] object_id = p[0][1] + auto_dr_type = None + auto_comment = None + if len(p[0]) == 4: + auto_dr_type = p[0][2] + auto_comment = p[0][3] line_drules = '' other_drules = '' if cl in visibilities: - for dr_type in sorted(visibilities[cl].keys(), key = lambda drt: dr_types_order.index(drt)): - for oid in sorted(visibilities[cl][dr_type].keys()): - dr_zoom = dr_type + oid if oid else dr_type - dr_zoom += ' ' + prettify_zooms(visibilities[cl][dr_type][oid], maxzoom) - # Drules matching this prio_range and object_id. - if (oid == object_id and - ((prio_range == PRIO_OVERLAYS and dr_type in ('icon', 'caption', 'shield', 'pathtext')) or - (prio_range in (PRIO_FG, PRIO_BG_TOP) and dr_type in ('line', 'area')) or - (prio_range == PRIO_BG_BY_SIZE and dr_type == 'area'))): - if line_drules: - line_drules += ' and ' - line_drules += dr_zoom + for dr_type_comment in sorted(visibilities[cl].keys(), key = lambda drt: dr_types_order.index(drt[0])): + for oid in sorted(visibilities[cl][dr_type_comment].keys()): + dr_type, dr_auto_comment = dr_type_comment + dr_zoom = dr_type + oid + if dr_auto_comment is not None: + dr_zoom = f'{dr_zoom}({dr_auto_comment})' + dr_zoom += ' ' + prettify_zooms(visibilities[cl][dr_type_comment][oid], maxzoom) + # Drules matching this prio_range and object_id and + # - an auto priority dr_type match or + # - any other non-auto dr_type suitable + is_auto_dr_match = dr_type == auto_dr_type and dr_auto_comment == auto_comment + is_not_auto_dr = auto_dr_type is None and dr_auto_comment is None + is_suitable_for_range = ( + (prio_range == PRIO_OVERLAYS and dr_type in ('icon', 'caption', 'pathtext', 'shield')) or + (prio_range in (PRIO_FG, PRIO_BG_TOP) and dr_type in ('line', 'area')) or + (prio_range == PRIO_BG_BY_SIZE and dr_type == 'area')) + if oid == object_id and (is_auto_dr_match or is_not_auto_dr and is_suitable_for_range): + if line_drules: + line_drules += ' and ' + line_drules += dr_zoom else: # Drules from other prio_ranges or with other object_ids. if other_drules: @@ -385,19 +403,24 @@ def dump_priorities(prio_range, path, maxzoom): if object_id: cl += object_id if not line_drules: - # TODO: produce similar warnings for all types in mapcss-mapping.csv but without a style. - line_drules = "WARNING: no style defined (the type will be not included into map data)" - print(f'{line_drules} for {cl}') - - info = '' - if line_drules or other_drules: - info = f' # {line_drules}' if other_drules: - info += f' (also has {other_drules})' - group += f'{cl:50} {info}\n' - outfile.write(f'{group}=== {group_prio}\n') + line_drules = "WARNING: no drule defined for the priority" + else: + line_drules = "WARNING: no style defined (the type will be not included into map data)" + print(f'{line_drules} for {cl} in {prio_range}') -def get_drape_priority(cl, dr_type, object_id): + info = '# ' + line_drules + if other_drules: + info += f' (also has {other_drules})' + if auto_dr_type is None: + group_comment = '' + else: + cl = '# ' + cl + group += f'{cl:50} {info}\n' + + outfile.write(f'{group}{group_comment}=== {group_prio}\n') + +def get_drape_priority(cl, dr_type, object_id, auto_dr_type = None, auto_comment = None, auto_prio_mod = 0): if object_id == '::default': object_id = '' prio_id = (cl, object_id) @@ -409,11 +432,18 @@ def get_drape_priority(cl, dr_type, object_id): ranges_to_check = (PRIO_BG_BY_SIZE, PRIO_BG_TOP, PRIO_FG) for r in ranges_to_check: if prio_id in prio_ranges[r]['priorities']: - return prio_ranges[r]['priorities'][prio_id] + prio_ranges[r]['base'] + priority = prio_ranges[r]['priorities'][prio_id] + if auto_dr_type is not None: + min_priority = -OVERLAYS_MAX_PRIORITY if r == PRIO_OVERLAYS else 0 + priority = max(priority + auto_prio_mod, min_priority) + auto_prio_id = (cl, object_id, auto_dr_type, auto_comment) + prio_ranges[r]['priorities'][auto_prio_id] = priority + return priority + prio_ranges[r]['base'] print(f'WARNING: priority is not set for {dr_type} {cl}{object_id}') return 0 + def komap_mapswithme(options): if options.data and os.path.isdir(options.data): ddir = options.data @@ -670,14 +700,14 @@ def komap_mapswithme(options): dr_line.width = round((base_width + st.get('casing-width') * 2) * WIDTH_SCALE, 2) dr_line.color = mwm_encode_color(colors, st, "casing") - dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) - # Casing line should be rendered below the "main" line, hence priority -1 (but not less than base). - if (st.get('object-id') == '::default' and dr_line.priority != 0 and - dr_line.priority != prio_ranges[PRIO_FG]['base'] and - dr_line.priority != prio_ranges[PRIO_BG_TOP]['base']): - store_visibility(cl, 'line', '(casing)', zoom) - dr_line.priority -= 1 + if st.get('object-id') == '::default': + # An automatic casing line should be rendered below the "main" line, hence auto priority -1. + auto_comment = 'casing' + dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id'), 'line', auto_comment, -1) + store_visibility(cl, 'line', st.get('object-id'), zoom, auto_comment) else: + # A casing line explicitly defined via ::object_id. + dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) store_visibility(cl, 'line', st.get('object-id'), zoom) for i in st.get('casing-dashes', st.get('dashes', [])): dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) @@ -790,16 +820,23 @@ def komap_mapswithme(options): dr_cur_subtext.is_optional = True dr_cur_subtext = dr_text.secondary + auto_comment = None if text_priority_key == 'caption' and dr_element.symbol.priority: - # Mandatory captions with icons use icon's priority. - dr_text.priority = get_drape_priority(cl, 'icon', st.get('object-id')) - # Optional captions (with icons) are automatically placed below all other overlays. + # A caption with an icon. + # Mandatory captions use icon's priority. + auto_prio_mod = 0 + auto_comment = 'mandatory' if dr_text.primary.is_optional: - dr_text.priority = max(dr_text.priority - OVERLAYS_MAX_PRIORITY, -OVERLAYS_MAX_PRIORITY) + # Optional captions are automatically placed below most other overlays. + auto_comment = 'optional' + auto_prio_mod = -OVERLAYS_MAX_PRIORITY + dr_text.priority = get_drape_priority(cl, 'icon', st.get('object-id'), + text_priority_key, auto_comment, auto_prio_mod) else: + # A pathtext or a standalone caption. dr_text.priority = get_drape_priority(cl, text_priority_key, st.get('object-id')) - store_visibility(cl, text_priority_key, st.get('object-id'), zoom) + store_visibility(cl, text_priority_key, st.get('object-id'), zoom, auto_comment) # Process captions block once. has_text = None -- 2.45.3 From ac01664b9895f0d01db570612b9298939da3b1fc Mon Sep 17 00:00:00 2001 From: Andrew Shkrob Date: Sun, 16 Jul 2023 19:03:21 +0200 Subject: [PATCH 34/46] [3party] Update protobuf to v4.23.4 Signed-off-by: Andrew Shkrob --- README | 1 - README.md | 9 + requirements.txt | 1 + src/drules_struct_pb2.py | 1136 ++------------------------------------ src/libkomwm.py | 13 +- 5 files changed, 59 insertions(+), 1101 deletions(-) delete mode 100644 README create mode 100644 README.md create mode 100644 requirements.txt diff --git a/README b/README deleted file mode 100644 index 3b13e84..0000000 --- a/README +++ /dev/null @@ -1 +0,0 @@ -Kothic Mapcss parser/processor tailored for Organic Maps use. diff --git a/README.md b/README.md new file mode 100644 index 0000000..dfc5d17 --- /dev/null +++ b/README.md @@ -0,0 +1,9 @@ +Kothic Mapcss parser/processor tailored for Organic Maps use. + +Dependencies: +* Python >= 3.8 + +Python dependencies: +```bash +pip3 install -r requirements.txt +``` diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..1d3ed8c --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +protobuf~=4.23.0 diff --git a/src/drules_struct_pb2.py b/src/drules_struct_pb2.py index ac627d0..a10ee5e 100644 --- a/src/drules_struct_pb2.py +++ b/src/drules_struct_pb2.py @@ -1,14 +1,11 @@ +# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: indexer/drules_struct.proto - -import sys -_b=sys.version_info[0]<3 and (lambda x:x) or (lambda x:x.encode('latin1')) -from google.protobuf.internal import enum_type_wrapper +# source: drules_struct.proto +"""Generated protocol buffer code.""" from google.protobuf import descriptor as _descriptor -from google.protobuf import message as _message -from google.protobuf import reflection as _reflection +from google.protobuf import descriptor_pool as _descriptor_pool from google.protobuf import symbol_database as _symbol_database -from google.protobuf import descriptor_pb2 +from google.protobuf.internal import builder as _builder # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -16,1086 +13,49 @@ _sym_db = _symbol_database.Default() -DESCRIPTOR = _descriptor.FileDescriptor( - name='indexer/drules_struct.proto', - package='', - syntax='proto3', - serialized_pb=_b('\n\x1bindexer/drules_struct.proto\"*\n\x0c\x44\x61shDotProto\x12\n\n\x02\x64\x64\x18\x01 \x03(\x01\x12\x0e\n\x06offset\x18\x02 \x01(\x01\":\n\x0cPathSymProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04step\x18\x02 \x01(\x01\x12\x0e\n\x06offset\x18\x03 \x01(\x01\"\xaf\x01\n\rLineRuleProto\x12\r\n\x05width\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x10\n\x08priority\x18\x04 \x01(\x05\x12\x1e\n\x07pathsym\x18\x05 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"\x9c\x01\n\x0cLineDefProto\x12\r\n\x05width\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x1e\n\x07pathsym\x18\x04 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"O\n\rAreaRuleProto\x12\r\n\x05\x63olor\x18\x01 \x01(\r\x12\x1d\n\x06\x62order\x18\x02 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"_\n\x0fSymbolRuleProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0e\x61pply_for_type\x18\x02 \x01(\x05\x12\x10\n\x08priority\x18\x03 \x01(\x05\x12\x14\n\x0cmin_distance\x18\x04 \x01(\x05\"\x8d\x01\n\x0f\x43\x61ptionDefProto\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x14\n\x0cstroke_color\x18\x03 \x01(\r\x12\x10\n\x08offset_x\x18\x04 \x01(\x05\x12\x10\n\x08offset_y\x18\x05 \x01(\x05\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x13\n\x0bis_optional\x18\x07 \x01(\x08\"l\n\x10\x43\x61ptionRuleProto\x12!\n\x07primary\x18\x01 \x01(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"a\n\x0f\x43ircleRuleProto\x12\x0e\n\x06radius\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1d\n\x06\x62order\x18\x03 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x04 \x01(\x05\"m\n\x11PathTextRuleProto\x12!\n\x07primary\x18\x01 \x01(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"\x9d\x01\n\x0fShieldRuleProto\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x14\n\x0cstroke_color\x18\x03 \x01(\r\x12\x10\n\x08priority\x18\x04 \x01(\x05\x12\x14\n\x0cmin_distance\x18\x05 \x01(\x05\x12\x12\n\ntext_color\x18\x06 \x01(\r\x12\x19\n\x11text_stroke_color\x18\x07 \x01(\r\"\xa1\x02\n\x10\x44rawElementProto\x12\r\n\x05scale\x18\x01 \x01(\x05\x12\x1d\n\x05lines\x18\x02 \x03(\x0b\x32\x0e.LineRuleProto\x12\x1c\n\x04\x61rea\x18\x03 \x01(\x0b\x32\x0e.AreaRuleProto\x12 \n\x06symbol\x18\x04 \x01(\x0b\x32\x10.SymbolRuleProto\x12\"\n\x07\x63\x61ption\x18\x05 \x01(\x0b\x32\x11.CaptionRuleProto\x12 \n\x06\x63ircle\x18\x06 \x01(\x0b\x32\x10.CircleRuleProto\x12%\n\tpath_text\x18\x07 \x01(\x0b\x32\x12.PathTextRuleProto\x12 \n\x06shield\x18\x08 \x01(\x0b\x32\x10.ShieldRuleProto\x12\x10\n\x08\x61pply_if\x18\t \x03(\t\"G\n\x13\x43lassifElementProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x07\x65lement\x18\x02 \x03(\x0b\x32\x11.DrawElementProto\"F\n\x11\x43olorElementProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\t\n\x01x\x18\x03 \x01(\x02\x12\t\n\x01y\x18\x04 \x01(\x02\"7\n\x12\x43olorsElementProto\x12!\n\x05value\x18\x01 \x03(\x0b\x32\x12.ColorElementProto\"Y\n\x0e\x43ontainerProto\x12\"\n\x04\x63ont\x18\x01 \x03(\x0b\x32\x14.ClassifElementProto\x12#\n\x06\x63olors\x18\x02 \x01(\x0b\x32\x13.ColorsElementProto*4\n\x08LineJoin\x12\r\n\tROUNDJOIN\x10\x00\x12\r\n\tBEVELJOIN\x10\x01\x12\n\n\x06NOJOIN\x10\x02*3\n\x07LineCap\x12\x0c\n\x08ROUNDCAP\x10\x00\x12\x0b\n\x07\x42UTTCAP\x10\x01\x12\r\n\tSQUARECAP\x10\x02\x42\x02H\x03\x62\x06proto3') -) +DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x13\x64rules_struct.proto\"*\n\x0c\x44\x61shDotProto\x12\n\n\x02\x64\x64\x18\x01 \x03(\x01\x12\x0e\n\x06offset\x18\x02 \x01(\x01\":\n\x0cPathSymProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x0c\n\x04step\x18\x02 \x01(\x01\x12\x0e\n\x06offset\x18\x03 \x01(\x01\"\xaf\x01\n\rLineRuleProto\x12\r\n\x05width\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x10\n\x08priority\x18\x04 \x01(\x05\x12\x1e\n\x07pathsym\x18\x05 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"\x9c\x01\n\x0cLineDefProto\x12\r\n\x05width\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1e\n\x07\x64\x61shdot\x18\x03 \x01(\x0b\x32\r.DashDotProto\x12\x1e\n\x07pathsym\x18\x04 \x01(\x0b\x32\r.PathSymProto\x12\x17\n\x04join\x18\x06 \x01(\x0e\x32\t.LineJoin\x12\x15\n\x03\x63\x61p\x18\x07 \x01(\x0e\x32\x08.LineCap\"O\n\rAreaRuleProto\x12\r\n\x05\x63olor\x18\x01 \x01(\r\x12\x1d\n\x06\x62order\x18\x02 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"_\n\x0fSymbolRuleProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\x16\n\x0e\x61pply_for_type\x18\x02 \x01(\x05\x12\x10\n\x08priority\x18\x03 \x01(\x05\x12\x14\n\x0cmin_distance\x18\x04 \x01(\x05\"\x8d\x01\n\x0f\x43\x61ptionDefProto\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x14\n\x0cstroke_color\x18\x03 \x01(\r\x12\x10\n\x08offset_x\x18\x04 \x01(\x05\x12\x10\n\x08offset_y\x18\x05 \x01(\x05\x12\x0c\n\x04text\x18\x06 \x01(\t\x12\x13\n\x0bis_optional\x18\x07 \x01(\x08\"l\n\x10\x43\x61ptionRuleProto\x12!\n\x07primary\x18\x01 \x01(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"a\n\x0f\x43ircleRuleProto\x12\x0e\n\x06radius\x18\x01 \x01(\x01\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x1d\n\x06\x62order\x18\x03 \x01(\x0b\x32\r.LineDefProto\x12\x10\n\x08priority\x18\x04 \x01(\x05\"m\n\x11PathTextRuleProto\x12!\n\x07primary\x18\x01 \x01(\x0b\x32\x10.CaptionDefProto\x12#\n\tsecondary\x18\x02 \x01(\x0b\x32\x10.CaptionDefProto\x12\x10\n\x08priority\x18\x03 \x01(\x05\"\x9d\x01\n\x0fShieldRuleProto\x12\x0e\n\x06height\x18\x01 \x01(\x05\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\x14\n\x0cstroke_color\x18\x03 \x01(\r\x12\x10\n\x08priority\x18\x04 \x01(\x05\x12\x14\n\x0cmin_distance\x18\x05 \x01(\x05\x12\x12\n\ntext_color\x18\x06 \x01(\r\x12\x19\n\x11text_stroke_color\x18\x07 \x01(\r\"\xa1\x02\n\x10\x44rawElementProto\x12\r\n\x05scale\x18\x01 \x01(\x05\x12\x1d\n\x05lines\x18\x02 \x03(\x0b\x32\x0e.LineRuleProto\x12\x1c\n\x04\x61rea\x18\x03 \x01(\x0b\x32\x0e.AreaRuleProto\x12 \n\x06symbol\x18\x04 \x01(\x0b\x32\x10.SymbolRuleProto\x12\"\n\x07\x63\x61ption\x18\x05 \x01(\x0b\x32\x11.CaptionRuleProto\x12 \n\x06\x63ircle\x18\x06 \x01(\x0b\x32\x10.CircleRuleProto\x12%\n\tpath_text\x18\x07 \x01(\x0b\x32\x12.PathTextRuleProto\x12 \n\x06shield\x18\x08 \x01(\x0b\x32\x10.ShieldRuleProto\x12\x10\n\x08\x61pply_if\x18\t \x03(\t\"G\n\x13\x43lassifElementProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\"\n\x07\x65lement\x18\x02 \x03(\x0b\x32\x11.DrawElementProto\"F\n\x11\x43olorElementProto\x12\x0c\n\x04name\x18\x01 \x01(\t\x12\r\n\x05\x63olor\x18\x02 \x01(\r\x12\t\n\x01x\x18\x03 \x01(\x02\x12\t\n\x01y\x18\x04 \x01(\x02\"7\n\x12\x43olorsElementProto\x12!\n\x05value\x18\x01 \x03(\x0b\x32\x12.ColorElementProto\"Y\n\x0e\x43ontainerProto\x12\"\n\x04\x63ont\x18\x01 \x03(\x0b\x32\x14.ClassifElementProto\x12#\n\x06\x63olors\x18\x02 \x01(\x0b\x32\x13.ColorsElementProto*4\n\x08LineJoin\x12\r\n\tROUNDJOIN\x10\x00\x12\r\n\tBEVELJOIN\x10\x01\x12\n\n\x06NOJOIN\x10\x02*3\n\x07LineCap\x12\x0c\n\x08ROUNDCAP\x10\x00\x12\x0b\n\x07\x42UTTCAP\x10\x01\x12\r\n\tSQUARECAP\x10\x02\x42\x02H\x03\x62\x06proto3') -_LINEJOIN = _descriptor.EnumDescriptor( - name='LineJoin', - full_name='LineJoin', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='ROUNDJOIN', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BEVELJOIN', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='NOJOIN', index=2, number=2, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=1859, - serialized_end=1911, -) -_sym_db.RegisterEnumDescriptor(_LINEJOIN) +_globals = globals() +_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) +_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'drules_struct_pb2', _globals) +if _descriptor._USE_C_DESCRIPTORS == False: -LineJoin = enum_type_wrapper.EnumTypeWrapper(_LINEJOIN) -_LINECAP = _descriptor.EnumDescriptor( - name='LineCap', - full_name='LineCap', - filename=None, - file=DESCRIPTOR, - values=[ - _descriptor.EnumValueDescriptor( - name='ROUNDCAP', index=0, number=0, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='BUTTCAP', index=1, number=1, - options=None, - type=None), - _descriptor.EnumValueDescriptor( - name='SQUARECAP', index=2, number=2, - options=None, - type=None), - ], - containing_type=None, - options=None, - serialized_start=1913, - serialized_end=1964, -) -_sym_db.RegisterEnumDescriptor(_LINECAP) - -LineCap = enum_type_wrapper.EnumTypeWrapper(_LINECAP) -ROUNDJOIN = 0 -BEVELJOIN = 1 -NOJOIN = 2 -ROUNDCAP = 0 -BUTTCAP = 1 -SQUARECAP = 2 - - - -_DASHDOTPROTO = _descriptor.Descriptor( - name='DashDotProto', - full_name='DashDotProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='dd', full_name='DashDotProto.dd', index=0, - number=1, type=1, cpp_type=5, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='offset', full_name='DashDotProto.offset', index=1, - number=2, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=31, - serialized_end=73, -) - - -_PATHSYMPROTO = _descriptor.Descriptor( - name='PathSymProto', - full_name='PathSymProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='PathSymProto.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='step', full_name='PathSymProto.step', index=1, - number=2, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='offset', full_name='PathSymProto.offset', index=2, - number=3, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=75, - serialized_end=133, -) - - -_LINERULEPROTO = _descriptor.Descriptor( - name='LineRuleProto', - full_name='LineRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='width', full_name='LineRuleProto.width', index=0, - number=1, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='LineRuleProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='dashdot', full_name='LineRuleProto.dashdot', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='LineRuleProto.priority', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='pathsym', full_name='LineRuleProto.pathsym', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='join', full_name='LineRuleProto.join', index=5, - number=6, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='cap', full_name='LineRuleProto.cap', index=6, - number=7, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=136, - serialized_end=311, -) - - -_LINEDEFPROTO = _descriptor.Descriptor( - name='LineDefProto', - full_name='LineDefProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='width', full_name='LineDefProto.width', index=0, - number=1, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='LineDefProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='dashdot', full_name='LineDefProto.dashdot', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='pathsym', full_name='LineDefProto.pathsym', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='join', full_name='LineDefProto.join', index=4, - number=6, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='cap', full_name='LineDefProto.cap', index=5, - number=7, type=14, cpp_type=8, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=314, - serialized_end=470, -) - - -_AREARULEPROTO = _descriptor.Descriptor( - name='AreaRuleProto', - full_name='AreaRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='color', full_name='AreaRuleProto.color', index=0, - number=1, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='border', full_name='AreaRuleProto.border', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='AreaRuleProto.priority', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=472, - serialized_end=551, -) - - -_SYMBOLRULEPROTO = _descriptor.Descriptor( - name='SymbolRuleProto', - full_name='SymbolRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='SymbolRuleProto.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='apply_for_type', full_name='SymbolRuleProto.apply_for_type', index=1, - number=2, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='SymbolRuleProto.priority', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='min_distance', full_name='SymbolRuleProto.min_distance', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=553, - serialized_end=648, -) - - -_CAPTIONDEFPROTO = _descriptor.Descriptor( - name='CaptionDefProto', - full_name='CaptionDefProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='height', full_name='CaptionDefProto.height', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='CaptionDefProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='stroke_color', full_name='CaptionDefProto.stroke_color', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='offset_x', full_name='CaptionDefProto.offset_x', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='offset_y', full_name='CaptionDefProto.offset_y', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='text', full_name='CaptionDefProto.text', index=5, - number=6, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='is_optional', full_name='CaptionDefProto.is_optional', index=6, - number=7, type=8, cpp_type=7, label=1, - has_default_value=False, default_value=False, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=651, - serialized_end=792, -) - - -_CAPTIONRULEPROTO = _descriptor.Descriptor( - name='CaptionRuleProto', - full_name='CaptionRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='primary', full_name='CaptionRuleProto.primary', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='secondary', full_name='CaptionRuleProto.secondary', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='CaptionRuleProto.priority', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=794, - serialized_end=902, -) - - -_CIRCLERULEPROTO = _descriptor.Descriptor( - name='CircleRuleProto', - full_name='CircleRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='radius', full_name='CircleRuleProto.radius', index=0, - number=1, type=1, cpp_type=5, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='CircleRuleProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='border', full_name='CircleRuleProto.border', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='CircleRuleProto.priority', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=904, - serialized_end=1001, -) - - -_PATHTEXTRULEPROTO = _descriptor.Descriptor( - name='PathTextRuleProto', - full_name='PathTextRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='primary', full_name='PathTextRuleProto.primary', index=0, - number=1, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='secondary', full_name='PathTextRuleProto.secondary', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='PathTextRuleProto.priority', index=2, - number=3, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1003, - serialized_end=1112, -) - - -_SHIELDRULEPROTO = _descriptor.Descriptor( - name='ShieldRuleProto', - full_name='ShieldRuleProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='height', full_name='ShieldRuleProto.height', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='ShieldRuleProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='stroke_color', full_name='ShieldRuleProto.stroke_color', index=2, - number=3, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='priority', full_name='ShieldRuleProto.priority', index=3, - number=4, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='min_distance', full_name='ShieldRuleProto.min_distance', index=4, - number=5, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='text_color', full_name='ShieldRuleProto.text_color', index=5, - number=6, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='text_stroke_color', full_name='ShieldRuleProto.text_stroke_color', index=6, - number=7, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1115, - serialized_end=1272, -) - - -_DRAWELEMENTPROTO = _descriptor.Descriptor( - name='DrawElementProto', - full_name='DrawElementProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='scale', full_name='DrawElementProto.scale', index=0, - number=1, type=5, cpp_type=1, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='lines', full_name='DrawElementProto.lines', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='area', full_name='DrawElementProto.area', index=2, - number=3, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='symbol', full_name='DrawElementProto.symbol', index=3, - number=4, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='caption', full_name='DrawElementProto.caption', index=4, - number=5, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='circle', full_name='DrawElementProto.circle', index=5, - number=6, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='path_text', full_name='DrawElementProto.path_text', index=6, - number=7, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='shield', full_name='DrawElementProto.shield', index=7, - number=8, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='apply_if', full_name='DrawElementProto.apply_if', index=8, - number=9, type=9, cpp_type=9, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1275, - serialized_end=1564, -) - - -_CLASSIFELEMENTPROTO = _descriptor.Descriptor( - name='ClassifElementProto', - full_name='ClassifElementProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='ClassifElementProto.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='element', full_name='ClassifElementProto.element', index=1, - number=2, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1566, - serialized_end=1637, -) - - -_COLORELEMENTPROTO = _descriptor.Descriptor( - name='ColorElementProto', - full_name='ColorElementProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='name', full_name='ColorElementProto.name', index=0, - number=1, type=9, cpp_type=9, label=1, - has_default_value=False, default_value=_b("").decode('utf-8'), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='color', full_name='ColorElementProto.color', index=1, - number=2, type=13, cpp_type=3, label=1, - has_default_value=False, default_value=0, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='x', full_name='ColorElementProto.x', index=2, - number=3, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='y', full_name='ColorElementProto.y', index=3, - number=4, type=2, cpp_type=6, label=1, - has_default_value=False, default_value=float(0), - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1639, - serialized_end=1709, -) - - -_COLORSELEMENTPROTO = _descriptor.Descriptor( - name='ColorsElementProto', - full_name='ColorsElementProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='value', full_name='ColorsElementProto.value', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1711, - serialized_end=1766, -) - - -_CONTAINERPROTO = _descriptor.Descriptor( - name='ContainerProto', - full_name='ContainerProto', - filename=None, - file=DESCRIPTOR, - containing_type=None, - fields=[ - _descriptor.FieldDescriptor( - name='cont', full_name='ContainerProto.cont', index=0, - number=1, type=11, cpp_type=10, label=3, - has_default_value=False, default_value=[], - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - _descriptor.FieldDescriptor( - name='colors', full_name='ContainerProto.colors', index=1, - number=2, type=11, cpp_type=10, label=1, - has_default_value=False, default_value=None, - message_type=None, enum_type=None, containing_type=None, - is_extension=False, extension_scope=None, - options=None), - ], - extensions=[ - ], - nested_types=[], - enum_types=[ - ], - options=None, - is_extendable=False, - syntax='proto3', - extension_ranges=[], - oneofs=[ - ], - serialized_start=1768, - serialized_end=1857, -) - -_LINERULEPROTO.fields_by_name['dashdot'].message_type = _DASHDOTPROTO -_LINERULEPROTO.fields_by_name['pathsym'].message_type = _PATHSYMPROTO -_LINERULEPROTO.fields_by_name['join'].enum_type = _LINEJOIN -_LINERULEPROTO.fields_by_name['cap'].enum_type = _LINECAP -_LINEDEFPROTO.fields_by_name['dashdot'].message_type = _DASHDOTPROTO -_LINEDEFPROTO.fields_by_name['pathsym'].message_type = _PATHSYMPROTO -_LINEDEFPROTO.fields_by_name['join'].enum_type = _LINEJOIN -_LINEDEFPROTO.fields_by_name['cap'].enum_type = _LINECAP -_AREARULEPROTO.fields_by_name['border'].message_type = _LINEDEFPROTO -_CAPTIONRULEPROTO.fields_by_name['primary'].message_type = _CAPTIONDEFPROTO -_CAPTIONRULEPROTO.fields_by_name['secondary'].message_type = _CAPTIONDEFPROTO -_CIRCLERULEPROTO.fields_by_name['border'].message_type = _LINEDEFPROTO -_PATHTEXTRULEPROTO.fields_by_name['primary'].message_type = _CAPTIONDEFPROTO -_PATHTEXTRULEPROTO.fields_by_name['secondary'].message_type = _CAPTIONDEFPROTO -_DRAWELEMENTPROTO.fields_by_name['lines'].message_type = _LINERULEPROTO -_DRAWELEMENTPROTO.fields_by_name['area'].message_type = _AREARULEPROTO -_DRAWELEMENTPROTO.fields_by_name['symbol'].message_type = _SYMBOLRULEPROTO -_DRAWELEMENTPROTO.fields_by_name['caption'].message_type = _CAPTIONRULEPROTO -_DRAWELEMENTPROTO.fields_by_name['circle'].message_type = _CIRCLERULEPROTO -_DRAWELEMENTPROTO.fields_by_name['path_text'].message_type = _PATHTEXTRULEPROTO -_DRAWELEMENTPROTO.fields_by_name['shield'].message_type = _SHIELDRULEPROTO -_CLASSIFELEMENTPROTO.fields_by_name['element'].message_type = _DRAWELEMENTPROTO -_COLORSELEMENTPROTO.fields_by_name['value'].message_type = _COLORELEMENTPROTO -_CONTAINERPROTO.fields_by_name['cont'].message_type = _CLASSIFELEMENTPROTO -_CONTAINERPROTO.fields_by_name['colors'].message_type = _COLORSELEMENTPROTO -DESCRIPTOR.message_types_by_name['DashDotProto'] = _DASHDOTPROTO -DESCRIPTOR.message_types_by_name['PathSymProto'] = _PATHSYMPROTO -DESCRIPTOR.message_types_by_name['LineRuleProto'] = _LINERULEPROTO -DESCRIPTOR.message_types_by_name['LineDefProto'] = _LINEDEFPROTO -DESCRIPTOR.message_types_by_name['AreaRuleProto'] = _AREARULEPROTO -DESCRIPTOR.message_types_by_name['SymbolRuleProto'] = _SYMBOLRULEPROTO -DESCRIPTOR.message_types_by_name['CaptionDefProto'] = _CAPTIONDEFPROTO -DESCRIPTOR.message_types_by_name['CaptionRuleProto'] = _CAPTIONRULEPROTO -DESCRIPTOR.message_types_by_name['CircleRuleProto'] = _CIRCLERULEPROTO -DESCRIPTOR.message_types_by_name['PathTextRuleProto'] = _PATHTEXTRULEPROTO -DESCRIPTOR.message_types_by_name['ShieldRuleProto'] = _SHIELDRULEPROTO -DESCRIPTOR.message_types_by_name['DrawElementProto'] = _DRAWELEMENTPROTO -DESCRIPTOR.message_types_by_name['ClassifElementProto'] = _CLASSIFELEMENTPROTO -DESCRIPTOR.message_types_by_name['ColorElementProto'] = _COLORELEMENTPROTO -DESCRIPTOR.message_types_by_name['ColorsElementProto'] = _COLORSELEMENTPROTO -DESCRIPTOR.message_types_by_name['ContainerProto'] = _CONTAINERPROTO -DESCRIPTOR.enum_types_by_name['LineJoin'] = _LINEJOIN -DESCRIPTOR.enum_types_by_name['LineCap'] = _LINECAP -_sym_db.RegisterFileDescriptor(DESCRIPTOR) - -DashDotProto = _reflection.GeneratedProtocolMessageType('DashDotProto', (_message.Message,), dict( - DESCRIPTOR = _DASHDOTPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:DashDotProto) - )) -_sym_db.RegisterMessage(DashDotProto) - -PathSymProto = _reflection.GeneratedProtocolMessageType('PathSymProto', (_message.Message,), dict( - DESCRIPTOR = _PATHSYMPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:PathSymProto) - )) -_sym_db.RegisterMessage(PathSymProto) - -LineRuleProto = _reflection.GeneratedProtocolMessageType('LineRuleProto', (_message.Message,), dict( - DESCRIPTOR = _LINERULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:LineRuleProto) - )) -_sym_db.RegisterMessage(LineRuleProto) - -LineDefProto = _reflection.GeneratedProtocolMessageType('LineDefProto', (_message.Message,), dict( - DESCRIPTOR = _LINEDEFPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:LineDefProto) - )) -_sym_db.RegisterMessage(LineDefProto) - -AreaRuleProto = _reflection.GeneratedProtocolMessageType('AreaRuleProto', (_message.Message,), dict( - DESCRIPTOR = _AREARULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:AreaRuleProto) - )) -_sym_db.RegisterMessage(AreaRuleProto) - -SymbolRuleProto = _reflection.GeneratedProtocolMessageType('SymbolRuleProto', (_message.Message,), dict( - DESCRIPTOR = _SYMBOLRULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:SymbolRuleProto) - )) -_sym_db.RegisterMessage(SymbolRuleProto) - -CaptionDefProto = _reflection.GeneratedProtocolMessageType('CaptionDefProto', (_message.Message,), dict( - DESCRIPTOR = _CAPTIONDEFPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:CaptionDefProto) - )) -_sym_db.RegisterMessage(CaptionDefProto) - -CaptionRuleProto = _reflection.GeneratedProtocolMessageType('CaptionRuleProto', (_message.Message,), dict( - DESCRIPTOR = _CAPTIONRULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:CaptionRuleProto) - )) -_sym_db.RegisterMessage(CaptionRuleProto) - -CircleRuleProto = _reflection.GeneratedProtocolMessageType('CircleRuleProto', (_message.Message,), dict( - DESCRIPTOR = _CIRCLERULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:CircleRuleProto) - )) -_sym_db.RegisterMessage(CircleRuleProto) - -PathTextRuleProto = _reflection.GeneratedProtocolMessageType('PathTextRuleProto', (_message.Message,), dict( - DESCRIPTOR = _PATHTEXTRULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:PathTextRuleProto) - )) -_sym_db.RegisterMessage(PathTextRuleProto) - -ShieldRuleProto = _reflection.GeneratedProtocolMessageType('ShieldRuleProto', (_message.Message,), dict( - DESCRIPTOR = _SHIELDRULEPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:ShieldRuleProto) - )) -_sym_db.RegisterMessage(ShieldRuleProto) - -DrawElementProto = _reflection.GeneratedProtocolMessageType('DrawElementProto', (_message.Message,), dict( - DESCRIPTOR = _DRAWELEMENTPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:DrawElementProto) - )) -_sym_db.RegisterMessage(DrawElementProto) - -ClassifElementProto = _reflection.GeneratedProtocolMessageType('ClassifElementProto', (_message.Message,), dict( - DESCRIPTOR = _CLASSIFELEMENTPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:ClassifElementProto) - )) -_sym_db.RegisterMessage(ClassifElementProto) - -ColorElementProto = _reflection.GeneratedProtocolMessageType('ColorElementProto', (_message.Message,), dict( - DESCRIPTOR = _COLORELEMENTPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:ColorElementProto) - )) -_sym_db.RegisterMessage(ColorElementProto) - -ColorsElementProto = _reflection.GeneratedProtocolMessageType('ColorsElementProto', (_message.Message,), dict( - DESCRIPTOR = _COLORSELEMENTPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:ColorsElementProto) - )) -_sym_db.RegisterMessage(ColorsElementProto) - -ContainerProto = _reflection.GeneratedProtocolMessageType('ContainerProto', (_message.Message,), dict( - DESCRIPTOR = _CONTAINERPROTO, - __module__ = 'indexer.drules_struct_pb2' - # @@protoc_insertion_point(class_scope:ContainerProto) - )) -_sym_db.RegisterMessage(ContainerProto) - - -DESCRIPTOR.has_options = True -DESCRIPTOR._options = _descriptor._ParseOptions(descriptor_pb2.FileOptions(), _b('H\003')) + DESCRIPTOR._options = None + DESCRIPTOR._serialized_options = b'H\003' + _globals['_LINEJOIN']._serialized_start=1851 + _globals['_LINEJOIN']._serialized_end=1903 + _globals['_LINECAP']._serialized_start=1905 + _globals['_LINECAP']._serialized_end=1956 + _globals['_DASHDOTPROTO']._serialized_start=23 + _globals['_DASHDOTPROTO']._serialized_end=65 + _globals['_PATHSYMPROTO']._serialized_start=67 + _globals['_PATHSYMPROTO']._serialized_end=125 + _globals['_LINERULEPROTO']._serialized_start=128 + _globals['_LINERULEPROTO']._serialized_end=303 + _globals['_LINEDEFPROTO']._serialized_start=306 + _globals['_LINEDEFPROTO']._serialized_end=462 + _globals['_AREARULEPROTO']._serialized_start=464 + _globals['_AREARULEPROTO']._serialized_end=543 + _globals['_SYMBOLRULEPROTO']._serialized_start=545 + _globals['_SYMBOLRULEPROTO']._serialized_end=640 + _globals['_CAPTIONDEFPROTO']._serialized_start=643 + _globals['_CAPTIONDEFPROTO']._serialized_end=784 + _globals['_CAPTIONRULEPROTO']._serialized_start=786 + _globals['_CAPTIONRULEPROTO']._serialized_end=894 + _globals['_CIRCLERULEPROTO']._serialized_start=896 + _globals['_CIRCLERULEPROTO']._serialized_end=993 + _globals['_PATHTEXTRULEPROTO']._serialized_start=995 + _globals['_PATHTEXTRULEPROTO']._serialized_end=1104 + _globals['_SHIELDRULEPROTO']._serialized_start=1107 + _globals['_SHIELDRULEPROTO']._serialized_end=1264 + _globals['_DRAWELEMENTPROTO']._serialized_start=1267 + _globals['_DRAWELEMENTPROTO']._serialized_end=1556 + _globals['_CLASSIFELEMENTPROTO']._serialized_start=1558 + _globals['_CLASSIFELEMENTPROTO']._serialized_end=1629 + _globals['_COLORELEMENTPROTO']._serialized_start=1631 + _globals['_COLORELEMENTPROTO']._serialized_end=1701 + _globals['_COLORSELEMENTPROTO']._serialized_start=1703 + _globals['_COLORSELEMENTPROTO']._serialized_end=1758 + _globals['_CONTAINERPROTO']._serialized_start=1760 + _globals['_CONTAINERPROTO']._serialized_end=1849 # @@protoc_insertion_point(module_scope) diff --git a/src/libkomwm.py b/src/libkomwm.py index af8feb8..84ba458 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -2,12 +2,12 @@ from mapcss import MapCSS from optparse import OptionParser import os import csv -import sys import functools from itertools import chain from multiprocessing import Pool, set_start_method from collections import OrderedDict import mapcss.webcolors +from drules_struct_pb2 import * whatever_to_hex = mapcss.webcolors.webcolors.whatever_to_hex whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo @@ -15,17 +15,6 @@ whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo PROFILE = False MULTIPROCESSING = True -# If path to the protobuf EGG is specified then apply it before import drules_struct_pb2 -PROTOBUF_EGG_PATH = os.environ.get("PROTOBUF_EGG_PATH") -if PROTOBUF_EGG_PATH: - # another version of protobuf may be installed, override it - for i in range(len(sys.path)): - if -1 != sys.path[i].find("protobuf-"): - sys.path[i] = PROTOBUF_EGG_PATH - sys.path.append(PROTOBUF_EGG_PATH) - -from drules_struct_pb2 import * - WIDTH_SCALE = 1.0 # Priority values defined in *.prio.txt files are adjusted -- 2.45.3 From feb3b878009fc6a06bfc62abe64fb3a42c76a9df Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Tue, 22 Aug 2023 17:36:50 +0300 Subject: [PATCH 35/46] Add a ': none;' syntax for disabling area and icon drules Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 84ba458..d69572b 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -640,9 +640,9 @@ def komap_mapswithme(options): st = dict([(k, v) for k, v in st.items() 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: + if 'icon-image' in st and st.get('icon-image') != 'none' or 'symbol-shape' in st or 'symbol-image' in st: has_icons = True - if 'fill-color' in st: + if 'fill-color' in st and st.get('fill-color') != 'none': has_fills = True has_text = None @@ -760,7 +760,7 @@ def komap_mapswithme(options): dr_element.shield.min_distance = int(st.get('shield-min-distance', 0)) if has_icons: - if st.get('icon-image'): + if st.get('icon-image') and st.get('icon-image') != 'none': icon = mwm_encode_image(st) dr_element.symbol.name = icon[0] dr_element.symbol.priority = get_drape_priority(cl, 'icon', st.get('object-id')) @@ -831,7 +831,7 @@ def komap_mapswithme(options): has_text = None if has_fills: - if ('fill-color' in st) and (float(st.get('fill-opacity', 1)) > 0): + if 'fill-color' in st and st.get('fill-color') != 'none' and float(st.get('fill-opacity', 1)) > 0: dr_element.area.color = mwm_encode_color(colors, st, "fill") dr_element.area.priority = get_drape_priority(cl, 'area', st.get('object-id')) store_visibility(cl, 'area', st.get('object-id'), zoom) -- 2.45.3 From ff3f8324f865ab2e8753725ee0f22ffbfb0b5814 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Thu, 24 Aug 2023 07:58:46 +0300 Subject: [PATCH 36/46] Fix fill-color: none; handling. Fixes #19. Signed-off-by: Konstantin Pastbin --- src/mapcss/StyleChooser.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index e307971..9d3518e 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -30,7 +30,7 @@ def make_nice_style(r): "checking and nicifying style table" if type(b) == TYPE_EVAL: ra[a] = b - elif "color" in a: + elif "color" in a and b.strip() != 'none': "parsing color value to 3-tuple" # print "res:", b if b and (type(b) != tuple): -- 2.45.3 From 432e0b0d67bdba60aa6de91e09642bcf1afc1437 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Wed, 6 Sep 2023 15:04:17 +0300 Subject: [PATCH 37/46] Fail on missing priorities errors Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index d69572b..c9381a4 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -3,6 +3,7 @@ from optparse import OptionParser import os import csv import functools +from sys import exit from itertools import chain from multiprocessing import Pool, set_start_method from collections import OrderedDict @@ -90,6 +91,8 @@ Priorities ranges' rendering order overview: - BG-by-size: landcover areas sorted by their size ''' +validation_errors_count = 0 + def to_boolean(s): s = s.lower() if s == "true" or s == "yes": @@ -429,7 +432,9 @@ def get_drape_priority(cl, dr_type, object_id, auto_dr_type = None, auto_comment prio_ranges[r]['priorities'][auto_prio_id] = priority return priority + prio_ranges[r]['base'] - print(f'WARNING: priority is not set for {dr_type} {cl}{object_id}') + print(f'ERROR: priority is not set for {dr_type} {cl}{object_id}') + global validation_errors_count + validation_errors_count += 1 return 0 @@ -850,6 +855,11 @@ def komap_mapswithme(options): validate_visibilities(options.maxzoom) + if validation_errors_count: + exit('FAILED to write regenerated drules files!\n' + f'There are {validation_errors_count} validation errors (see in the log above).\n' + 'Fix all errors first and re-run.') + output = '' for prio_range in prio_ranges.keys(): dump_priorities(prio_range, options.priorities_path, options.maxzoom) -- 2.45.3 From d58e496bd4c9ceee4019ff637a1cbc7d983aa2c2 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sat, 2 Sep 2023 21:49:29 +0300 Subject: [PATCH 38/46] Make shields prioritizable independently Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 24 ++++++++++++------------ src/mapcss/__init__.py | 2 +- 2 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index c9381a4..adff65e 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -751,18 +751,18 @@ def komap_mapswithme(options): store_visibility(cl, 'line', st.get('object-id'), zoom) 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.text_color = mwm_encode_color(colors, st, "shield-text") - if st.get('shield-text-halo-radius', 0) != 0: - dr_element.shield.text_stroke_color = mwm_encode_color(colors, st, "shield-text-halo", "white") - dr_element.shield.color = mwm_encode_color(colors, st, "shield") - if st.get('shield-outline-radius', 0) != 0: - dr_element.shield.stroke_color = mwm_encode_color(colors, st, "shield-outline", "white") - dr_element.shield.priority = get_drape_priority(cl, 'shield', st.get('object-id')) - store_visibility(cl, 'shield', st.get('object-id'), zoom) - if st.get('shield-min-distance', 0) != 0: - dr_element.shield.min_distance = int(st.get('shield-min-distance', 0)) + if st.get('shield-font-size'): + dr_element.shield.height = int(st.get('shield-font-size', 10)) + dr_element.shield.text_color = mwm_encode_color(colors, st, "shield-text") + if st.get('shield-text-halo-radius', 0) != 0: + dr_element.shield.text_stroke_color = mwm_encode_color(colors, st, "shield-text-halo", "white") + dr_element.shield.color = mwm_encode_color(colors, st, "shield") + if st.get('shield-outline-radius', 0) != 0: + dr_element.shield.stroke_color = mwm_encode_color(colors, st, "shield-outline", "white") + dr_element.shield.priority = get_drape_priority(cl, 'shield', st.get('object-id')) + store_visibility(cl, 'shield', st.get('object-id'), zoom) + if st.get('shield-min-distance', 0) != 0: + dr_element.shield.min_distance = int(st.get('shield-min-distance', 0)) if has_icons: if st.get('icon-image') and st.get('icon-image') != 'none': diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 1a1368a..8af9730 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -23,7 +23,7 @@ from .Condition import Condition NEEDED_KEYS = set(["width", "casing-width", "casing-width-add", "fill-color", "fill-image", "icon-image", "text", "extrude", - "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"]) + "background-image", "background-color", "pattern-image", "shield-color", "symbol-shape"]) WHITESPACE = re.compile(r'\s+ ', re.S | re.X) -- 2.45.3 From 5b22c7a14c9f5ac67de3fc47aab644f4b3063832 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sat, 16 Sep 2023 13:11:50 +0300 Subject: [PATCH 39/46] Remove int_name, add a warning for invalid dashdot size Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index adff65e..023f301 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -703,8 +703,12 @@ def komap_mapswithme(options): # A casing line explicitly defined via ::object_id. dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) store_visibility(cl, 'line', st.get('object-id'), zoom) - for i in st.get('casing-dashes', st.get('dashes', [])): - dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) + dd = st.get('casing-dashes', st.get('dashes', [])) + for i in dd: + if float(i) < 1: + print(f'WARNING: forcing minimum dashdot value to 1 in "{dd}" for z{zoom} line(casing) {cl}') + i = 1.0 + dr_line.dashdot.dd.extend([i * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN) @@ -731,8 +735,12 @@ def komap_mapswithme(options): dr_line = LineRuleProto() dr_line.width = (st.get('width', 0) * WIDTH_SCALE) dr_line.color = mwm_encode_color(colors, st) - for i in st.get('dashes', []): - dr_line.dashdot.dd.extend([max(float(i), 1) * WIDTH_SCALE]) + dd = st.get('dashes', []) + for i in dd: + if float(i) < 1: + print(f'WARNING: forcing minimum dashdot value to 1 in {dd} for z{zoom} line {cl}') + i = 1.0 + dr_line.dashdot.dd.extend([i * WIDTH_SCALE]) addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) @@ -801,7 +809,7 @@ def komap_mapswithme(options): 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)) - if 'text' in sp and sp.get('text') != 'name': + if 'text' in sp and sp.get('text') not in ('name', 'int_name'): dr_cur_subtext.text = sp.get('text') if 'text-optional' in sp: is_valid, value = to_boolean(sp.get('text-optional', '')) -- 2.45.3 From 611abc0d726689874e6e578b5a67f53dfff2dca7 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 15 Sep 2023 21:07:18 +0300 Subject: [PATCH 40/46] Validate pathtexts and shields without lines Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 35 +++++++++++++++++++++++++++-------- 1 file changed, 27 insertions(+), 8 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 023f301..3e5a486 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -310,19 +310,37 @@ def prettify_zooms(zooms, maxzoom): def validate_visibilities(maxzoom): - for cl, dr_types in visibilities.items(): - for dr_type, object_ids in dr_types.items(): + for cl, dr_types_comments in visibilities.items(): + for dr_type_comment, object_ids in dr_types_comments.items(): for object_id, zooms in object_ids.items(): zoom_range = prettify_zooms(zooms, maxzoom) if zoom_range.find(',') != -1: - print(f'WARNING: non-contiguous visibility range {zoom_range} for {cl} {dr_type}{object_id}') + print(f'WARNING: non-contiguous visibility range {zoom_range} for {cl} {dr_type_comment}{object_id}') - if dr_type == 'caption' and 'icon' in dr_types and object_id in dr_types['icon']: - icon_zooms = sorted(dr_types['icon'][object_id]) - if sorted(zooms)[0] < icon_zooms[0]: - print(f'WARNING: caption {zoom_range} appears before icon {prettify_zooms(icon_zooms, maxzoom)}' - f' for {cl}{object_id}') + dr_type = dr_type_comment[0] + icon_dr_type_comment = ('icon', None) + if (dr_type == 'caption' and icon_dr_type_comment in dr_types_comments and + object_id in dr_types_comments[icon_dr_type_comment]): + icon_zooms = sorted(dr_types_comments[icon_dr_type_comment][object_id]) + if min(zooms) < icon_zooms[0]: + print(f'WARNING: caption {zoom_range} appears before icon {prettify_zooms(icon_zooms, maxzoom)}' + f' for {cl}{object_id}') + line_dr_type_comment = ('line', None) + if dr_type in ('pathtext', 'shield'): + lines_min_zoom = maxzoom + 1 + if line_dr_type_comment in dr_types_comments: + lines_min_zoom = maxzoom + 1 + for line_object_id, line_zooms in dr_types_comments[line_dr_type_comment].items(): + min_zoom = min(line_zooms) + if min_zoom < lines_min_zoom: + lines_min_zoom = min_zoom + min_zoom = min(zooms) + if min_zoom < lines_min_zoom: + missing_zooms = prettify_zooms(range(min_zoom, lines_min_zoom), maxzoom) + print(f'ERROR: {dr_type} without line at {missing_zooms} for {cl}{object_id}') + global validation_errors_count + validation_errors_count += 1 def dump_priorities(prio_range, path, maxzoom): with open(get_priorities_filename(prio_range, path), 'w') as outfile: @@ -864,6 +882,7 @@ def komap_mapswithme(options): validate_visibilities(options.maxzoom) if validation_errors_count: + print() exit('FAILED to write regenerated drules files!\n' f'There are {validation_errors_count} validation errors (see in the log above).\n' 'Fix all errors first and re-run.') -- 2.45.3 From 51864cec294c56ca949618671e06dcc4f2543874 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 13 Oct 2023 14:06:36 +0300 Subject: [PATCH 41/46] Validate presence of text-color and text-offset attributes Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 3e5a486..42ab0eb 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -618,6 +618,7 @@ def komap_mapswithme(options): all_draw_elements = set() + global validation_errors_count for results in imapfunc(query_style, ((cl, classificator[cl], options.minzoom, options.maxzoom) for cl in class_order)): for result in results: cl, zoom, runtime_conditions, zstyle = result @@ -820,13 +821,19 @@ def komap_mapswithme(options): dr_cur_subtext = dr_text.primary for sp in has_text: dr_cur_subtext.height = int(float(sp.get('font-size', "10").split(",")[0])) + if 'text-color' not in st: + print(f'ERROR: text-color not set for z{zoom} {cl}') + validation_errors_count += 1 dr_cur_subtext.color = mwm_encode_color(colors, sp, "text") if st.get('text-halo-radius', 0) != 0: dr_cur_subtext.stroke_color = mwm_encode_color(colors, 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: + elif 'text-offset-x' in sp: dr_cur_subtext.offset_x = int(sp.get('text-offset-x', 0)) + elif st.get('text-position', 'center') == 'center' and dr_element.symbol.priority: + print(f'ERROR: an icon is present, but caption\'s text-offset is not set for z{zoom} {cl}') + validation_errors_count += 1 if 'text' in sp and sp.get('text') not in ('name', 'int_name'): dr_cur_subtext.text = sp.get('text') if 'text-optional' in sp: -- 2.45.3 From b9d308f0cd206d181d26db75ca3177218ee63f87 Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Sat, 7 Oct 2023 19:54:55 +0300 Subject: [PATCH 42/46] Generate drules for z20 Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 42ab0eb..7b4f8d3 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -969,7 +969,7 @@ def main(): 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", + parser.add_option("-t", "--maxzoom", dest="maxzoom", default=20, type="int", help="maximal available zoom level", metavar="ZOOM") parser.add_option("-o", "--output-file", dest="outfile", default="-", help="output filename", metavar="FILE") -- 2.45.3 From 2796db7ae3c3f3c00ae07880dc2d8dfc8edd776b Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Thu, 23 Nov 2023 19:57:07 +0200 Subject: [PATCH 43/46] Allow <1 dashdot values Signed-off-by: Konstantin Pastbin --- src/libkomwm.py | 26 ++++++++------------------ 1 file changed, 8 insertions(+), 18 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 7b4f8d3..2a85102 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -16,8 +16,6 @@ whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo PROFILE = False MULTIPROCESSING = True -WIDTH_SCALE = 1.0 - # Priority values defined in *.prio.txt files are adjusted # to fit into the following "priorities ranges": # [-10000; 10000): overlays (icons, captions...) @@ -711,7 +709,7 @@ def komap_mapswithme(options): st['casing-width'] = base_width + st.get('casing-width-add') base_width = 0 - dr_line.width = round((base_width + st.get('casing-width') * 2) * WIDTH_SCALE, 2) + dr_line.width = round(base_width + st.get('casing-width') * 2, 2) dr_line.color = mwm_encode_color(colors, st, "casing") if st.get('object-id') == '::default': # An automatic casing line should be rendered below the "main" line, hence auto priority -1. @@ -722,12 +720,8 @@ def komap_mapswithme(options): # A casing line explicitly defined via ::object_id. dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) store_visibility(cl, 'line', st.get('object-id'), zoom) - dd = st.get('casing-dashes', st.get('dashes', [])) - for i in dd: - if float(i) < 1: - print(f'WARNING: forcing minimum dashdot value to 1 in "{dd}" for z{zoom} line(casing) {cl}') - i = 1.0 - dr_line.dashdot.dd.extend([i * WIDTH_SCALE]) + for i in st.get('casing-dashes', st.get('dashes', [])): + dr_line.dashdot.dd.extend([float(i)]) addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('casing-linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('casing-linejoin', 'round'), ROUNDJOIN) @@ -735,12 +729,12 @@ def komap_mapswithme(options): if has_fills and is_area_st and float(st.get('fill-opacity', 1)) > 0: dr_element.area.border.color = mwm_encode_color(colors, st, "casing") - dr_element.area.border.width = st.get('casing-width', 0) * WIDTH_SCALE + dr_element.area.border.width = st.get('casing-width', 0) # 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.width = st.get('width', 0) + (st.get('casing-width') * 2) # dr_line.color = mwm_encode_color(colors, st, "casing") # dr_line.priority = -15000 # dashes = st.get('casing-dashes', st.get('dashes', [])) @@ -752,14 +746,10 @@ def komap_mapswithme(options): if has_lines: if st.get('width'): dr_line = LineRuleProto() - dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + dr_line.width = st.get('width', 0) dr_line.color = mwm_encode_color(colors, st) - dd = st.get('dashes', []) - for i in dd: - if float(i) < 1: - print(f'WARNING: forcing minimum dashdot value to 1 in {dd} for z{zoom} line {cl}') - i = 1.0 - dr_line.dashdot.dd.extend([i * WIDTH_SCALE]) + for i in st.get('dashes', []): + dr_line.dashdot.dd.extend([float(i)]) addPattern(dr_line.dashdot.dd) dr_line.cap = dr_linecaps.get(st.get('linecap', 'butt'), BUTTCAP) dr_line.join = dr_linejoins.get(st.get('linejoin', 'round'), ROUNDJOIN) -- 2.45.3 From 2850ec5077f59dd94de231c317dacd20e224a61c Mon Sep 17 00:00:00 2001 From: Konstantin Pastbin Date: Fri, 29 Nov 2024 21:40:53 +0300 Subject: [PATCH 44/46] Require protobuf 3.21+ Signed-off-by: Konstantin Pastbin --- requirements.txt | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 1d3ed8c..577a8b3 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -protobuf~=4.23.0 +# The core is using protobuf 3.3.0 still (3party/protobuf/), so no point to require newer versions. +# E.g. Ubuntu 24.04 LTS ships with python3-protobuf 3.21.12 and it works fine. +protobuf~=3.21.0 -- 2.45.3 From 70301732f9cf7463cad5fb80c9ec16425fdd2725 Mon Sep 17 00:00:00 2001 From: "S. Kozyr" Date: Fri, 13 Dec 2024 15:14:22 +0200 Subject: [PATCH 45/46] Added unit tests and integration tests --- .gitignore | 1 + README.md | 28 ++ integration-tests/drules/.gitkeep | 0 integration-tests/full_drules_gen.py | 73 ++++ tests/__init__.py | 0 tests/assets/case-1-import/colors.mapcss | 6 + tests/assets/case-1-import/import1.mapcss | 1 + tests/assets/case-1-import/import2.mapcss | 1 + tests/assets/case-1-import/main.mapcss | 1 + .../case-2-generate-drules-mini/.gitignore | 7 + .../include/Roads.mapcss | 136 +++++++ .../include/Roads_label.mapcss | 75 ++++ .../include/colors.mapcss | 16 + .../include/priorities_1_BG-by-size.prio.txt | 16 + .../include/priorities_2_BG-top.prio.txt | 18 + .../include/priorities_3_FG.prio.txt | 41 ++ .../include/priorities_4_overlays.prio.txt | 61 +++ .../case-2-generate-drules-mini/main.mapcss | 133 +++++++ .../mapcss-dynamic.txt | 4 + .../mapcss-mapping.csv | 148 +++++++ .../case-2-generate-drules-mini/readme.md | 4 + .../assets/case-3-styles-validation/readme.md | 1 + tests/testCondition.py | 300 +++++++++++++++ tests/testEval.py | 124 ++++++ tests/testLibkomwm.py | 68 ++++ tests/testMapCSS.py | 364 ++++++++++++++++++ tests/testRule.py | 114 ++++++ tests/testStyleChooser.py | 297 ++++++++++++++ 28 files changed, 2038 insertions(+) create mode 100644 integration-tests/drules/.gitkeep create mode 100755 integration-tests/full_drules_gen.py create mode 100644 tests/__init__.py create mode 100644 tests/assets/case-1-import/colors.mapcss create mode 100644 tests/assets/case-1-import/import1.mapcss create mode 100644 tests/assets/case-1-import/import2.mapcss create mode 100644 tests/assets/case-1-import/main.mapcss create mode 100644 tests/assets/case-2-generate-drules-mini/.gitignore create mode 100644 tests/assets/case-2-generate-drules-mini/include/Roads.mapcss create mode 100644 tests/assets/case-2-generate-drules-mini/include/Roads_label.mapcss create mode 100644 tests/assets/case-2-generate-drules-mini/include/colors.mapcss create mode 100644 tests/assets/case-2-generate-drules-mini/include/priorities_1_BG-by-size.prio.txt create mode 100644 tests/assets/case-2-generate-drules-mini/include/priorities_2_BG-top.prio.txt create mode 100644 tests/assets/case-2-generate-drules-mini/include/priorities_3_FG.prio.txt create mode 100644 tests/assets/case-2-generate-drules-mini/include/priorities_4_overlays.prio.txt create mode 100644 tests/assets/case-2-generate-drules-mini/main.mapcss create mode 100644 tests/assets/case-2-generate-drules-mini/mapcss-dynamic.txt create mode 100644 tests/assets/case-2-generate-drules-mini/mapcss-mapping.csv create mode 100644 tests/assets/case-2-generate-drules-mini/readme.md create mode 100644 tests/assets/case-3-styles-validation/readme.md create mode 100644 tests/testCondition.py create mode 100644 tests/testEval.py create mode 100644 tests/testLibkomwm.py create mode 100644 tests/testMapCSS.py create mode 100644 tests/testRule.py create mode 100644 tests/testStyleChooser.py diff --git a/.gitignore b/.gitignore index bf52402..b589e30 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,4 @@ src/tiles/ *pycache* *swp *bak +/.idea \ No newline at end of file diff --git a/README.md b/README.md index dfc5d17..79382c7 100644 --- a/README.md +++ b/README.md @@ -7,3 +7,31 @@ Python dependencies: ```bash pip3 install -r requirements.txt ``` + +## Running unittests + +To run all unittests execute next command from project root folder: + +```bash +python3 -m unittest discover -s tests +``` + +this will search for all `test*.py` files within `tests` directory +and execute tests from those files. + +## Running integration tests + +File `integration-tests/full_drules_gen.py` is intended to generate drules +files for all 6 themes from main Organic Maps repo. It could be used to understand +which parts of the project are actually used by Organic Maps repo. + +Usage: + +```shell +cd integration-tests +python3 full_drules_gen.py -d ../../../data -o drules --txt +``` + +This command will run generation for styles - default light, default dark, +outdoors light, outdoors dark, vehicle light, vehicle dark and put `*.bin` +and `*.txt` files into 'drules' subfolder. diff --git a/integration-tests/drules/.gitkeep b/integration-tests/drules/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/integration-tests/full_drules_gen.py b/integration-tests/full_drules_gen.py new file mode 100755 index 0000000..986a9af --- /dev/null +++ b/integration-tests/full_drules_gen.py @@ -0,0 +1,73 @@ +#!/usr/bin/env python3 + +import sys +from copy import deepcopy +from optparse import OptionParser +from pathlib import Path +import logging + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +import libkomwm + +FORMAT = '%(asctime)s [%(levelname)s] %(message)s' +logging.basicConfig(format=FORMAT) +log = logging.getLogger('test_drules_gen') +log.setLevel(logging.INFO) + +styles = { + 'default_light': ['styles/default/light/style.mapcss', 'styles/default/include'], + 'default_dark': ['styles/default/dark/style.mapcss', 'styles/default/include'], + 'outdoors_light': ['styles/outdoors/light/style.mapcss', 'styles/outdoors/include'], + 'outdoors_dark': ['styles/outdoors/dark/style.mapcss', 'styles/outdoors/include'], + 'vehicle_light': ['styles/vehicle/light/style.mapcss', 'styles/vehicle/include'], + 'vehicle_dark': ['styles/vehicle/dark/style.mapcss', 'styles/vehicle/include'], +} + + +def full_styles_regenerate(options): + log.info("Start generating styles") + libkomwm.MULTIPROCESSING = False + prio_ranges_orig = deepcopy(libkomwm.prio_ranges) + + for name, (style_path, include_path) in styles.items(): + log.info(f"Generating {name} style ...") + + # Restore initial state + libkomwm.prio_ranges = deepcopy(prio_ranges_orig) + libkomwm.visibilities = {} + + options.filename = options.data + '/' + style_path + options.priorities_path = options.data + '/' + include_path + options.outfile = options.outdir + '/' + name + + # Run generation + libkomwm.komap_mapswithme(options) + log.info(f"Done!") + +def main(): + parser = OptionParser() + parser.add_option("-d", "--data-path", dest="data", + help="path to mapcss-mapping.csv and other files", metavar="PATH") + parser.add_option("-o", "--output-dir", dest="outdir", default="drules", + help="output directory", metavar="DIR") + 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=20, type="int", + help="maximal available zoom level", metavar="ZOOM") + parser.add_option("-x", "--txt", dest="txt", action="store_true", + help="create a text file for output", default=False) + + (options, args) = parser.parse_args() + + if options.data is None: + parser.error("Please specify base 'data' path.") + + if options.outdir is None: + parser.error("Please specify base output path.") + + full_styles_regenerate(options) + +if __name__ == '__main__': + main() diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/tests/assets/case-1-import/colors.mapcss b/tests/assets/case-1-import/colors.mapcss new file mode 100644 index 0000000..db1c68b --- /dev/null +++ b/tests/assets/case-1-import/colors.mapcss @@ -0,0 +1,6 @@ +colors { + GuiText-color: #FFFFFF; + GuiText-opacity: 0.7; + Route-color: #0000FF; + Route-opacity: 0.5; +} diff --git a/tests/assets/case-1-import/import1.mapcss b/tests/assets/case-1-import/import1.mapcss new file mode 100644 index 0000000..cfa0b6a --- /dev/null +++ b/tests/assets/case-1-import/import1.mapcss @@ -0,0 +1 @@ +@import("import2.mapcss"); diff --git a/tests/assets/case-1-import/import2.mapcss b/tests/assets/case-1-import/import2.mapcss new file mode 100644 index 0000000..9ab0b8d --- /dev/null +++ b/tests/assets/case-1-import/import2.mapcss @@ -0,0 +1 @@ +@import("colors.mapcss"); diff --git a/tests/assets/case-1-import/main.mapcss b/tests/assets/case-1-import/main.mapcss new file mode 100644 index 0000000..ca997c1 --- /dev/null +++ b/tests/assets/case-1-import/main.mapcss @@ -0,0 +1 @@ +@import("import1.mapcss"); diff --git a/tests/assets/case-2-generate-drules-mini/.gitignore b/tests/assets/case-2-generate-drules-mini/.gitignore new file mode 100644 index 0000000..24a9e5d --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/.gitignore @@ -0,0 +1,7 @@ +classificator.txt +colors.txt +patterns.txt +style.bin.bin +style.bin.txt +types.txt +visibility.txt diff --git a/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss b/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss new file mode 100644 index 0000000..794dd08 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss @@ -0,0 +1,136 @@ +/* ~~~~ CONTENT OF ROADS ~~~~~ + +1.Z-INDEX ROADS +2.WORLD LEVEL ROAD 4-9 ZOOM +3.TRUNK & MOTORWAY 6-22 ZOOM + 3.1 Trunk & Motorway 6-22 ZOOM + 3.2 Trunk & Motorway tunnel 12-22 ZOOM + 3.3 Trunk & Motorway bridge 13-22 ZOOM +4.PRIMARY 8-22 ZOOM + 4.1 Primary 8-22 ZOOM + 4.2 Primary tunnel 14-22 ZOOM + 4.3 Primary bridge 14-22 ZOOM +5.SECONDARY 10-22 ZOOM + 5.1 Secondary 10-22 ZOOM + 5.2 Secondary tunnel 16-22 ZOOM + 5.3 Secondary bridge 14-22 ZOOM +6.TERTIARY & UNCLASSIFIED 11-22 ZOOM + 6.1 Tertiary & Unclassified 11-22 ZOOM + 6.2 Tertiary & Unclassified tunnel 16-22 ZOOM + 6.3 Tertiary & Unclassified bridge 14-22 ZOOM +7.RESIDENTAL, ROAD, STREETS & SERVICE 12-22 ZOOM + 7.1 Residential, Road, Street 12-22 ZOOM + 7.2 Residential, Road, Street tunnel 16-22 ZOOM + 7.3 Residential, Road, Street bridge 14-22 ZOOM + 7.4 Service 15-22 ZOOM +8.OTHERS ROADS 13-22 ZOOM + 8.1 Pedestrian & ford 13-22 ZOOM + 8.2 Pedestrian & ford tunnel 16-22 ZOOM + 8.3 Pedestrian & other brige 13-22 ZOOM + 8.4 Cycleway 13-22 ZOOM + 8.5 Construction 13-22 ZOOM + 8.6 Track & Path 14-22 ZOOM + 8.7 Footway 15-22 ZOOM + 8.8 Steps 15-22 ZOOM + 8.9 Bridleway 14-22 ZOOM + 8.11 Runway 12-22 ZOOM +9.RAIL 11-22 ZOOM + 9.1 RAIL 11-22 ZOOM + 9.2 Rail tunnel 14-22 ZOOM + 9.3 Rail bridge 14-22 ZOOM + 9.4 Monorail 14-22 ZOOM + 9.5 Tram line 13-22 ZOOM + 9.6 Funicular 12-22 ZOOM +10.PISTE 12-22 ZOOM + 10.1 Lift 12-22 ZOOM + 10.2 Aerialway 12-22 ZOOM + 10.3 Piste & Route 14-22 ZOOM +11.FERRY +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +/* 2.WORLD LEVEL ROAD 4-9 ZOOM */ + +line|z6-9[highway=world_towns_level], +line|z4-9[highway=world_level], +{color: @trunk1;opacity: 1;} + +line|z4[highway=world_level] +{width: 0.5;} +line|z5-6[highway=world_level], +{width: 0.7;} +line|z6[highway=world_towns_level], +{width: 0.9;} +line|z7[highway=world_towns_level], +line|z7[highway=world_level] +{width: 0.7;} +line|z8[highway=world_towns_level], +line|z8[highway=world_level] +{width: 0.9;} +line|z9[highway=world_towns_level], +line|z9[highway=world_level] +{width: 0.8;} + +/* 3.TRUNK & MOTORWAY 6-22 ZOOM */ + +line|z6[highway=trunk], +line|z6[highway=motorway], +{color: @trunk0; opacity: 0.3;} +line|z7-9[highway=trunk], +line|z7-9[highway=motorway], +{color: @trunk0; opacity: 0.7;} + +line|z10-[highway=trunk], +line|z10-[highway=motorway], +{color: @trunk1; opacity: 0.7;} +line|z10-[highway=motorway_link], +line|z10-[highway=trunk_link], +{color: @primary0; opacity: 0.7;} + +/* 3.1 Trunk & Motorway 6-22 ZOOM */ + +line|z6[highway=trunk], +line|z6[highway=motorway], +{width: 0.8;} +line|z7[highway=trunk], +line|z7[highway=motorway] +{width: 0.9;} +line|z8[highway=trunk], +line|z8[highway=motorway] +{width: 1.1;} +line|z9[highway=trunk], +line|z9[highway=motorway] +{width: 1.2;} +line|z10[highway=trunk], +line|z10[highway=motorway] +{width: 1.5;} + +line|z10[highway=motorway_link], +line|z10[highway=trunk_link] +{width: 0.8;} + + +/* 4.PRIMARY 8-22 ZOOM */ + +line|z8-10[highway=primary], +{color: @primary0; opacity: 0.7;} + +/* 4.1 Primary 8-22 ZOOM */ + +line|z8[highway=primary], +{width: 0.7;} +line|z9[highway=primary], +{width: 0.8;} +line|z10[highway=primary], +{width: 1.2;} + +/* 5.SECONDARY 10-22 ZOOM */ + +line|z10[highway=secondary], +{color: @secondary0; opacity: 0.8;} + +/* 5.1 Secondary 10-22 ZOOM */ + +line|z10[highway=secondary], +{width: 1.2;} + diff --git a/tests/assets/case-2-generate-drules-mini/include/Roads_label.mapcss b/tests/assets/case-2-generate-drules-mini/include/Roads_label.mapcss new file mode 100644 index 0000000..718d47d --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/Roads_label.mapcss @@ -0,0 +1,75 @@ +/* ~~~~ CONTENT OF ROADS ~~~~~ + +1.Z-INDEX ROADS +2.SHIELD 10-22 ZOOM +3.TRUNK & MOTORWAY 10-22 ZOOM +4.PRIMARY 10-22 ZOOM +5.SECONDARY 10-22 ZOOM +6.RESIDENTAL & TERTIARY 12-22 ZOOM +7.ROAD, STREETS, UNCLASSIFIED & SERVICE 15-22 ZOOM +8.OTHERS ROADS 15-22 ZOOM +9.RAIL 15-22 ZOOM ???? + 9.1 Monorail 14-22 ZOOM + 9.2 Tram line 13-22 ZOOM + 9.3 Funicular 12-22 ZOOM +10.PISTE 12-22 ZOOM ???? + 10.1 Lift 12-22 ZOOM + 10.2 Aerialway 12-22 ZOOM + 10.3 Piste & Route 14-22 ZOOM +11.FERRY 10-22 ZOOM +12.ONEWAY ARROWS 15-22 ZOOM +13.JUNCTION 15-22 ZOOM +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +*/ + +line[highway] +{text-position: line;} + +/* 2.SHIELD 10-22 ZOOM */ + +line|z10-[highway=motorway]::shield, +line|z10-[highway=trunk]::shield, +line|z10-[highway=motorway_link]::shield, +line|z10-[highway=trunk_link]::shield, +line|z10-[highway=primary]::shield, +{shield-font-size: 9;shield-text-color: @shield_text;shield-text-halo-radius: 0;shield-text-halo-color: @shield_text_halo;shield-color: @shield;shield-outline-radius: 1;shield-outline-color: @shield_outline;} + +line|z10[highway=motorway]::shield, +line|z10[highway=trunk]::shield, +line|z10[highway=motorway_link]::shield, +line|z10[highway=trunk_link]::shield, +line|z10[highway=primary]::shield, +{shield-min-distance: 85;} + +/* 3.TRUNK & MOTORWAY 10-22 ZOOM */ + +line|z10-[highway=trunk], +line|z10-[highway=motorway], +line|z10-[highway=motorway_link], +line|z10-[highway=trunk_link], +{text: name; text-halo-radius: 1; text-halo-color: @label_halo_medium;} + +line|z10-[highway=motorway], +line|z10-[highway=trunk], +{font-size: 11; text-color: @label_medium; text-halo-opacity: 0.9;} + +line|z10-[highway=motorway_link], +line|z10-[highway=trunk_link], +{font-size: 10; text-color: @label_medium; text-halo-opacity: 0.7;} + +/* 4.PRIMARY 10-22 ZOOM */ + +line|z10-[highway=primary], +{text: name; text-halo-radius: 1; text-halo-color: @label_halo_medium;} + +line|z10-[highway=primary], +{font-size: 10; text-color: @label_medium; text-halo-opacity: 0.7;} + +/* 5.SECONDARY 10-22 ZOOM */ + +line|z10-[highway=secondary], +{text: name; text-halo-radius: 1; text-halo-color: @label_halo_medium;} + +line|z10-[highway=secondary], +{font-size: 10; text-color: @label_light; text-halo-opacity: 0.7;} + diff --git a/tests/assets/case-2-generate-drules-mini/include/colors.mapcss b/tests/assets/case-2-generate-drules-mini/include/colors.mapcss new file mode 100644 index 0000000..d7b4817 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/colors.mapcss @@ -0,0 +1,16 @@ +/* 5.1 All roads */ +@trunk0: #FF7326; +@trunk1: #FF7A26; +@primary0: #FF8726; +@secondary0: #FFB226; + +/* 6.1 Main labels */ +@label_medium: #333333; +@label_light: #444444; +@label_halo_medium: #EDEBDB; + +/* 6.4 Road labels */ +@shield_text: #000000; +@shield_text_halo: #000000; +@shield: #FFFFFF; +@shield_outline: #000000; diff --git a/tests/assets/case-2-generate-drules-mini/include/priorities_1_BG-by-size.prio.txt b/tests/assets/case-2-generate-drules-mini/include/priorities_1_BG-by-size.prio.txt new file mode 100644 index 0000000..385b1ab --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/priorities_1_BG-by-size.prio.txt @@ -0,0 +1,16 @@ +# This file is automatically re-formatted and re-sorted in priorities descending order +# when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.) +# are generated automatically for information only. Custom formatting and comments are not preserved. +# +# BG-by-size geometry: background areas rendered below BG-top and everything else. +# Smaller areas are rendered above larger ones (area's size is estimated as the size of its' bounding box). +# So effectively priority values of BG-by-size areas are not used at the moment. +# But we might use them later for some special cases, e.g. to determine a main area type of a multi-type feature. +# Keep them in a logical importance order please. +# +# Priorities ranges' rendering order overview: +# - overlays (icons, captions...) +# - FG: foreground areas and lines +# - BG-top: water (linear and areal) +# - BG-by-size: landcover areas sorted by their size + diff --git a/tests/assets/case-2-generate-drules-mini/include/priorities_2_BG-top.prio.txt b/tests/assets/case-2-generate-drules-mini/include/priorities_2_BG-top.prio.txt new file mode 100644 index 0000000..9e47413 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/priorities_2_BG-top.prio.txt @@ -0,0 +1,18 @@ +# This file is automatically re-formatted and re-sorted in priorities descending order +# when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.) +# are generated automatically for information only. Custom formatting and comments are not preserved. +# +# BG-top geometry: background lines and areas that should be always below foreground ones +# (including e.g. layer=-10 underwater tunnels), but above background areas sorted by size (BG-by-size), +# because ordering by size doesn't always work with e.g. water mapped over a forest, +# so water should be on top of other landcover always, but linear waterways should be hidden beneath it. +# Still, e.g. a layer=-1 BG-top feature will be rendered under a layer=0 BG-by-size feature +# (so areal water tunnels are hidden beneath other landcover area) and a layer=1 landcover areas +# are displayed above layer=0 BG-top. +# +# Priorities ranges' rendering order overview: +# - overlays (icons, captions...) +# - FG: foreground areas and lines +# - BG-top: water (linear and areal) +# - BG-by-size: landcover areas sorted by their size + diff --git a/tests/assets/case-2-generate-drules-mini/include/priorities_3_FG.prio.txt b/tests/assets/case-2-generate-drules-mini/include/priorities_3_FG.prio.txt new file mode 100644 index 0000000..f249605 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/priorities_3_FG.prio.txt @@ -0,0 +1,41 @@ +# This file is automatically re-formatted and re-sorted in priorities descending order +# when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.) +# are generated automatically for information only. Custom formatting and comments are not preserved. +# +# FG geometry: foreground lines and areas (e.g. buildings) are rendered always below overlays +# and always on top of background geometry (BG-top & BG-by-size) even if a foreground feature +# is layer=-10 (as tunnels should be visibile over landcover and water). +# +# Priorities ranges' rendering order overview: +# - overlays (icons, captions...) +# - FG: foreground areas and lines +# - BG-top: water (linear and areal) +# - BG-by-size: landcover areas sorted by their size + +highway-motorway # line z6- (also has pathtext z10-, shield::shield z10-) +highway-motorway-bridge # line z6- (also has pathtext z10-, shield::shield z10-) +highway-motorway-tunnel # line z6- (also has pathtext z10-, shield::shield z10-) +highway-trunk # line z6- (also has pathtext z10-, shield::shield z10-) +highway-trunk-bridge # line z6- (also has pathtext z10-, shield::shield z10-) +highway-trunk-tunnel # line z6- (also has pathtext z10-, shield::shield z10-) +highway-world_level # line z4-9 +highway-world_towns_level # line z6-9 +=== 310 + +highway-primary # line z8- (also has pathtext z10-, shield::shield z10-) +highway-primary-bridge # line z8- (also has pathtext z10-, shield::shield z10-) +highway-primary-tunnel # line z8- (also has pathtext z10-, shield::shield z10-) +=== 290 + +highway-secondary # line z10- (also has pathtext z10-) +highway-secondary-bridge # line z10- (also has pathtext z10-) +highway-secondary-tunnel # line z10- (also has pathtext z10-) +=== 270 + +highway-motorway_link # line z10- (also has pathtext z10-, shield::shield z10-) +highway-motorway_link-bridge # line z10- (also has pathtext z10-, shield::shield z10-) +highway-motorway_link-tunnel # line z10- (also has pathtext z10-, shield::shield z10-) +highway-trunk_link # line z10- (also has pathtext z10-, shield::shield z10-) +highway-trunk_link-bridge # line z10- (also has pathtext z10-, shield::shield z10-) +highway-trunk_link-tunnel # line z10- (also has pathtext z10-, shield::shield z10-) +=== 228 diff --git a/tests/assets/case-2-generate-drules-mini/include/priorities_4_overlays.prio.txt b/tests/assets/case-2-generate-drules-mini/include/priorities_4_overlays.prio.txt new file mode 100644 index 0000000..2eaecb6 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/include/priorities_4_overlays.prio.txt @@ -0,0 +1,61 @@ +# This file is automatically re-formatted and re-sorted in priorities descending order +# when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.) +# are generated automatically for information only. Custom formatting and comments are not preserved. +# +# Overlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas). +# Overlays don't overlap each other, instead the ones with higher priority displace the less important ones. +# Optional captions (which have an icon) are usually displayed only if there are no other overlays in their way +# (technically, max overlays priority value (10000) is subtracted from their priorities automatically). +# +# Priorities ranges' rendering order overview: +# - overlays (icons, captions...) +# - FG: foreground areas and lines +# - BG-top: water (linear and areal) +# - BG-by-size: landcover areas sorted by their size + +highway-motorway # pathtext z10- (also has shield::shield z10-, line z6-) +highway-motorway-bridge # pathtext z10- (also has shield::shield z10-, line z6-) +highway-motorway-tunnel # pathtext z10- (also has shield::shield z10-, line z6-) +highway-trunk # pathtext z10- (also has shield::shield z10-, line z6-) +highway-trunk-bridge # pathtext z10- (also has shield::shield z10-, line z6-) +highway-trunk-tunnel # pathtext z10- (also has shield::shield z10-, line z6-) +=== 6750 + +highway-motorway::shield # shield::shield z10- (also has pathtext z10-, line z6-) +highway-motorway-bridge::shield # shield::shield z10- (also has pathtext z10-, line z6-) +highway-motorway-tunnel::shield # shield::shield z10- (also has pathtext z10-, line z6-) +highway-trunk::shield # shield::shield z10- (also has pathtext z10-, line z6-) +highway-trunk-bridge::shield # shield::shield z10- (also has pathtext z10-, line z6-) +highway-trunk-tunnel::shield # shield::shield z10- (also has pathtext z10-, line z6-) +=== 6740 + +highway-primary # pathtext z10- (also has shield::shield z10-, line z8-) +highway-primary-bridge # pathtext z10- (also has shield::shield z10-, line z8-) +highway-primary-tunnel # pathtext z10- (also has shield::shield z10-, line z8-) +=== 6200 + +highway-motorway_link # pathtext z10- (also has shield::shield z10-, line z10-) +highway-motorway_link-bridge # pathtext z10- (also has shield::shield z10-, line z10-) +highway-motorway_link-tunnel # pathtext z10- (also has shield::shield z10-, line z10-) +highway-trunk_link # pathtext z10- (also has shield::shield z10-, line z10-) +highway-trunk_link-bridge # pathtext z10- (also has shield::shield z10-, line z10-) +highway-trunk_link-tunnel # pathtext z10- (also has shield::shield z10-, line z10-) +=== 6150 + +highway-motorway_link::shield # shield::shield z10- (also has pathtext z10-, line z10-) +highway-motorway_link-bridge::shield # shield::shield z10- (also has pathtext z10-, line z10-) +highway-motorway_link-tunnel::shield # shield::shield z10- (also has pathtext z10-, line z10-) +highway-trunk_link::shield # shield::shield z10- (also has pathtext z10-, line z10-) +highway-trunk_link-bridge::shield # shield::shield z10- (also has pathtext z10-, line z10-) +highway-trunk_link-tunnel::shield # shield::shield z10- (also has pathtext z10-, line z10-) +=== 6140 + +highway-secondary # pathtext z10- (also has line z10-) +highway-secondary-bridge # pathtext z10- (also has line z10-) +highway-secondary-tunnel # pathtext z10- (also has line z10-) +=== 5600 + +highway-primary::shield # shield::shield z10- (also has pathtext z10-, line z8-) +highway-primary-bridge::shield # shield::shield z10- (also has pathtext z10-, line z8-) +highway-primary-tunnel::shield # shield::shield z10- (also has pathtext z10-, line z8-) +=== 2975 diff --git a/tests/assets/case-2-generate-drules-mini/main.mapcss b/tests/assets/case-2-generate-drules-mini/main.mapcss new file mode 100644 index 0000000..300b07d --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/main.mapcss @@ -0,0 +1,133 @@ +*::int_name +{ + text-offset: 1; +} + +@import("include/colors.mapcss"); +@import("include/Roads.mapcss"); +@import("include/Roads_label.mapcss"); + +colors +{ + GuiText-color: #4D4D4D; + GuiText-opacity: 0.86; + MyPositionAccuracy-color: #000000; + MyPositionAccuracy-opacity: 0.08; + Selection-color: #1E96F0; + Selection-opacity: 0.64; + Route-color: #0087FF; + RouteOutline-color: #055FCD; + RouteTrafficG0-color: #9B2300; + RouteTrafficG1-color: #E82705; + RouteTrafficG2-color: #E82705; + RouteTrafficG3-color: #FFE500; + RouteTrafficG3-opacity: 0.0; + RoutePedestrian-color: #1D339E; + RoutePedestrian-opacity: 0.8; + RouteBicycle-color: #9C27B0; + RouteBicycle-opacity: 0.8; + RouteRuler-color: #66347F; + RouteRuler-opacity: 0.9; + RoutePreview-color: #000000; + RoutePreview-opacity: 0.3; + RouteMaskCar-color: #000000; + RouteMaskCar-opacity: 0.3; + RouteFirstSegmentArrowsMaskCar-color: #033B80; + RouteFirstSegmentArrowsMaskCar-opacity: 0.0; + RouteArrowsMaskCar-color: #033B80; + RouteArrowsMaskCar-opacity: 0.2; + RouteMaskBicycle-color: #000000; + RouteMaskBicycle-opacity: 0.5; + RouteFirstSegmentArrowsMaskBicycle-color: #9C27B0; + RouteFirstSegmentArrowsMaskBicycle-opacity: 0.0; + RouteArrowsMaskBicycle-color: #9C27B0; + RouteArrowsMaskBicycle-opacity: 0.2; + RouteMaskPedestrian-color: #000000; + RouteMaskPedestrian-opacity: 0.5; + RouteFake-color: #A8A8A8; + RouteFakeOutline-color: #717171; + Arrow3D-color: #50AAFF; + Arrow3DObsolete-color: #82AAC8; + Arrow3DObsolete-opacity: 0.72; + Arrow3DShadow-color: #3C3C3C; + Arrow3DShadow-opacity: 0.24; + Arrow3DOutline-color: #FFFFFF; + TrackHumanSpeed-color: #1D339E; + TrackCarSpeed-color: #7C8EDE; + TrackPlaneSpeed-color: #A8B7ED; + TrackUnknownDistance-color: #616161; + TrafficG0-color: #7E1712; + TrafficG1-color: #E42300; + TrafficG2-color: #E42300; + TrafficG3-color: #FCDE00; + TrafficG3-opacity: 0.0; + TrafficG4-color: #39962E; + TrafficG5-color: #39962E; + TrafficTempBlock-color: #525252; + TrafficUnknown-color: #000000; + TrafficArrowLight-color: #FFFFFF; + TrafficArrowDark-color: #473635; + TrafficOutline-color: #E8E6DC; + RoadShieldBlackText-color: #000000; + RoadShieldWhiteText-color: #FFFFFF; + RoadShieldUKYellowText-color: #FFD400; + RoadShieldBlueBackground-color: #1A5EC1; + RoadShieldGreenBackground-color: #309302; + RoadShieldRedBackground-color: #E63534; + RoadShieldOrangeBackground-color: #FFBE00; + PoiHotelTextOutline-color: #FFFFFF; + PoiHotelTextOutline-opacity: 0.6; + PoiDeletedMask-color: #FFFFFF; + PoiDeletedMask-opacity: 0.3; + PoiVisitedMask-color: #FFFFFF; + PoiVisitedMask-opacity: 0.7; + DefaultTrackColor-color: #1E96F0; + RouteMarkPrimaryText-color: #000000; + RouteMarkPrimaryTextOutline-color: #FFFFFF; + RouteMarkSecondaryText-color: #000000; + RouteMarkSecondaryTextOutline-color: #FFFFFF; + TransitMarkPrimaryText-color: #000000; + TransitMarkPrimaryTextOutline-color: #FFFFFF; + TransitMarkSecondaryText-color: #000000; + TransitMarkSecondaryTextOutline-color: #FFFFFF; + TransitTransferOuterMarker-color: #000000; + TransitTransferInnerMarker-color: #FFFFFF; + TransitStopInnerMarker-color: #FFFFFF; + LocalAdsPrimaryText-color: #000000; + LocalAdsPrimaryTextOutline-color: #FFFFFF; + LocalAdsSecondaryText-color: #000000; + LocalAdsSecondaryTextOutline-color: #FFFFFF; + TransitBackground-color: #FFFFFF; + TransitBackground-opacity: 0.4; + BookmarkRed-color: #E51B23; + BookmarkPink-color: #FF4182; + BookmarkPurple-color: #9B24B2; + BookmarkDeepPurple-color: #6639BF; + BookmarkBlue-color: #0066CC; + BookmarkLightBlue-color: #249CF2; + BookmarkCyan-color: #14BECD; + BookmarkTeal-color: #00A58C; + BookmarkGreen-color: #3C8C3C; + BookmarkLime-color: #93BF39; + BookmarkYellow-color: #FFC800; + BookmarkOrange-color: #FF9600; + BookmarkDeepOrange-color: #F06432; + BookmarkBrown-color: #804633; + BookmarkGray-color: #737373; + BookmarkBlueGray-color: #597380; + SearchmarkPreparing-color: #597380; + SearchmarkNotAvailable-color: #597380; + SearchmarkSelectedNotAvailable-color: #F06432; + RatingBad-color: #F06432; + RatingGood-color: #3C8C3C; + RatingNone-color: #249CF2; + SearchmarkDefault-color: #249CF2; + RatingText-color: #FFFFFF; + UGCRatingText-color: #000000; + SpeedCameraMarkText-color: #FFFFFF; + SpeedCameraMarkBg-color: #F51E30; + SpeedCameraMarkOutline-color: #FFFFFF; + GuideCityMarkText-color: #6639BF; + GuideOutdoorMarkText-color: #3C8C3C; + HotelPriceText-color: #000000; +} diff --git a/tests/assets/case-2-generate-drules-mini/mapcss-dynamic.txt b/tests/assets/case-2-generate-drules-mini/mapcss-dynamic.txt new file mode 100644 index 0000000..60746b0 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/mapcss-dynamic.txt @@ -0,0 +1,4 @@ +population +name +bbox_area +rating diff --git a/tests/assets/case-2-generate-drules-mini/mapcss-mapping.csv b/tests/assets/case-2-generate-drules-mini/mapcss-mapping.csv new file mode 100644 index 0000000..077f771 --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/mapcss-mapping.csv @@ -0,0 +1,148 @@ +highway|residential;2; +highway|service;3; +highway|unclassified;5; +highway|footway;7; +highway|track;8; +highway|tertiary;9; +highway|secondary;13; +highway|path;16; +highway|bus_stop;17; +highway|footway|sidewalk;[highway=footway][footway=sidewalk];;name;int_name;18; +highway|primary;27; +highway|service|parking_aisle;[highway=service][service=parking_aisle];;name;int_name;29; +moved:highway|road:05.2024;31;highway|road +deprecated:highway|track|grade2:04.2024;[highway=track][tracktype=grade2];x;name;int_name;32;highway|track +deprecated:highway|track|grade3:04.4024;[highway=track][tracktype=grade3];x;name;int_name;34;highway|track +highway|cycleway;37; +deprecated:highway|track|grade1:04.2024;[highway=track][tracktype=grade1];x;name;int_name;40;highway|track +highway|service|driveway;[highway=service][service=driveway];;name;int_name;42; +highway|motorway_link;44; +deprecated:highway|track|grade4:04.2024;[highway=track][tracktype=grade4];x;name;int_name;46;highway|track +highway|footway|crossing;[highway=footway][footway=crossing];;name;int_name;51; +highway|path|bicycle;[highway=path][bicycle=designated];;;;53; +highway|living_street;55; +highway|motorway;58; +highway|steps;59; +deprecated:highway|track|grade5:04.2024;[highway=track][tracktype=grade5];x;name;int_name;63;highway|track +highway|trunk;66; +highway|pedestrian;70; +highway|motorway|bridge;[highway=motorway][bridge?];;name;int_name;72; +highway|residential|bridge;[highway=residential][bridge?];;name;int_name;81; +highway|secondary|bridge;[highway=secondary][bridge?];;name;int_name;85; +highway|tertiary|bridge;[highway=tertiary][bridge?];;name;int_name;86; +highway|trunk_link;91; +highway|unclassified|bridge;[highway=unclassified][bridge?];;name;int_name;92; +highway|primary|bridge;[highway=primary][bridge?];;name;int_name;95; +highway|primary_link;96; +highway|footway|bridge;[highway=footway][bridge?];;name;int_name;98; +deprecated:highway|path|hiking:04.2024;[highway=path][route=hiking],[highway=path][sac_scale=hiking];x;name;int_name;113;highway|path +highway|trunk|bridge;[highway=trunk][bridge?];;name;int_name;116; +highway|motorway_junction;121; +highway|footway|bicycle;[highway=footway][bicycle=designated];;;;141; +highway|motorway_link|bridge;[highway=motorway_link][bridge?];;name;int_name;143; +deprecated:highway|footway|permissive:12.2023;[highway=footway][access=permissive],[highway=footway][foot=permissive];x;name;int_name;153;highway|footway +highway|pedestrian|area;[highway=pedestrian][area?];;name;int_name;158; +highway|construction;163; +highway|cycleway|bridge;[highway=cycleway][bridge?];;name;int_name;164; +deprecated:highway|path|mountain_hiking:04.2024;[highway=path][sac_scale=mountain_hiking];x;name;int_name;166;highway|path +highway|bridleway;168; +highway|secondary_link;177; +highway|footway|tunnel;[highway=footway][tunnel?],[highway=footway][location=underground];;name;int_name;183; +highway|track|bridge;[highway=track][bridge?];;name;int_name;193; +highway|path|bridge;[highway=path][bridge?];;name;int_name;194; +highway|service|bridge;[highway=service][bridge?];;name;int_name;203; +highway|service|area;[highway=service][area?];;name;int_name;226; +highway|residential|area;[highway=residential][area?];;name;int_name;227; +deprecated:highway|track|permissive:12.2023;[highway=track][access=permissive];x;name;int_name;229;highway|track +highway|cycleway|tunnel;[highway=cycleway][tunnel?];;name;int_name;232; +highway|unclassified|tunnel;[highway=unclassified][tunnel?];;name;int_name;235; +highway|residential|tunnel;[highway=residential][tunnel?];;name;int_name;238; +deprecated:highway|path|permissive:12.2023;[highway=path][access=permissive];x;name;int_name;240;highway|path +highway|trunk_link|bridge;[highway=trunk_link][bridge?];;name;int_name;261; +highway|service|tunnel;[highway=service][tunnel?];;name;int_name;263; +highway|tertiary|tunnel;[highway=tertiary][tunnel?];;name;int_name;269; +highway|tertiary_link;273; +highway|footway|area;[highway=footway][area?];;name;int_name;276; +highway|road|bridge;[highway=road][bridge?];;name;int_name;280; +highway|secondary|tunnel;[highway=secondary][tunnel?];;name;int_name;297; +deprecated:highway|path|demanding_mountain_hiking:04.2024;[highway=path][sac_scale=demanding_mountain_hiking];x;name;int_name;300;highway|path|difficult +highway|pedestrian|bridge;[highway=pedestrian][bridge?];;name;int_name;304; +highway|raceway;308; +highway|primary|tunnel;[highway=primary][tunnel?];;name;int_name;309; +highway|primary_link|bridge;[highway=primary_link][bridge?];;name;int_name;310; +deprecated:highway|footway|hiking:04.2024;[highway=footway][sac_scale=hiking];x;name;int_name;314;highway|path +highway|path|horse;[highway=path][horse?];;name;int_name;317; +highway|trunk|tunnel;[highway=trunk][tunnel?];;name;int_name;326; +highway|steps|tunnel;[highway=steps][tunnel?],[highway=steps][location=underground];;name;int_name;327; +highway|steps|bridge;[highway=steps][bridge?];;name;int_name;330; +highway|pedestrian|tunnel;[highway=pedestrian][tunnel?],[highway=pedestrian][location=underground];;name;int_name;332; +highway|path|tunnel;[highway=path][tunnel?],[highway=path][location=underground];;name;int_name;336; +deprecated:highway|path|alpine_hiking:04.2024;[highway=path][sac_scale=alpine_hiking];x;name;int_name;350;highway|path|expert +deprecated:highway|cycleway|permissive:12.2023;[highway=cycleway][access=permissive];x;name;int_name;353;highway|cycleway +highway|unclassified|area;[highway=unclassified][area?];;name;int_name;354; +deprecated:highway|footway|mountain_hiking:04.2024;[highway=footway][sac_scale=mountain_hiking];x;name;int_name;361;highway|path +deprecated:highway|service|driveway|bridge:01.2020;[highway=service][service=driveway][bridge?];x;name;int_name;362;highway|service|driveway +deprecated:highway|bridleway|permissive:12.2023;[highway=bridleway][access=permissive];x;name;int_name;370;highway|bridleway +highway|bridleway|bridge;[highway=bridleway][bridge?];;name;int_name;378; +deprecated:highway|service|driveway|tunnel:01.2020;[highway=service][service=driveway][tunnel?];x;name;int_name;379;highway|service|driveway +deprecated:highway|service|driveway|area:01.2020;[highway=service][service=driveway][area?];x;name;int_name;386;highway|service|driveway +deprecated:highway|path|demanding_alpine_hiking:04.2024;[highway=path][sac_scale=demanding_alpine_hiking];x;name;int_name;395;highway|path|expert +highway|secondary_link|bridge;[highway=secondary_link][bridge?];;name;int_name;397; +area:highway|living_street;401; +highway|living_street|bridge;[highway=living_street][bridge?];;name;int_name;407; +highway|road;411; +highway|motorway|tunnel;[highway=motorway][tunnel?];;name;int_name;416; +area:highway|service;418; +highway|road|tunnel;[highway=road][tunnel?];;name;int_name;423; +highway|ford;427; +area:highway|path;428; +highway|track|area;[highway=track][area?];;name;int_name;430; +deprecated:highway|path|difficult_alpine_hiking:04.2024;[highway=path][sac_scale=difficult_alpine_hiking];x;name;int_name;444;highway|path|expert +deprecated:highway|footway|demanding_mountain_hiking:04.2024;[highway=footway][sac_scale=demanding_mountain_hiking];x;name;int_name;452;highway|path|difficult +highway|living_street|tunnel;[highway=living_street][tunnel?];;name;int_name;457; +highway|path|difficult;[highway=path][_path_grade=difficult];;name;int_name;464; +highway|path|expert;[highway=path][_path_grade=expert];;name;int_name;465; +area:highway|steps;470; +highway|bridleway|tunnel;[highway=bridleway][tunnel?];;name;int_name;488; +highway|motorway_link|tunnel;[highway=motorway_link][tunnel?];;name;int_name;489; +highway|tertiary_link|bridge;[highway=tertiary_link][bridge?];;name;int_name;493; +highway|trunk_link|tunnel;[highway=trunk_link][tunnel?];;name;int_name;503; +highway|primary_link|tunnel;[highway=primary_link][tunnel?];;name;int_name;528; +deprecated:highway|footway|alpine_hiking:04.2024;[highway=footway][sac_scale=alpine_hiking];x;name;int_name;529;highway|path|expert +deprecated:amenity|speed_trap:10.2021;542;highway|speed_camera +area:highway|track;543; +area:highway|primary;544; +deprecated:highway|footway|demanding_alpine_hiking:04.2024;[highway=footway][sac_scale=demanding_alpine_hiking];x;name;int_name;555;highway|path|expert +highway|secondary_link|tunnel;[highway=secondary_link][tunnel?];;name;int_name;578; +highway|track|grade3|permissive;[highway=track][tracktype=grade3][access=permissive];x;name;int_name;591;highway|track +deprecated:highway|footway|difficult_alpine_hiking:04.2024;[highway=footway][sac_scale=difficult_alpine_hiking];x;name;int_name;627;highway|path|expert +highway|track|grade5|permissive;[highway=track][tracktype=grade5][access=permissive];x;name;int_name;631;highway|track +highway|tertiary_link|tunnel;[highway=tertiary_link][tunnel?];;name;int_name;634; +highway|track|grade4|permissive;[highway=track][tracktype=grade4][access=permissive];x;name;int_name;675;highway|track +highway|track|grade3|no-access;[highway=track][tracktype=grade3][access=no];x;name;int_name;821;highway|track +highway|track|grade4|no-access;[highway=track][tracktype=grade4][access=no];x;name;int_name;822;highway|track +highway|track|grade5|no-access;[highway=track][tracktype=grade5][access=no];x;name;int_name;823;highway|track +highway|track|no-access;[highway=track][access=no];;name;int_name;824; +deprecated:highway|service|busway:10.2023;[highway=service][service=busway];x;name;int_name;857;highway|busway +highway|busway;[highway=busway],[highway=service][service=busway],[highway=service][service=bus];;name;int_name;858; +highway|busway|bridge;[highway=busway][bridge?];;name;int_name;859; +highway|busway|tunnel;[highway=busway][tunnel?];;name;int_name;860; +area:highway|footway;866; +area:highway|residential;868; +area:highway|secondary;869; +area:highway|tertiary;870; +area:highway|pedestrian;873; +area:highway|unclassified;874; +area:highway|cycleway;877; +area:highway|motorway;879; +area:highway|trunk;880; +highway|speed_camera;991; +highway|world_level;1052; +highway|world_towns_level;1053; +highway|elevator;1059; +highway|rest_area;1080; +highway|traffic_signals;1081; +hwtag|nobicycle;1114; +hwtag|yesbicycle;1115; +hwtag|bidir_bicycle;1116; +highway|services;1173; diff --git a/tests/assets/case-2-generate-drules-mini/readme.md b/tests/assets/case-2-generate-drules-mini/readme.md new file mode 100644 index 0000000..d57323c --- /dev/null +++ b/tests/assets/case-2-generate-drules-mini/readme.md @@ -0,0 +1,4 @@ +Files for testLibkomwm.test_generate_drules_mini() method. + +These styles contain only zooms 0-10 and only highway=* rules. +So we can verify generated files content. diff --git a/tests/assets/case-3-styles-validation/readme.md b/tests/assets/case-3-styles-validation/readme.md new file mode 100644 index 0000000..a937244 --- /dev/null +++ b/tests/assets/case-3-styles-validation/readme.md @@ -0,0 +1 @@ +Files for testLibkomwm.test_generate_drules_validation_errors() method. diff --git a/tests/testCondition.py b/tests/testCondition.py new file mode 100644 index 0000000..5ab7b68 --- /dev/null +++ b/tests/testCondition.py @@ -0,0 +1,300 @@ +import re +import unittest +import sys +from pathlib import Path + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +from mapcss import parseCondition +from mapcss.Condition import Condition + +class ConditionTest(unittest.TestCase): + + def test_parser_eq(self): + cond:Condition = parseCondition("natural=coastline") + self.assertEqual(cond.type, "eq") + self.assertEqual(cond.params, ("natural", "coastline")) + self.assertTrue(cond.test({'natural': 'coastline'})) + self.assertFalse(cond.test({'Natural': 'Coastline'})) + + cond = parseCondition(" highway\t=\tprimary") + self.assertEqual(cond.type, "eq") + self.assertEqual(cond.params, ("highway", "primary")) + self.assertTrue(cond.test({'highway': 'primary'})) + self.assertFalse(cond.test({'highway': 'secondary'})) + + cond = parseCondition(" admin_level = 3") + self.assertEqual(cond.type, "eq") + self.assertEqual(cond.params, ("admin_level", "3")) + self.assertTrue(cond.test({'admin_level': '3'})) + self.assertFalse(cond.test({'admin_level': '32'})) + + cond = Condition('eq', ("::class", "::*")) + self.assertEqual(cond.type, "eq") + self.assertEqual(cond.params, ("::class", "::*")) + self.assertEqual(cond.extract_tag(), "*") + self.assertEqual(cond.test({'any_key': 'any_value'}), "::*") + self.assertTrue(cond.test({'any_key': 'any_value'})) + + cond = Condition('eq', ("::class", "::int_name")) + self.assertEqual(cond.type, "eq") + self.assertEqual(cond.params, ("::class", "::int_name")) + self.assertEqual(cond.extract_tag(), "*") + self.assertEqual(cond.test({'any_key': 'any_value'}), "::int_name") + self.assertTrue(cond.test({'any_key': 'any_value'})) + + def test_parser_regex(self): + """ Test conditions in format natural =~/water.+/ + Note that such conditions are not used by Organic Maps styles. + """ + cond:Condition = parseCondition("natural =~/water.+/") + self.assertEqual(cond.type, "regex") + self.assertEqual(cond.params, ("natural", "water.+")) + self.assertEqual(type(cond.regex), re.Pattern) + self.assertTrue(cond.test({"natural": "waterway"})) + self.assertTrue(cond.test({"natural": "water123"})) + self.assertFalse(cond.test({"natural": "water"})) + self.assertFalse(cond.test({"natural": " waterway "})) + + def test_parser_ge(self): + cond:Condition = parseCondition("population>=0") + self.assertEqual(cond.type, ">=") + self.assertEqual(cond.params, ("population", "0")) + self.assertTrue(cond.test({"population": "0"})) + self.assertTrue(cond.test({"population": "100000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "-1"})) + + cond:Condition = parseCondition("population >= 150000") + self.assertEqual(cond.type, ">=") + self.assertEqual(cond.params, ("population", "150000")) + self.assertTrue(cond.test({"population": "150000"})) + self.assertTrue(cond.test({"population": "250000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "10000"})) + + cond:Condition = parseCondition("\tbbox_area >= 4000000") + self.assertEqual(cond.type, ">=") + self.assertEqual(cond.params, ("bbox_area", "4000000")) + self.assertTrue(cond.test({"bbox_area": "4000000"})) + self.assertTrue(cond.test({"bbox_area": "8000000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"bbox_area": "999"})) + + def test_parser_gt(self): + """ Test conditions in format population > 100000 + Note that such conditions are not used by Organic Maps styles. + """ + cond:Condition = parseCondition("population>0") + self.assertEqual(cond.type, ">") + self.assertEqual(cond.params, ("population", "0")) + self.assertTrue(cond.test({"population": "100"})) + self.assertFalse(cond.test({"population": "000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "-1"})) + + cond:Condition = parseCondition("population > 150000") + self.assertEqual(cond.type, ">") + self.assertEqual(cond.params, ("population", "150000")) + self.assertTrue(cond.test({"population": "250000"})) + self.assertFalse(cond.test({"population": "150000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "10000"})) + + cond:Condition = parseCondition("\tbbox_area > 4000000 ") + self.assertEqual(cond.type, ">") + self.assertEqual(cond.params, ("bbox_area", "4000000 ")) # TODO fix parser to exclude trailing space + self.assertTrue(cond.test({"bbox_area": "8000000"})) + self.assertFalse(cond.test({"bbox_area": "4000000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"bbox_area": "999"})) + + def test_parser_lt(self): + cond:Condition = parseCondition("population<40000") + self.assertEqual(cond.type, "<") + self.assertEqual(cond.params, ("population", "40000")) + self.assertTrue(cond.test({"population": "100"})) + self.assertTrue(cond.test({"population": "-1"})) + self.assertFalse(cond.test({"population": "40000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "500000"})) + + cond:Condition = parseCondition("\tbbox_area < 4000000\n") + self.assertEqual(cond.type, "<") + self.assertEqual(cond.params, ("bbox_area", "4000000\n")) # TODO fix parser to exclude trailing \n + self.assertTrue(cond.test({"bbox_area": "100"})) + self.assertTrue(cond.test({"bbox_area": "-1"})) + self.assertTrue(cond.test({"bbox_area": "000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"bbox_area": "4000000"})) + self.assertFalse(cond.test({"bbox_area": "8000000"})) + + def test_parser_le(self): + """ Test conditions in format population <= 100000 + Note that such conditions are not used by Organic Maps styles. + """ + cond:Condition = parseCondition("population<=40000") + self.assertEqual(cond.type, "<=") + self.assertEqual(cond.params, ("population", "40000")) + self.assertTrue(cond.test({"population": "100"})) + self.assertTrue(cond.test({"population": "-1"})) + self.assertTrue(cond.test({"population": "40000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"population": "500000"})) + + cond:Condition = parseCondition("\tbbox_area <= 4000000\n") + self.assertEqual(cond.type, "<=") + self.assertEqual(cond.params, ("bbox_area", "4000000\n")) # TODO fix parser to exclude trailing \n + self.assertTrue(cond.test({"bbox_area": "100"})) + self.assertTrue(cond.test({"bbox_area": "-1"})) + self.assertTrue(cond.test({"bbox_area": "000"})) + self.assertTrue(cond.test({"bbox_area": "4000000"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"bbox_area": "8000000"})) + + def test_parser_ne(self): + cond:Condition = parseCondition("capital!=2") + self.assertEqual(cond.type, "ne") + self.assertEqual(cond.params, ("capital", "2")) + self.assertTrue(cond.test({"capital": "1"})) + self.assertTrue(cond.test({"capital": "22"})) + self.assertTrue(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"capital": "2"})) + + cond:Condition = parseCondition("\tcapital != 2") + self.assertEqual(cond.type, "ne") + self.assertEqual(cond.params, ("capital", "2")) + self.assertTrue(cond.test({"capital": "1"})) + self.assertTrue(cond.test({"capital": "22"})) + self.assertTrue(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"capital": "2"})) + + cond:Condition = parseCondition("garden:type != residential") + self.assertEqual(cond.type, "ne") + self.assertEqual(cond.params, ("garden:type", "residential")) + self.assertTrue(cond.test({"garden:type": "public"})) + self.assertTrue(cond.test({"garden:type": "res"})) + self.assertTrue(cond.test({"garden:type": "residential_plus"})) + self.assertTrue(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"garden:type": "residential"})) + + def test_parser_set(self): + cond:Condition = parseCondition("tunnel") + self.assertEqual(cond.type, "set") + self.assertEqual(cond.params, ("tunnel", )) + self.assertTrue(cond.test({"tunnel": "yes"})) + self.assertTrue(cond.test({"tunnel": "maybe"})) + self.assertTrue(cond.test({"tunnel": "+1"})) + self.assertFalse(cond.test({"highway": "secondary"})) + + cond:Condition = parseCondition("building\t") + self.assertEqual(cond.type, "set") + self.assertEqual(cond.params, ("building", )) + self.assertTrue(cond.test({"building": "yes"})) + self.assertTrue(cond.test({"building": "apartment"})) + self.assertTrue(cond.test({"building": "1"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"building:part": "yes"})) + + cond:Condition = parseCondition(" addr:housenumber ") + self.assertEqual(cond.type, "set") + self.assertEqual(cond.params, ("addr:housenumber", )) + self.assertTrue(cond.test({"addr:housenumber": "1"})) + self.assertTrue(cond.test({"addr:housenumber": "yes"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"addr:street": "Baker st"})) + + cond:Condition = parseCondition(" some-tag ") + self.assertEqual(cond.type, "set") + self.assertEqual(cond.params, ("some-tag", )) + self.assertTrue(cond.test({"some-tag": "1"})) + self.assertTrue(cond.test({"some-tag": "yes"})) + self.assertFalse(cond.test({"highway": "secondary"})) + self.assertFalse(cond.test({"some": "tag"})) + + def test_parser_unset(self): + cond:Condition = parseCondition("!tunnel") + self.assertEqual(cond.type, "unset") + self.assertEqual(cond.params, ("tunnel", )) + self.assertTrue(cond.test({"capital": "1"})) + self.assertFalse(cond.test({"tunnel": "yes"})) + self.assertFalse(cond.test({"tunnel": "no"})) + + cond:Condition = parseCondition("\t!name ") + self.assertEqual(cond.type, "unset") + self.assertEqual(cond.params, ("name", )) + self.assertTrue(cond.test({"capital": "1"})) + self.assertTrue(cond.test({"int_name": "1"})) + self.assertFalse(cond.test({"name": "London"})) + + def test_parser_false(self): + """ Test conditions in format some_tag = no + Note that such conditions are not used by Organic Maps styles. + """ + cond:Condition = parseCondition("access=no") + self.assertEqual(cond.type, "false") + self.assertEqual(cond.params, ("access", )) + #self.assertTrue(cond.test({"access": "no"})) # test is not implemented for `false` condition + #self.assertTrue(cond.test({"access": "private"})) # test is not implemented for `false` condition + self.assertFalse(cond.test({"tunnel": "yes"})) + + def test_parser_invTrue(self): + """ Test conditions in format [!some_tag?] It works the same way as [some_tag != yes] + Note that such conditions are not used by Organic Maps styles. + """ + cond:Condition = parseCondition("!oneway?") + self.assertEqual(cond.type, "ne") + self.assertEqual(cond.params, ("oneway", "yes")) + self.assertTrue(cond.test({"oneway": "no"})) + self.assertTrue(cond.test({"oneway": "nobody_knows"})) + self.assertTrue(cond.test({"access": "private"})) + self.assertFalse(cond.test({"oneway": "yes"})) + + cond:Condition = parseCondition("\t! intermittent ?\n") + self.assertEqual(cond.type, "ne") + self.assertEqual(cond.params, ("intermittent", "yes")) + self.assertTrue(cond.test({"intermittent": "no"})) + self.assertTrue(cond.test({"intermittent": "maybe"})) + self.assertTrue(cond.test({"access": "private"})) + self.assertFalse(cond.test({"intermittent": "yes"})) + + def test_parser_true(self): + """ Test conditions in format [some_tag?] It works the same way as [some_tag = yes] """ + cond:Condition = parseCondition("area?") + self.assertEqual(cond.type, "true") + self.assertEqual(cond.params, ("area", )) + self.assertTrue(cond.test({"area": "yes"})) + self.assertFalse(cond.test({"area": "no"})) + self.assertFalse(cond.test({"access": "private"})) + self.assertFalse(cond.test({"oneway": "nobody_knows"})) + + cond:Condition = parseCondition("\tbridge ? ") + self.assertEqual(cond.type, "true") + self.assertEqual(cond.params, ("bridge", )) + self.assertTrue(cond.test({"bridge": "yes"})) + self.assertFalse(cond.test({"bridge": "no"})) + self.assertFalse(cond.test({"access": "private"})) + self.assertFalse(cond.test({"bridge": "maybe"})) + + def test_untrue(self): + """ parseCondition(...) doesn't support this type of condition. + Not sure if it's ever used. + """ + cond:Condition = Condition("untrue", "access") + self.assertEqual(cond.type, "untrue") + self.assertEqual(cond.params, ("access", )) + self.assertTrue(cond.test({"access": "no"})) + self.assertFalse(cond.test({"access": "private"})) + self.assertFalse(cond.test({"oneway": "yes"})) + + def test_parser_errors(self): + with self.assertRaises(Exception): + parseCondition("! tunnel") + with self.assertRaises(Exception): + """ Symbol '-' is only supported in simple 'set' rule. E.g. [key-with-dash] + But not in 'unset' rule [!key-with-dash] """ + parseCondition("key-with-dash?") + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testEval.py b/tests/testEval.py new file mode 100644 index 0000000..94edc2a --- /dev/null +++ b/tests/testEval.py @@ -0,0 +1,124 @@ +import unittest +import sys +from pathlib import Path + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +from mapcss.Eval import Eval + +class EvalTest(unittest.TestCase): + """ Test eval(...) feature for CSS properties. + NOTE: eval() is not used in Organic Maps styles. We can drop it completely. + """ + def test_eval_tag(self): + a = Eval("""eval( tag("lanes") )""") + self.assertEqual(a.compute({"lanes": "4"}), "4") + self.assertEqual(a.compute({"natural": "trees"}), "") + self.assertSetEqual(a.extract_tags(), {"lanes"}) + + def test_eval_prop(self): + a = Eval("""eval( prop("dpi") / 2 )""") + self.assertEqual(a.compute({"lanes": "4"}, {"dpi": 144}), "72") + self.assertEqual(a.compute({"lanes": "4"}, {"orientation": "vertical"}), "") + self.assertSetEqual(a.extract_tags(), set()) + + def test_eval_num(self): + a = Eval("""eval( num(tag("lanes")) + 2 )""") + self.assertEqual(a.compute({"lanes": "4"}), "6") + self.assertEqual(a.compute({"lanes": "many"}), "2") + self.assertSetEqual(a.extract_tags(), {"lanes"}) + + def test_eval_metric(self): + a = Eval("""eval( metric(tag("height")) )""") + self.assertEqual(a.compute({"height": "512"}), "512") + self.assertEqual(a.compute({"height": "10m"}), "10") + self.assertEqual(a.compute({"height": " 10m"}), "10") + self.assertEqual(a.compute({"height": "500cm"}), "5") + self.assertEqual(a.compute({"height": "500 cm"}), "5") + self.assertEqual(a.compute({"height": "250CM"}), "2.5") + self.assertEqual(a.compute({"height": "250 CM"}), "2.5") + self.assertEqual(a.compute({"height": "30см"}), "0.3") + self.assertEqual(a.compute({"height": " 30 см"}), "0.3") + self.assertEqual(a.compute({"height": "1200 mm"}), "1.2") + self.assertEqual(a.compute({"height": "2400MM"}), "2.4") + self.assertEqual(a.compute({"height": "2800 мм"}), "2.8") + self.assertSetEqual(a.extract_tags(), {"height"}) + + def test_eval_metric_with_scale(self): + a = Eval("""eval( metric(tag("height")) )""") + self.assertEqual(a.compute({"height": "512"}, xscale=4), "2048") + self.assertEqual(a.compute({"height": "512"}, zscale=4), "512") + self.assertEqual(a.compute({"height": "10m"}, xscale=4), "40") + self.assertEqual(a.compute({"height": " 10m"}, xscale=4), "40") + self.assertEqual(a.compute({"height": "500cm"}, xscale=4), "20") + self.assertEqual(a.compute({"height": "500 cm"}, xscale=4), "20") + self.assertEqual(a.compute({"height": "250CM"}, xscale=4), "10") + self.assertEqual(a.compute({"height": "250 CM"}, xscale=4), "10") + self.assertEqual(a.compute({"height": "30см"}, xscale=4), "1.2") + self.assertEqual(a.compute({"height": " 30 см"}, xscale=4), "1.2") + self.assertEqual(a.compute({"height": "1200 mm"}, xscale=4), "4.8") + self.assertEqual(a.compute({"height": "2400MM"}, xscale=4), "9.6") + self.assertEqual(a.compute({"height": "2800 мм"}, xscale=4), "11.2") + self.assertSetEqual(a.extract_tags(), {"height"}) + + def test_eval_zmetric(self): + a = Eval("""eval( zmetric(tag("depth")) )""") + self.assertEqual(a.compute({"depth": "512"}), "256") + self.assertEqual(a.compute({"depth": "10m"}), "5") + self.assertEqual(a.compute({"depth": " 10m"}), "5") + self.assertEqual(a.compute({"depth": "500cm"}), "2.5") + self.assertEqual(a.compute({"depth": "500 cm"}), "2.5") + self.assertEqual(a.compute({"depth": "250CM"}), "1.25") + self.assertEqual(a.compute({"depth": "250 CM"}), "1.25") + self.assertEqual(a.compute({"depth": "30см"}), "0.15") + self.assertEqual(a.compute({"depth": " 30 см"}), "0.15") + self.assertEqual(a.compute({"depth": "1200 mm"}), "0.6") + self.assertEqual(a.compute({"depth": "2400MM"}), "1.2") + self.assertEqual(a.compute({"depth": "2800 мм"}), "1.4") + self.assertSetEqual(a.extract_tags(), {"depth"}) + + def test_eval_str(self): + a = Eval("""eval( str( num(tag("width")) - 200 ) )""") + self.assertEqual(a.compute({"width": "400"}), "200.0") + self.assertSetEqual(a.extract_tags(), {"width"}) + + def test_eval_any(self): + a = Eval("""eval( any(tag("building"), tag("building:part"), "no") )""") + self.assertEqual(a.compute({"building": "apartment"}), "apartment") + self.assertEqual(a.compute({"building:part": "roof"}), "roof") + self.assertEqual(a.compute({"junction": "roundabout"}), "no") + self.assertSetEqual(a.extract_tags(), {"building", "building:part"}) + + def test_eval_min(self): + a = Eval("""eval( min( num(tag("building:levels")) * 3, 50) )""") + self.assertEqual(a.compute({"natural": "wood"}), "0") + self.assertEqual(a.compute({"building:levels": "0"}), "0") + self.assertEqual(a.compute({"building:levels": "10"}), "30") + self.assertEqual(a.compute({"building:levels": "30"}), "50") + self.assertSetEqual(a.extract_tags(), {"building:levels"}) + + def test_eval_max(self): + a = Eval("""eval( max( tag("speed:limit"), 60) )""") + self.assertEqual(a.compute({"natural": "wood"}), "60") + self.assertEqual(a.compute({"speed:limit": "30"}), "60") + self.assertEqual(a.compute({"speed:limit": "60"}), "60") + self.assertEqual(a.compute({"speed:limit": "90"}), "90") + self.assertSetEqual(a.extract_tags(), {"speed:limit"}) + + def test_eval_cond(self): + a = Eval("""eval( cond( boolean(tag("oneway")), 200, 100) )""") + self.assertEqual(a.compute({"natural": "wood"}), "100") + self.assertEqual(a.compute({"oneway": "yes"}), "200") + self.assertEqual(a.compute({"oneway": "no"}), "100") + self.assertEqual(a.compute({"oneway": "true"}), "200") + self.assertEqual(a.compute({"oneway": "probably no"}), "200") + self.assertSetEqual(a.extract_tags(), {"oneway"}) + + def test_complex_eval(self): + a = Eval(""" eval( any( metric(tag("height")), metric ( num(tag("building:levels")) * 3), metric("1m"))) """) + self.assertEqual(a.compute({"building:levels": "3"}), "9") + self.assertSetEqual(a.extract_tags(), {"height", "building:levels"}) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testLibkomwm.py b/tests/testLibkomwm.py new file mode 100644 index 0000000..c2f3e52 --- /dev/null +++ b/tests/testLibkomwm.py @@ -0,0 +1,68 @@ +import unittest +import sys +from pathlib import Path +from copy import deepcopy + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +import libkomwm +from libkomwm import komap_mapswithme + + +class LibKomwmTest(unittest.TestCase): + def test_generate_drules_mini(self): + assets_dir = Path(__file__).parent / 'assets' / 'case-2-generate-drules-mini' + + class Options(object): + pass + + options = Options() + options.data = None + options.minzoom = 0 + options.maxzoom = 10 + options.txt = True + options.filename = str( assets_dir / "main.mapcss" ) + options.outfile = str( assets_dir / "style_output" ) + options.priorities_path = str( assets_dir / "include" ) + + try: + # Save state + libkomwm.MULTIPROCESSING = False + prio_ranges_orig = deepcopy(libkomwm.prio_ranges) + libkomwm.visibilities = {} + + # Run style generation + komap_mapswithme(options) + + # Restore state + libkomwm.prio_ranges = prio_ranges_orig + libkomwm.MULTIPROCESSING = True + libkomwm.visibilities = {} + + # Check that types.txt contains 1173 lines + with open(assets_dir / "types.txt", "rt") as typesFile: + lines = [l.strip() for l in typesFile] + self.assertEqual(len(lines), 1173, "Generated types.txt file should contain 1173 lines") + self.assertEqual(len([l for l in lines if l!="mapswithme"]), 148, "Actual types count should be 148 as in mapcss-mapping.csv") + + # Check that style_output.bin has 20 styles + with open(assets_dir / "style_output.bin", "rb") as protobuf_file: + protobuf_data = protobuf_file.read() + drules = libkomwm.ContainerProto() + drules.ParseFromString(protobuf_data) + + self.assertEqual(len(drules.cont), 20, "Generated style_output.bin should contain 20 styles") + + finally: + # Clean up generated files + files2delete = ["classificator.txt", "colors.txt", "patterns.txt", "style_output.bin", + "style_output.txt", "types.txt", "visibility.txt"] + for filename in files2delete: + (assets_dir / filename).unlink(missing_ok=True) + + def test_generate_drules_validation_errors(self): + assets_dir = Path(__file__).parent / 'assets' / 'case-3-styles-validation' + # TODO: needs refactoring of libkomwm.validation_errors_count to have a list + # of validation errors. + self.assertTrue(True) diff --git a/tests/testMapCSS.py b/tests/testMapCSS.py new file mode 100644 index 0000000..c5ffa6d --- /dev/null +++ b/tests/testMapCSS.py @@ -0,0 +1,364 @@ +import unittest +import sys +from pathlib import Path + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +from mapcss import parseDeclaration, MapCSS + + +class MapCSSTest(unittest.TestCase): + def test_declarations(self): + decl = parseDeclaration(""" linejoin: round; """) + self.assertEqual(len(decl), 1) + self.assertEqual(decl[0], {"linejoin": "round"}) + + decl = parseDeclaration("""\tlinejoin :\nround ; """) + self.assertEqual(len(decl), 1) + self.assertEqual(decl[0], {"linejoin": "round"}) + + decl = parseDeclaration(""" icon-image: parking_private-s.svg; text: "name"; """) + self.assertEqual(len(decl), 1) + self.assertEqual(decl[0], { + "icon-image": "parking_private-s.svg", + "text": "name" + }) + + decl = parseDeclaration(""" + pattern-offset: 90\t; + pattern-image:\tarrow-m.svg ; + pattern-spacing: @trunk0 ;""") + self.assertEqual(len(decl), 1) + self.assertEqual(decl[0], { + "pattern-offset": "90", + "pattern-image": "arrow-m.svg", + "pattern-spacing": "@trunk0", + }) + + def test_parse_variables(self): + parser = MapCSS() + parser.parse(""" +@city_label: #999999; +@country_label: #444444; +@wave_length: 25; +""") + self.assertEqual(parser.variables, { + "city_label": "#999999", + "country_label": "#444444", + "wave_length": "25" + }) + + def test_parse_colors(self): + parser = MapCSS() + parser.parse(""" +@city_label : #999999; +@country_label: #444444 ; + @wave_length: 25; +""") + self.assertEqual(parser.variables, { + "city_label": "#999999", + "country_label": "#444444", + "wave_length": "25" + }) + + def test_parse_import(self): + parser = MapCSS() + mapcssFile = Path(__file__).parent / 'assets' / 'case-1-import' / 'main.mapcss' + parser.parse(filename=str(mapcssFile)) + + colors = parser.get_colors() + self.assertEqual(colors, { + "GuiText-color": (1.0, 1.0, 1.0), + "GuiText-opacity": 0.7, + "Route-color": (0.0, 0.0, 1.0), + "Route-opacity": 0.5, + }) + + def test_parse_basic_chooser(self): + parser = MapCSS() + static_tags = {"tourism": True, "office": True, + "craft": True, "amenity": True} + parser.parse(""" +node|z17-[tourism], +area|z17-[tourism], +node|z18-[office], +area|z18-[office], +node|z18-[craft], +area|z18-[craft], +node|z19-[amenity], +area|z19-[amenity], +{text: name; text-color: #000030; text-offset: 1;} +""", static_tags=static_tags) + + self.assertEqual(len(parser.choosers), 1) + self.assertEqual(len(parser.choosers[0].ruleChains), 8) + + def test_parse_basic_chooser_2(self): + parser = MapCSS() + static_tags = {"highway": True} + parser.parse(""" +@trunk0: #FF7326; + +line|z6[highway=trunk], +line|z6[highway=motorway], +{color: @trunk0; opacity: 0.3;} +line|z7-9[highway=trunk], +line|z7-9[highway=motorway], +{color: @trunk0; opacity: 0.7;} +""", static_tags=static_tags) + + self.assertEqual(len(parser.choosers), 2) + self.assertEqual(len(parser.choosers[0].ruleChains), 2) + self.assertEqual(parser.choosers[0].ruleChains[0].subject, 'line') + self.assertEqual(parser.choosers[0].selzooms, [6, 6]) + self.assertEqual(parser.choosers[1].selzooms, [7, 9]) + + rule, object_id = parser.choosers[0].testChains({"highway": "trunk"}) + self.assertEqual(object_id, "::default") + + def test_parse_basic_chooser_3(self): + parser = MapCSS() + static_tags = {"addr:housenumber": True, "addr:street": False} + parser.parse(""" +/* Some Comment Here */ + +/* + This sample is borrowed from Organic Maps Basemap_label.mapcss file + */ +node|z18-[addr:housenumber][addr:street]::int_name +{text: int_name; text-color: #65655E; text-position: center;} +""", static_tags=static_tags) + + building_tags = {"building": "yes", "addr:housenumber": "12", "addr:street": "Baker street"} + + # Check that mapcss parsed correctly + self.assertEqual(len(parser.choosers), 1) + styleChooser = parser.choosers[0] + self.assertEqual(len(styleChooser.ruleChains), 1) + self.assertEqual(styleChooser.selzooms, [18, 19]) + rule, object_id = styleChooser.testChains(building_tags) + self.assertEqual(object_id, "::int_name") + + rule = styleChooser.ruleChains[0] + self.assertEqual(rule.subject, 'node') + self.assertEqual(rule.extract_tags(), {'addr:housenumber', 'addr:street'}) + + def test_parse_basic_chooser_class(self): + parser = MapCSS() + parser.parse(""" +way|z-13::* +{ + linejoin: round; +} +""") + + # Check that mapcss parsed correctly + self.assertEqual(len(parser.choosers), 1) + styleChooser = parser.choosers[0] + self.assertEqual(len(styleChooser.ruleChains), 1) + self.assertEqual(styleChooser.selzooms, [0, 13]) + rule, object_id = styleChooser.testChains({}) + self.assertEqual(object_id, "::*") + + rule = styleChooser.ruleChains[0] + self.assertEqual(rule.subject, 'way') + self.assertEqual(rule.extract_tags(), {'*'}) + + def test_parse_basic_chooser_class_2(self): + parser = MapCSS() + parser.parse(""" +way|z10-::* +{ + linejoin: round; +} +""") + + # Check that mapcss parsed correctly + self.assertEqual(len(parser.choosers), 1) + styleChooser = parser.choosers[0] + self.assertEqual(len(styleChooser.ruleChains), 1) + self.assertEqual(styleChooser.selzooms, [10, 19]) + rule, object_id = styleChooser.testChains({}) + self.assertEqual(object_id, "::*") + + rule = styleChooser.ruleChains[0] + self.assertEqual(rule.subject, 'way') + self.assertEqual(rule.extract_tags(), {'*'}) + + def test_parse_basic_chooser_colors(self): + parser = MapCSS() + parser.parse(""" +way|z-6::* +{ + linejoin: round; +} + +colors { + GuiText-color: #FFFFFF; + GuiText-opacity: 0.7; + MyPositionAccuracy-color: #FFFFFF; + MyPositionAccuracy-opacity: 0.06; + Selection-color: #FFFFFF; + Selection-opacity: 0.64; + Route-color: #0000FF; + RouteOutline-color: #00FFFF; +} +""") + + # Check that colors from mapcss parsed correctly + colors = parser.get_colors() + self.assertEqual(colors, { + "GuiText-color": (1.0, 1.0, 1.0), + "GuiText-opacity": 0.7, + "MyPositionAccuracy-color": (1.0, 1.0, 1.0), + "MyPositionAccuracy-opacity": 0.06, + "Selection-color": (1.0, 1.0, 1.0), + "Selection-opacity": 0.64, + "Route-color": (0.0, 0.0, 1.0), + "RouteOutline-color": (0.0, 1.0, 1.0) + }) + + def test_parser_choosers_tree(self): + parser = MapCSS() + static_tags = {"tourism": True, "office": True, + "craft": True, "amenity": True} + + parser.parse(""" +node|z17-[office=lawyer], +area|z17-[office=lawyer], +{text: name;text-color: #444444;text-offset: 1;font-size: 10;} + +node|z17-[tourism], +area|z17-[tourism], +node|z18-[office], +area|z18-[office], +node|z18-[craft], +area|z18-[craft], +node|z19-[amenity], +area|z19-[amenity], +{text: name; text-color: #000030; text-offset: 1;} + +node|z18-[office], +area|z18-[office], +node|z18-[craft], +area|z18-[craft], +{font-size: 11;} + +node|z17-[office=lawyer], +area|z17-[office=lawyer] +{icon-image: lawyer-m.svg;} +""", static_tags=static_tags) + + for obj_type in ["line", "area", "node"]: + parser.build_choosers_tree("tourism", obj_type, "tourism") + parser.build_choosers_tree("office", obj_type, "office") + parser.build_choosers_tree("craft", obj_type, "craft") + parser.build_choosers_tree("amenity", obj_type, "amenity") + + parser.finalize_choosers_tree() + + # Pick style for zoom = 17 + styles18 = parser.get_style("office", "node", {"office": "lawyer"}, + zoom=18, xscale=1, zscale=1, filter_by_runtime_conditions=False) + + self.assertEqual(len(styles18), 1), + self.assertEqual(styles18[0], {'object-id': '::default', + 'font-size': '11', + 'text': 'name', + 'text-color': (0, 0, 16*3/255), + 'text-offset': 1.0, + 'icon-image': 'lawyer-m.svg'}) + + # Pick style for zoom = 17 + styles17 = parser.get_style("office", "node", {"office": "lawyer"}, + zoom=17, xscale=1, zscale=1, filter_by_runtime_conditions=False) + + self.assertEqual(len(styles17), 1), + self.assertEqual(styles17[0], {'object-id': '::default', + 'font-size': '10', + 'text': 'name', + 'text-color': (68/255, 68/255, 68/255), + 'text-offset': 1.0, + 'icon-image': 'lawyer-m.svg'}) + + # Pick style for zoom = 15 + styles15 = parser.get_style("office", "node", {"office": "lawyer"}, + zoom=15, xscale=1, zscale=1, filter_by_runtime_conditions=False) + + self.assertEqual(styles15, []), + + def test_parser_choosers_tree_with_classes(self): + parser = MapCSS() + static_tags = {"highway": True} + + parser.parse(""" +line|z10-[highway=motorway]::shield, +line|z10-[highway=trunk]::shield, +line|z10-[highway=motorway_link]::shield, +line|z10-[highway=trunk_link]::shield, +line|z10-[highway=primary]::shield, +line|z11-[highway=primary_link]::shield, +line|z12-[highway=secondary]::shield, +line|z13-[highway=tertiary]::shield, +line|z15-[highway=residential]::shield, +{ + shield-font-size: 9; + shield-text-color: #000000; + shield-text-halo-radius: 0; + shield-color: #FFFFFF; + shield-outline-radius: 1; +} + +line|z12-[highway=residential], +line|z12-[highway=tertiary], +line|z18-[highway=tertiary_link] +{ + text: name; + text-color: #333333; + text-halo-opacity: 0.8; + text-halo-radius: 1; +} + +line|z12-13[highway=residential], +line|z12-13[highway=tertiary] +{ + font-size: 12; + text-color: #444444; +} +""", static_tags=static_tags) + + parser.build_choosers_tree("highway", "line", "highway") + parser.finalize_choosers_tree() + + # Pick style for zoom = 10 + styles10 = parser.get_style("highway", "line", {"highway": "primary"}, + zoom=10, xscale=1, zscale=1, filter_by_runtime_conditions=False) + + self.assertEqual(len(styles10), 1), + self.assertEqual(styles10[0], {'object-id': '::shield', + 'shield-font-size': '9', + 'shield-text-color': (0.0, 0.0, 0.0), + 'shield-text-halo-radius': 0.0, + 'shield-color': (1.0, 1.0, 1.0), + 'shield-outline-radius': 1.0}) + + # Pick style for zoom = 15. Expecting two `object-id` values: '::shield' and '::default' + styles15 = parser.get_style("highway", "line", {"highway": "tertiary"}, + zoom=15, xscale=1, zscale=1, filter_by_runtime_conditions=False) + + self.assertEqual(len(styles15), 2), + self.assertEqual(styles15[0], {'object-id': '::shield', + 'shield-font-size': '9', + 'shield-text-color': (0.0, 0.0, 0.0), + 'shield-text-halo-radius': 0.0, + 'shield-color': (1.0, 1.0, 1.0), + 'shield-outline-radius': 1.0}) + self.assertEqual(styles15[1], {'object-id': '::default', + 'text': 'name', + 'text-color': (51/255, 51/255, 51/255), + 'text-halo-opacity': 0.8, + 'text-halo-radius': 1.0}) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testRule.py b/tests/testRule.py new file mode 100644 index 0000000..f3eec89 --- /dev/null +++ b/tests/testRule.py @@ -0,0 +1,114 @@ +import unittest +import sys +from pathlib import Path + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +from mapcss.Rule import Rule +from mapcss.Condition import Condition +from mapcss import parseCondition + +class RuleTest(unittest.TestCase): + def test_rule_subject(self): + self.assertEqual(Rule().subject, "") + self.assertEqual(Rule("*").subject, "") + self.assertEqual(Rule("way").subject, "way") + self.assertEqual(Rule("area").subject, "area") + self.assertEqual(Rule("node").subject, "node") + self.assertEqual(Rule("planet").subject, "planet") + + def test_rule_type_matches(self): + self.assertCountEqual(Rule().type_matches, ('area', 'line', 'way', 'node')) + self.assertCountEqual(Rule("*").type_matches, ('area', 'line', 'way', 'node')) + self.assertCountEqual(Rule("way").type_matches, ('area', 'line', 'way')) + self.assertCountEqual(Rule("area").type_matches, ('area', 'way')) + self.assertCountEqual(Rule("node").type_matches, ('node', )) + self.assertCountEqual(Rule("planet").type_matches, set()) + + def test_rule_with_conditions(self): + rule = Rule() + rule.conditions = [ + parseCondition("aeroway=aerodrome"), + parseCondition("aerodrome=international") + ] + + tt = rule.test({ + "aeroway": "aerodrome", + "aerodrome": "international", + "name": "JFK" + }) + self.assertTrue(tt) + self.assertEqual(tt, "::default") + + self.assertCountEqual(rule.extract_tags(), ["aeroway", "aerodrome"]) + + # Negative test cases + self.assertFalse(rule.test({ + "aeroway": "aerodrome", + "name": "JFK" + })) + + def test_rule_with_class(self): + rule = Rule() + rule.conditions = [ + parseCondition("highway=unclassified"), + parseCondition("bridge?"), + Condition("eq", ("::class", "::bridgeblack")) + ] + + tt = rule.test({ + "highway": "unclassified", + "bridge": "yes", + "layer": "1" + }) + self.assertTrue(tt) + self.assertEqual(tt, "::bridgeblack") + + self.assertCountEqual(rule.extract_tags(), ["highway", "bridge"]) + + # Negative test cases + self.assertFalse(rule.test({ + "highway": "unclassified", + "bridge": "no", + "layer": "1" + })) + self.assertFalse(rule.test({ + "highway": "unclassified", + "tunnel": "yes", + "layer": "-1" + })) + + def test_tags_from_rule_with_class(self): + # Class condition doesn't add new tags + rule = Rule() + rule.conditions = [ + parseCondition("highway=unclassified"), + parseCondition("bridge?"), + Condition("eq", ("::class", "::bridgeblack")), + ] + + self.assertCountEqual(rule.extract_tags(), ["highway", "bridge"]) + + # Class condition doesn't add new tags + rule = Rule() + rule.conditions = [ + parseCondition("highway=unclassified"), + Condition("eq", ("::class", "::*")), + parseCondition("bridge?"), + ] + + self.assertCountEqual(rule.extract_tags(), ["highway", "bridge"]) + + # BUT having class as a first item overrides all the others + rule = Rule() + rule.conditions = [ + Condition("eq", ("::class", "::int_name")), + parseCondition("highway=unclassified"), + parseCondition("bridge?"), + ] + + self.assertCountEqual(rule.extract_tags(), ["*"]) + +if __name__ == '__main__': + unittest.main() diff --git a/tests/testStyleChooser.py b/tests/testStyleChooser.py new file mode 100644 index 0000000..a359e58 --- /dev/null +++ b/tests/testStyleChooser.py @@ -0,0 +1,297 @@ +import unittest +import sys +from pathlib import Path + +from mapcss.Rule import Rule + +# Add `src` directory to the import paths +sys.path.insert(0, str(Path(__file__).parent.parent / 'src')) + +from mapcss import parseCondition, Condition +from mapcss.Eval import Eval +from mapcss.StyleChooser import StyleChooser, make_nice_style + + +class StyleChooserTest(unittest.TestCase): + def test_rules_chain(self): + sc = StyleChooser((0, 16)) + + sc.newObject() + sc.addCondition(parseCondition("highway=footway")) + sc.addCondition(parseCondition("footway=sidewalk")) + + sc.newObject() + sc.addCondition(parseCondition("highway=footway")) + sc.addCondition(parseCondition("footway=crossing")) + sc.addCondition(Condition("eq", ("::class", "::*"))) + + self.assertTrue( sc.testChains({ "highway": "footway", "footway": "sidewalk" }) ) + self.assertTrue( sc.testChains({ "highway": "footway", "footway": "crossing" }) ) + self.assertFalse( sc.testChains({ "highway": "footway"}) ) + self.assertFalse( sc.testChains({ "highway": "residential", "footway": "crossing" }) ) + + rule1, tt = sc.testChains({ "highway": "footway", "footway": "sidewalk" }) + self.assertEqual(tt, "::default") + + rule2, tt = sc.testChains({ "highway": "footway", "footway": "crossing" }) + self.assertEqual(tt, "::*") + + self.assertNotEqual(rule1, rule2) + + def test_zoom(self): + sc = StyleChooser((0, 16)) + + sc.newObject() + sc.addZoom( (10, 19) ) + sc.addCondition(parseCondition("railway=station")) + sc.addCondition(parseCondition("transport=subway")) + sc.addCondition(parseCondition("city=yerevan")) + + sc.newObject() + sc.addZoom( (4, 15) ) + sc.addCondition(parseCondition("railway=station")) + sc.addCondition(parseCondition("transport=subway")) + sc.addCondition(parseCondition("city=yokohama")) + + rule1, tt = sc.testChains({ "railway": "station", "transport": "subway", "city": "yerevan" }) + self.assertEqual(rule1.minZoom, 10) + self.assertEqual(rule1.maxZoom, 19) + + rule2, tt = sc.testChains({ "railway": "station", "transport": "subway", "city": "yokohama" }) + self.assertEqual(rule2.minZoom, 4) + self.assertEqual(rule2.maxZoom, 15) + + def test_extract_tags(self): + sc = StyleChooser((0, 16)) + + sc.newObject() + sc.addCondition(parseCondition("aerialway=rope_tow")) + + sc.newObject() + sc.addCondition(parseCondition("piste:type=downhill")) + + self.assertSetEqual(sc.extract_tags(), {"aerialway", "piste:type"}) + + sc = StyleChooser((0, 16)) + + sc.newObject() + sc.addCondition(parseCondition("aeroway=terminal")) + sc.addCondition(parseCondition("building")) + + sc.newObject() + sc.addCondition(parseCondition("waterway=dam")) + sc.addCondition(parseCondition("building:part")) + + self.assertSetEqual(sc.extract_tags(), {"waterway", "building:part", "building", "aeroway"}) + + def test_make_nice_style(self): + style = make_nice_style({ + "outline-color": "none", + "bg-color": "red", + "dash-color": "#ffff00", + "front-color": "rgb(0, 255, 255)", + "line-width": Eval("""eval(min(tag("line_width"), 10))"""), + "outline-width": "2.5", + "arrow-opacity": "0.5", + "offset-2": "20", + "border-radius": "4", + "line-extrude": "16", + "dashes": "3,3,1.5,3", + "wrong-dashes": "yes, yes, yes, no", + "make-nice": True, + "additional-len": 44.5 + }) + + expectedStyle = { + "outline-color": "none", + "bg-color": (1.0, 0.0, 0.0), + "dash-color": (1.0, 1.0, 0.0), + "front-color": (0.0, 1.0, 1.0), + "line-width": Eval("""eval(min(tag("line_width"), 10))"""), + "outline-width": 2.5, + "arrow-opacity": 0.5, + "offset-2": 20.0, + "border-radius": 4.0, + "line-extrude": 16.0, + "dashes": [3.0, 3.0, 1.5, 3.0], + "wrong-dashes": [], + "make-nice": True, + "additional-len": 44.5 + } + + self.assertEqual(style, expectedStyle) + + def test_add_styles(self): + sc = StyleChooser((15, 19)) + sc.newObject() + sc.addStyles([{ + "width": "1.3", + "opacity": "0.6", + "bg-color": "blue" + }]) + sc.addStyles([{ + "color": "#FFFFFF", + "casing-width": "+10" + }]) + + self.assertEqual(len(sc.styles), 2) + self.assertEqual(sc.styles[0], { + "width": 1.3, + "opacity": 0.6, + "bg-color": (0.0, 0.0, 1.0) + }) + self.assertEqual(sc.styles[1], { + "color": (1.0, 1.0, 1.0), + "casing-width": 5.0 + }) + + def test_update_styles(self): + styles = [{"primary_color": (1.0, 1.0, 1.0)}] + + sc = StyleChooser((15, 19)) + sc.newObject() + sc.addStyles([{ + "width": "1.3", + "opacity": "0.6", + "bg-color": """eval( prop("primary_color") )""", # Check that property from `styles` is applied + "text-offset": """eval( cond( boolean(tag("oneway")), 10, 5) )""" # Check that tags are applied + }]) + + object_tags = {"highway": "service", + "oneway": "yes"} + new_styles = sc.updateStyles(styles, object_tags, 1.0, 1.0, False) + expected_new_styles = { + "width": 1.3, + "opacity": 0.6, + "bg-color": (1.0, 1.0, 1.0), + "text-offset": 10.0, + "object-id": "::default" + } + + self.assertEqual(len(new_styles), 2) + self.assertEqual(new_styles[-1], expected_new_styles) + + def test_update_styles_2(self): + styles = [] + + sc = StyleChooser((15, 19)) + + sc.newObject() + sc.addCondition(Condition("eq", ("::class", "::int_name") )) # Class should be added to the style + sc.addCondition(parseCondition("oneway?")) + + sc.addStyles([{ + "width": "1.3", + "bg-color": "black" + }]) + + object_tags = {"highway": "service", "oneway": "yes"} + new_styles = sc.updateStyles(styles, object_tags, 1.0, 1.0, False) + expected_new_styles = { + "width": 1.3, + "bg-color": (0.0, 0.0, 0.0), + "object-id": "::int_name" # Check that class from sc.ruleChains is added to the style + } + + self.assertEqual(len(new_styles), 1) + self.assertEqual(new_styles[-1], expected_new_styles) + + + def test_update_styles_by_class(self): + # Predefined styles + styles = [{ + "some-width": 2.5, + "object-id": "::flats" + }, + { + "some-width": 3.5, + "object-id": "::bridgeblack" + }, + { + "some-width": 4.5, + "object-id": "::default" + }] + + sc = StyleChooser((15, 19)) + + sc.newObject() + sc.addCondition(Condition("eq", ("::class", "::flats") )) # `sc` styles should apply only to `::flats` class + sc.addCondition(parseCondition("oneway?")) + + sc.newObject() + sc.addCondition(Condition("eq", ("::class", "::bridgeblack") )) # This class is ignored by StyleChooser + sc.addCondition(parseCondition("oneway?")) + + sc.addStyles([{ + "some-width": "1.5", + "other-offset": "4" + }]) + + object_tags = {"highway": "service", "oneway": "yes"} + + # Apply new style to predefined styles with filter by class + new_styles = sc.updateStyles(styles, object_tags, 1.0, 1.0, False) + + expected_new_styles = [{ # The first style changes + "some-width": 1.5, + "other-offset": 4.0, + "object-id": "::flats" + }, + { # Style not changed (class is not `::flats`) + "some-width": 3.5, + "object-id": "::bridgeblack" + }, + { # Style not changed (class is not `::flats`) + "some-width": 4.5, + "object-id": "::default" + }] + + self.assertEqual(len(new_styles), 3) + self.assertEqual(new_styles, expected_new_styles) + + + def test_update_styles_by_class_all(self): + # Predefined styles + styles = [{ # This is applied to StyleChooser styles + "some-width": 2.5, + "corner-radius": 2.5, + "object-id": "::*" + }, + { + "some-width": 3.5, + "object-id": "::bridgeblack" + }] + + sc = StyleChooser((15, 19)) + + sc.newObject() + sc.addCondition(parseCondition("tunnel")) + + sc.addStyles([{ + "some-width": "1.5", + "other-offset": "4" + }]) + object_tags = {"highway": "service", "tunnel": "yes"} + + # Apply new style to predefined styles with filter by class + new_styles = sc.updateStyles(styles, object_tags, 1.0, 1.0, False) + + # Check that new style with new `object-id` is added. + # This style is built from `styles[0]` and styles from `sc` + expected_new_style = { + "some-width": 1.5, + "corner-radius": 2.5, + "other-offset": 4.0, + "object-id": "::default" # New class, never listed in `styles` + } + + self.assertEqual(len(new_styles), 3) + self.assertEqual(new_styles[-1], expected_new_style) + + + def test_runtime_conditions(self): + # TODO: Create test with sc.addRuntimeCondition(Condition(condType, ('extra_tag', cond))) + pass + +if __name__ == '__main__': + unittest.main() -- 2.45.3 From 6e1746357549ca4b09ee1560e9bf25e3fcce20ea Mon Sep 17 00:00:00 2001 From: "S. Kozyr" Date: Fri, 13 Dec 2024 15:15:48 +0200 Subject: [PATCH 46/46] Fixed regex condition. Fixed unclosed files. Added TODO comments --- src/libkomwm.py | 22 +++++++++++-- src/mapcss/Condition.py | 2 +- src/mapcss/Eval.py | 11 ++++++- src/mapcss/StyleChooser.py | 5 +++ src/mapcss/__init__.py | 66 +++++++++++++++++++++++++------------- 5 files changed, 80 insertions(+), 26 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 2a85102..ac6992b 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -89,6 +89,7 @@ Priorities ranges' rendering order overview: - BG-by-size: landcover areas sorted by their size ''' +# TODO: Implement better error handling validation_errors_count = 0 def to_boolean(s): @@ -104,6 +105,9 @@ def mwm_encode_color(colors, st, prefix='', default='black'): if prefix: prefix += "-" opacity = hex(255 - int(255 * float(st.get(prefix + "opacity", 1)))) + # TODO: Refactoring idea: here color is converted from float to hex. While MapCSS class + # reads colors from *.mapcss files and converts to float. How about changing MapCSS + # to keep hex values and avoid Hex->Float->Hex operations? color = whatever_to_hex(st.get(prefix + 'color', default))[1:] result = int(opacity + color, 16) colors.add(result) @@ -118,6 +122,7 @@ def mwm_encode_image(st, prefix='icon', bgprefix='symbol'): return False # strip last ".svg" handle = st.get(prefix + "image")[:-4] + # TODO: return `handle` only once return handle, handle @@ -454,6 +459,7 @@ def get_drape_priority(cl, dr_type, object_id, auto_dr_type = None, auto_comment return 0 +# TODO: Split large function to smaller ones def komap_mapswithme(options): if options.data and os.path.isdir(options.data): ddir = options.data @@ -464,6 +470,7 @@ def komap_mapswithme(options): class_order = [] class_tree = {} + # TODO: Introduce new function to parse `colors.txt` for better testability colors_file_name = os.path.join(ddir, 'colors.txt') colors = set() if os.path.exists(colors_file_name): @@ -472,6 +479,7 @@ def komap_mapswithme(options): colors.add(int(colorLine)) colors_in_file.close() + # TODO: Introduce new function to parse `patterns.txt` for better testability patterns = [] def addPattern(dashes): if dashes and dashes not in patterns: @@ -488,9 +496,11 @@ def komap_mapswithme(options): types_file = open(os.path.join(ddir, 'types.txt'), "w") # The mapcss-mapping.csv format is described inside the file itself. + # TODO: introduce new function to parse 'mapcss-mapping.csv' for better testability cnt = 1 unique_types_check = set() - for row in csv.reader(open(os.path.join(ddir, 'mapcss-mapping.csv')), delimiter=';'): + mapping_file = open(os.path.join(ddir, 'mapcss-mapping.csv')) + for row in csv.reader(mapping_file, delimiter=';'): if len(row) <= 1 or row[0].startswith('#'): # Allow for empty lines and comment lines starting with '#'. continue @@ -537,6 +547,7 @@ def komap_mapswithme(options): print("mapswithme", file=types_file) class_tree[cl] = row[0] class_order.sort() + mapping_file.close() types_file.close() output = '' @@ -554,8 +565,10 @@ def komap_mapswithme(options): for i, t in enumerate(v.keys()): mapcss_static_tags[t] = mapcss_static_tags.get(t, True) and i == 0 + # TODO: Introduce new function to parse `mapcss-dynamic.txt` for better testability # Get all mapcss dynamic tags from mapcss-dynamic.txt - mapcss_dynamic_tags = set([line.rstrip() for line in open(os.path.join(ddir, 'mapcss-dynamic.txt'))]) + with open(os.path.join(ddir, 'mapcss-dynamic.txt')) as dynamic_file: + mapcss_dynamic_tags = set([line.rstrip() for line in dynamic_file]) # Parse style mapcss global style @@ -579,6 +592,7 @@ def komap_mapswithme(options): style.finalize_choosers_tree() + # TODO: Introduce new function to work with colors for better testability # Get colors section from style style_colors = {} raw_style_colors = style.get_colors() @@ -616,6 +630,7 @@ def komap_mapswithme(options): all_draw_elements = set() + # TODO: refactor next for-loop for readability and testability global validation_errors_count for results in imapfunc(query_style, ((cl, classificator[cl], options.minzoom, options.maxzoom) for cl in class_order)): for result in results: @@ -920,6 +935,7 @@ def komap_mapswithme(options): return -1 viskeys.sort(key=functools.cmp_to_key(cmprepl)) + # TODO: Introduce new function to dump `visibility.txt` and `classificator.txt` for better testability visibility_file = open(os.path.join(ddir, 'visibility.txt'), "w") classificator_file = open(os.path.join(ddir, 'classificator.txt'), "w") @@ -942,11 +958,13 @@ def komap_mapswithme(options): visibility_file.close() classificator_file.close() + # TODO: Introduce new function to dump `colors.txt` for better testability colors_file = open(colors_file_name, "w") for c in sorted(colors): colors_file.write("%d\n" % (c)) colors_file.close() + # TODO: Introduce new function to dump `patterns.txt` for better testability patterns_file = open(patterns_file_name, "w") for p in patterns: patterns_file.write("%s\n" % (' '.join(str(elem) for elem in p))) diff --git a/src/mapcss/Condition.py b/src/mapcss/Condition.py index a3b84cb..50034ed 100644 --- a/src/mapcss/Condition.py +++ b/src/mapcss/Condition.py @@ -24,7 +24,7 @@ class Condition: params = (params,) self.params = params # e.g. ('highway','primary') if typez == "regex": - self.regex = re.compile(self.params[0], re.I) + self.regex = re.compile(self.params[1], re.I) def extract_tag(self): if self.params[0][:2] == "::" or self.type == "regex": diff --git a/src/mapcss/Eval.py b/src/mapcss/Eval.py index 131e6bb..e37e670 100644 --- a/src/mapcss/Eval.py +++ b/src/mapcss/Eval.py @@ -15,6 +15,10 @@ # You should have received a copy of the GNU General Public License # along with kothic. If not, see . +import logging + +logger = logging.getLogger('mapcss.Eval') +logger.setLevel(logging.ERROR) class Eval(): def __init__(self, s='eval()'): @@ -57,6 +61,8 @@ class Eval(): "any": fake_compute, "min": fake_compute, "max": fake_compute, + "cond": fake_compute, + "boolean": fake_compute, }) return tags @@ -99,12 +105,15 @@ class Eval(): return "{:.4g}".format(result) return str(result) - except: + except Exception as e: + logger.warning(f"Error evaluating expression `{self.expr_text}`", e) return "" def __repr__(self): return "eval(%s)" % self.expr_text + def __eq__(self, other): + return type(self) == type(other) and self.expr_text == other.expr_text def m_boolean(expr): expr = str(expr) diff --git a/src/mapcss/StyleChooser.py b/src/mapcss/StyleChooser.py index 9d3518e..0f5eb50 100644 --- a/src/mapcss/StyleChooser.py +++ b/src/mapcss/StyleChooser.py @@ -76,6 +76,8 @@ class StyleChooser: The styles property is an array of all the style objects to be drawn if any of the ruleChains evaluate to true. """ + # TODO: use logging for debug logs + def __repr__(self): return "{(%s) : [%s] }\n" % (self.ruleChains, self.styles) @@ -122,6 +124,7 @@ class StyleChooser: return rule.runtime_conditions + # TODO: Rename to "applyStyles" def updateStyles(self, sl, tags, xscale, zscale, filter_by_runtime_conditions): # Are any of the ruleChains fulfilled? rule_and_object_id = self.testChains(tags) @@ -143,6 +146,7 @@ class StyleChooser: for a, b in r.items(): "calculating eval()'s" if type(b) == self.eval_type: + # TODO: Move next block to a separate function combined_style = {} for t in sl: combined_style.update(t) @@ -235,6 +239,7 @@ class StyleChooser: """ adds to this.styles """ + # TODO: move next for-loop to a new method. Don't call it on every style append for r in self.ruleChains: if not self.selzooms: self.selzooms = [r.minZoom, r.maxZoom] diff --git a/src/mapcss/__init__.py b/src/mapcss/__init__.py index 8af9730..cc1713a 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -25,6 +25,7 @@ from .Condition import Condition NEEDED_KEYS = set(["width", "casing-width", "casing-width-add", "fill-color", "fill-image", "icon-image", "text", "extrude", "background-image", "background-color", "pattern-image", "shield-color", "symbol-shape"]) +# TODO: Unused constant WHITESPACE = re.compile(r'\s+ ', re.S | re.X) COMMENT = re.compile(r'\/\* .*? \*\/ \s* ', re.S | re.X) @@ -40,29 +41,30 @@ VARIABLE_SET = re.compile(r'@([a-z][\w\d]*) \s* : \s* (.+?) \s* ; \s* ', re.S | 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_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* $', re.I | re.S | re.X) +# TODO: move to Condition.py +CONDITION_TRUE = re.compile(r'\s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X) CONDITION_invTRUE = re.compile(r'\s* [!] \s* ([:\w]+) \s* [?] \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) +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) +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) oNONE = 0 oZOOM = 2 @@ -73,6 +75,7 @@ oDECLARATION = 6 oSUBPART = 7 oVARIABLE_SET = 8 +# TODO: Following block of variables is never used DASH = re.compile(r'\-/g') COLOR = re.compile(r'color$/') BOLD = re.compile(r'^bold$/i') @@ -81,6 +84,7 @@ UNDERLINE = re.compile(r'^underline$/i') CAPS = re.compile(r'^uppercase$/i') CENTER = re.compile(r'^center$/i') +# TODO: Remove unused HEX variable HEX = re.compile(r'^#([0-9a-f]+)$/i') VARIABLE = re.compile(r'@([a-z][\w\d]*)') @@ -98,6 +102,7 @@ class MapCSS(): self.choosers_by_type = {} self.choosers_by_type_zoom_tag = {} self.variables = {} + self.unused_variables = set() self.style_loaded = False def parseZoom(self, s): @@ -110,6 +115,7 @@ class MapCSS(): elif ZOOM_SINGLE.match(s): return float(ZOOM_SINGLE.match(s).groups()[0]), float(ZOOM_SINGLE.match(s).groups()[0]) else: + # TODO: Should we raise an exception here? logging.error("unparsed zoom: %s" % s) def build_choosers_tree(self, clname, type, cltag): @@ -163,6 +169,8 @@ class MapCSS(): runtime_rules.append(runtime_conditions) return runtime_rules + # TODO: Renamed to `get_styles` because it returns a list of styles for each class `::XXX` + # Refactoring idea: Maybe return dict with `object-id` as a key def get_style(self, clname, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions): style = [] if type in self.choosers_by_type_zoom_tag: @@ -207,6 +215,8 @@ class MapCSS(): def get_variable(self, m): name = m.group()[1:] + if name in self.unused_variables: + self.unused_variables.remove(name) if not name in self.variables: raise Exception("Variable not found: " + str(format(name))) return self.variables[name] if name in self.variables else m.group() @@ -219,7 +229,8 @@ class MapCSS(): if filename: basepath = os.path.dirname(filename) if not css: - css = open(filename).read() + with open(filename) as css_file: + css = css_file.read() if not self.style_loaded: self.choosers = [] @@ -339,7 +350,8 @@ class MapCSS(): import_filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) try: css = IMPORT.sub("", css, 1) - import_text = open(import_filename, "r").read() + with open(import_filename, "r") as import_file: + import_text = import_file.read() stck[-1][1] = css # store remained part stck.append([import_filename, import_text, import_text]) wasBroken = True @@ -352,6 +364,7 @@ class MapCSS(): name = VARIABLE_SET.match(css).groups()[0] log.debug("variable set found: %s" % name) self.variables[name] = VARIABLE_SET.match(css).groups()[1] + self.unused_variables.add( name ) css = VARIABLE_SET.sub("", css, 1) previous = oVARIABLE_SET @@ -359,7 +372,7 @@ class MapCSS(): elif UNKNOWN.match(css): raise Exception("Unknown construction: " + UNKNOWN.match(css).group()) - # Must be unreacheable + # Must be unreachable else: raise Exception("Unexpected construction: " + css) @@ -377,10 +390,13 @@ class MapCSS(): css_orig = stck[-1][2] # original css = stck[-1][1] # remained line = css_orig[:-len(css)].count("\n") + 1 + # TODO: Handle filename is None msg = str(e) + "\nFile: " + filename + "\nLine: " + str(line) + # TODO: Print stack trace of original exception `e` raise Exception(msg) try: + # TODO: Drop support of z-index because `clamp` is always False and z-index properties unused in Organic Maps) if clamp: "clamp z-indexes, so they're tightly following integers" zindex = set() @@ -398,8 +414,10 @@ class MapCSS(): else: stylez['z-index'] = res except TypeError: + # TODO: Better error handling here pass + # Group MapCSS styles by object type: 'area', 'line', 'way', 'node' for chooser in self.choosers: for t in chooser.compatible_types: if t not in self.choosers_by_type: @@ -407,7 +425,11 @@ class MapCSS(): else: self.choosers_by_type[t].append(chooser) + if self.unused_variables: + # TODO: Do not print warning here. Instead let libkomwn.komap_mapswithme(...) analyze unused_variables + print(f"Warning: Unused variables: {', '.join(self.unused_variables)}") +# TODO: move to Condition.py def parseCondition(s): log = logging.getLogger('mapcss.parser.condition') @@ -488,7 +510,7 @@ def parseDeclaration(s): logging.debug("%s == %s" % (tzz[0], tzz[1])) else: logging.debug("unknown %s" % (a)) - return [t] + return [t] # TODO: don't wrap `t` dict into a list. Return `t` instead. if __name__ == "__main__": -- 2.45.3