From a8a25235e6d1fcc275ddf217e8aec7994022b23f Mon Sep 17 00:00:00 2001 From: Alexander Borsuk Date: Fri, 11 Sep 2026 17:50:22 +0200 Subject: [PATCH] Treat a type's forbidden [!key] as an absent key For the generator, a [!key] in mapcss-mapping.csv matches features where the key is absent or "no". Kothic put key=no into the type's tags that styles are evaluated against, but MapCSS treats key=no as set: [key] matched such a type and [!key] did not. So a type defined with e.g. [natural=water][intermittent=yes][!tunnel] inherited the tunnel rules. Leave such keys out of the type's tags instead. A key that occurs only as [!key] is then not a static tag, so styles can't test it; no such key exists now. The current Organic Maps styles produce the same drawing rules. Signed-off-by: Alexander Borsuk --- src/libkomwm.py | 33 +++++++++++++++++++++------------ tests/testLibkomwm.py | 13 +++++++++++++ 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/src/libkomwm.py b/src/libkomwm.py index 1a3a11b..ab2ef4a 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -460,6 +460,26 @@ def get_drape_priority(cl, dr_type, object_id, auto_dr_type = None, auto_comment return 0 +def get_type_tags(selectors): + """Returns the tags that styles are evaluated against for a classificator type: the tags of the first + selector of its mapcss-mapping.csv row, e.g. '[highway=primary][bridge?]' -> {highway: primary, bridge: yes}. + The order matters, the first tag is the type's main one. + A forbidden '[!key]' is left out rather than set to "no", which MapCSS '[key]' would treat as set. So a key + that occurs only in such conditions is not a static tag, and styles can't test it (an "Unknown tag" error). + """ + tags = OrderedDict() + # Only the first selector: the others are alternative OSM spellings of the same type, e.g. + # "[natural=water][intermittent=yes],[natural=water][seasonal?]", while the style has to be evaluated + # against one canonical set of tags. So a style can't test a key that only a later selector carries: + # such a condition is false if another type makes that key a static tag, and an "Unknown tag" error if not. + # TODO: revisit - either take the tags of every selector into account, or validate styles against them. + for cond in selectors.split(',')[0].split('['): + key, eq, value = cond.strip(']').partition('=') + if key and not key.startswith('!'): + tags[key.rstrip('?')] = value if eq else 'yes' + return tags + + # TODO: Split large function to smaller ones def komap_mapswithme(options): if options.data and os.path.isdir(options.data): @@ -523,19 +543,8 @@ def addPattern(dashes): cl = row[0].replace("|", "-") if cl in unique_types_check and row[2] != 'x': raise Exception('Duplicate type: {0}'.format(row[0])) - pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')] - kv = OrderedDict() - for i in pairs: - if len(i) == 1: - if i[0]: - if i[0][0] == "!": - kv[i[0][1:].strip('?')] = "no" - else: - kv[i[0].strip('?')] = "yes" - else: - kv[i[0]] = i[1] if row[2] != "x": - classificator[cl] = kv + classificator[cl] = get_type_tags(row[1]) class_order.append(cl) unique_types_check.add(cl) # Mark original type to distinguish it among replacing types. diff --git a/tests/testLibkomwm.py b/tests/testLibkomwm.py index 76fadd5..4ea6df1 100644 --- a/tests/testLibkomwm.py +++ b/tests/testLibkomwm.py @@ -12,6 +12,19 @@ class LibKomwmTest(unittest.TestCase): + def test_get_type_tags(self): + def items(selectors): + # The order matters: the first tag is the type's main one. + return list(libkomwm.get_type_tags(selectors).items()) + + self.assertEqual(items('[highway=primary][bridge?]'), [('highway', 'primary'), ('bridge', 'yes')]) + # Only the first selector counts. + self.assertEqual(items('[amenity=parking][fee],[amenity=parking][parking=lane]'), + [('amenity', 'parking'), ('fee', 'yes')]) + # A forbidden key is absent, so that MapCSS [!tunnel] matches the type. + self.assertEqual(items('[natural=water][intermittent=yes][!tunnel]'), + [('natural', 'water'), ('intermittent', 'yes')]) + def test_generate_drules_mini(self): assets_dir = Path(__file__).parent / 'assets' / 'case-2-generate-drules-mini'