From c193901e155ff4b532ed6f7cf8993b9a6c0a26c6 Mon Sep 17 00:00:00 2001 From: Sergey Vartanov Date: Wed, 10 Nov 2021 02:28:04 +0300 Subject: [PATCH] Issue #83: display transition as connection. Display road part with placement=transition as connection between two roads. --- .github/workflows/test.yml | 2 +- map_machine/feature/road.py | 77 ++++++++++++++++++++++------------- tests/test_elements.py | 80 +++++++++++++++++++++---------------- 3 files changed, 95 insertions(+), 64 deletions(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index a22e424..42962ac 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -46,7 +46,7 @@ jobs: pip install . - name: Check code style with Black run: | - black -l 80 --check map_machine tests + black -l 80 --check map_machine setup.py tests - name: Lint with Flake8 run: | flake8 --max-line-length=80 --ignore=E203,W503 diff --git a/map_machine/feature/road.py b/map_machine/feature/road.py index 1da7b79..7f70509 100644 --- a/map_machine/feature/road.py +++ b/map_machine/feature/road.py @@ -2,6 +2,7 @@ WIP: road shape drawing. """ import logging +from collections import defaultdict from dataclasses import dataclass from typing import Any, Optional, Union @@ -376,7 +377,7 @@ class Road(Tagged): self.matcher: RoadMatcher = matcher self.line: Polyline = Polyline( - [flinger.fling(x.coordinates) for x in self.nodes] + [flinger.fling(node.coordinates) for node in self.nodes] ) self.width: Optional[float] = matcher.default_width self.lanes: list[Lane] = [] @@ -420,19 +421,19 @@ class Road(Tagged): self.layer = float(tags["layer"]) self.placement_offset: float = 0.0 - self.transition: bool = False + self.is_transition: bool = False if "placement" in tags: value: str = tags["placement"] if value == "transition": self.is_transition = True elif ":" in value and len(parts := value.split(":")) == 2: - place, lane = parts - lane_number: int = int(lane) + place, lane_string = parts + lane_number: int = int(lane_string) self.placement_offset = ( sum( - x.get_width(self.scale) - for x in self.lanes[: lane_number - 1] + lane.get_width(self.scale) + for lane in self.lanes[: lane_number - 1] ) - self.width * self.scale / 2 ) @@ -629,8 +630,12 @@ class Connector: self.road_1, self.index_1 = connections[0] self.priority = self.road_1.matcher.priority - self.min_layer: float = min(x[0].layer for x in connections) - self.max_layer: float = max(x[0].layer for x in connections) + self.min_layer: float = min( + connection[0].layer for connection in connections + ) + self.max_layer: float = max( + connection[0].layer for connection in connections + ) self.scale: float = self.road_1.scale self.flinger: Flinger = flinger @@ -697,8 +702,11 @@ class ComplexConnector(Connector): self.road_1.line.shorten(self.index_1, length) self.road_2.line.shorten(self.index_2, length) - node: OSMNode = self.road_1.nodes[self.index_1] - point: np.ndarray = flinger.fling(node.coordinates) + node_1: OSMNode = self.road_1.nodes[self.index_1] + point_1: np.ndarray = flinger.fling(node_1.coordinates) + node_2: OSMNode = self.road_2.nodes[self.index_2] + point_2: np.ndarray = flinger.fling(node_2.coordinates) + point = (point_1 + point_2) / 2 points_1: list[np.ndarray] = get_curve_points( self.road_1, @@ -796,15 +804,29 @@ class Roads: if not self.roads: return - layered_roads: dict[float, list[Road]] = {} - layered_connectors: dict[float, list[Connector]] = {} + layered_roads: dict[float, list[Road]] = defaultdict(list) + layered_connectors: dict[float, list[Connector]] = defaultdict(list) for road in self.roads: - if road.layer not in layered_roads: - layered_roads[road.layer] = [] - layered_roads[road.layer].append(road) + if not road.is_transition: + layered_roads[road.layer].append(road) + else: + connections = [] + for end in 0, -1: + connections.append( + [ + connection + for connection in self.nodes[road.nodes[end].id_] + if not connection[0].is_transition + ] + ) + if len(connections[0]) == 1 and len(connections[1]) == 1: + connector: Connector = ComplexConnector( + [connections[0][0], connections[1][0]], flinger + ) + layered_connectors[road.layer].append(connector) - for _, connected in self.nodes.items(): + for connected in self.nodes.values(): connector: Connector if len(connected) <= 1: @@ -819,19 +841,16 @@ class Roads: or index_2 not in [0, len(road_2.nodes) - 1] ): connector = SimpleConnector(connected, flinger) - else: + elif not road_1.is_transition and not road_2.is_transition: connector = ComplexConnector(connected, flinger) + else: + continue else: # We can also use SimpleIntersection(connected, flinger, scale) # here. continue - if connector.min_layer not in layered_connectors: - layered_connectors[connector.min_layer] = [] layered_connectors[connector.min_layer].append(connector) - - if connector.max_layer not in layered_connectors: - layered_connectors[connector.max_layer] = [] layered_connectors[connector.max_layer].append(connector) for layer in sorted(layered_roads.keys()): @@ -844,17 +863,19 @@ class Roads: for road in roads: road.draw(svg, True) - for connector in connectors: - if connector.min_layer == layer: - connector.draw_border(svg) + if connectors: + for connector in connectors: + if connector.min_layer == layer: + connector.draw_border(svg) # Draw inner parts. for road in roads: road.draw(svg, False) - for connector in connectors: - if connector.max_layer == layer: - connector.draw(svg) + if connectors: + for connector in connectors: + if connector.max_layer == layer: + connector.draw(svg) # Draw lane separators. diff --git a/tests/test_elements.py b/tests/test_elements.py index d795b78..4c4191a 100644 --- a/tests/test_elements.py +++ b/tests/test_elements.py @@ -24,36 +24,37 @@ SCHEME: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH) SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor( workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH ) +DEFAULT_ZOOM: float = 18.0 -ROAD_TYPES: list[dict[str, str]] = [ - {"highway": "motorway"}, - {"highway": "trunk"}, - {"highway": "primary"}, - {"highway": "secondary"}, - {"highway": "tertiary"}, - {"highway": "unclassified"}, - {"highway": "residential"}, - {"highway": "service"}, - {"highway": "service_minor"}, - {"highway": "road"}, - {"highway": "pedestrian"}, - {"highway": "living_street"}, - {"highway": "bridleway"}, - {"highway": "cycleway"}, - {"highway": "footway"}, - {"highway": "steps"}, - {"highway": "path"}, - {"highway": "track"}, - {"highway": "raceway"}, +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_TYPES: list[dict[str, str]] = [ - {"aeroway": "runway"}, - {"aeroway": "taxiway"}, +AEROWAY_VALUES: list[str] = [ + "runway", + "taxiway", ] -RAILWAY_TYPES: list[dict[str, str]] = [ +RAILWAY_TAGS: list[dict[str, str]] = [ {"railway": "rail"}, {"railway": "light_rail"}, {"railway": "monorail"}, @@ -120,9 +121,9 @@ class Grid: """Creating map with elements ordered in grid.""" def __init__(self) -> None: - self.x_step: float = 0.0003 + self.x_step: float = 0.0002 self.y_step: float = 0.0003 - self.x_start: float = 0.0028 + self.x_start: float = 0.0 self.index: int = 0 self.nodes: dict[OSMNode, tuple[int, int]] = {} self.max_j: float = 0 @@ -156,7 +157,6 @@ def road_features( ) -> None: """Draw test image with different road features.""" osm_data: OSMData = OSMData() - grid: Grid = Grid() for i, type_ in enumerate(types): @@ -166,8 +166,8 @@ def road_features( node: OSMNode = grid.add_node({}, i, j) if previous: - tags: dict[str, str] = dict(features[j - 1]) - tags |= type_ + tags: dict[str, str] = dict(type_) + tags |= dict(features[j - 1]) way: OSMWay = OSMWay( tags, i * (len(features) + 1) + j, [previous, node] ) @@ -178,12 +178,15 @@ def road_features( def draw( - osm_data: OSMData, output_path: Path, boundary_box: BoundaryBox + 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, 18, osm_data.equator_length) + 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 @@ -200,16 +203,23 @@ def draw( if __name__ == "__main__": logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO) + 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 + ] + road_features( - ROAD_TYPES, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg" + highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg" ) road_features( - ROAD_TYPES + RAILWAY_TYPES + AEROWAY_TYPES, + highway_tags + RAILWAY_TAGS + aeroway_tags, ROAD_WIDTHS_AND_FEATURES, Path("out") / "width.svg", ) road_features( - ROAD_TYPES, - PLACEMENT_FEATURES_1 + [{}] + PLACEMENT_FEATURES_2, + highway_tags, + PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2, Path("out") / "placement.svg", )