From 9385a2233e9f046b4527fc1835289a9c58b59845 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Fri, 20 May 2022 23:57:25 +0300 Subject: [PATCH 01/21] Reformat icon tests. --- tests/test_icons.py | 37 ++++++++++++++++++------------------- 1 file changed, 18 insertions(+), 19 deletions(-) diff --git a/tests/test_icons.py b/tests/test_icons.py index c8f1154..a29f908 100644 --- a/tests/test_icons.py +++ b/tests/test_icons.py @@ -6,6 +6,7 @@ from typing import Optional from colour import Color +from map_machine.osm.osm_reader import Tags from map_machine.pictogram.icon import IconSet, ShapeSpecification, Icon from map_machine.pictogram.icon_collection import IconCollection from tests import SCHEME, SHAPE_EXTRACTOR, workspace @@ -41,7 +42,7 @@ def test_icons_by_name() -> None: assert (path / "LICENSE").is_file() -def get_icon(tags: dict[str, str]) -> IconSet: +def get_icon(tags: Tags) -> IconSet: """Construct icon from tags.""" processed: set[str] = set() icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed) @@ -69,11 +70,13 @@ def test_no_icons_but_color() -> None: def check_icon_set( - icon: IconSet, + tags: Tags, main_specification: list[tuple[str, Optional[Color]]], extra_specifications: list[list[tuple[str, Optional[Color]]]], ) -> None: """Check icon set using simple specification.""" + icon: IconSet = get_icon(tags) + if not main_specification: assert icon.main_icon.is_default() else: @@ -102,17 +105,17 @@ def test_icon() -> None: Tags that should be visualized with single main icon and without extra icons. """ - icon: IconSet = get_icon({"natural": "tree"}) - check_icon_set(icon, [("tree", Color("#98AC64"))], []) + check_icon_set({"natural": "tree"}, [("tree", Color("#98AC64"))], []) def test_icon_1_extra() -> None: """ Tags that should be visualized with single main icon and single extra icon. """ - icon: IconSet = get_icon({"barrier": "gate", "access": "private"}) check_icon_set( - icon, [("gate", DEFAULT_COLOR)], [[("lock_with_keyhole", EXTRA_COLOR)]] + {"barrier": "gate", "access": "private"}, + [("gate", DEFAULT_COLOR)], + [[("lock_with_keyhole", EXTRA_COLOR)]], ) @@ -120,11 +123,8 @@ def test_icon_2_extra() -> None: """ Tags that should be visualized with single main icon and two extra icons. """ - icon: IconSet = get_icon( - {"barrier": "gate", "access": "private", "bicycle": "yes"} - ) check_icon_set( - icon, + {"barrier": "gate", "access": "private", "bicycle": "yes"}, [("gate", DEFAULT_COLOR)], [ [("bicycle", EXTRA_COLOR)], @@ -137,17 +137,17 @@ def test_no_icon_1_extra() -> None: """ Tags that should be visualized with default main icon and single extra icon. """ - icon: IconSet = get_icon({"access": "private"}) - check_icon_set(icon, [], [[("lock_with_keyhole", EXTRA_COLOR)]]) + check_icon_set( + {"access": "private"}, [], [[("lock_with_keyhole", EXTRA_COLOR)]] + ) def test_no_icon_2_extra() -> None: """ Tags that should be visualized with default main icon and two extra icons. """ - icon: IconSet = get_icon({"access": "private", "bicycle": "yes"}) check_icon_set( - icon, + {"access": "private", "bicycle": "yes"}, [], [[("bicycle", EXTRA_COLOR)], [("lock_with_keyhole", EXTRA_COLOR)]], ) @@ -157,9 +157,8 @@ def test_icon_regex() -> None: """ Tags that should be visualized with default main icon and single extra icon. """ - icon: IconSet = get_icon({"traffic_sign": "maxspeed", "maxspeed": "42"}) check_icon_set( - icon, + {"traffic_sign": "maxspeed", "maxspeed": "42"}, [("circle_11", DEFAULT_COLOR), ("digit_4", WHITE), ("digit_2", WHITE)], [], ) @@ -172,17 +171,17 @@ def test_vending_machine() -> None: See https://github.com/enzet/map-machine/issues/132 """ check_icon_set( - get_icon({"amenity": "vending_machine"}), + {"amenity": "vending_machine"}, [("vending_machine", DEFAULT_COLOR)], [], ) check_icon_set( - get_icon({"amenity": "vending_machine", "vending": "drinks"}), + {"amenity": "vending_machine", "vending": "drinks"}, [("vending_bottle", DEFAULT_COLOR)], [], ) check_icon_set( - get_icon({"vending": "drinks"}), + {"vending": "drinks"}, [("vending_bottle", DEFAULT_COLOR)], [], ) From 95f844ef67ed97500e6c324d24a9c6450c22a434 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Sat, 21 May 2022 00:00:47 +0300 Subject: [PATCH 02/21] Optimize imports. --- map_machine/constructor.py | 4 +--- map_machine/doc/doc_collections.py | 2 +- map_machine/doc/icons.py | 1 - map_machine/doc/preview.py | 6 +++--- map_machine/doc/wiki.py | 1 + map_machine/element/test_elements.py | 4 ++-- tests/test_ways.py | 2 +- 7 files changed, 9 insertions(+), 11 deletions(-) diff --git a/map_machine/constructor.py b/map_machine/constructor.py index bf1c50c..76d7e98 100644 --- a/map_machine/constructor.py +++ b/map_machine/constructor.py @@ -15,10 +15,8 @@ from map_machine.feature.building import Building, BUILDING_SCALE from map_machine.feature.crater import Crater from map_machine.feature.direction import DirectionSector from map_machine.feature.road import Road, Roads -from map_machine.figure import ( - StyledFigure, -) from map_machine.feature.tree import Tree +from map_machine.figure import StyledFigure from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import DrawingMode, MapConfiguration from map_machine.osm.osm_reader import ( diff --git a/map_machine/doc/doc_collections.py b/map_machine/doc/doc_collections.py index a3e8904..181a644 100644 --- a/map_machine/doc/doc_collections.py +++ b/map_machine/doc/doc_collections.py @@ -9,8 +9,8 @@ from typing import Any, Optional import numpy as np import svgwrite from svgwrite import Drawing -from svgwrite.text import Text from svgwrite.shapes import Line, Rect +from svgwrite.text import Text from map_machine.map_configuration import MapConfiguration from map_machine.osm.osm_reader import Tags diff --git a/map_machine/doc/icons.py b/map_machine/doc/icons.py index 7ec9df8..376059b 100644 --- a/map_machine/doc/icons.py +++ b/map_machine/doc/icons.py @@ -15,7 +15,6 @@ from map_machine.pictogram.icon import ( from map_machine.pictogram.icon_collection import IconCollection from map_machine.workspace import workspace - SKIP: bool = True diff --git a/map_machine/doc/preview.py b/map_machine/doc/preview.py index 2c5f5d7..1cb7035 100755 --- a/map_machine/doc/preview.py +++ b/map_machine/doc/preview.py @@ -9,19 +9,19 @@ from typing import Optional import numpy as np import svgwrite -from map_machine.geometry.boundary_box import BoundaryBox from map_machine.constructor import Constructor +from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger -from map_machine.pictogram.icon import ShapeExtractor -from map_machine.mapper import Map from map_machine.map_configuration import ( BuildingMode, DrawingMode, LabelMode, MapConfiguration, ) +from map_machine.mapper import Map from map_machine.osm.osm_getter import get_osm from map_machine.osm.osm_reader import OSMData +from map_machine.pictogram.icon import ShapeExtractor from map_machine.scheme import Scheme doc_path: Path = Path("doc") diff --git a/map_machine/doc/wiki.py b/map_machine/doc/wiki.py index 1a5ede4..516b432 100644 --- a/map_machine/doc/wiki.py +++ b/map_machine/doc/wiki.py @@ -6,6 +6,7 @@ from pathlib import Path from typing import Optional from map_machine.doc.collections import Collection + from map_machine.map_configuration import MapConfiguration from map_machine.osm.osm_reader import Tags from map_machine.pictogram.icon import Icon, ShapeExtractor diff --git a/map_machine/element/test_elements.py b/map_machine/element/test_elements.py index 2e68e6b..a75860b 100644 --- a/map_machine/element/test_elements.py +++ b/map_machine/element/test_elements.py @@ -8,13 +8,13 @@ from typing import Optional import numpy as np from svgwrite import Drawing -from map_machine.geometry.boundary_box import BoundaryBox from map_machine.constructor import Constructor +from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger -from map_machine.pictogram.icon import ShapeExtractor from map_machine.map_configuration import MapConfiguration from map_machine.mapper import Map from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay +from map_machine.pictogram.icon import ShapeExtractor from map_machine.scheme import Scheme from map_machine.workspace import Workspace diff --git a/tests/test_ways.py b/tests/test_ways.py index 3708c24..cb6ba93 100644 --- a/tests/test_ways.py +++ b/tests/test_ways.py @@ -1,10 +1,10 @@ import numpy as np +from map_machine.constructor import Constructor from map_machine.figure import Figure from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import MapConfiguration -from map_machine.constructor import Constructor from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode from tests import SCHEME, SHAPE_EXTRACTOR From d095cbeec38b3ce7eb6889fb1c936c13487f0583 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 23 May 2022 23:53:07 +0300 Subject: [PATCH 03/21] Remove redundant field. --- map_machine/element/test_elements.py | 1 - 1 file changed, 1 deletion(-) diff --git a/map_machine/element/test_elements.py b/map_machine/element/test_elements.py index a75860b..8ca93b6 100644 --- a/map_machine/element/test_elements.py +++ b/map_machine/element/test_elements.py @@ -123,7 +123,6 @@ class Grid: def __init__(self) -> None: self.x_step: float = 0.0002 self.y_step: float = 0.0003 - self.x_start: float = 0.0 self.index: int = 0 self.nodes: dict[OSMNode, tuple[int, int]] = {} self.max_j: float = 0 From 1bfdcee1d75ed13248d2ee6d55763485341b0fe9 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 23 May 2022 23:54:25 +0300 Subject: [PATCH 04/21] Move drawing elements file. --- map_machine/{element/test_elements.py => doc/draw_elements.py} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename map_machine/{element/test_elements.py => doc/draw_elements.py} (100%) diff --git a/map_machine/element/test_elements.py b/map_machine/doc/draw_elements.py similarity index 100% rename from map_machine/element/test_elements.py rename to map_machine/doc/draw_elements.py From df67d8c37acffc633fd1522800c03619e25f086e Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 23 May 2022 23:58:33 +0300 Subject: [PATCH 05/21] Move tags to separate file. --- map_machine/doc/draw_elements.py | 40 +------------------------------- map_machine/osm/tags.py | 35 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 39 deletions(-) create mode 100644 map_machine/osm/tags.py diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index 8ca93b6..a4dfab2 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -14,6 +14,7 @@ from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import MapConfiguration from map_machine.mapper import Map from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay +from map_machine.osm.tags import HIGHWAY_VALUES, AEROWAY_VALUES, RAILWAY_TAGS from map_machine.pictogram.icon import ShapeExtractor from map_machine.scheme import Scheme from map_machine.workspace import Workspace @@ -26,45 +27,6 @@ SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor( ) DEFAULT_ZOOM: float = 18.0 - -HIGHWAY_VALUES: list[str] = [ - "motorway", - "trunk", - "primary", - "secondary", - "tertiary", - "unclassified", - "residential", - "service", - "service_minor", - "road", - "pedestrian", - "living_street", - "bridleway", - "cycleway", - "footway", - "steps", - "path", - "track", - "raceway", -] - -AEROWAY_VALUES: list[str] = [ - "runway", - "taxiway", -] - -RAILWAY_TAGS: list[dict[str, str]] = [ - {"railway": "rail"}, - {"railway": "light_rail"}, - {"railway": "monorail"}, - {"railway": "funicular"}, - {"railway": "narrow_gauge"}, - {"railway": "subway"}, - {"railway": "subway", "color": "red"}, - {"railway": "subway", "color": "blue"}, -] - ROAD_WIDTHS_AND_FEATURES: list[dict[str, str]] = [ {"width": "4"}, {"width": "8"}, diff --git a/map_machine/osm/tags.py b/map_machine/osm/tags.py new file mode 100644 index 0000000..95b2b4e --- /dev/null +++ b/map_machine/osm/tags.py @@ -0,0 +1,35 @@ +HIGHWAY_VALUES: list[str] = [ + "motorway", + "trunk", + "primary", + "secondary", + "tertiary", + "unclassified", + "residential", + "service", + "service_minor", + "road", + "pedestrian", + "living_street", + "bridleway", + "cycleway", + "footway", + "steps", + "path", + "track", + "raceway", +] +AEROWAY_VALUES: list[str] = [ + "runway", + "taxiway", +] +RAILWAY_TAGS: list[dict[str, str]] = [ + {"railway": "rail"}, + {"railway": "light_rail"}, + {"railway": "monorail"}, + {"railway": "funicular"}, + {"railway": "narrow_gauge"}, + {"railway": "subway"}, + {"railway": "subway", "color": "red"}, + {"railway": "subway", "color": "blue"}, +] From f15a025524018100fc0c480ce5e0adc527b485c0 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Tue, 24 May 2022 01:58:57 +0300 Subject: [PATCH 06/21] Extract path variable. --- map_machine/doc/draw_elements.py | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index a4dfab2..34f2f83 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -41,8 +41,6 @@ ROAD_WIDTHS_AND_FEATURES: list[dict[str, str]] = [ {"embankment": "yes", "width": "4"}, {"embankment": "yes", "width": "8"}, ] - - ROAD_LANES_AND_FEATURES: list[dict[str, str]] = [ {"lanes": "1"}, {"lanes": "2"}, @@ -68,7 +66,6 @@ PLACEMENT_FEATURES_1: list[dict[str, str]] = [ {"placement": "transition"}, {"lanes": "3", "placement": "right_of:1"}, # or placement=left_of:2 ] - PLACEMENT_FEATURES_2: list[dict[str, str]] = [ {"lanes": "2"}, # or placement:backward=left_of:1 @@ -113,7 +110,7 @@ class Grid: ) -def road_features( +def draw_road_features( types: list[dict[str, str]], features: list[dict[str, str]], path: Path ) -> None: """Draw test image with different road features.""" @@ -164,6 +161,8 @@ def draw( if __name__ == "__main__": logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO) + out_path: Path = Path("out") + highway_tags: list[dict[str, str]] = [ {"highway": value} for value in HIGHWAY_VALUES ] @@ -171,16 +170,16 @@ if __name__ == "__main__": {"aeroway": value} for value in AEROWAY_VALUES ] - road_features( - highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg" + draw_road_features( + highway_tags, ROAD_LANES_AND_FEATURES, out_path / "lanes.svg" ) - road_features( + draw_road_features( highway_tags + RAILWAY_TAGS + aeroway_tags, ROAD_WIDTHS_AND_FEATURES, - Path("out") / "width.svg", + out_path / "width.svg", ) - road_features( + draw_road_features( highway_tags, PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2, - Path("out") / "placement.svg", + out_path / "placement.svg", ) From 080f37d2c63c5d6b3996bb0ddea6772ec2ab219c Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Tue, 24 May 2022 09:55:47 +0300 Subject: [PATCH 07/21] Add overlapping ways drawing. --- map_machine/doc/draw_elements.py | 41 ++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 7 deletions(-) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index 34f2f83..9394472 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -2,6 +2,7 @@ Draw test nodes, ways, and relations. """ import logging +from dataclasses import dataclass, field from pathlib import Path from typing import Optional @@ -76,16 +77,16 @@ PLACEMENT_FEATURES_2: list[dict[str, str]] = [ ] +@dataclass class Grid: """Creating map with elements ordered in grid.""" - def __init__(self) -> None: - self.x_step: float = 0.0002 - self.y_step: float = 0.0003 - self.index: int = 0 - self.nodes: dict[OSMNode, tuple[int, int]] = {} - self.max_j: float = 0 - self.max_i: float = 0 + x_step: float = 0.0002 + y_step: float = 0.0003 + index: int = 0 + nodes: dict[OSMNode, tuple[int, int]] = field(default_factory=dict) + max_j: float = 0 + max_i: float = 0 def add_node(self, tags: dict[str, str], i: int, j: int) -> OSMNode: """Add OSM node to the grid.""" @@ -110,6 +111,31 @@ class Grid: ) +def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None: + """ + Draw two sets of ways intersecting each other to show how they overlapping. + """ + osm_data: OSMData = OSMData() + grid: Grid = Grid(0.00012, 0.00012) + way_id: int = 0 + + for i, type_1 in enumerate(types): + node_1: OSMNode = grid.add_node({}, i + 1, 0) + node_2: OSMNode = grid.add_node({}, i + 1, len(types) + 1) + way: OSMWay = OSMWay(type_1, way_id, [node_1, node_2]) + way_id += 1 + osm_data.add_way(way) + + for i, type_1 in enumerate(types): + node_1: OSMNode = grid.add_node({}, 0, i + 1) + node_2: OSMNode = grid.add_node({}, len(types) + 1, i + 1) + way: OSMWay = OSMWay(type_1, way_id, [node_1, node_2]) + way_id += 1 + osm_data.add_way(way) + + draw(osm_data, path, grid.get_boundary_box()) + + def draw_road_features( types: list[dict[str, str]], features: list[dict[str, str]], path: Path ) -> None: @@ -183,3 +209,4 @@ if __name__ == "__main__": PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2, out_path / "placement.svg", ) + draw_overlapped_ways(highway_tags, out_path / "overlap.svg") From 7373eadaa675ecf7b347ac5e3a904a511574bee0 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Tue, 24 May 2022 10:18:42 +0300 Subject: [PATCH 08/21] Refactor element drawing. --- map_machine/doc/draw_elements.py | 101 ++++++++++++++----------------- 1 file changed, 47 insertions(+), 54 deletions(-) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index 9394472..1358bf5 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -2,7 +2,6 @@ Draw test nodes, ways, and relations. """ import logging -from dataclasses import dataclass, field from pathlib import Path from typing import Optional @@ -14,7 +13,7 @@ from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import MapConfiguration from map_machine.mapper import Map -from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay +from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay, Tags from map_machine.osm.tags import HIGHWAY_VALUES, AEROWAY_VALUES, RAILWAY_TAGS from map_machine.pictogram.icon import ShapeExtractor from map_machine.scheme import Scheme @@ -77,18 +76,20 @@ PLACEMENT_FEATURES_2: list[dict[str, str]] = [ ] -@dataclass class Grid: """Creating map with elements ordered in grid.""" - x_step: float = 0.0002 - y_step: float = 0.0003 - index: int = 0 - nodes: dict[OSMNode, tuple[int, int]] = field(default_factory=dict) - max_j: float = 0 - max_i: float = 0 + def __init__(self, x_step: float = 0.0002, y_step: float = 0.0003): + self.x_step: float = x_step + self.y_step: float = y_step + self.index: int = 0 + self.nodes: dict[OSMNode, tuple[int, int]] = {} + self.max_j: float = 0 + self.max_i: float = 0 + self.way_id: int = 0 + self.osm_data: OSMData = OSMData() - def add_node(self, tags: dict[str, str], i: int, j: int) -> OSMNode: + def add_node(self, tags: Tags, i: int, j: int) -> OSMNode: """Add OSM node to the grid.""" self.index += 1 node: OSMNode = OSMNode( @@ -101,6 +102,12 @@ class Grid: self.max_i = max(self.max_i, i * self.y_step) return node + def add_way(self, tags: Tags, nodes: list[OSMNode]) -> None: + """Add OSM way to the grid.""" + osm_way: OSMWay = OSMWay(tags, self.way_id, nodes) + self.osm_data.add_way(osm_way) + self.way_id += 1 + def get_boundary_box(self) -> BoundaryBox: """Compute resulting boundary box with margin of one grid step.""" return BoundaryBox( @@ -110,37 +117,49 @@ class Grid: self.y_step, ) + def draw(self, output_path: Path, zoom: float = DEFAULT_ZOOM) -> None: + """Draw grid.""" + configuration: MapConfiguration = MapConfiguration(level="all") + + flinger: Flinger = Flinger( + self.get_boundary_box(), zoom, self.osm_data.equator_length + ) + svg: Drawing = Drawing(output_path.name, flinger.size) + constructor: Constructor = Constructor( + self.osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration + ) + constructor.construct() + map_: Map = Map(flinger, svg, SCHEME, configuration) + map_.draw(constructor) + + with output_path.open("w") as output_file: + svg.write(output_file) + logging.info(f"Map is drawn to {output_path}.") + def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None: """ Draw two sets of ways intersecting each other to show how they overlapping. """ - osm_data: OSMData = OSMData() grid: Grid = Grid(0.00012, 0.00012) - way_id: int = 0 - for i, type_1 in enumerate(types): - node_1: OSMNode = grid.add_node({}, i + 1, 0) - node_2: OSMNode = grid.add_node({}, i + 1, len(types) + 1) - way: OSMWay = OSMWay(type_1, way_id, [node_1, node_2]) - way_id += 1 - osm_data.add_way(way) + for index, tags in enumerate(types): + node_1: OSMNode = grid.add_node({}, index + 1, 0) + node_2: OSMNode = grid.add_node({}, index + 1, len(types) + 1) + grid.add_way(tags, [node_1, node_2]) - for i, type_1 in enumerate(types): - node_1: OSMNode = grid.add_node({}, 0, i + 1) - node_2: OSMNode = grid.add_node({}, len(types) + 1, i + 1) - way: OSMWay = OSMWay(type_1, way_id, [node_1, node_2]) - way_id += 1 - osm_data.add_way(way) + for index, tags in enumerate(types): + node_1: OSMNode = grid.add_node({}, 0, index + 1) + node_2: OSMNode = grid.add_node({}, len(types) + 1, index + 1) + grid.add_way(tags, [node_1, node_2]) - draw(osm_data, path, grid.get_boundary_box()) + grid.draw(path) def draw_road_features( types: list[dict[str, str]], features: list[dict[str, str]], path: Path ) -> None: """Draw test image with different road features.""" - osm_data: OSMData = OSMData() grid: Grid = Grid() for i, type_ in enumerate(types): @@ -152,36 +171,10 @@ def draw_road_features( if previous: tags: dict[str, str] = dict(type_) tags |= dict(features[j - 1]) - way: OSMWay = OSMWay( - tags, i * (len(features) + 1) + j, [previous, node] - ) - osm_data.add_way(way) + grid.add_way(tags, [previous, node]) previous = node - draw(osm_data, path, grid.get_boundary_box()) - - -def draw( - osm_data: OSMData, - output_path: Path, - boundary_box: BoundaryBox, - zoom: float = DEFAULT_ZOOM, -) -> None: - """Draw map.""" - configuration: MapConfiguration = MapConfiguration(level="all") - - flinger: Flinger = Flinger(boundary_box, zoom, osm_data.equator_length) - svg: Drawing = Drawing(output_path.name, flinger.size) - constructor: Constructor = Constructor( - osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration - ) - constructor.construct() - map_: Map = Map(flinger, svg, SCHEME, configuration) - map_.draw(constructor) - - with output_path.open("w") as output_file: - svg.write(output_file) - logging.info(f"Map is drawn to {output_path}.") + grid.draw(path) if __name__ == "__main__": From 9b5066fd4412ff70a306fe130daa3830d3fe45ba Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Wed, 25 May 2022 01:05:32 +0300 Subject: [PATCH 09/21] Make data credit optional. --- map_machine/doc/draw_elements.py | 13 +++++++------ map_machine/map_configuration.py | 1 + map_machine/mapper.py | 18 ++++++++++-------- 3 files changed, 18 insertions(+), 14 deletions(-) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index 1358bf5..d29be80 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -111,16 +111,17 @@ class Grid: def get_boundary_box(self) -> BoundaryBox: """Compute resulting boundary box with margin of one grid step.""" return BoundaryBox( - -self.x_step, - -self.max_i - self.y_step, - self.max_j + self.x_step, - self.y_step, + -self.x_step * 1.5, + -self.max_i - self.y_step * 1.5, + self.max_j + self.x_step * 1.5, + self.y_step * 1.5, ) def draw(self, output_path: Path, zoom: float = DEFAULT_ZOOM) -> None: """Draw grid.""" - configuration: MapConfiguration = MapConfiguration(level="all") - + configuration: MapConfiguration = MapConfiguration( + level="all", credit=None + ) flinger: Flinger = Flinger( self.get_boundary_box(), zoom, self.osm_data.equator_length ) diff --git a/map_machine/map_configuration.py b/map_machine/map_configuration.py index 4727652..9161391 100644 --- a/map_machine/map_configuration.py +++ b/map_machine/map_configuration.py @@ -57,6 +57,7 @@ class MapConfiguration: draw_roofs: bool = True use_building_colors: bool = False show_overlapped: bool = False + credit: Optional[str] = "© OpenStreetMap contributors" @classmethod def from_options( diff --git a/map_machine/mapper.py b/map_machine/mapper.py index 7f0b116..e8c67d0 100644 --- a/map_machine/mapper.py +++ b/map_machine/mapper.py @@ -34,8 +34,6 @@ from map_machine.workspace import workspace __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" -OPENSTREETMAP_CREDIT: str = "© OpenStreetMap contributors" - class Map: """Map drawing.""" @@ -207,13 +205,17 @@ class Map: text_color: Color = Color("#888888") outline_color: Color = Color("#FFFFFF") - for text, point in ( - ( - f"Data: {OPENSTREETMAP_CREDIT}", + credit_list: list[tuple[str, tuple[float, float]]] = [ + (f"Rendering: {__project__}", (right_margin, bottom_margin)) + ] + if self.configuration.credit: + data_credit: tuple[str, tuple[float, float]] = ( + f"Data: {self.configuration.credit}", (right_margin, bottom_margin + font_size + vertical_spacing), - ), - (f"Rendering: {__project__}", (right_margin, bottom_margin)), - ): + ) + credit_list.append(data_credit) + + for text, point in credit_list: for stroke_width, stroke, opacity in ( (3.0, outline_color, 0.7), (1.0, None, 1.0), From d83205aad0815013c8e909178ff6296d598a3746 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Wed, 25 May 2022 01:06:31 +0300 Subject: [PATCH 10/21] Fix #133: typo in motorcar=yes. --- map_machine/scheme/default.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/map_machine/scheme/default.yml b/map_machine/scheme/default.yml index 77ec7c2..4aff00c 100644 --- a/map_machine/scheme/default.yml +++ b/map_machine/scheme/default.yml @@ -1949,7 +1949,7 @@ node_icons: - tags: {bus: "yes"} add_shapes: [bus] - - tags: {motocar: "yes"} + - tags: {motorcar: "yes"} add_shapes: [car] - tags: {car: "yes"} add_shapes: [car] From 147a5de0d5616ea60b68947839140074dd0de212 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Fri, 27 May 2022 01:25:08 +0300 Subject: [PATCH 11/21] Refactor tags in element drawing. --- map_machine/doc/draw_elements.py | 26 +++++++++++++++++++------- map_machine/osm/tags.py | 26 ++++++++++++++++---------- 2 files changed, 35 insertions(+), 17 deletions(-) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index d29be80..60cf4ef 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -7,6 +7,7 @@ from typing import Optional import numpy as np from svgwrite import Drawing +from svgwrite.text import Text from map_machine.constructor import Constructor from map_machine.geometry.boundary_box import BoundaryBox @@ -14,7 +15,12 @@ from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import MapConfiguration from map_machine.mapper import Map from map_machine.osm.osm_reader import OSMData, OSMNode, OSMWay, Tags -from map_machine.osm.tags import HIGHWAY_VALUES, AEROWAY_VALUES, RAILWAY_TAGS +from map_machine.osm.tags import ( + HIGHWAY_VALUES, + AEROWAY_VALUES, + RAILWAY_VALUES, + ROAD_VALUES, +) from map_machine.pictogram.icon import ShapeExtractor from map_machine.scheme import Scheme from map_machine.workspace import Workspace @@ -145,13 +151,13 @@ def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None: grid: Grid = Grid(0.00012, 0.00012) for index, tags in enumerate(types): - node_1: OSMNode = grid.add_node({}, index + 1, 0) - node_2: OSMNode = grid.add_node({}, index + 1, len(types) + 1) + node_1: OSMNode = grid.add_node({}, index + 1, 8) + node_2: OSMNode = grid.add_node({}, index + 1, len(types) + 9) grid.add_way(tags, [node_1, node_2]) for index, tags in enumerate(types): - node_1: OSMNode = grid.add_node({}, 0, index + 1) - node_2: OSMNode = grid.add_node({}, len(types) + 1, index + 1) + node_1: OSMNode = grid.add_node({}, 0, index + 9) + node_2: OSMNode = grid.add_node({}, len(types) + 1, index + 9) grid.add_way(tags, [node_1, node_2]) grid.draw(path) @@ -183,18 +189,24 @@ if __name__ == "__main__": out_path: Path = Path("out") + road_tags: list[dict[str, str]] = [ + {"highway": value} for value in ROAD_VALUES + ] highway_tags: list[dict[str, str]] = [ {"highway": value} for value in HIGHWAY_VALUES ] aeroway_tags: list[dict[str, str]] = [ {"aeroway": value} for value in AEROWAY_VALUES ] + railway_tags: list[dict[str, str]] = [ + {"railway": value} for value in RAILWAY_VALUES + ] draw_road_features( highway_tags, ROAD_LANES_AND_FEATURES, out_path / "lanes.svg" ) draw_road_features( - highway_tags + RAILWAY_TAGS + aeroway_tags, + highway_tags + railway_tags + aeroway_tags, ROAD_WIDTHS_AND_FEATURES, out_path / "width.svg", ) @@ -203,4 +215,4 @@ if __name__ == "__main__": PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2, out_path / "placement.svg", ) - draw_overlapped_ways(highway_tags, out_path / "overlap.svg") + draw_overlapped_ways(road_tags + railway_tags, out_path / "overlap.svg") diff --git a/map_machine/osm/tags.py b/map_machine/osm/tags.py index 95b2b4e..effa0e6 100644 --- a/map_machine/osm/tags.py +++ b/map_machine/osm/tags.py @@ -1,4 +1,4 @@ -HIGHWAY_VALUES: list[str] = [ +ROAD_VALUES: list[str] = [ "motorway", "trunk", "primary", @@ -7,6 +7,8 @@ HIGHWAY_VALUES: list[str] = [ "unclassified", "residential", "service", +] +HIGHWAY_VALUES: list[str] = ROAD_VALUES + [ "service_minor", "road", "pedestrian", @@ -23,13 +25,17 @@ AEROWAY_VALUES: list[str] = [ "runway", "taxiway", ] -RAILWAY_TAGS: list[dict[str, str]] = [ - {"railway": "rail"}, - {"railway": "light_rail"}, - {"railway": "monorail"}, - {"railway": "funicular"}, - {"railway": "narrow_gauge"}, - {"railway": "subway"}, - {"railway": "subway", "color": "red"}, - {"railway": "subway", "color": "blue"}, +RAILWAY_VALUES: list[str] = [ + "rail", + "subway", + "light_rail", + "monorail", + "narrow_gauge", + "tram", + "funicular", + "miniature", + "preserved", + "construction", + "disused", + "abandoned", ] From 2fca317bef169beee933e0830e002f71a5b91430 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Fri, 27 May 2022 01:25:26 +0300 Subject: [PATCH 12/21] Add texts element drawing. --- map_machine/doc/draw_elements.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/map_machine/doc/draw_elements.py b/map_machine/doc/draw_elements.py index 60cf4ef..a23b101 100644 --- a/map_machine/doc/draw_elements.py +++ b/map_machine/doc/draw_elements.py @@ -94,6 +94,7 @@ class Grid: self.max_i: float = 0 self.way_id: int = 0 self.osm_data: OSMData = OSMData() + self.texts: list[tuple[str, int, int]] = [] def add_node(self, tags: Tags, i: int, j: int) -> OSMNode: """Add OSM node to the grid.""" @@ -114,6 +115,9 @@ class Grid: self.osm_data.add_way(osm_way) self.way_id += 1 + def add_text(self, text: str, i: int, j: int) -> None: + self.texts.append((text, i, j)) + def get_boundary_box(self) -> BoundaryBox: """Compute resulting boundary box with margin of one grid step.""" return BoundaryBox( @@ -139,6 +143,16 @@ class Grid: map_: Map = Map(flinger, svg, SCHEME, configuration) map_.draw(constructor) + for text, i, j in self.texts: + svg.add( + Text( + text, + flinger.fling((-i * self.y_step, j * self.x_step)) + (0, 3), + font_family="JetBrains Mono", + font_size=12, + ) + ) + with output_path.open("w") as output_file: svg.write(output_file) logging.info(f"Map is drawn to {output_path}.") @@ -154,6 +168,7 @@ def draw_overlapped_ways(types: list[dict[str, str]], path: Path) -> None: node_1: OSMNode = grid.add_node({}, index + 1, 8) node_2: OSMNode = grid.add_node({}, index + 1, len(types) + 9) grid.add_way(tags, [node_1, node_2]) + grid.add_text(", ".join(f"{k}={tags[k]}" for k in tags), index + 1, 0) for index, tags in enumerate(types): node_1: OSMNode = grid.add_node({}, 0, index + 9) From a6c94a091fdb14282c10dd9d2ec720d9ae6ab054 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Sat, 28 May 2022 23:49:19 +0300 Subject: [PATCH 13/21] Issue #125: add style for disused railways. --- map_machine/scheme/default.yml | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/map_machine/scheme/default.yml b/map_machine/scheme/default.yml index 4aff00c..1ebada6 100644 --- a/map_machine/scheme/default.yml +++ b/map_machine/scheme/default.yml @@ -2574,10 +2574,24 @@ ways: stroke: "#BBBBBB" priority: 42.0 - tags: {railway: construction} + style: + stroke-width: 2.0 + stroke: "#000000" + stroke-dasharray: 6,3 + opacity: 0.3 + priority: 42.0 + - tags: {railway: disused} style: stroke-width: 3.0 stroke: "#000000" - stroke-dasharray: 3,3 + stroke-dasharray: 6,6 + opacity: 0.3 + priority: 42.0 + - tags: {railway: abandoned} + style: + stroke-width: 3.0 + stroke: "#000000" + stroke-dasharray: 6,9 opacity: 0.3 priority: 42.0 From d0ef85cd0bd6ffedc69a94337397dacba425872d Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Tue, 31 May 2022 00:43:45 +0300 Subject: [PATCH 14/21] Issue #125: split way priority. Now 40.0 is a special way priority. Ways that have priority less than 40.0 will be drawn below the road layer, and ways that have priority greater than 40.0 will be drawn above the road layer. --- map_machine/mapper.py | 21 ++++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/map_machine/mapper.py b/map_machine/mapper.py index e8c67d0..59cbe3e 100644 --- a/map_machine/mapper.py +++ b/map_machine/mapper.py @@ -19,6 +19,7 @@ from map_machine.constructor import Constructor from map_machine.drawing import draw_text from map_machine.feature.building import Building, draw_walls, BUILDING_SCALE from map_machine.feature.road import Intersection, Road, RoadPart +from map_machine.figure import StyledFigure from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger from map_machine.geometry.vector import Segment @@ -34,6 +35,8 @@ from map_machine.workspace import workspace __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" +ROAD_PRIORITY: float = 40.0 + class Map: """Map drawing.""" @@ -61,7 +64,16 @@ class Map: ) logging.info("Drawing ways...") - for figure in constructor.get_sorted_figures(): + figures: list[StyledFigure] = constructor.get_sorted_figures() + + top_figures: list[StyledFigure] = [ + x for x in figures if x.line_style.priority >= ROAD_PRIORITY + ] + bottom_figures: list[StyledFigure] = [ + x for x in figures if x.line_style.priority < ROAD_PRIORITY + ] + + for figure in bottom_figures: path_commands: str = figure.get_path(self.flinger) if path_commands: path: SVGPath = SVGPath(d=path_commands) @@ -70,6 +82,13 @@ class Map: constructor.roads.draw(self.svg, self.flinger) + for figure in top_figures: + path_commands: str = figure.get_path(self.flinger) + if path_commands: + path: SVGPath = SVGPath(d=path_commands) + path.update(figure.line_style.style) + self.svg.add(path) + for tree in constructor.trees: tree.draw(self.svg, self.flinger, self.scheme) for crater in constructor.craters: From ac2d777c995258c5daa81528b1fc7f87b9f5d1d4 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 02:09:42 +0300 Subject: [PATCH 15/21] Add `/` as a delimiter for coordinates. Now coordinates in `--coordinates` option can be copied and pasted from OpenStreetMap URL. --- map_machine/mapper.py | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/map_machine/mapper.py b/map_machine/mapper.py index 59cbe3e..970db86 100644 --- a/map_machine/mapper.py +++ b/map_machine/mapper.py @@ -276,12 +276,19 @@ def render_map(arguments: argparse.Namespace) -> None: if arguments.boundary_box: boundary_box = BoundaryBox.from_text(arguments.boundary_box) + elif arguments.coordinates and arguments.size: - coordinates: np.ndarray = np.array( - list(map(float, arguments.coordinates.split(","))) - ) - if len(coordinates) != 2: - fatal("Wrong number of coordinates.") + coordinates: Optional[np.ndarray] = None + + for delimiter in ",", "/": + if delimiter in arguments.coordinates: + coordinates = np.array( + list(map(float, arguments.coordinates.split(delimiter))) + ) + + if coordinates is None or len(coordinates) != 2: + fatal("Wrong coordinates format.") + width, height = np.array(list(map(float, arguments.size.split(",")))) boundary_box = BoundaryBox.from_coordinates( coordinates, configuration.zoom_level, width, height From efda73a43612e209fa86b5c21f8959e7dc807cbf Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 02:14:18 +0300 Subject: [PATCH 16/21] Fix shebangs. --- data/githooks/pre-commit | 2 +- data/githooks/pre-push | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/data/githooks/pre-commit b/data/githooks/pre-commit index 012262d..571fb45 100755 --- a/data/githooks/pre-commit +++ b/data/githooks/pre-commit @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash python_files="map_machine setup.py tests data/githooks/commit-msg" diff --git a/data/githooks/pre-push b/data/githooks/pre-push index 2576f9a..c748aef 100755 --- a/data/githooks/pre-push +++ b/data/githooks/pre-push @@ -1,4 +1,4 @@ -#!/bin/sh +#!/usr/bin/env bash echo "Looking for changes..." files=`git status --porcelain | wc -l` From 2db6cc8d49cfbfc96feb94c0600a39074ebf7311 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 09:05:42 +0300 Subject: [PATCH 17/21] Add documentation. --- data/dictionary.xml | 1 + tests/test_icons.py | 5 ++++- tests/test_ways.py | 6 ++++++ 3 files changed, 11 insertions(+), 1 deletion(-) diff --git a/data/dictionary.xml b/data/dictionary.xml index e2e904f..f5b4b0b 100644 --- a/data/dictionary.xml +++ b/data/dictionary.xml @@ -36,6 +36,7 @@ rasterized röntgen scoria + skillion subattributes subelement subparser diff --git a/tests/test_icons.py b/tests/test_icons.py index a29f908..767c5a5 100644 --- a/tests/test_icons.py +++ b/tests/test_icons.py @@ -1,5 +1,8 @@ """ Test icon generation for nodes. + +Tests check that for the given node described by tags, Map Machine generates +expected icons with expected colors. """ from pathlib import Path from typing import Optional @@ -166,7 +169,7 @@ def test_icon_regex() -> None: def test_vending_machine() -> None: """ - Check that specific vending machines doesn't render with generic icon. + Check that specific vending machines aren't rendered with generic icon. See https://github.com/enzet/map-machine/issues/132 """ diff --git a/tests/test_ways.py b/tests/test_ways.py index cb6ba93..a7245bd 100644 --- a/tests/test_ways.py +++ b/tests/test_ways.py @@ -1,3 +1,9 @@ +""" +Test map generation for ways. + +Tests check that for the given ways described by tags, Map Machine generates +expected figures in the expected order. +""" import numpy as np from map_machine.constructor import Constructor From 4c0f9d60bace81635a2b1027447f10a8c1b7bfe0 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 09:09:39 +0300 Subject: [PATCH 18/21] Remove redundant function. --- map_machine/ui/cli.py | 31 ------------------------------- 1 file changed, 31 deletions(-) diff --git a/map_machine/ui/cli.py b/map_machine/ui/cli.py index d81e65b..efb8319 100644 --- a/map_machine/ui/cli.py +++ b/map_machine/ui/cli.py @@ -2,7 +2,6 @@ Command-line user interface. """ import argparse -import sys from map_machine import __version__ from map_machine.map_configuration import BuildingMode, DrawingMode, LabelMode @@ -11,9 +10,6 @@ from map_machine.osm.osm_reader import STAGES_OF_DECAY __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" -BOXES: str = " ▏▎▍▌▋▊▉" -BOXES_LENGTH: int = len(BOXES) - COMMAND_LINES: dict[str, list[str]] = { "render": ["render", "-b", "10.000,20.000,10.001,20.001"], "render_with_tooltips": [ @@ -356,30 +352,3 @@ def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None: f"number of node and area selectors by {len(STAGES_OF_DECAY) + 1} " f"times", ) - - -def progress_bar( - number: int, total: int, length: int = 20, step: int = 1000, text: str = "" -) -> None: - """ - Draw progress bar using Unicode symbols. - - :param number: current value - :param total: maximum value - :param length: progress bar length. - :param step: frequency of progress bar updating (assuming that numbers go - subsequently) - :param text: short description - """ - if number == -1: - sys.stdout.write(f"100 % {length * '█'}▏{text}\n") - elif number % step == 0: - ratio: float = number / total - parts: int = int(ratio * length * BOXES_LENGTH) - fill_length: int = int(parts / BOXES_LENGTH) - box: str = BOXES[int(parts - fill_length * BOXES_LENGTH)] - sys.stdout.write( - f"{str(int(int(ratio * 1000.0) / 10.0)):>3} % " - f"{fill_length * '█'}{box}" - f"{int(length - fill_length - 1) * ' '}▏{text}\n\033[F" - ) From 7a56d0df8710d2a1a226ecfeee05b436e489a463 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 09:33:27 +0300 Subject: [PATCH 19/21] Refactor way test. --- tests/test_ways.py | 24 ++++++++++++------------ 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/tests/test_ways.py b/tests/test_ways.py index a7245bd..ec6d61b 100644 --- a/tests/test_ways.py +++ b/tests/test_ways.py @@ -11,7 +11,7 @@ from map_machine.figure import Figure from map_machine.geometry.boundary_box import BoundaryBox from map_machine.geometry.flinger import Flinger from map_machine.map_configuration import MapConfiguration -from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode +from map_machine.osm.osm_reader import OSMData, OSMWay, OSMNode, Tags from tests import SCHEME, SHAPE_EXTRACTOR @@ -26,24 +26,24 @@ def get_constructor(osm_data: OSMData) -> Constructor: return constructor +def create_way(tags: Tags, index: int) -> OSMWay: + """Create simple OSM way with two arbitrary nodes.""" + nodes: list[OSMNode] = [ + OSMNode({}, 1, np.array((-0.01, -0.01))), + OSMNode({}, 2, np.array((0.01, 0.01))), + ] + return OSMWay(tags, index, nodes) + + def test_river_and_wood() -> None: """ Check that river is above the wood. See https://github.com/enzet/map-machine/issues/126 """ - nodes_1: list[OSMNode] = [ - OSMNode({}, 1, np.array((-0.01, -0.01))), - OSMNode({}, 2, np.array((0.01, 0.01))), - ] - nodes_2: list[OSMNode] = [ - OSMNode({}, 3, np.array((-0.01, -0.01))), - OSMNode({}, 4, np.array((0.01, 0.01))), - ] - osm_data: OSMData = OSMData() - osm_data.add_way(OSMWay({"natural": "wood"}, 1, nodes_1)) - osm_data.add_way(OSMWay({"waterway": "river"}, 2, nodes_2)) + osm_data.add_way(create_way({"natural": "wood"}, 1)) + osm_data.add_way(create_way({"waterway": "river"}, 2)) figures: list[Figure] = get_constructor(osm_data).get_sorted_figures() From 8dbc03c973f89daef1d731f8c6476bfd8d2fb120 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 10:18:17 +0300 Subject: [PATCH 20/21] Issue #128: add test. --- tests/test_ways.py | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/tests/test_ways.py b/tests/test_ways.py index ec6d61b..25a69bb 100644 --- a/tests/test_ways.py +++ b/tests/test_ways.py @@ -16,6 +16,10 @@ from tests import SCHEME, SHAPE_EXTRACTOR def get_constructor(osm_data: OSMData) -> Constructor: + """ + Get custom constructor for bounds (-0.01, -0.01, 0.01, 0.01) and zoom level + 18. + """ flinger: Flinger = Flinger( BoundaryBox(-0.01, -0.01, 0.01, 0.01), 18, osm_data.equator_length ) @@ -26,13 +30,15 @@ def get_constructor(osm_data: OSMData) -> Constructor: return constructor -def create_way(tags: Tags, index: int) -> OSMWay: +def create_way(osm_data: OSMData, tags: Tags, index: int) -> None: """Create simple OSM way with two arbitrary nodes.""" nodes: list[OSMNode] = [ OSMNode({}, 1, np.array((-0.01, -0.01))), OSMNode({}, 2, np.array((0.01, 0.01))), ] - return OSMWay(tags, index, nodes) + for node in nodes: + osm_data.add_node(node) + osm_data.add_way(OSMWay(tags, index, nodes)) def test_river_and_wood() -> None: @@ -42,8 +48,8 @@ def test_river_and_wood() -> None: See https://github.com/enzet/map-machine/issues/126 """ osm_data: OSMData = OSMData() - osm_data.add_way(create_way({"natural": "wood"}, 1)) - osm_data.add_way(create_way({"waterway": "river"}, 2)) + create_way(osm_data, {"natural": "wood"}, 1) + create_way(osm_data, {"waterway": "river"}, 2) figures: list[Figure] = get_constructor(osm_data).get_sorted_figures() @@ -52,6 +58,19 @@ def test_river_and_wood() -> None: assert figures[1].tags["waterway"] == "river" +def test_placement_and_lanes() -> None: + """ + Check that `placement` tag is processed correctly when `lanes` tag is not + specified. + + See https://github.com/enzet/map-machine/issues/128 + """ + osm_data: OSMData = OSMData() + create_way(osm_data, {"highway": "motorway", "placement": "right_of:2"}, 1) + + get_constructor(osm_data) + + def test_empty_ways() -> None: """Ways without nodes.""" osm_data: OSMData = OSMData() From 023a524f73f432eaa2a13487bfd45d9f895be0b4 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Mon, 6 Jun 2022 22:48:57 +0300 Subject: [PATCH 21/21] Issue #128: fix `placement` tag processing. If `lanes` tag is not specified or specified incorrectly, number of lanes should be adjusted to fit `placement` tag values. --- map_machine/__init__.py | 2 +- map_machine/feature/road.py | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/map_machine/__init__.py b/map_machine/__init__.py index 7b41d42..d55351f 100644 --- a/map_machine/__init__.py +++ b/map_machine/__init__.py @@ -11,7 +11,7 @@ __url__ = "https://github.com/enzet/map-machine" __doc_url__ = f"{__url__}/blob/main/README.md" __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" -__version__ = "0.1.4" +__version__ = "0.1.5" REQUIREMENTS: list[str] = [ "CairoSVG>=2.5.0", diff --git a/map_machine/feature/road.py b/map_machine/feature/road.py index 4dd49fe..b4770d6 100644 --- a/map_machine/feature/road.py +++ b/map_machine/feature/road.py @@ -395,6 +395,13 @@ class Road(Tagged): except ValueError: pass + if "placement" in tags: + value: str = tags["placement"] + if ":" in value and len(parts := value.split(":")) == 2: + _, lane_string = parts + if (lane_number := int(lane_string) - 1) >= len(self.lanes): + self.lanes += [Lane()] * (lane_number + 1 - len(self.lanes)) + if "width:lanes" in tags: try: widths: list[float] = list(