forked from mapsme/kothic
-
Notifications
You must be signed in to change notification settings - Fork 5
Code refactoring #35
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
AndrewShkrob
wants to merge
1
commit into
organicmaps:master
Choose a base branch
from
AndrewShkrob:dev/refactoring
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Code refactoring #35
Changes from all commits
Commits
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| """ | ||
| Classificator parsing and management. | ||
| """ | ||
| import os | ||
| import csv | ||
| import logging | ||
| from typing import Dict, List, Set | ||
| from collections import OrderedDict | ||
|
|
||
| # Configure module logger | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # Constants | ||
| EXPECTED_SHORT_COLUMNS = 3 | ||
| EXPECTED_FULL_COLUMNS = 7 | ||
|
|
||
|
|
||
| class ClassificatorManager: | ||
| """Manages classificator types and their parsing.""" | ||
|
|
||
| def __init__(self) -> None: | ||
| """Initialize classificator manager.""" | ||
| self.classificator: Dict[str, OrderedDict] = {} | ||
| self.class_order: List[str] = [] | ||
| self.class_tree: Dict[str, str] = {} | ||
|
|
||
| def parse_mapcss_mapping(self, data_dir: str) -> Set[str]: | ||
| """ | ||
| Parse mapcss-mapping.csv file. | ||
|
|
||
| Args: | ||
| data_dir: Directory containing mapcss-mapping.csv | ||
|
|
||
| Returns: | ||
| Set of unique types for validation | ||
|
|
||
| Raises: | ||
| FileNotFoundError: If required files don't exist | ||
| ValueError: If file format is invalid | ||
| """ | ||
| # Validate input directory | ||
| if not os.path.isdir(data_dir): | ||
| raise FileNotFoundError(f"Data directory not found: {data_dir}") | ||
|
|
||
| types_file_path = os.path.join(data_dir, 'types.txt') | ||
| mapping_file_path = os.path.join(data_dir, 'mapcss-mapping.csv') | ||
|
|
||
| if not os.path.isfile(mapping_file_path): | ||
| raise FileNotFoundError(f"Mapping file not found: {mapping_file_path}") | ||
|
|
||
| try: | ||
| with open(types_file_path, "w") as types_file: | ||
| cnt = 1 | ||
| unique_types_check = set() | ||
|
|
||
| with open(mapping_file_path) as mapping_file: | ||
| for row_num, row in enumerate(csv.reader(mapping_file, delimiter=';'), start=1): | ||
| if len(row) <= 1 or row[0].startswith('#'): | ||
| # Allow for empty lines and comment lines starting with '#'. | ||
| continue | ||
|
|
||
| if len(row) == EXPECTED_SHORT_COLUMNS: | ||
| # Short format: type name, type id, x / replacement type name | ||
| tag = row[0].replace('|', '=') | ||
| obsolete = len(row[2].strip()) > 0 | ||
| row = (row[0], '[{0}]'.format(tag), 'x' if obsolete else '', 'name', 'int_name', row[1], row[2] if row[2] != 'x' else '') | ||
|
|
||
| if len(row) != EXPECTED_FULL_COLUMNS: | ||
| raise ValueError( | ||
| f'Row {row_num}: Expecting {EXPECTED_SHORT_COLUMNS} or {EXPECTED_FULL_COLUMNS} columns, ' | ||
| f'got {len(row)}: {";".join(row)}' | ||
| ) | ||
|
|
||
| try: | ||
| type_id = int(row[5]) | ||
| except ValueError: | ||
| raise ValueError(f'Row {row_num}: Invalid type id "{row[5]}"') | ||
|
|
||
| if type_id < cnt: | ||
| raise ValueError( | ||
| f'Row {row_num}: Wrong type id {type_id}, expected >= {cnt}: {";".join(row)}' | ||
| ) | ||
|
|
||
| while type_id > cnt: | ||
| types_file.write("mapswithme\n") | ||
| cnt += 1 | ||
| cnt += 1 | ||
|
|
||
| cl = row[0].replace("|", "-") | ||
| if cl in unique_types_check and row[2] != 'x': | ||
| raise ValueError(f'Row {row_num}: Duplicate type: {row[0]}') | ||
|
|
||
| pairs = [i.strip(']').split("=") for i in row[1].split(',')[0].split('[')] | ||
| kv = OrderedDict() | ||
| for i in pairs: | ||
| if len(i) == 1: | ||
| if i[0]: | ||
| if i[0][0] == "!": | ||
| kv[i[0][1:].strip('?')] = "no" | ||
| else: | ||
| kv[i[0].strip('?')] = "yes" | ||
| else: | ||
| kv[i[0]] = i[1] | ||
|
|
||
| if row[2] != "x": | ||
| self.classificator[cl] = kv | ||
| self.class_order.append(cl) | ||
| unique_types_check.add(cl) | ||
| # Mark original type to distinguish it among replacing types. | ||
| types_file.write(f"*{row[0]}\n") | ||
| else: | ||
| # compatibility mode | ||
| if row[6]: | ||
| types_file.write(f"{row[6]}\n") | ||
| else: | ||
| types_file.write("mapswithme\n") | ||
|
|
||
| self.class_tree[cl] = row[0] | ||
|
|
||
| except IOError as e: | ||
| logger.error(f"Error reading/writing files: {e}") | ||
| raise | ||
| except csv.Error as e: | ||
| logger.error(f"CSV parsing error: {e}") | ||
| raise | ||
|
|
||
| self.class_order.sort() | ||
| return unique_types_check | ||
|
|
||
| def get_mapcss_static_tags(self) -> Dict[str, bool]: | ||
| """ | ||
| Get all mapcss static tags used in mapcss-mapping.csv. | ||
|
|
||
| Returns: | ||
| Dictionary with main_tag flags (True = appears first in types) | ||
| """ | ||
| mapcss_static_tags = {} | ||
| for v in list(self.classificator.values()): | ||
| for i, t in enumerate(v.keys()): | ||
| mapcss_static_tags[t] = mapcss_static_tags.get(t, True) and i == 0 | ||
| return mapcss_static_tags | ||
|
|
||
|
|
||
| def load_mapcss_dynamic_tags(data_dir: str) -> Set[str]: | ||
| """ | ||
| Load mapcss dynamic tags from mapcss-dynamic.txt. | ||
|
|
||
| Args: | ||
| data_dir: Directory containing mapcss-dynamic.txt | ||
|
|
||
| Returns: | ||
| Set of dynamic tag names | ||
|
|
||
| Raises: | ||
| FileNotFoundError: If mapcss-dynamic.txt doesn't exist | ||
| """ | ||
| dynamic_file_path = os.path.join(data_dir, 'mapcss-dynamic.txt') | ||
|
|
||
| if not os.path.isfile(dynamic_file_path): | ||
| raise FileNotFoundError(f"Dynamic tags file not found: {dynamic_file_path}") | ||
|
|
||
| try: | ||
| with open(dynamic_file_path) as dynamic_file: | ||
| return {line.rstrip() for line in dynamic_file} | ||
| except IOError as e: | ||
| logger.error(f"Error reading dynamic tags file: {e}") | ||
| raise |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,143 @@ | ||
| """ | ||
| Configuration constants for drules generation. | ||
|
|
||
| This module defines priority ranges for drawing rules and their associated metadata. | ||
| Priority ranges control the rendering order of map features. | ||
| """ | ||
| import logging | ||
| from typing import Dict, Any, NamedTuple | ||
|
|
||
| # Configure module logger | ||
| logger = logging.getLogger(__name__) | ||
|
|
||
| # ============================================================================= | ||
| # CONSTANTS | ||
| # ============================================================================= | ||
|
|
||
| # 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 | ||
|
|
||
| # Priority range identifiers | ||
| PRIO_OVERLAYS = 'overlays' | ||
| PRIO_FG = 'FG' | ||
| PRIO_BG_TOP = 'BG-top' | ||
| PRIO_BG_BY_SIZE = 'BG-by-size' | ||
|
|
||
| # ============================================================================= | ||
| # FILE HEADER COMMENTS | ||
| # ============================================================================= | ||
|
|
||
| COMMENT_AUTOFORMAT = ( | ||
| 'This file is automatically re-formatted and re-sorted in priorities descending order\n' | ||
| 'when generate_drules.sh is run. All comments (automatic priorities of e.g. optional captions, drule types visibilities, etc.)\n' | ||
| 'are generated automatically for information only. Custom formatting and comments are not preserved.\n' | ||
| ) | ||
|
|
||
| COMMENT_RANGES_OVERVIEW = ( | ||
| "\nPriorities ranges' rendering order overview:\n" | ||
| '- overlays (icons, captions...)\n' | ||
| '- FG: foreground areas and lines\n' | ||
| '- BG-top: water (linear and areal)\n' | ||
| '- BG-by-size: landcover areas sorted by their size\n' | ||
| ) | ||
|
|
||
| # ============================================================================= | ||
| # PRIORITY RANGE DESCRIPTIONS | ||
| # ============================================================================= | ||
|
|
||
| COMMENT_OVERLAYS = ( | ||
| '\nOverlays (icons, captions, path texts and shields) are rendered on top of all the geometry (lines, areas).\n' | ||
| "Overlays don't overlap each other, instead the ones with higher priority displace the less important ones.\n" | ||
| 'Optional captions (which have an icon) are usually displayed only if there are no other overlays in their way\n' | ||
| f'(technically, max overlays priority value ({OVERLAYS_MAX_PRIORITY}) is subtracted from their priorities automatically).\n' | ||
| ) | ||
|
|
||
| COMMENT_FG = ( | ||
| '\nFG geometry: foreground lines and areas (e.g. buildings) are rendered always below overlays\n' | ||
| 'and always on top of background geometry (BG-top & BG-by-size) even if a foreground feature\n' | ||
| 'is layer=-10 (as tunnels should be visibile over landcover and water).\n' | ||
| ) | ||
|
|
||
| COMMENT_BG_TOP = ( | ||
| '\nBG-top geometry: background lines and areas that should be always below foreground ones\n' | ||
| '(including e.g. layer=-10 underwater tunnels), but above background areas sorted by size (BG-by-size),\n' | ||
| "because ordering by size doesn't always work with e.g. water mapped over a forest,\n" | ||
| 'so water should be on top of other landcover always, but linear waterways should be hidden beneath it.\n' | ||
| 'Still, e.g. a layer=-1 BG-top feature will be rendered under a layer=0 BG-by-size feature\n' | ||
| '(so areal water tunnels are hidden beneath other landcover area) and a layer=1 landcover areas\n' | ||
| 'are displayed above layer=0 BG-top.\n' | ||
| ) | ||
|
|
||
| COMMENT_BG_BY_SIZE = ( | ||
| '\nBG-by-size geometry: background areas rendered below BG-top and everything else.\n' | ||
| "Smaller areas are rendered above larger ones (area's size is estimated as the size of its' bounding box).\n" | ||
| 'So effectively priority values of BG-by-size areas are not used at the moment.\n' | ||
| 'But we might use them later for some special cases, e.g. to determine a main area type of a multi-type feature.\n' | ||
| 'Keep them in a logical importance order please.\n' | ||
| ) | ||
|
|
||
| # ============================================================================= | ||
| # PRIORITY RANGE CONFIGURATION | ||
| # ============================================================================= | ||
|
|
||
|
|
||
| class PriorityRangeConfig(NamedTuple): | ||
| """Configuration for a priority range.""" | ||
| pos: int # Rendering position (higher = rendered later/on top) | ||
| base: int # Base priority offset | ||
| comment: str # Description text for the priority file | ||
|
|
||
|
|
||
| # Priority range configurations - centralized and easy to maintain | ||
| PRIORITY_RANGE_CONFIGS = { | ||
| PRIO_OVERLAYS: PriorityRangeConfig( | ||
| pos=4, | ||
| base=0, | ||
| comment=COMMENT_OVERLAYS | ||
| ), | ||
| PRIO_FG: PriorityRangeConfig( | ||
| pos=3, | ||
| base=0, | ||
| comment=COMMENT_FG | ||
| ), | ||
| PRIO_BG_TOP: PriorityRangeConfig( | ||
| pos=2, | ||
| base=-1000, | ||
| comment=COMMENT_BG_TOP | ||
| ), | ||
| PRIO_BG_BY_SIZE: PriorityRangeConfig( | ||
| pos=1, | ||
| base=-2000, | ||
| comment=COMMENT_BG_BY_SIZE | ||
| ), | ||
| } | ||
|
|
||
|
|
||
| def get_prio_ranges() -> Dict[str, Dict[str, Any]]: | ||
| """ | ||
| Initialize and return priority ranges with their configurations. | ||
|
|
||
| Returns: | ||
| Dictionary mapping priority range names to their configuration dicts. | ||
| Each configuration contains: | ||
| - pos: Rendering position (higher = on top) | ||
| - base: Base priority offset | ||
| - comment: Description for priority file | ||
| - priorities: Dictionary of (type, object_id) -> priority mappings (empty initially) | ||
| """ | ||
| prio_ranges = {} | ||
|
|
||
| for range_name, config in PRIORITY_RANGE_CONFIGS.items(): | ||
| prio_ranges[range_name] = { | ||
| 'pos': config.pos, | ||
| 'base': config.base, | ||
| 'comment': config.comment, | ||
| 'priorities': {} # Will be populated when loading priority files | ||
| } | ||
|
|
||
| return prio_ranges | ||
|
|
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What was the use-case for these options?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
MULTIPROCESSINGflags enables paraller processing of CSS rules. But on practice enabling it doesn't give signifficant improvements. Generating of drules takes less than 2 second. And adding paralellisation speed up is insignifficant.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
2 seconds is still too much. There is a plan to fix/revive Map Style Designer in the repo, it should regenerate map style on every edit/change, to immediately see the difference on the map.