diff --git a/data/githooks/commit-msg b/data/githooks/commit-msg index 94f55b5..769f4f6 100755 --- a/data/githooks/commit-msg +++ b/data/githooks/commit-msg @@ -18,7 +18,7 @@ def check_file(file_name: str) -> Optional[str]: :param file_name: commit message file name """ - with open(file_name) as input_file: + with open(file_name, encoding="utf-8") as input_file: parts: List[str] = list(map(lambda x: x[:-1], input_file.readlines())) return check_commit_message(parts) @@ -94,15 +94,15 @@ def check_commit_message(parts: List[str]) -> Optional[str]: for verb in verbs_2: verbs[verb + "d"] = verb - for verb in verbs: - if short_message.startswith(f"{verb} ") or short_message.startswith( - f"{first_letter_uppercase(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)} " ): return ( f'Commit message should start with the verb in infinitive ' f'form. Please, use ' - f'"{first_letter_uppercase(verbs[verb])} ..." instead of ' - f'"{first_letter_uppercase(verb)} ...".' + f'"{first_letter_uppercase(right_verb)} ..." instead of ' + f'"{first_letter_uppercase(wrong_verb)} ...".' ) @@ -113,6 +113,7 @@ def check(commit_message: str) -> None: def test(): + """Test rules.""" check("start with lowercase letter.") check("Added foo.") check("Created foo.") diff --git a/map_machine/constructor.py b/map_machine/constructor.py index 71838ed..89b6002 100644 --- a/map_machine/constructor.py +++ b/map_machine/constructor.py @@ -217,7 +217,7 @@ class Constructor: outers: list[list[OSMNode]], ) -> None: """ - Way or relation construction. + Construct way or relation. :param line: OpenStreetMap way or relation :param inners: list of polygons that compose inner boundary @@ -310,43 +310,45 @@ class Constructor: ) self.points.append(point) - if not line_styles: - if DEBUG: - style: dict[str, Any] = { - "fill": "none", - "stroke": Color("red").hex, - "stroke-width": 1, - } - figure: StyledFigure = StyledFigure( - line.tags, inners, outers, LineStyle(style, 1000) - ) - self.figures.append(figure) + if line_styles: + return - processed: set[str] = set() + self.add_point_for_line(center_point, inners, line, outers) - priority: int - icon_set: IconSet - icon_set, priority = self.scheme.get_icon( - self.extractor, + def add_point_for_line(self, center_point, inners, line, outers) -> None: + """Add icon at the center point of the way or relation.""" + if DEBUG: + style: dict[str, Any] = { + "fill": "none", + "stroke": Color("red").hex, + "stroke-width": 1, + } + figure: StyledFigure = StyledFigure( + line.tags, inners, outers, LineStyle(style, 1000) + ) + self.figures.append(figure) + + processed: set[str] = set() + priority: int + icon_set: IconSet + icon_set, priority = self.scheme.get_icon( + self.extractor, line.tags, processed, self.configuration + ) + if icon_set is not None: + labels: list[Label] = self.scheme.construct_text( + line.tags, processed, self.configuration.label_mode + ) + point: Point = Point( + icon_set, + labels, line.tags, processed, - self.configuration, + center_point, + is_for_node=False, + priority=priority, + add_tooltips=self.configuration.show_tooltips, ) - if icon_set is not None: - labels: list[Label] = self.scheme.construct_text( - line.tags, processed, self.configuration.label_mode - ) - point: Point = Point( - icon_set, - labels, - line.tags, - processed, - center_point, - is_for_node=False, - priority=priority, - add_tooltips=self.configuration.show_tooltips, - ) - self.points.append(point) + self.points.append(point) def draw_special_mode( self, diff --git a/map_machine/doc/moire_manager.py b/map_machine/doc/moire_manager.py index 8a4154f..a6bd0d5 100644 --- a/map_machine/doc/moire_manager.py +++ b/map_machine/doc/moire_manager.py @@ -56,10 +56,8 @@ class ArgumentParser(argparse.ArgumentParser): def add_argument(self, *args, **kwargs) -> None: """Just store argument with options.""" super().add_argument(*args, **kwargs) - argument: dict[str, Any] = {"arguments": [x for x in args]} - - for key in kwargs: - argument[key] = kwargs[key] + argument: dict[str, Any] = {"arguments": args} + argument |= kwargs self.arguments.append(argument) @@ -116,9 +114,7 @@ class ArgumentParser(argparse.ArgumentParser): class MapMachineMoire(Default, ABC): - """ - Moire extension stub for Map Machine. - """ + """Moire extension stub for Map Machine.""" def osm(self, args: Arguments) -> str: """OSM tag key or key–value pair of tag.""" @@ -130,8 +126,8 @@ class MapMachineMoire(Default, ABC): + "=" + self.get_ref_(f"{PREFIX}Tag:{key}={tag}", self.m([tag])) ) - else: - return self.get_ref_(f"{PREFIX}Key:{spec}", self.m([spec])) + + return self.get_ref_(f"{PREFIX}Key:{spec}", self.m([spec])) def color(self, args: Arguments) -> str: """Simple color sample.""" @@ -181,9 +177,7 @@ class MapMachineMoire(Default, ABC): class MapMachineHTML(MapMachineMoire, DefaultHTML): - """ - Simple HTML. - """ + """Simple HTML.""" def __init__(self) -> None: super().__init__() @@ -196,9 +190,9 @@ class MapMachineHTML(MapMachineMoire, DefaultHTML): ["" + self.parse(td, in_block=True) + "" for td in arg[0]] ) content += f"{cell}" - for tr in arg[1:]: + for row in arg[1:]: cell: str = "".join( - ["" + self.parse(td, in_block=True) + "" for td in tr] + ["" + self.parse(td, in_block=True) + "" for td in row] ) content += f"{cell}" return f"{content}
" @@ -260,8 +254,8 @@ class MapMachineOSMWiki(MapMachineMoire, DefaultWiki): if "=" in spec: key, tag = spec.split("=") return f"{{{{Key|{key}|{tag}}}}}" - else: - return f"{{{{Tag|{spec}}}}}" + + return f"{{{{Tag|{spec}}}}}" def color(self, args: Arguments) -> str: """Simple color sample.""" @@ -276,9 +270,7 @@ class MapMachineOSMWiki(MapMachineMoire, DefaultWiki): class MapMachineMarkdown(MapMachineMoire, DefaultMarkdown): - """ - GitHub flavored markdown. - """ + """GitHub flavored markdown.""" images = {} diff --git a/map_machine/element.py b/map_machine/element.py index 79ddc93..68b4f23 100644 --- a/map_machine/element.py +++ b/map_machine/element.py @@ -9,6 +9,7 @@ import numpy as np import svgwrite from svgwrite.path import Path as SVGPath +from map_machine.map_configuration import LabelMode from map_machine.pictogram.icon import ShapeExtractor from map_machine.pictogram.point import Point from map_machine.scheme import LineStyle, Scheme @@ -33,17 +34,17 @@ def draw_element(options: argparse.Namespace) -> None: target = "area" tags_description = options.area - tags: dict[str, str] = dict( - [x.split("=") for x in tags_description.split(",")] - ) + tags: dict[str, str] = { + x.split("=")[0]: x.split("=")[1] for x in tags_description.split(",") + } scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH) extractor: ShapeExtractor = ShapeExtractor( workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH ) processed: set[str] = set() - icon, priority = scheme.get_icon(extractor, tags, processed) + icon, _ = scheme.get_icon(extractor, tags, processed) is_for_node: bool = target == "node" - labels: list[Label] = scheme.construct_text(tags, "all", processed) + labels: list[Label] = scheme.construct_text(tags, processed, LabelMode.ALL) point: Point = Point( icon, labels, diff --git a/map_machine/feature/direction.py b/map_machine/feature/direction.py index 9ff5d07..237f481 100644 --- a/map_machine/feature/direction.py +++ b/map_machine/feature/direction.py @@ -109,10 +109,11 @@ class Sector: if self.main_direction is not None: if np.allclose(self.main_direction[0], 0): return None - elif self.main_direction[0] > 0: + if self.main_direction[0] > 0: return True - else: - return False + return False + + return None def __str__(self) -> str: return f"{self.start}-{self.end}" diff --git a/map_machine/feature/road.py b/map_machine/feature/road.py index d6f51dc..04664a9 100644 --- a/map_machine/feature/road.py +++ b/map_machine/feature/road.py @@ -293,9 +293,8 @@ class Intersection: def __init__(self, parts: list[RoadPart]) -> None: self.parts: list[RoadPart] = sorted(parts, key=lambda x: x.get_angle()) - for index in range(len(self.parts)): + for index, part_1 in enumerate(self.parts): next_index: int = 0 if index == len(self.parts) - 1 else index + 1 - part_1: RoadPart = self.parts[index] part_2: RoadPart = self.parts[next_index] line_1: Line = Line( part_1.point_1 + part_1.right_vector, @@ -312,9 +311,8 @@ class Intersection: part_1.update() part_2.update() - for index in range(len(self.parts)): + for index, part_1 in enumerate(self.parts): next_index: int = 0 if index == len(self.parts) - 1 else index + 1 - part_1: RoadPart = self.parts[index] part_2: RoadPart = self.parts[next_index] part_1.update() part_2.update() @@ -412,10 +410,10 @@ class Road(Tagged): number: int if "lanes:forward" in tags: number = int(tags["lanes:forward"]) - [x.set_forward(True) for x in self.lanes[-number:]] + map(lambda x: x.set_forward(True), self.lanes[-number:]) if "lanes:backward" in tags: number = int(tags["lanes:backward"]) - [x.set_forward(False) for x in self.lanes[:number]] + map(lambda x: x.set_forward(False), self.lanes[:number]) if "width" in tags: try: @@ -731,20 +729,8 @@ class ComplexConnector(Connector): ] # fmt: on - def draw(self, svg: Drawing, draw_circle: bool = False) -> None: + def draw(self, svg: Drawing) -> None: """Draw connection fill.""" - if draw_circle: - for road, index in [ - (self.road_1, self.index_1), - (self.road_2, self.index_2), - ]: - circle: Circle = svg.circle( - road.line.points[index] - road.placement_offset, - road.width * self.scale / 2, - fill=road.get_color(), - ) - svg.add(circle) - path: Path = svg.path( d=["M"] + self.curve_1 + ["L"] + self.curve_2 + ["Z"], fill=self.road_1.get_color(), diff --git a/map_machine/main.py b/map_machine/main.py index 4feb30a..c68136f 100644 --- a/map_machine/main.py +++ b/map_machine/main.py @@ -34,7 +34,7 @@ def main() -> None: elif arguments.command == "tile": from map_machine.slippy import tile - tile.ui(arguments) + tile.generate_tiles(arguments) elif arguments.command == "icons": from map_machine.pictogram.icon_collection import draw_icons @@ -54,11 +54,11 @@ def main() -> None: elif arguments.command == "server": from map_machine.slippy import server - server.ui(arguments) + server.run_server(arguments) elif arguments.command == "taginfo": from map_machine.scheme import Scheme - from doc.taginfo import write_taginfo_project_file + from map_machine.doc.taginfo import write_taginfo_project_file write_taginfo_project_file(Scheme(workspace.DEFAULT_SCHEME_PATH)) diff --git a/map_machine/mapper.py b/map_machine/mapper.py index 0e9d954..6c73c63 100644 --- a/map_machine/mapper.py +++ b/map_machine/mapper.py @@ -231,7 +231,7 @@ def render_map(arguments: argparse.Namespace) -> None: for input_file_name in input_file_names: if not input_file_name.is_file(): logging.fatal(f"No such file: {input_file_name}.") - exit(1) + sys.exit(1) if input_file_name.name.endswith(".json"): osm_data.parse_overpass(input_file_name) diff --git a/map_machine/osm/osm_getter.py b/map_machine/osm/osm_getter.py index c69ccc5..e47f875 100644 --- a/map_machine/osm/osm_getter.py +++ b/map_machine/osm/osm_getter.py @@ -51,8 +51,8 @@ def get_osm( "Cannot download data: too many nodes (limit is 50000). Try " "to request smaller area." ) - else: - raise NetworkError("Cannot download data.") + + raise NetworkError("Cannot download data.") with cache_file_path.open("bw+") as output_file: output_file.write(content) diff --git a/map_machine/osm/osm_reader.py b/map_machine/osm/osm_reader.py index bc8f540..38bdf77 100644 --- a/map_machine/osm/osm_reader.py +++ b/map_machine/osm/osm_reader.py @@ -127,9 +127,9 @@ class OSMNode(Tagged): def from_xml_structure(cls, element: Element) -> "OSMNode": """Parse node from OSM XML `` element.""" attributes = element.attrib - tags: dict[str, str] = dict( - [(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"] - ) + tags: dict[str, str] = { + x.attrib["k"]: x.attrib["v"] for x in element if x.tag == "tag" + } return cls( tags, int(attributes["id"]), @@ -182,9 +182,9 @@ class OSMWay(Tagged): ) -> "OSMWay": """Parse way from OSM XML `` element.""" attributes = element.attrib - tags: dict[str, str] = dict( - [(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"] - ) + tags: dict[str, str] = { + x.attrib["k"]: x.attrib["v"] for x in element if x.tag == "tag" + } return cls( tags, int(element.attrib["id"]), @@ -302,8 +302,6 @@ class NotWellFormedOSMDataException(Exception): OSM data structure is not well-formed. """ - pass - class OSMData: """ diff --git a/map_machine/scheme.py b/map_machine/scheme.py index 4a6834e..6976d92 100644 --- a/map_machine/scheme.py +++ b/map_machine/scheme.py @@ -367,14 +367,16 @@ class Scheme: specification: Union[str, dict] = self.colors[color] if isinstance(specification, str): return Color(self.colors[color]) - else: - color: Color = self.get_color(specification["color"]) - if "darken" in specification: - percent: float = float(specification["darken"]) - color.set_luminance(color.get_luminance() * (1 - percent)) - return color + + color: Color = self.get_color(specification["color"]) + if "darken" in specification: + percent: float = float(specification["darken"]) + color.set_luminance(color.get_luminance() * (1 - percent)) + return color + if color.lower() in self.colors: return Color(self.colors[color.lower()]) + try: return Color(color) except (ValueError, AttributeError): @@ -593,11 +595,9 @@ class Scheme: :param tags: input tag dictionary :param processed: processed set """ - [ - processed.add(tag) - for tag in tags - if self.is_no_drawable(tag, tags[tag]) - ] + processed.update( + set(tag for tag in tags if self.is_no_drawable(tag, tags[tag])) + ) def get_shape_specification( self, diff --git a/map_machine/slippy/server.py b/map_machine/slippy/server.py index 281fc63..2bb1b6e 100644 --- a/map_machine/slippy/server.py +++ b/map_machine/slippy/server.py @@ -66,7 +66,7 @@ class _Handler(SimpleHTTPRequestHandler): return -def ui(options: argparse.Namespace) -> None: +def run_server(options: argparse.Namespace) -> None: """Command-line interface for tile server.""" server: Optional[HTTPServer] = None try: diff --git a/map_machine/slippy/tile.py b/map_machine/slippy/tile.py index a7f972d..e112d47 100644 --- a/map_machine/slippy/tile.py +++ b/map_machine/slippy/tile.py @@ -457,9 +457,9 @@ def parse_zoom_level(zoom_level_specification: str) -> list[int]: result: list[int] = [] for part in parts: if "-" in part: - from_, to = part.split("-") - from_zoom_level: int = parse(from_) - to_zoom_level: int = parse(to) + start, end = part.split("-") + from_zoom_level: int = parse(start) + to_zoom_level: int = parse(end) if from_zoom_level > to_zoom_level: raise ScaleConfigurationException("Wrong range.") result += range(from_zoom_level, to_zoom_level + 1) @@ -469,7 +469,7 @@ def parse_zoom_level(zoom_level_specification: str) -> list[int]: return result -def ui(options: argparse.Namespace) -> None: +def generate_tiles(options: argparse.Namespace) -> None: """Simple user interface for tile generation.""" directory: Path = workspace.get_tile_path() diff --git a/map_machine/text.py b/map_machine/text.py index 04738ec..8b4ec1c 100644 --- a/map_machine/text.py +++ b/map_machine/text.py @@ -104,6 +104,7 @@ 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 ) -> list[Label]: + """Construct list of labels from OSM tags.""" texts: list[Label] = [] diff --git a/setup.py b/setup.py index 277fc3e..a979a9d 100644 --- a/setup.py +++ b/setup.py @@ -14,7 +14,7 @@ from map_machine import ( REQUIREMENTS, ) -with Path("README.md").open() as input_file: +with Path("README.md").open(encoding="utf-8") as input_file: long_description: str = input_file.read() setup( diff --git a/tests/test_command_line.py b/tests/test_command_line.py index 102cb1e..80780fe 100644 --- a/tests/test_command_line.py +++ b/tests/test_command_line.py @@ -24,18 +24,18 @@ LOG: bytes = ( def error_run(arguments: list[str], message: bytes) -> None: """Run command that should fail and check error message.""" - p = Popen(["map-machine"] + arguments, stderr=PIPE) - _, error = p.communicate() - assert p.returncode != 0 - assert error == message + with Popen(["map-machine"] + arguments, stderr=PIPE) as pipe: + _, error = pipe.communicate() + assert pipe.returncode != 0 + assert error == message def run(arguments: list[str], message: bytes) -> None: """Run command that should fail and check error message.""" - p = Popen(["map-machine"] + arguments, stderr=PIPE) - _, error = p.communicate() - assert p.returncode == 0 - assert error == message + with Popen(["map-machine"] + arguments, stderr=PIPE) as pipe: + _, error = pipe.communicate() + assert pipe.returncode == 0 + assert error == message def test_wrong_render_arguments() -> None: @@ -53,7 +53,7 @@ def test_render() -> None: COMMAND_LINES["render"] + ["--cache", "tests/data"], LOG + b"INFO Writing output SVG to out/map.svg...\n", ) - with Path("out/map.svg").open() as output_file: + with Path("out/map.svg").open(encoding="utf-8") as output_file: root: Element = ElementTree.parse(output_file).getroot() # 4 expected elements: `defs`, `rect` (background), `g` (outline), @@ -70,7 +70,7 @@ def test_render_with_tooltips() -> None: COMMAND_LINES["render_with_tooltips"] + ["--cache", "tests/data"], LOG + b"INFO Writing output SVG to out/map.svg...\n", ) - with Path("out/map.svg").open() as output_file: + with Path("out/map.svg").open(encoding="utf-8") as output_file: root: Element = ElementTree.parse(output_file).getroot() # 4 expected elements: `defs`, `rect` (background), `g` (outline), diff --git a/tests/test_elements.py b/tests/test_elements.py index 781373e..d795b78 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -159,7 +159,7 @@ def road_features( grid: Grid = Grid() - for i in range(len(types)): + for i, type_ in enumerate(types): previous: Optional[OSMNode] = None for j in range(len(features) + 1): @@ -167,7 +167,7 @@ def road_features( if previous: tags: dict[str, str] = dict(features[j - 1]) - tags |= types[i] + tags |= type_ way: OSMWay = OSMWay( tags, i * (len(features) + 1) + j, [previous, node] ) diff --git a/tests/test_icons.py b/tests/test_icons.py index 4837ffd..e2b99b8 100644 --- a/tests/test_icons.py +++ b/tests/test_icons.py @@ -7,7 +7,7 @@ import pytest from colour import Color from map_machine.pictogram.icon_collection import IconCollection -from map_machine.pictogram.icon import IconSet +from map_machine.pictogram.icon import IconSet, ShapeSpecification, Icon from tests import SCHEME, SHAPE_EXTRACTOR, workspace __author__ = "Sergey Vartanov" @@ -47,14 +47,14 @@ def test_no_icons() -> None: Tags that has no description in scheme and should be visualized with default shape. """ - icon = get_icon({"aaa": "bbb"}) + icon: IconSet = get_icon({"aaa": "bbb"}) assert icon.main_icon.is_default() def check_icon_set( icon: IconSet, main_specification: list[tuple[str, Optional[str]]], - extra_specification: list[list[tuple[str, Optional[str]]]], + extra_specifications: list[list[tuple[str, Optional[str]]]], ) -> None: """Check icon set using simple specification.""" if not main_specification: @@ -64,17 +64,20 @@ def check_icon_set( assert len(main_specification) == len( icon.main_icon.shape_specifications ) - for i, s in enumerate(main_specification): - assert icon.main_icon.shape_specifications[i].shape.id_ == s[0] - assert icon.main_icon.shape_specifications[i].color == Color(s[1]) + for index, shape in enumerate(main_specification): + shape_specification: ShapeSpecification = ( + icon.main_icon.shape_specifications[index] + ) + assert shape_specification.shape.id_ == shape[0] + assert shape_specification.color == Color(shape[1]) - assert len(extra_specification) == len(icon.extra_icons) - for i, x in enumerate(extra_specification): - extra_icon = icon.extra_icons[i] - assert len(x) == len(extra_icon.shape_specifications) - for j, s in enumerate(x): - assert extra_icon.shape_specifications[j].shape.id_ == s[0] - assert extra_icon.shape_specifications[j].color == Color(s[1]) + assert len(extra_specifications) == len(icon.extra_icons) + for i, extra_specification in enumerate(extra_specifications): + extra_icon: Icon = icon.extra_icons[i] + assert len(extra_specification) == len(extra_icon.shape_specifications) + for j, shape in enumerate(extra_specification): + assert extra_icon.shape_specifications[j].shape.id_ == shape[0] + assert extra_icon.shape_specifications[j].color == Color(shape[1]) def test_icon() -> None: @@ -90,7 +93,7 @@ def test_icon_1_extra() -> None: """ Tags that should be visualized with single main icon and single extra icon. """ - icon = get_icon({"barrier": "gate", "access": "private"}) + icon: IconSet = get_icon({"barrier": "gate", "access": "private"}) check_icon_set( icon, [("gate", "#444444")], [[("lock_with_keyhole", "#888888")]] ) @@ -100,7 +103,9 @@ def test_icon_2_extra() -> None: """ Tags that should be visualized with single main icon and two extra icons. """ - icon = get_icon({"barrier": "gate", "access": "private", "bicycle": "yes"}) + icon: IconSet = get_icon( + {"barrier": "gate", "access": "private", "bicycle": "yes"} + ) check_icon_set( icon, [("gate", "#444444")], @@ -115,7 +120,7 @@ def test_no_icon_1_extra() -> None: """ Tags that should be visualized with default main icon and single extra icon. """ - icon = get_icon({"access": "private"}) + icon: IconSet = get_icon({"access": "private"}) check_icon_set(icon, [], [[("lock_with_keyhole", "#888888")]]) @@ -123,7 +128,7 @@ def test_no_icon_2_extra() -> None: """ Tags that should be visualized with default main icon and two extra icons. """ - icon = get_icon({"access": "private", "bicycle": "yes"}) + icon: IconSet = get_icon({"access": "private", "bicycle": "yes"}) check_icon_set( icon, [], @@ -138,7 +143,7 @@ def test_icon_regex() -> None: """ Tags that should be visualized with default main icon and single extra icon. """ - icon = get_icon({"traffic_sign": "maxspeed", "maxspeed": "42"}) + icon: IconSet = get_icon({"traffic_sign": "maxspeed", "maxspeed": "42"}) check_icon_set( icon, [ diff --git a/tests/test_requirements.py b/tests/test_requirements.py index a2527fa..70eb1f7 100644 --- a/tests/test_requirements.py +++ b/tests/test_requirements.py @@ -1,13 +1,15 @@ """ Check whether `requirements.txt` contains all requirements from `setup.py`. """ -from map_machine import REQUIREMENTS from pathlib import Path +from map_machine import REQUIREMENTS + def test_requirements() -> None: + """Test whether `requirements.txt` has the same packages as `setup.py`.""" requirements: list[str] - with Path("requirements.txt").open() as requirements_file: + with Path("requirements.txt").open(encoding="utf-8") as requirements_file: requirements = list( map(lambda x: x[:-1], requirements_file.readlines()) )