map-machine/map_machine/element/test_elements.py
2022-05-11 23:55:50 +03:00

225 lines
6.2 KiB
Python

"""
Draw test nodes, ways, and relations.
"""
import logging
from pathlib import Path
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.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.scheme import Scheme
from map_machine.workspace import Workspace
workspace: Workspace = Workspace(Path("temp"))
SCHEME: Scheme = Scheme.from_file(workspace.DEFAULT_SCHEME_PATH)
SHAPE_EXTRACTOR: ShapeExtractor = ShapeExtractor(
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
)
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"},
{"width": "12"},
{"width": "16"},
{"bridge": "yes", "width": "4"},
{"bridge": "yes", "width": "8"},
{"tunnel": "yes", "width": "4"},
{"tunnel": "yes", "width": "8"},
{"ford": "yes", "width": "4"},
{"ford": "yes", "width": "8"},
{"embankment": "yes", "width": "4"},
{"embankment": "yes", "width": "8"},
]
ROAD_LANES_AND_FEATURES: list[dict[str, str]] = [
{"lanes": "1"},
{"lanes": "2"},
{"lanes": "3"},
{"lanes": "4"},
{"bridge": "yes", "lanes": "1"},
{"bridge": "yes", "lanes": "2"},
{"tunnel": "yes", "lanes": "1"},
{"tunnel": "yes", "lanes": "2"},
{"ford": "yes", "lanes": "1"},
{"ford": "yes", "lanes": "2"},
{"embankment": "yes", "lanes": "1"},
{"embankment": "yes", "lanes": "2"},
]
# See https://wiki.openstreetmap.org/wiki/Proposed_features/placement
PLACEMENT_FEATURES_1: list[dict[str, str]] = [
{"lanes": "1"},
{"lanes": "2", "placement": "middle_of:1"},
{"lanes": "4", "placement": "middle_of:2"},
{"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
{"lanes": "3", "placement:forward": "left_of:1"},
{"lanes": "3", "placement": "transition"},
{"lanes": "4", "placement:backward": "middle_of:1"},
{"lanes": "3"},
]
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.x_start: float = 0.0
self.index: int = 0
self.nodes: dict[OSMNode, tuple[int, int]] = {}
self.max_j: float = 0
self.max_i: float = 0
def add_node(self, tags: dict[str, str], i: int, j: int) -> OSMNode:
"""Add OSM node to the grid."""
self.index += 1
node: OSMNode = OSMNode(
tags,
self.index,
np.array((-i * self.y_step, j * self.x_step)),
)
self.nodes[node] = (j, i)
self.max_j = max(self.max_j, j * self.x_step)
self.max_i = max(self.max_i, i * self.y_step)
return node
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,
)
def 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):
previous: Optional[OSMNode] = None
for j in range(len(features) + 1):
node: OSMNode = grid.add_node({}, i, j)
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)
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}.")
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(
highway_tags, ROAD_LANES_AND_FEATURES, Path("out") / "lanes.svg"
)
road_features(
highway_tags + RAILWAY_TAGS + aeroway_tags,
ROAD_WIDTHS_AND_FEATURES,
Path("out") / "width.svg",
)
road_features(
highway_tags,
PLACEMENT_FEATURES_1 + [{"highway": "none"}] + PLACEMENT_FEATURES_2,
Path("out") / "placement.svg",
)