diff --git a/README.md b/README.md index 79382c78..7bd36132 100644 --- a/README.md +++ b/README.md @@ -22,7 +22,7 @@ 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 +files for all 8 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: @@ -33,5 +33,5 @@ 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. +outdoors light, outdoors dark, cycling light, cycling dark, vehicle light, +vehicle dark and put `*.bin` and `*.txt` files into 'drules' subfolder. diff --git a/integration-tests/full_drules_gen.py b/integration-tests/full_drules_gen.py index 5edec443..cfaaddea 100755 --- a/integration-tests/full_drules_gen.py +++ b/integration-tests/full_drules_gen.py @@ -16,14 +16,10 @@ 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'], -} +# Keep vehicle last: every style rewrites visibility.txt & classificator.txt in the data dir, +# and the apps ship the ones produced by the vehicle style. +STYLES = ('default', 'outdoors', 'cycling', 'vehicle') +VARIANTS = ('light', 'dark') def full_styles_regenerate(options): @@ -31,19 +27,21 @@ def full_styles_regenerate(options): 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 ...") + for style in STYLES: + for variant in VARIANTS: + name = f'{style}_{variant}' + log.info(f"Generating {name} style ...") - # Restore initial state - libkomwm.prio_ranges = deepcopy(prio_ranges_orig) - libkomwm.visibilities = {} + # 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 + options.filename = f'{options.data}/styles/{style}/{variant}/style.mapcss' + options.priorities_path = f'{options.data}/styles/{style}/include' + options.outfile = f'{options.outdir}/{name}' - # Run generation - libkomwm.komap_mapswithme(options) + # Run generation + libkomwm.komap_mapswithme(options) log.info("Done!") def main(): diff --git a/src/libkomwm.py b/src/libkomwm.py index 1a3a11bf..35f55f49 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -7,7 +7,7 @@ from multiprocessing import Pool, set_start_method from collections import OrderedDict import mapcss.webcolors -from drules import (BEVELJOIN, BUTTCAP, NOJOIN, ROUNDCAP, ROUNDJOIN, +from drules import (BEVELJOIN, BUTTCAP, NOJOIN, ROUNDCAP, ROUNDJOIN, SQUARECAP, ClassifElement, ColorElement, Container, DrawElement, LineRule, serialize_binary, serialize_text) @@ -434,6 +434,16 @@ def dump_priorities(prio_range, path, maxzoom): outfile.write(f'{group}{group_comment}=== {group_prio}\n') +def get_line_style(values, st, key, default, cl, zoom): + """Maps a MapCSS linecap/linejoin value onto its drules constant.""" + value = st.get(key, default) + if value not in values: + print(f'ERROR: unsupported {key} value "{value}" for z{zoom} {cl}') + global validation_errors_count + validation_errors_count += 1 + value = default + return values[value] + 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 = '' @@ -606,7 +616,7 @@ def addPattern(dashes): visibility = {} - dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP} + dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP, 'square': SQUARECAP} dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN} # Build drules tree @@ -708,7 +718,7 @@ def rule_sort_key(dict_): for st in zstyle: 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': + if has_lines and not is_area_st: dr_line = LineRule() base_width = st.get('width', 0) @@ -718,10 +728,10 @@ def rule_sort_key(dict_): # 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) - # '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 + # '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(base_width + st.get('casing-width') * 2, 2) dr_line.color = mwm_encode_color(colors, st, "casing") @@ -737,8 +747,8 @@ def rule_sort_key(dict_): 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) + dr_line.cap = get_line_style(dr_linecaps, st, 'casing-linecap', 'butt', cl, zoom) + dr_line.join = get_line_style(dr_linejoins, st, 'casing-linejoin', 'round', cl, zoom) dr_element.lines.extend([dr_line]) if has_fills and is_area_st and float(st.get('fill-opacity', 1)) > 0: @@ -753,8 +763,8 @@ def rule_sort_key(dict_): 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) + dr_line.cap = get_line_style(dr_linecaps, st, 'linecap', 'butt', cl, zoom) + dr_line.join = get_line_style(dr_linejoins, st, 'linejoin', 'round', cl, zoom) 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]) diff --git a/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss b/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss index 794dd083..568c1a2b 100644 --- a/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss +++ b/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss @@ -134,3 +134,12 @@ line|z10[highway=secondary], line|z10[highway=secondary], {width: 1.2;} +/* Automatic (::default) casings, exercised by testLibkomwm: + any casing-linecap is honoured, and casing-width-add is resolved + even when the line carries its own width. */ + +line|z4-9[highway=world_level], +{casing-width: 1; casing-color: #FF0000; casing-linecap: round;} + +line|z6-9[highway=world_towns_level], +{casing-width-add: 1; casing-color: #00FF00;} 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 index f249605d..d076fce8 100644 --- 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 @@ -18,10 +18,14 @@ highway-motorway-tunnel # line z6- (also has pathtex 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 +highway-world_level # line z4-9 (also has line(casing) z4-9) +highway-world_towns_level # line z6-9 (also has line(casing) z6-9) === 310 +# highway-world_level # line(casing) z4-9 (also has line z4-9) +# highway-world_towns_level # line(casing) z6-9 (also has line z6-9) +# === 309 + 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-) diff --git a/tests/testLibkomwm.py b/tests/testLibkomwm.py index 76fadd54..bac86243 100644 --- a/tests/testLibkomwm.py +++ b/tests/testLibkomwm.py @@ -52,6 +52,24 @@ class Options(object): self.assertEqual(len(container.cont), 20, "Generated style_output.bin should contain 20 types with drawing rules") + def lines_at(type_name, zoom): + classif = next(c for c in container.cont if c.name == type_name) + return next(e for e in classif.element if e.scale == zoom).lines + + # An automatic casing is rendered below its line (priority - 1), keeps its own + # linecap and is 2 * casing-width wider than the line. Both casing rules in + # include/Roads.mapcss use a width of 1. + casing, line = lines_at("highway-world_level", 4) + self.assertEqual((line.cap, line.priority), (drules.BUTTCAP, 310)) + self.assertEqual((casing.cap, casing.priority), (drules.ROUNDCAP, 309)) + self.assertAlmostEqual(casing.width, line.width + 2, places=5) + + # 'casing-width-add' widens the line first, so the casing is 2 * (width + add). + # It must be resolved even when the line carries its own width. + casing, line = lines_at("highway-world_towns_level", 6) + self.assertEqual(casing.priority, line.priority - 1) + self.assertAlmostEqual(casing.width, (line.width + 1) * 2, places=5) + finally: # Clean up generated files files2delete = ["classificator.txt", "colors.txt", "patterns.txt", "style_output.bin",