diff --git a/data/githooks/commit-msg b/data/githooks/commit-msg index 769f4f6..de77a2e 100755 --- a/data/githooks/commit-msg +++ b/data/githooks/commit-msg @@ -29,7 +29,8 @@ def check_commit_message(parts: List[str]) -> Optional[str]: if short_message[0] != short_message[0].upper(): return ( - short_message + "\n^" + short_message + + "\n^" + "\nCommit message short description should start with uppercase " + "letter." ) @@ -95,12 +96,12 @@ def check_commit_message(parts: List[str]) -> Optional[str]: verbs[verb + "d"] = verb for wrong_verb, right_verb in verbs.items(): - if short_message.startswith(f"{wrong_verb} ") or short_message.startswith( - f"{first_letter_uppercase(wrong_verb)} " - ): + if short_message.startswith( + f"{wrong_verb} " + ) or short_message.startswith(f"{first_letter_uppercase(wrong_verb)} "): return ( - f'Commit message should start with the verb in infinitive ' - f'form. Please, use ' + f"Commit message should start with the verb in infinitive " + f"form. Please, use " f'"{first_letter_uppercase(right_verb)} ..." instead of ' f'"{first_letter_uppercase(wrong_verb)} ...".' ) diff --git a/data/githooks/pre-commit b/data/githooks/pre-commit index 8d43f7a..012262d 100755 --- a/data/githooks/pre-commit +++ b/data/githooks/pre-commit @@ -1,8 +1,10 @@ #!/bin/sh +python_files="map_machine setup.py tests data/githooks/commit-msg" + echo "Checking code format with Black..." -if ! black -l 80 --check tests map_machine; then - black -l 80 --diff --color tests map_machine +if ! black -l 80 --check ${python_files}; then + black -l 80 --diff --color ${python_files} echo "FAIL" exit 1 fi @@ -13,5 +15,5 @@ echo "Lint with Flake8..." flake8 \ --max-line-length=80 \ --ignore=E203,W503,ANN002,ANN003,ANN101,ANN102 \ - map_machine setup.py tests \ + ${python_files} \ || { echo "FAIL"; exit 1; } diff --git a/map_machine/constructor.py b/map_machine/constructor.py index e8d6c4c..5da18e1 100644 --- a/map_machine/constructor.py +++ b/map_machine/constructor.py @@ -2,6 +2,7 @@ Construct Map Machine nodes and ways. """ import logging +import sys from datetime import datetime from hashlib import sha256 from typing import Any, Iterator, Optional, Union @@ -26,6 +27,7 @@ from map_machine.osm.osm_reader import ( OSMRelation, OSMWay, parse_levels, + Tags, ) from map_machine.pictogram.icon import ( DEFAULT_SMALL_SHAPE_ID, @@ -231,8 +233,14 @@ class Constructor: color: Color if self.configuration.drawing_mode == DrawingMode.AUTHOR: color = get_user_color(line.user, self.configuration.seed) - else: # self.mode == TIME_MODE + elif self.configuration.drawing_mode == DrawingMode.TIME: color = get_time_color(line.timestamp, self.osm_data.time) + elif self.configuration.drawing_mode != DrawingMode.NORMAL: + logging.fatal( + f"Drawing mode {self.configuration.drawing_mode} is not " + f"supported." + ) + sys.exit(1) self.draw_special_mode(line, inners, outers, color) return @@ -475,7 +483,7 @@ class Constructor: self.points.append(point) -def check_level_number(tags: dict[str, Any], level: float) -> bool: +def check_level_number(tags: Tags, level: float) -> bool: """Check if element described by tags is no the specified level.""" if "level" in tags: if level not in parse_levels(tags["level"]): @@ -485,12 +493,11 @@ def check_level_number(tags: dict[str, Any], level: float) -> bool: return True -def check_level_overground(tags: dict[str, Any]) -> bool: +def check_level_overground(tags: Tags) -> bool: """Check if element described by tags is overground.""" if "level" in tags: try: - levels: map = map(float, tags["level"].replace(",", ".").split(";")) - for level in levels: + for level in map(float, tags["level"].replace(",", ".").split(";")): if level < 0: return False except ValueError: diff --git a/map_machine/doc/moire_manager.py b/map_machine/doc/moire_manager.py index f97f8ef..762deac 100644 --- a/map_machine/doc/moire_manager.py +++ b/map_machine/doc/moire_manager.py @@ -73,8 +73,8 @@ class ArgumentParser(argparse.ArgumentParser): continue array: Code = [ - [Tag("no_wrap", [Tag("m", [x])]), ", "] - for x in option["arguments"] + [Tag("no_wrap", [Tag("m", [text])]), ", "] + for text in option["arguments"] ] cell: Code = [x for y in array for x in y][:-1] if "metavar" in option: diff --git a/map_machine/doc/taginfo.py b/map_machine/doc/taginfo.py index f9796d2..9c6fcb4 100644 --- a/map_machine/doc/taginfo.py +++ b/map_machine/doc/taginfo.py @@ -53,8 +53,8 @@ class TaginfoProjectFile: key: str = list(matcher.tags.keys())[0] value: str = matcher.tags[key] ids: list[str] = [ - (x if isinstance(x, str) else x["shape"]) - for x in matcher.shapes + (shape if isinstance(shape, str) else shape["shape"]) + for shape in matcher.shapes ] icon_id: str = "___".join(ids) if value == "*": diff --git a/map_machine/element.py b/map_machine/element.py index 68b4f23..acc315c 100644 --- a/map_machine/element.py +++ b/map_machine/element.py @@ -35,7 +35,8 @@ def draw_element(options: argparse.Namespace) -> None: tags_description = options.area tags: dict[str, str] = { - x.split("=")[0]: x.split("=")[1] for x in tags_description.split(",") + tag.split("=")[0]: tag.split("=")[1] + for tag in tags_description.split(",") } scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH) extractor: ShapeExtractor = ShapeExtractor( diff --git a/map_machine/feature/direction.py b/map_machine/feature/direction.py index cffd8a2..f42d47f 100644 --- a/map_machine/feature/direction.py +++ b/map_machine/feature/direction.py @@ -149,7 +149,7 @@ class DirectionSet: :return: true if direction is right, false if direction is left, and None otherwise. """ - result: list[bool] = [x.is_right() for x in self.sectors] + result: list[bool] = [sector.is_right() for sector in self.sectors] if result == [True] * len(result): return True if result == [False] * len(result): diff --git a/map_machine/figure.py b/map_machine/figure.py index d06739a..f8ad915 100644 --- a/map_machine/figure.py +++ b/map_machine/figure.py @@ -395,5 +395,5 @@ def make_counter_clockwise(polygon: list[OSMNode]) -> list[OSMNode]: def get_path(nodes: list[OSMNode], shift: np.ndarray, flinger: Flinger) -> str: """Construct SVG path commands from nodes.""" return Polyline( - [flinger.fling(x.coordinates) + shift for x in nodes] + [flinger.fling(node.coordinates) + shift for node in nodes] ).get_path() diff --git a/map_machine/geometry/boundary_box.py b/map_machine/geometry/boundary_box.py index 19332f8..0cb60da 100644 --- a/map_machine/geometry/boundary_box.py +++ b/map_machine/geometry/boundary_box.py @@ -121,13 +121,13 @@ class BoundaryBox: """Get maximum coordinates.""" return np.array((self.top, self.right)) - def get_left_top(self) -> (np.ndarray, np.ndarray): + def get_left_top(self) -> np.ndarray: """Get left top corner of the boundary box.""" - return self.top, self.left + return np.array((self.top, self.left)) - def get_right_bottom(self) -> (np.ndarray, np.ndarray): + def get_right_bottom(self) -> np.ndarray: """Get right bottom corner of the boundary box.""" - return self.bottom, self.right + return np.array((self.bottom, self.right)) def round(self) -> "BoundaryBox": """Round boundary box.""" diff --git a/map_machine/geometry/vector.py b/map_machine/geometry/vector.py index 4a5666a..6e12c03 100644 --- a/map_machine/geometry/vector.py +++ b/map_machine/geometry/vector.py @@ -62,8 +62,12 @@ class Polyline: ) except ValueError: points = self.points - path: str = "M " + " L ".join(f"{x[0]},{x[1]}" for x in points) - return path + (" Z" if np.allclose(points[0], points[-1]) else "") + + return ( + "M " + + " L ".join(f"{point[0]},{point[1]}" for point in points) + + (" Z" if np.allclose(points[0], points[-1]) else "") + ) def shorten(self, index: int, length: float) -> None: """Make shorten part specified with index.""" diff --git a/map_machine/icons/config.json b/map_machine/icons/config.json index c246e2f..f9499a6 100644 --- a/map_machine/icons/config.json +++ b/map_machine/icons/config.json @@ -191,6 +191,9 @@ "name": "comb and scissors" }, "counterclockwise": {}, + "crane": { + "name": "crane" + }, "crater": { "name": "crater" }, @@ -500,6 +503,9 @@ }, "oat": {}, "oat_2": {}, + "observatory": { + "name": "observatory" + }, "onion_roof_shape": { "name": "onion roof shape" }, diff --git a/map_machine/osm/osm_reader.py b/map_machine/osm/osm_reader.py index fecf536..af3b91a 100644 --- a/map_machine/osm/osm_reader.py +++ b/map_machine/osm/osm_reader.py @@ -25,6 +25,9 @@ METERS_PATTERN: re.Pattern = re.compile("^(?P\\d*\\.?\\d*)\\s*m$") KILOMETERS_PATTERN: re.Pattern = re.compile("^(?P\\d*\\.?\\d*)\\s*km$") MILES_PATTERN: re.Pattern = re.compile("^(?P\\d*\\.?\\d*)\\s*mi$") +EARTH_EQUATOR_LENGTH: float = 40_075_017.0 + +Tags = dict[str, str] # See https://wiki.openstreetmap.org/wiki/Lifecycle_prefix#Stages_of_decay STAGES_OF_DECAY: list[str] = [ @@ -61,7 +64,7 @@ def parse_levels(string: str) -> list[float]: class Tagged: """Something with tags (string to string mapping).""" - tags: dict[str, str] + tags: Tags def get_tag(self, key: str) -> Optional[str]: """ @@ -125,7 +128,7 @@ class OSMNode(Tagged): def from_xml_structure(cls, element: Element) -> "OSMNode": """Parse node from OSM XML `` element.""" attributes = element.attrib - tags: dict[str, str] = { + tags: Tags = { x.attrib["k"]: x.attrib["v"] for x in element if x.tag == "tag" } return cls( @@ -180,7 +183,7 @@ class OSMWay(Tagged): ) -> "OSMWay": """Parse way from OSM XML `` element.""" attributes = element.attrib - tags: dict[str, str] = { + tags: Tags = { x.attrib["k"]: x.attrib["v"] for x in element if x.tag == "tag" } return cls( @@ -250,7 +253,7 @@ class OSMRelation(Tagged): """Parse relation from OSM XML `` element.""" attributes = element.attrib members: list[OSMMember] = [] - tags: dict[str, str] = {} + tags: Tags = {} for subelement in element: if subelement.tag == "member": subattributes = subelement.attrib @@ -309,7 +312,7 @@ class OSMData: self.levels: set[float] = set() self.time: MinMax = MinMax() self.view_box: Optional[BoundaryBox] = None - self.equator_length: float = 40_075_017.0 + self.equator_length: float = EARTH_EQUATOR_LENGTH def add_node(self, node: OSMNode) -> None: """Add node and update map parameters.""" diff --git a/map_machine/pictogram/icon.py b/map_machine/pictogram/icon.py index 88c515b..289e251 100644 --- a/map_machine/pictogram/icon.py +++ b/map_machine/pictogram/icon.py @@ -353,7 +353,7 @@ class Icon: point: np.ndarray, tags: dict[str, Any] = None, outline: bool = False, - scale: float = 1, + scale: float = 1.0, ) -> None: """ Draw icon to SVG. @@ -362,6 +362,7 @@ class Icon: :param point: 2D position of the icon centre :param tags: tags to be displayed as a tooltip :param outline: draw outline for the icon + :param scale: scale icon by the magnitude """ if outline: bright: bool = is_bright(self.shape_specifications[0].color) diff --git a/map_machine/scheme.py b/map_machine/scheme.py index fd79fed..2e93bcd 100644 --- a/map_machine/scheme.py +++ b/map_machine/scheme.py @@ -14,6 +14,7 @@ from colour import Color from map_machine.feature.direction import DirectionSet from map_machine.map_configuration import MapConfiguration, LabelMode +from map_machine.osm.osm_reader import Tags from map_machine.pictogram.icon import ( DEFAULT_COLOR, DEFAULT_SHAPE_ID, @@ -52,7 +53,7 @@ class MatchingType(Enum): def is_matched_tag( matcher_tag_key: str, matcher_tag_value: Union[str, list], - tags: dict[str, str], + tags: Tags, ) -> tuple[MatchingType, list[str]]: """ Check whether element tags contradict tag matcher. @@ -107,7 +108,7 @@ class Matcher: def __init__( self, structure: dict[str, Any], group: Optional[dict[str, Any]] = None ) -> None: - self.tags: dict[str, str] = structure["tags"] + self.tags: Tags = structure["tags"] self.exception: dict[str, str] = {} if "exception" in structure: @@ -132,9 +133,7 @@ class Matcher: ) def is_matched( - self, - tags: dict[str, str], - configuration: Optional[MapConfiguration] = None, + self, tags: Tags, configuration: Optional[MapConfiguration] = None ) -> tuple[bool, dict[str, str]]: """ Check whether element tags matches tag matcher. @@ -294,7 +293,7 @@ class RoadMatcher(Matcher): if "priority" in structure: self.priority = structure["priority"] - def get_priority(self, tags: dict[str, str]) -> float: + def get_priority(self, tags: Tags) -> float: """Get priority for drawing order.""" layer: float = 0 if "layer" in tags: @@ -556,7 +555,7 @@ class Scheme: return None def construct_text( - self, tags: dict[str, str], processed: set[str], label_mode: LabelMode + self, tags: Tags, processed: set[str], label_mode: LabelMode ) -> list[Label]: """Construct labels for not processed tags.""" texts: list[Label] = construct_text(tags, processed, label_mode) @@ -566,7 +565,7 @@ class Scheme: texts.append(Label(tags[tag])) return texts - def is_area(self, tags: dict[str, str]) -> bool: + def is_area(self, tags: Tags) -> bool: """Check whether way described by tags is area.""" for matcher in self.area_matchers: matching, _ = matcher.is_matched(tags) @@ -574,9 +573,7 @@ class Scheme: return True return False - def process_ignored( - self, tags: dict[str, str], processed: set[str] - ) -> None: + def process_ignored(self, tags: Tags, processed: set[str]) -> None: """ Mark all ignored tag as processed. diff --git a/map_machine/slippy/server.py b/map_machine/slippy/server.py index 6440bf3..fbaba34 100644 --- a/map_machine/slippy/server.py +++ b/map_machine/slippy/server.py @@ -9,6 +9,7 @@ from typing import Optional import cairosvg +from map_configuration import MapConfiguration from map_machine.slippy.tile import Tile from map_machine.workspace import workspace @@ -16,12 +17,12 @@ __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" -class _Handler(SimpleHTTPRequestHandler): +class TileServerHandler(SimpleHTTPRequestHandler): """HTTP request handler that process sloppy map tile requests.""" cache: Path = Path("cache") update_cache: bool = False - options = None + options: Optional[argparse.Namespace] = None def __init__( self, @@ -29,6 +30,7 @@ class _Handler(SimpleHTTPRequestHandler): client_address: tuple[str, int], server: HTTPServer, ) -> None: + # TODO: delete? super().__init__(request, client_address, server) def do_GET(self) -> None: @@ -48,7 +50,11 @@ class _Handler(SimpleHTTPRequestHandler): if self.update_cache: if not png_path.exists(): if not svg_path.exists(): - tile.draw(tile_path, self.cache, self.options) + tile.draw( + tile_path, + self.cache, + MapConfiguration(zoom_level=zoom_level), + ) with svg_path.open(encoding="utf-8") as input_file: cairosvg.svg2png( file_obj=input_file, write_to=str(png_path) @@ -68,7 +74,7 @@ def run_server(options: argparse.Namespace) -> None: """Command-line interface for tile server.""" server: Optional[HTTPServer] = None try: - handler = _Handler + handler = TileServerHandler handler.cache = Path(options.cache) handler.options = options server: HTTPServer = HTTPServer(("", options.port), handler) diff --git a/map_machine/slippy/tile.py b/map_machine/slippy/tile.py index 4618984..76b38d9 100644 --- a/map_machine/slippy/tile.py +++ b/map_machine/slippy/tile.py @@ -202,9 +202,7 @@ class Tile: for i in range(scale): for j in range(scale): tile: Tile = Tile( - scale * self.x + i, - scale * self.y + j, - zoom_level, + scale * self.x + i, scale * self.y + j, zoom_level ) tiles.append(tile) return tiles diff --git a/map_machine/text.py b/map_machine/text.py index d234819..f11eba5 100644 --- a/map_machine/text.py +++ b/map_machine/text.py @@ -2,11 +2,12 @@ OSM address tag processing. """ from dataclasses import dataclass -from typing import Any +from typing import Any, Optional from colour import Color from map_machine.map_configuration import LabelMode +from map_machine.osm.osm_reader import Tags __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" @@ -100,14 +101,15 @@ def get_text(tags: dict[str, Any], processed: set[str]) -> list[Label]: def construct_text( - tags: dict[str, str], processed: set[str], label_mode: LabelMode + tags: Tags, processed: set[str], label_mode: LabelMode ) -> list[Label]: """Construct list of labels from OSM tags.""" texts: list[Label] = [] - name = None - alt_name = None + name: Optional[str] = None + alternative_name: Optional[str] = None + if "name" in tags: name = tags["name"] processed.add("name") @@ -117,25 +119,25 @@ def construct_text( processed.add("name:en") processed.add("name:en") if "alt_name" in tags: - if alt_name: - alt_name += ", " + if alternative_name: + alternative_name += ", " else: - alt_name = "" - alt_name += tags["alt_name"] + alternative_name = "" + alternative_name += tags["alt_name"] processed.add("alt_name") if "old_name" in tags: - if alt_name: - alt_name += ", " + if alternative_name: + alternative_name += ", " else: - alt_name = "" - alt_name += "ex " + tags["old_name"] + alternative_name = "" + alternative_name += "ex " + tags["old_name"] address: list[str] = get_address(tags, processed, label_mode) if name: texts.append(Label(name, Color("black"))) - if alt_name: - texts.append(Label(f"({alt_name})")) + if alternative_name: + texts.append(Label(f"({alternative_name})")) if address: texts.append(Label(", ".join(address))) diff --git a/map_machine/ui/cli.py b/map_machine/ui/cli.py index edc94be..0ec5fd3 100644 --- a/map_machine/ui/cli.py +++ b/map_machine/ui/cli.py @@ -119,16 +119,17 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None: "--buildings", metavar="", default="flat", - choices=(x.value for x in BuildingMode), + choices=(mode.value for mode in BuildingMode), help="building drawing mode: " - + ", ".join(x.value for x in BuildingMode), + + ", ".join(mode.value for mode in BuildingMode), ) parser.add_argument( "--mode", default="normal", metavar="", - choices=(x.value for x in DrawingMode), - help="map drawing mode: " + ", ".join(x.value for x in DrawingMode), + choices=(mode.value for mode in DrawingMode), + help="map drawing mode: " + + ", ".join(mode.value for mode in DrawingMode), ) parser.add_argument( "--overlap", @@ -143,8 +144,9 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None: dest="label_mode", default="main", metavar="", - choices=(x.value for x in LabelMode), - help="label drawing mode: " + ", ".join(x.value for x in LabelMode), + choices=(mode.value for mode in LabelMode), + help="label drawing mode: " + + ", ".join(mode.value for mode in LabelMode), ) parser.add_argument( "--level",