diff --git a/.gitignore b/.gitignore index b589e30..bf52402 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,3 @@ src/tiles/ *pycache* *swp *bak -/.idea \ No newline at end of file diff --git a/README b/README new file mode 100644 index 0000000..6e6e675 --- /dev/null +++ b/README @@ -0,0 +1,4 @@ +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 + diff --git a/README.md b/README.md deleted file mode 100644 index 79382c7..0000000 --- a/README.md +++ /dev/null @@ -1,37 +0,0 @@ -Kothic Mapcss parser/processor tailored for Organic Maps use. - -Dependencies: -* Python >= 3.8 - -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 deleted file mode 100644 index e69de29..0000000 diff --git a/integration-tests/full_drules_gen.py b/integration-tests/full_drules_gen.py deleted file mode 100755 index 986a9af..0000000 --- a/integration-tests/full_drules_gen.py +++ /dev/null @@ -1,73 +0,0 @@ -#!/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/requirements.txt b/requirements.txt deleted file mode 100644 index 577a8b3..0000000 --- a/requirements.txt +++ /dev/null @@ -1,3 +0,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 diff --git a/src/drules_struct_pb2.py b/src/drules_struct_pb2.py index a10ee5e..ac627d0 100644 --- a/src/drules_struct_pb2.py +++ b/src/drules_struct_pb2.py @@ -1,11 +1,14 @@ -# -*- coding: utf-8 -*- # Generated by the protocol buffer compiler. DO NOT EDIT! -# source: drules_struct.proto -"""Generated protocol buffer code.""" +# 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 from google.protobuf import descriptor as _descriptor -from google.protobuf import descriptor_pool as _descriptor_pool +from google.protobuf import message as _message +from google.protobuf import reflection as _reflection from google.protobuf import symbol_database as _symbol_database -from google.protobuf.internal import builder as _builder +from google.protobuf import descriptor_pb2 # @@protoc_insertion_point(imports) _sym_db = _symbol_database.Default() @@ -13,49 +16,1086 @@ _sym_db = _symbol_database.Default() -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') +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') +) -_globals = globals() -_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals) -_builder.BuildTopDescriptorsAndMessages(DESCRIPTOR, 'drules_struct_pb2', _globals) -if _descriptor._USE_C_DESCRIPTORS == False: +_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) - 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 +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')) # @@protoc_insertion_point(module_scope) diff --git a/src/libkomwm.py b/src/libkomwm.py index ac6992b..eb66880 100644 --- a/src/libkomwm.py +++ b/src/libkomwm.py @@ -2,13 +2,11 @@ from mapcss import MapCSS from optparse import OptionParser import os import csv +import sys import functools -from sys import exit -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 @@ -16,81 +14,19 @@ whatever_to_cairo = mapcss.webcolors.webcolors.whatever_to_cairo PROFILE = False MULTIPROCESSING = True -# 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 -# 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. +# 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) -# 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 most other overlays. -OVERLAYS_MAX_PRIORITY = 10000 +from drules_struct_pb2 import * -# Drules are arranged into following ranges. -PRIO_OVERLAYS = 'overlays' -PRIO_FG = 'FG' -PRIO_BG_TOP = 'BG-top' -PRIO_BG_BY_SIZE = 'BG-by-size' +WIDTH_SCALE = 1.0 -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': {}}, -} - -visibilities = {} - -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 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). -''' - -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. 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 = ''' -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 -''' - -# TODO: Implement better error handling -validation_errors_count = 0 def to_boolean(s): s = s.lower() @@ -105,9 +41,6 @@ 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) @@ -122,7 +55,6 @@ 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 @@ -140,33 +72,21 @@ def query_style(args): results = [] for zoom in range(minzoom, maxzoom + 1): - all_runtime_conditions_arr = [] + runtime_conditions_arr = [] + # Get runtime conditions which are used for class 'cl' on zoom 'zoom' if "area" not in cltags: - 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)) + runtime_conditions_arr.extend(style.get_runtime_rules(clname, "line", cltags, zoom)) + runtime_conditions_arr.extend(style.get_runtime_rules(clname, "area", cltags, zoom)) if "area" not in cltags: - all_runtime_conditions_arr.extend(style.get_runtime_rules(clname, "node", cltags, zoom)) + runtime_conditions_arr.extend(style.get_runtime_rules(clname, "node", cltags, zoom)) - runtime_conditions_arr = [] - if len(all_runtime_conditions_arr) == 0: - # If there is no runtime conditions, do not filter style by runtime conditions + # If there is no any runtime conditions, do not filter style by runtime conditions + if len(runtime_conditions_arr) == 0: 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 zstyle = {} # Get style for class 'cl' on zoom 'zoom' with corresponding runtime conditions @@ -174,292 +94,19 @@ 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, runtime_conditions, list(zstyle.values()))) + results.append((cl, zoom, has_icons_for_areas, runtime_conditions, list(zstyle.values()))) return results -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, 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 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 = [] - for line in f: - line = line.strip() - # Strip comments. - line = line.split('#', 1)[0].strip() - if not line: - continue - tokens = line.split() - if len(tokens) > 2: - print_warning('skipping malformed line') - continue - if tokens[0] == "===": - try: - priority = int(tokens[1]) - except ValueError: - print_warning('skipping invalid priority value') - else: - 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 [{priority_min};{priority_max}) range priority value') - group = [] - else: - 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') - key = (cl, object_id) - if key in prio_ranges[prio_range]['priorities']: - print_warning(f'overriding previously set priority 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') - - 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 - - # 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()) - 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 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_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): - - def add_zrange(first, last, result, maxzoom): - first = str(first) - last = str(last) - if last == str(maxzoom): - zrange = first + '-' - elif first == last: - zrange = first - else: - zrange = first + '-' + last - 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_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_comment}{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: - comment = COMMENT_AUTOFORMAT + prio_ranges[prio_range]['comment'] + COMMENT_RANGES_OVERVIEW - for s in comment.splitlines(): - 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: - 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_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: - other_drules += ', ' - other_drules += dr_zoom - if object_id: - cl += object_id - if not line_drules: - if other_drules: - 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}') - - 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) - - 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']: - 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'ERROR: priority is not set for {dr_type} {cl}{object_id}') - global validation_errors_count - validation_errors_count += 1 - 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 @@ -470,16 +117,14 @@ 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): - 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() - # TODO: Introduce new function to parse `patterns.txt` for better testability patterns = [] def addPattern(dashes): if dashes and dashes not in patterns: @@ -487,22 +132,40 @@ 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 + # + # 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. - # TODO: introduce new function to parse 'mapcss-mapping.csv' for better testability cnt = 1 unique_types_check = set() - 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 '#'. + 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 if len(row) == 3: # Short format: type name, type id, x / replacement type name @@ -547,17 +210,8 @@ def komap_mapswithme(options): print("mapswithme", file=types_file) class_tree[cl] = row[0] class_order.sort() - mapping_file.close() types_file.close() - output = '' - for prio_range in prio_ranges.keys(): - 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}.') - - 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 = {} @@ -565,34 +219,26 @@ 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 - with open(os.path.join(ddir, 'mapcss-dynamic.txt')) as dynamic_file: - mapcss_dynamic_tags = set([line.rstrip() for line in dynamic_file]) + 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 - style = MapCSS(options.minzoom, options.maxzoom) - style.parse(clamp=False, stretch=LAYER_PRIORITY_RANGE, - filename=options.filename, static_tags=mapcss_static_tags, + style = MapCSS(options.minzoom, options.maxzoom + 1) + style.parse(filename=options.filename, static_tags=mapcss_static_tags, dynamic_tags=mapcss_dynamic_tags) - # Build optimization tree - class/zoom/type -> StyleChoosers - clname_cltag_unique = set() + # Build optimization tree - class/type -> StyleChoosers 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())) - 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) + cltags = classificator[cl] + style.build_choosers_tree(clname, "line", cltags) + style.build_choosers_tree(clname, "area", cltags) + style.build_choosers_tree(clname, "node", cltags) + style.restore_choosers_order("line") + style.restore_choosers_order("area") + style.restore_choosers_order("node") - 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() @@ -605,6 +251,8 @@ def komap_mapswithme(options): visibility = {} + bgpos = 0 + dr_linecaps = {'none': BUTTCAP, 'butt': BUTTCAP, 'round': ROUNDCAP} dr_linejoins = {'none': NOJOIN, 'bevel': BEVELJOIN, 'round': ROUNDJOIN} @@ -630,14 +278,12 @@ 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: - cl, zoom, runtime_conditions, zstyle = result + cl, zoom, has_icons_for_areas, runtime_conditions, zstyle = result - # First, sort rules by ::object-id in captions (primary, secondary, none ..) - # then by other ::object-id in ascending order. + # First, sort rules by 'object-id' in captions (primary, secondary, none ..); + # Then by 'z-index' in ascending order. def rule_sort_key(dict_): first = 0 if dict_.get('text'): @@ -645,12 +291,12 @@ def komap_mapswithme(options): first = 1 if str(dict_.get('text')) == 'none': first = 2 - return (first, dict_.get('object-id')) + return (first, int(dict_.get('z-index', 0))) zstyle.sort(key = rule_sort_key) # For debug purpose. - # if str(cl) == 'highway-path' and int(zoom) == 19: + # if str(cl) == 'entrance' and int(zoom) == 19: # print(cl) # print(zstyle) @@ -663,7 +309,6 @@ 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) @@ -677,9 +322,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 and st.get('icon-image') != 'none' or 'symbol-shape' in st or 'symbol-image' in st: + if 'icon-image' in st or 'symbol-shape' in st or 'symbol-image' in st: has_icons = True - if 'fill-color' in st and st.get('fill-color') != 'none': + if 'fill-color' in st: has_fills = True has_text = None @@ -696,9 +341,6 @@ def komap_mapswithme(options): visstring[zoom] = "1" - if zoom == 0: - continue - dr_element = DrawElementProto() dr_element.scale = zoom @@ -707,36 +349,23 @@ def komap_mapswithme(options): dr_element.apply_if.append(str(rc)) 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')): + 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): # 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() - - 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) - # '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.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 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. - 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) + if '-x-me-casing-line-priority' in st: + dr_line.priority = int(st.get('-x-me-casing-line-priority')) 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([float(i)]) + dr_line.priority = min(int(st.get('z-index', 0) + 999), 20000) + dashes = st.get('casing-dashes', st.get('dashes', [])) + dr_line.dashdot.dd.extend(dashes) 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) @@ -744,12 +373,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) + dr_element.area.border.width = st.get('casing-width', 0) * WIDTH_SCALE # 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) + (st.get('casing-width') * 2) + # dr_line.width = (st.get('width', 0) * WIDTH_SCALE) + (st.get('casing-width') * WIDTH_SCALE * 2) # dr_line.color = mwm_encode_color(colors, st, "casing") # dr_line.priority = -15000 # dashes = st.get('casing-dashes', st.get('dashes', [])) @@ -761,15 +390,17 @@ def komap_mapswithme(options): if has_lines: if st.get('width'): dr_line = LineRuleProto() - dr_line.width = st.get('width', 0) + 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([float(i)]) + dr_line.dashdot.dd.extend([max(float(i), 1) * 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) - dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) - store_visibility(cl, 'line', st.get('object-id'), zoom) + if '-x-me-line-priority' in st: + dr_line.priority = int(st.get('-x-me-line-priority')) + else: + dr_line.priority = min((int(st.get('z-index', 0)) + 1000), 20000) dr_element.lines.extend([dr_line]) if st.get('pattern-image'): dr_line = LineRuleProto() @@ -779,38 +410,46 @@ 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) - dr_line.priority = get_drape_priority(cl, 'line', st.get('object-id')) - store_visibility(cl, 'line', st.get('object-id'), zoom) + if '-x-me-line-priority' in st: + dr_line.priority = int(st.get('-x-me-line-priority')) + else: + dr_line.priority = int(st.get('z-index', 0)) + 1000 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") + 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)))) + 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': + 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] - dr_element.symbol.priority = get_drape_priority(cl, 'icon', st.get('object-id')) - store_visibility(cl, 'icon', st.get('object-id'), zoom) + 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)))) 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') - dr_element.circle.priority = get_drape_priority(cl, 'circle', st.get('object-id')) - store_visibility(cl, 'circle', st.get('object-id'), zoom) + 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)))) has_icons = False if has_text and st.get('text') and st.get('text') != 'none': @@ -818,28 +457,22 @@ def komap_mapswithme(options): has_text = has_text[:2] dr_text = dr_element.caption - text_priority_key = 'caption' + base_z = 15000 if st.get('text-position', 'center') == 'line': dr_text = dr_element.path_text - text_priority_key = 'pathtext' + base_z = 16000 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))) - elif 'text-offset-x' in sp: + if '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'): + if 'text' in sp and sp.get('text') != 'name': dr_cur_subtext.text = sp.get('text') if 'text-optional' in sp: is_valid, value = to_boolean(sp.get('text-optional', '')) @@ -847,37 +480,40 @@ def komap_mapswithme(options): dr_cur_subtext.is_optional = value else: dr_cur_subtext.is_optional = True - 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 - auto_comment = None - if text_priority_key == 'caption' and dr_element.symbol.priority: - # A caption with an icon. - # Mandatory captions use icon's priority. - auto_prio_mod = 0 - auto_comment = 'mandatory' - if dr_text.primary.is_optional: - # 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) + # 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: - # 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, auto_comment) + 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) # Process captions block once. has_text = None if has_fills: - if 'fill-color' in st and st.get('fill-color') != 'none' and float(st.get('fill-opacity', 1)) > 0: + 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) + priority = 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 + 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 has_fills = False str_dr_element = dr_cont.name + "/" + str(dr_element) @@ -891,20 +527,6 @@ def komap_mapswithme(options): visibility["world|" + class_tree[cl] + "|"] = "".join(visstring) - 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.') - - output = '' - for prio_range in prio_ranges.keys(): - 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}.') - # Write drules_proto.bin and drules_proto.txt files drules_bin = open(os.path.join(options.outfile + '.bin'), "wb") @@ -935,9 +557,8 @@ 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") + 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: @@ -958,14 +579,12 @@ 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") + colors_file = open(colors_file_name, "w", encoding="utf-8") 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") + 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() @@ -977,14 +596,12 @@ 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=20, type="int", + parser.add_option("-t", "--maxzoom", dest="maxzoom", default=19, type="int", help="maximal available zoom level", metavar="ZOOM") parser.add_option("-o", "--output-file", dest="outfile", default="-", 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-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") @@ -996,10 +613,6 @@ 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/Condition.py b/src/mapcss/Condition.py index 50034ed..1612480 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[1], re.I) + self.regex = re.compile(self.params[0], re.I) def extract_tag(self): if self.params[0][:2] == "::" or self.type == "regex": @@ -33,51 +33,49 @@ class Condition: def test(self, tags): """ - Test tags against this condition + Test a hash 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] - 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])) - + 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 False def __repr__(self): 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/Eval.py b/src/mapcss/Eval.py index e37e670..131e6bb 100644 --- a/src/mapcss/Eval.py +++ b/src/mapcss/Eval.py @@ -15,10 +15,6 @@ # 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()'): @@ -61,8 +57,6 @@ class Eval(): "any": fake_compute, "min": fake_compute, "max": fake_compute, - "cond": fake_compute, - "boolean": fake_compute, }) return tags @@ -105,15 +99,12 @@ class Eval(): return "{:.4g}".format(result) return str(result) - except Exception as e: - logger.warning(f"Error evaluating expression `{self.expr_text}`", e) + except: 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/Rule.py b/src/mapcss/Rule.py index db516c1..a652f30 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 = None + self.runtime_conditions = [] self.conditions = [] # self.isAnd = True self.minZoom = 0 @@ -33,12 +33,17 @@ class Rule(): if s == "*": s = "" self.subject = s # "", "way", "node" or "relation" - self.type_matches = type_matches[s] if s in type_matches else set() def __repr__(self): return "%s|z%s-%s %s %s" % (self.subject, self.minZoom, self.maxZoom, self.conditions, self.runtime_conditions) - def test(self, tags): + def test(self, obj, tags, zoom): + if (zoom < self.minZoom) or (zoom > self.maxZoom): + return False + + if (self.subject != '') and not _test_feature_compatibility(obj, self.subject, tags): + return False + subpart = "::default" for condition in self.conditions: res = condition.test(tags) @@ -54,10 +59,33 @@ class Rule(): def extract_tags(self): a = set() for condition in self.conditions: - tag = condition.extract_tag() - if tag != '*': - a.add(tag) - elif len(a) == 0: - return set(["*"]) - + a.add(condition.extract_tag()) + if "*" in a: + a = set(["*"]) + break 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 0f5eb50..9291e6b 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 and b.strip() != 'none': + elif "color" in a: "parsing color value to 3-tuple" # print "res:", b if b and (type(b) != tuple): @@ -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", "opacity", "offset", "radius", "extrude")): + elif any(x in a for x in ("width", "z-index", "opacity", "offset", "radius", "extrude")): "these things are float's or not in table at all" try: ra[a] = float(b) @@ -76,8 +76,6 @@ 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) @@ -89,7 +87,6 @@ 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): @@ -99,35 +96,62 @@ class StyleChooser: for r in self.ruleChains: a.update(r.extract_tags()) if "*" in a: - a = set('*') + a.clear() + a.add("*") 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 len(a) == 0: - a = set('*') + if "*" in a or len(a) == 0: + a.clear() + a.add("*") self.cached_tags = a return a - def get_runtime_conditions(self, tags): - if not self.has_runtime_conditions: - return None + def get_runtime_conditions(self, ftype, tags, zoom): + if self.selzooms: + if zoom < self.selzooms[0] or zoom > self.selzooms[1]: + return None - rule_and_object_id = self.testChains(tags) + rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom) if not rule_and_object_id: return None rule = rule_and_object_id[0] + if (len(rule.runtime_conditions) == 0): + return None + return rule.runtime_conditions - # TODO: Rename to "applyStyles" - def updateStyles(self, sl, tags, xscale, zscale, filter_by_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): # Are any of the ruleChains fulfilled? - rule_and_object_id = self.testChains(tags) + if self.selzooms: + if zoom < self.selzooms[0] or zoom > self.selzooms[1]: + return sl + + #if ftype not in self.compatible_types: + #return sl + + rule_and_object_id = self.testChain(self.ruleChains, ftype, tags, zoom) if not rule_and_object_id: return sl @@ -135,9 +159,7 @@ class StyleChooser: rule = rule_and_object_id[0] object_id = rule_and_object_id[1] - if (filter_by_runtime_conditions is not None - and rule.runtime_conditions is not None - and filter_by_runtime_conditions != rule.runtime_conditions): + if not self.isCorrespondingRule(filter_by_runtime_conditions, rule): return sl for r in self.styles: @@ -146,7 +168,6 @@ 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) @@ -182,12 +203,12 @@ class StyleChooser: return sl - def testChains(self, tags): + def testChain(self, chain, obj, tags, zoom): """ Tests an object against a chain """ - for r in self.ruleChains: - tt = r.test(tags) + for r in chain: + tt = r.test(obj, tags, zoom) if tt: return r, tt return False @@ -228,18 +249,14 @@ class StyleChooser: """ adds into the current ruleChain (existing Rule) """ - 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) + self.ruleChains[-1].runtime_conditions.append(c) + self.ruleChains[-1].runtime_conditions.sort() def addStyles(self, a): # print "addStyle ", a """ 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] @@ -260,6 +277,9 @@ 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 cc1713a..b102260 100644 --- a/src/mapcss/__init__.py +++ b/src/mapcss/__init__.py @@ -22,10 +22,9 @@ from .StyleChooser import StyleChooser 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"]) +NEEDED_KEYS = set(["width", "casing-width", "fill-color", "fill-image", "icon-image", "text", "extrude", + "background-image", "background-color", "pattern-image", "shield-text", "symbol-shape"]) -# TODO: Unused constant WHITESPACE = re.compile(r'\s+ ', re.S | re.X) COMMENT = re.compile(r'\/\* .*? \*\/ \s* ', re.S | re.X) @@ -41,30 +40,29 @@ 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) -# TODO: move to Condition.py -CONDITION_TRUE = re.compile(r'\s* ([:\w]+) \s* [?] \s* $', re.I | re.S | re.X) +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 @@ -75,7 +73,6 @@ 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') @@ -84,7 +81,6 @@ 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]*)') @@ -100,9 +96,8 @@ class MapCSS(): self.scalepair = (minscale, maxscale) self.choosers = [] self.choosers_by_type = {} - self.choosers_by_type_zoom_tag = {} + self.choosers_by_type_and_tag = {} self.variables = {} - self.unused_variables = set() self.style_loaded = False def parseZoom(self, s): @@ -115,67 +110,47 @@ 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): - 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()} + def build_choosers_tree(self, clname, type, tags={}): + if type not in self.choosers_by_type_and_tag: + self.choosers_by_type_and_tag[type] = {} + if clname not in self.choosers_by_type_and_tag[type]: + self.choosers_by_type_and_tag[type][clname] = set() if type in self.choosers_by_type: for chooser in self.choosers_by_type[type]: - chooser_tags = chooser.extract_tags() - if '*' in chooser_tags or cltag in chooser_tags: - for zoom in range(int(chooser.selzooms[0]), int(chooser.selzooms[1]) + 1): - if chooser not in self.choosers_by_type_zoom_tag[type][zoom][clname]['set']: - self.choosers_by_type_zoom_tag[type][zoom][clname]['arr'].append(chooser) - self.choosers_by_type_zoom_tag[type][zoom][clname]['set'].add(chooser) - - def finalize_choosers_tree(self): - for ftype in self.choosers_by_type_zoom_tag.keys(): - for zoom in self.choosers_by_type_zoom_tag[ftype].keys(): - for clname in self.choosers_by_type_zoom_tag[ftype][zoom].keys(): - # Discard unneeded unique set of choosers. - self.choosers_by_type_zoom_tag[ftype][zoom][clname] = self.choosers_by_type_zoom_tag[ftype][zoom][clname]['arr'] - for i in range(0, len(self.choosers_by_type_zoom_tag[ftype][zoom][clname])): - chooser = self.choosers_by_type_zoom_tag[ftype][zoom][clname][i] - optimized = StyleChooser(chooser.scalepair) - optimized.styles = chooser.styles - optimized.eval_type = chooser.eval_type - optimized.has_evals = chooser.has_evals - optimized.has_runtime_conditions = chooser.has_runtime_conditions - optimized.selzooms = [zoom, zoom] - optimized.ruleChains = [] - for rule in chooser.ruleChains: - # Discard chooser's rules that don't match type or zoom. - if ftype in rule.type_matches and zoom >= rule.minZoom and zoom <= rule.maxZoom: - optimized.ruleChains.append(rule) - self.choosers_by_type_zoom_tag[ftype][zoom][clname][i] = optimized + for tag in chooser.extract_tags(): + if tag == "*" or tag in tags: + if chooser not in self.choosers_by_type_and_tag[type][clname]: + self.choosers_by_type_and_tag[type][clname].add(chooser) + break + def restore_choosers_order(self, type): + ethalon_choosers = self.choosers_by_type[type] + for tag, choosers_for_tag in list(self.choosers_by_type_and_tag[type].items()): + tmp = [] + for ec in ethalon_choosers: + if ec in choosers_for_tag: + tmp.append(ec) + self.choosers_by_type_and_tag[type][tag] = tmp def get_runtime_rules(self, clname, type, tags, zoom): """ Returns array of runtime_conditions which are used for clname/type/tags/zoom """ runtime_rules = [] - if type in self.choosers_by_type_zoom_tag: - for chooser in self.choosers_by_type_zoom_tag[type][zoom][clname]: - runtime_conditions = chooser.get_runtime_conditions(tags) + if type in self.choosers_by_type_and_tag: + for chooser in self.choosers_by_type_and_tag[type][clname]: + runtime_conditions = chooser.get_runtime_conditions(type, tags, zoom) if runtime_conditions: 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: - for chooser in self.choosers_by_type_zoom_tag[type][zoom][clname]: - style = chooser.updateStyles(style, tags, xscale, zscale, filter_by_runtime_conditions) + if type in self.choosers_by_type_and_tag: + for chooser in self.choosers_by_type_and_tag[type][clname]: + style = chooser.updateStyles(style, type, tags, zoom, xscale, zscale, filter_by_runtime_conditions) style = [x for x in style if x["object-id"] != "::*"] for x in style: for k, v in [('width', 0), ('casing-width', 0)]: @@ -215,8 +190,6 @@ 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() @@ -229,8 +202,7 @@ class MapCSS(): if filename: basepath = os.path.dirname(filename) if not css: - with open(filename) as css_file: - css = css_file.read() + css = open(filename, encoding="utf-8").read() if not self.style_loaded: self.choosers = [] @@ -350,8 +322,7 @@ class MapCSS(): import_filename = os.path.join(basepath, IMPORT.match(css).groups()[0]) try: css = IMPORT.sub("", css, 1) - with open(import_filename, "r") as import_file: - import_text = import_file.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 @@ -364,7 +335,6 @@ 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 @@ -372,7 +342,7 @@ class MapCSS(): elif UNKNOWN.match(css): raise Exception("Unknown construction: " + UNKNOWN.match(css).group()) - # Must be unreachable + # Must be unreacheable else: raise Exception("Unexpected construction: " + css) @@ -390,13 +360,10 @@ 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() @@ -405,19 +372,18 @@ 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))) + res = zindex.index(float(stylez.get('z-index', 0))) - zoffset if stretch: - stylez['z-index'] = stretch * res / len(zindex) + stylez['z-index'] = 1. * res / len(zindex) * stretch 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: @@ -425,11 +391,7 @@ 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') @@ -510,7 +472,7 @@ def parseDeclaration(s): logging.debug("%s == %s" % (tzz[0], tzz[1])) else: logging.debug("unknown %s" % (a)) - return [t] # TODO: don't wrap `t` dict into a list. Return `t` instead. + return [t] if __name__ == "__main__": diff --git a/tests/__init__.py b/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/tests/assets/case-1-import/colors.mapcss b/tests/assets/case-1-import/colors.mapcss deleted file mode 100644 index db1c68b..0000000 --- a/tests/assets/case-1-import/colors.mapcss +++ /dev/null @@ -1,6 +0,0 @@ -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 deleted file mode 100644 index cfa0b6a..0000000 --- a/tests/assets/case-1-import/import1.mapcss +++ /dev/null @@ -1 +0,0 @@ -@import("import2.mapcss"); diff --git a/tests/assets/case-1-import/import2.mapcss b/tests/assets/case-1-import/import2.mapcss deleted file mode 100644 index 9ab0b8d..0000000 --- a/tests/assets/case-1-import/import2.mapcss +++ /dev/null @@ -1 +0,0 @@ -@import("colors.mapcss"); diff --git a/tests/assets/case-1-import/main.mapcss b/tests/assets/case-1-import/main.mapcss deleted file mode 100644 index ca997c1..0000000 --- a/tests/assets/case-1-import/main.mapcss +++ /dev/null @@ -1 +0,0 @@ -@import("import1.mapcss"); diff --git a/tests/assets/case-2-generate-drules-mini/.gitignore b/tests/assets/case-2-generate-drules-mini/.gitignore deleted file mode 100644 index 24a9e5d..0000000 --- a/tests/assets/case-2-generate-drules-mini/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -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 deleted file mode 100644 index 794dd08..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/Roads.mapcss +++ /dev/null @@ -1,136 +0,0 @@ -/* ~~~~ 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 deleted file mode 100644 index 718d47d..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/Roads_label.mapcss +++ /dev/null @@ -1,75 +0,0 @@ -/* ~~~~ 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 deleted file mode 100644 index d7b4817..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/colors.mapcss +++ /dev/null @@ -1,16 +0,0 @@ -/* 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 deleted file mode 100644 index 385b1ab..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/priorities_1_BG-by-size.prio.txt +++ /dev/null @@ -1,16 +0,0 @@ -# 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 deleted file mode 100644 index 9e47413..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/priorities_2_BG-top.prio.txt +++ /dev/null @@ -1,18 +0,0 @@ -# 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 deleted file mode 100644 index f249605..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/priorities_3_FG.prio.txt +++ /dev/null @@ -1,41 +0,0 @@ -# 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 deleted file mode 100644 index 2eaecb6..0000000 --- a/tests/assets/case-2-generate-drules-mini/include/priorities_4_overlays.prio.txt +++ /dev/null @@ -1,61 +0,0 @@ -# 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 deleted file mode 100644 index 300b07d..0000000 --- a/tests/assets/case-2-generate-drules-mini/main.mapcss +++ /dev/null @@ -1,133 +0,0 @@ -*::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 deleted file mode 100644 index 60746b0..0000000 --- a/tests/assets/case-2-generate-drules-mini/mapcss-dynamic.txt +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index 077f771..0000000 --- a/tests/assets/case-2-generate-drules-mini/mapcss-mapping.csv +++ /dev/null @@ -1,148 +0,0 @@ -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 deleted file mode 100644 index d57323c..0000000 --- a/tests/assets/case-2-generate-drules-mini/readme.md +++ /dev/null @@ -1,4 +0,0 @@ -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 deleted file mode 100644 index a937244..0000000 --- a/tests/assets/case-3-styles-validation/readme.md +++ /dev/null @@ -1 +0,0 @@ -Files for testLibkomwm.test_generate_drules_validation_errors() method. diff --git a/tests/testCondition.py b/tests/testCondition.py deleted file mode 100644 index 5ab7b68..0000000 --- a/tests/testCondition.py +++ /dev/null @@ -1,300 +0,0 @@ -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 deleted file mode 100644 index 94edc2a..0000000 --- a/tests/testEval.py +++ /dev/null @@ -1,124 +0,0 @@ -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 deleted file mode 100644 index c2f3e52..0000000 --- a/tests/testLibkomwm.py +++ /dev/null @@ -1,68 +0,0 @@ -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 deleted file mode 100644 index c5ffa6d..0000000 --- a/tests/testMapCSS.py +++ /dev/null @@ -1,364 +0,0 @@ -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 deleted file mode 100644 index f3eec89..0000000 --- a/tests/testRule.py +++ /dev/null @@ -1,114 +0,0 @@ -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 deleted file mode 100644 index a359e58..0000000 --- a/tests/testStyleChooser.py +++ /dev/null @@ -1,297 +0,0 @@ -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()