Issue #83: display transition as connection.

Display road part with placement=transition as connection between
two roads.
This commit is contained in:
Sergey Vartanov 2021-11-10 02:28:04 +03:00
parent c3dfaf0604
commit c193901e15
3 changed files with 95 additions and 64 deletions

View file

@ -46,7 +46,7 @@ jobs:
pip install . pip install .
- name: Check code style with Black - name: Check code style with Black
run: | run: |
black -l 80 --check map_machine tests black -l 80 --check map_machine setup.py tests
- name: Lint with Flake8 - name: Lint with Flake8
run: | run: |
flake8 --max-line-length=80 --ignore=E203,W503 flake8 --max-line-length=80 --ignore=E203,W503

View file

@ -2,6 +2,7 @@
WIP: road shape drawing. WIP: road shape drawing.
""" """
import logging import logging
from collections import defaultdict
from dataclasses import dataclass from dataclasses import dataclass
from typing import Any, Optional, Union from typing import Any, Optional, Union
@ -376,7 +377,7 @@ class Road(Tagged):
self.matcher: RoadMatcher = matcher self.matcher: RoadMatcher = matcher
self.line: Polyline = Polyline( 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.width: Optional[float] = matcher.default_width
self.lanes: list[Lane] = [] self.lanes: list[Lane] = []
@ -420,19 +421,19 @@ class Road(Tagged):
self.layer = float(tags["layer"]) self.layer = float(tags["layer"])
self.placement_offset: float = 0.0 self.placement_offset: float = 0.0
self.transition: bool = False self.is_transition: bool = False
if "placement" in tags: if "placement" in tags:
value: str = tags["placement"] value: str = tags["placement"]
if value == "transition": if value == "transition":
self.is_transition = True self.is_transition = True
elif ":" in value and len(parts := value.split(":")) == 2: elif ":" in value and len(parts := value.split(":")) == 2:
place, lane = parts place, lane_string = parts
lane_number: int = int(lane) lane_number: int = int(lane_string)
self.placement_offset = ( self.placement_offset = (
sum( sum(
x.get_width(self.scale) lane.get_width(self.scale)
for x in self.lanes[: lane_number - 1] for lane in self.lanes[: lane_number - 1]
) )
- self.width * self.scale / 2 - self.width * self.scale / 2
) )
@ -629,8 +630,12 @@ class Connector:
self.road_1, self.index_1 = connections[0] self.road_1, self.index_1 = connections[0]
self.priority = self.road_1.matcher.priority self.priority = self.road_1.matcher.priority
self.min_layer: float = min(x[0].layer for x in connections) self.min_layer: float = min(
self.max_layer: float = max(x[0].layer for x in connections) 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.scale: float = self.road_1.scale
self.flinger: Flinger = flinger self.flinger: Flinger = flinger
@ -697,8 +702,11 @@ class ComplexConnector(Connector):
self.road_1.line.shorten(self.index_1, length) self.road_1.line.shorten(self.index_1, length)
self.road_2.line.shorten(self.index_2, length) self.road_2.line.shorten(self.index_2, length)
node: OSMNode = self.road_1.nodes[self.index_1] node_1: OSMNode = self.road_1.nodes[self.index_1]
point: np.ndarray = flinger.fling(node.coordinates) 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( points_1: list[np.ndarray] = get_curve_points(
self.road_1, self.road_1,
@ -796,15 +804,29 @@ class Roads:
if not self.roads: if not self.roads:
return return
layered_roads: dict[float, list[Road]] = {} layered_roads: dict[float, list[Road]] = defaultdict(list)
layered_connectors: dict[float, list[Connector]] = {} layered_connectors: dict[float, list[Connector]] = defaultdict(list)
for road in self.roads: for road in self.roads:
if road.layer not in layered_roads: if not road.is_transition:
layered_roads[road.layer] = []
layered_roads[road.layer].append(road) 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 connector: Connector
if len(connected) <= 1: if len(connected) <= 1:
@ -819,19 +841,16 @@ class Roads:
or index_2 not in [0, len(road_2.nodes) - 1] or index_2 not in [0, len(road_2.nodes) - 1]
): ):
connector = SimpleConnector(connected, flinger) connector = SimpleConnector(connected, flinger)
else: elif not road_1.is_transition and not road_2.is_transition:
connector = ComplexConnector(connected, flinger) connector = ComplexConnector(connected, flinger)
else:
continue
else: else:
# We can also use SimpleIntersection(connected, flinger, scale) # We can also use SimpleIntersection(connected, flinger, scale)
# here. # here.
continue continue
if connector.min_layer not in layered_connectors:
layered_connectors[connector.min_layer] = []
layered_connectors[connector.min_layer].append(connector) 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) layered_connectors[connector.max_layer].append(connector)
for layer in sorted(layered_roads.keys()): for layer in sorted(layered_roads.keys()):
@ -844,6 +863,7 @@ class Roads:
for road in roads: for road in roads:
road.draw(svg, True) road.draw(svg, True)
if connectors:
for connector in connectors: for connector in connectors:
if connector.min_layer == layer: if connector.min_layer == layer:
connector.draw_border(svg) connector.draw_border(svg)
@ -852,6 +872,7 @@ class Roads:
for road in roads: for road in roads:
road.draw(svg, False) road.draw(svg, False)
if connectors:
for connector in connectors: for connector in connectors:
if connector.max_layer == layer: if connector.max_layer == layer:
connector.draw(svg) connector.draw(svg)

View file

@ -24,36 +24,37 @@ SCHEME: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor( SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor(
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
) )
DEFAULT_ZOOM: float = 18.0
ROAD_TYPES: list[dict[str, str]] = [ HIGHWAY_VALUES: list[str] = [
{"highway": "motorway"}, "motorway",
{"highway": "trunk"}, "trunk",
{"highway": "primary"}, "primary",
{"highway": "secondary"}, "secondary",
{"highway": "tertiary"}, "tertiary",
{"highway": "unclassified"}, "unclassified",
{"highway": "residential"}, "residential",
{"highway": "service"}, "service",
{"highway": "service_minor"}, "service_minor",
{"highway": "road"}, "road",
{"highway": "pedestrian"}, "pedestrian",
{"highway": "living_street"}, "living_street",
{"highway": "bridleway"}, "bridleway",
{"highway": "cycleway"}, "cycleway",
{"highway": "footway"}, "footway",
{"highway": "steps"}, "steps",
{"highway": "path"}, "path",
{"highway": "track"}, "track",
{"highway": "raceway"}, "raceway",
] ]
AEROWAY_TYPES: list[dict[str, str]] = [ AEROWAY_VALUES: list[str] = [
{"aeroway": "runway"}, "runway",
{"aeroway": "taxiway"}, "taxiway",
] ]
RAILWAY_TYPES: list[dict[str, str]] = [ RAILWAY_TAGS: list[dict[str, str]] = [
{"railway": "rail"}, {"railway": "rail"},
{"railway": "light_rail"}, {"railway": "light_rail"},
{"railway": "monorail"}, {"railway": "monorail"},
@ -120,9 +121,9 @@ class Grid:
"""Creating map with elements ordered in grid.""" """Creating map with elements ordered in grid."""
def __init__(self) -> None: def __init__(self) -> None:
self.x_step: float = 0.0003 self.x_step: float = 0.0002
self.y_step: float = 0.0003 self.y_step: float = 0.0003
self.x_start: float = 0.0028 self.x_start: float = 0.0
self.index: int = 0 self.index: int = 0
self.nodes: dict[OSMNode, tuple[int, int]] = {} self.nodes: dict[OSMNode, tuple[int, int]] = {}
self.max_j: float = 0 self.max_j: float = 0
@ -156,7 +157,6 @@ def road_features(
) -> None: ) -> None:
"""Draw test image with different road features.""" """Draw test image with different road features."""
osm_data: OSMData = OSMData() osm_data: OSMData = OSMData()
grid: Grid = Grid() grid: Grid = Grid()
for i, type_ in enumerate(types): for i, type_ in enumerate(types):
@ -166,8 +166,8 @@ def road_features(
node: OSMNode = grid.add_node({}, i, j) node: OSMNode = grid.add_node({}, i, j)
if previous: if previous:
tags: dict[str, str] = dict(features[j - 1]) tags: dict[str, str] = dict(type_)
tags |= type_ tags |= dict(features[j - 1])
way: OSMWay = OSMWay( way: OSMWay = OSMWay(
tags, i * (len(features) + 1) + j, [previous, node] tags, i * (len(features) + 1) + j, [previous, node]
) )
@ -178,12 +178,15 @@ def road_features(
def draw( 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: ) -> None:
"""Draw map.""" """Draw map."""
configuration: MapConfiguration = MapConfiguration(level="all") 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) svg: Drawing = Drawing(output_path.name, flinger.size)
constructor: Constructor = Constructor( constructor: Constructor = Constructor(
osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration osm_data, flinger, SCHEME, SHAPE_EXTRACTOR, configuration
@ -200,16 +203,23 @@ def draw(
if __name__ == "__main__": if __name__ == "__main__":
logging.basicConfig(format="%(levelname)s %(message)s", level=logging.INFO) 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_features(
ROAD_TYPES, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg" highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg"
) )
road_features( road_features(
ROAD_TYPES + RAILWAY_TYPES + AEROWAY_TYPES, highway_tags + RAILWAY_TAGS + aeroway_tags,
ROAD_WIDTHS_AND_FEATURES, ROAD_WIDTHS_AND_FEATURES,
Path("out") / "width.svg", Path("out") / "width.svg",
) )
road_features( road_features(
ROAD_TYPES, highway_tags,
PLACEMENT_FEATURES_1 + [{}] + PLACEMENT_FEATURES_2, PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2,
Path("out") / "placement.svg", Path("out") / "placement.svg",
) )