mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-22 13:36:26 +02:00
Issue #81: support lane drawing.
This commit is contained in:
parent
cc9826ae57
commit
0a7bead1ca
6 changed files with 97 additions and 15 deletions
|
@ -29,7 +29,13 @@ from map_machine.icon import (
|
|||
ShapeSpecification,
|
||||
)
|
||||
from map_machine.map_configuration import DrawingMode, MapConfiguration
|
||||
from map_machine.osm_reader import OSMData, OSMNode, OSMRelation, OSMWay
|
||||
from map_machine.osm_reader import (
|
||||
OSMData,
|
||||
OSMNode,
|
||||
OSMRelation,
|
||||
OSMWay,
|
||||
parse_levels,
|
||||
)
|
||||
from map_machine.point import Point
|
||||
from map_machine.scheme import DEFAULT_COLOR, LineStyle, RoadMatcher, Scheme
|
||||
from map_machine.text import Label
|
||||
|
@ -478,8 +484,7 @@ class Constructor:
|
|||
def check_level_number(tags: dict[str, Any], level: float) -> bool:
|
||||
"""Check if element described by tags is no the specified level."""
|
||||
if "level" in tags:
|
||||
levels: map = map(float, tags["level"].replace(",", ".").split(";"))
|
||||
if level not in levels:
|
||||
if level not in parse_levels(tags["level"]):
|
||||
return False
|
||||
else:
|
||||
return False
|
||||
|
|
|
@ -5,6 +5,7 @@ from typing import Any, Iterator, Optional
|
|||
|
||||
import numpy as np
|
||||
from colour import Color
|
||||
from shapely.geometry import LineString
|
||||
from svgwrite import Drawing
|
||||
from svgwrite.container import Group
|
||||
from svgwrite.path import Path
|
||||
|
@ -42,24 +43,38 @@ class Figure(Tagged):
|
|||
)
|
||||
|
||||
def get_path(
|
||||
self, flinger: Flinger, shift: np.ndarray = np.array((0, 0))
|
||||
self, flinger: Flinger, offset: np.ndarray = np.array((0, 0))
|
||||
) -> str:
|
||||
"""
|
||||
Get SVG path commands.
|
||||
|
||||
:param flinger: converter for geo coordinates
|
||||
:param shift: shift vector
|
||||
:param offset: offset vector
|
||||
"""
|
||||
path: str = ""
|
||||
|
||||
for outer_nodes in self.outers:
|
||||
path += f"{get_path(outer_nodes, shift, flinger)} "
|
||||
path += f"{get_path(outer_nodes, offset, flinger)} "
|
||||
|
||||
for inner_nodes in self.inners:
|
||||
path += f"{get_path(inner_nodes, shift, flinger)} "
|
||||
path += f"{get_path(inner_nodes, offset, flinger)} "
|
||||
|
||||
return path
|
||||
|
||||
def get_outer_path(
|
||||
self, flinger: Flinger, parallel_offset: float = 0
|
||||
) -> str:
|
||||
"""Get path of the first outer node list."""
|
||||
points: list[tuple[float, float]] = [
|
||||
tuple(flinger.fling(x.coordinates)) for x in self.outers[0]
|
||||
]
|
||||
offset = LineString(points).parallel_offset(parallel_offset)
|
||||
|
||||
path: str = ""
|
||||
for index, point in enumerate(offset.coords):
|
||||
path += ("L" if index else "M") + f" {point[0]},{point[1]} "
|
||||
return path[:-1]
|
||||
|
||||
|
||||
class Building(Figure):
|
||||
"""
|
||||
|
@ -281,6 +296,25 @@ class Road(Figure):
|
|||
path.update(style)
|
||||
svg.add(path)
|
||||
|
||||
def draw_lanes(self, svg: Drawing, flinger: Flinger, color: Color) -> None:
|
||||
scale: float = flinger.get_scale(self.outers[0][0].coordinates)
|
||||
if len(self.lanes) < 2:
|
||||
return
|
||||
for index in range(1, len(self.lanes)):
|
||||
shift = scale * (
|
||||
-self.width / 2 + index * self.width / len(self.lanes)
|
||||
)
|
||||
path: Path = Path(d=self.get_outer_path(flinger, shift))
|
||||
style: dict[str, Any] = {
|
||||
"fill": "none",
|
||||
"stroke": color.hex,
|
||||
"stroke-linejoin": "round",
|
||||
"stroke-width": 1,
|
||||
"opacity": 0.5,
|
||||
}
|
||||
path.update(style)
|
||||
svg.add(path)
|
||||
|
||||
|
||||
class Crater(Tagged):
|
||||
"""
|
||||
|
|
|
@ -77,11 +77,17 @@ class Map:
|
|||
layered_roads[road.layer].append(road)
|
||||
|
||||
for layer in sorted(layered_roads.keys()):
|
||||
roads = layered_roads[layer]
|
||||
roads = sorted(
|
||||
layered_roads[layer], key=lambda x: x.matcher.priority
|
||||
)
|
||||
for road in roads:
|
||||
road.draw(self.svg, self.flinger, road.matcher.border_color, 2)
|
||||
for road in roads:
|
||||
road.draw(self.svg, self.flinger, road.matcher.color)
|
||||
for road in roads:
|
||||
road.draw_lanes(
|
||||
self.svg, self.flinger, road.matcher.border_color
|
||||
)
|
||||
|
||||
for tree in constructor.trees:
|
||||
tree.draw(self.svg, self.flinger, self.scheme)
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Parse OSM XML file.
|
||||
"""
|
||||
import json
|
||||
import logging
|
||||
import re
|
||||
from dataclasses import dataclass, field
|
||||
from datetime import datetime
|
||||
|
@ -20,9 +21,9 @@ __email__ = "me@enzet.ru"
|
|||
|
||||
OSM_TIME_PATTERN: str = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
METERS_PATTERN = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*m$")
|
||||
KILOMETERS_PATTERN = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*km$")
|
||||
MILES_PATTERN = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*mi$")
|
||||
METERS_PATTERN: re.Pattern = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*m$")
|
||||
KILOMETERS_PATTERN: re.Pattern = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*km$")
|
||||
MILES_PATTERN: re.Pattern = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*mi$")
|
||||
|
||||
|
||||
# See https://wiki.openstreetmap.org/wiki/Lifecycle_prefix#Stages_of_decay
|
||||
|
@ -46,6 +47,16 @@ def parse_float(string: str) -> Optional[float]:
|
|||
return None
|
||||
|
||||
|
||||
def parse_levels(string: str) -> list[float]:
|
||||
"""Parse string representation of level sequence value."""
|
||||
# TODO: add `-` parsing
|
||||
try:
|
||||
return list(map(float, string.replace(",", ".").split(";")))
|
||||
except ValueError:
|
||||
logging.warning(f"Cannot parse level description from `{string}`.")
|
||||
return []
|
||||
|
||||
|
||||
@dataclass
|
||||
class Tagged:
|
||||
"""
|
||||
|
@ -320,7 +331,7 @@ class OSMData:
|
|||
if node.user:
|
||||
self.authors.add(node.user)
|
||||
if node.tags.get("level"):
|
||||
self.levels.add(float(node.tags.get("level")))
|
||||
self.levels.union(parse_levels(node.tags["level"]))
|
||||
self.time.update(node.timestamp)
|
||||
|
||||
def add_way(self, way: OSMWay) -> None:
|
||||
|
@ -333,7 +344,7 @@ class OSMData:
|
|||
if way.user:
|
||||
self.authors.add(way.user)
|
||||
if way.tags.get("level"):
|
||||
self.levels.union(map(float, way.tags["level"].split(";")))
|
||||
self.levels.union(parse_levels(way.tags["level"]))
|
||||
self.time.update(way.timestamp)
|
||||
|
||||
def add_relation(self, relation: OSMRelation) -> None:
|
||||
|
|
|
@ -19,8 +19,7 @@ colors:
|
|||
barley_dark_color: "#908F62"
|
||||
sunflower_dark_color: "#DEAC4A"
|
||||
|
||||
# motorway_border_color: "#CC8800"
|
||||
motorway_border_color: "#FFFFFF"
|
||||
motorway_border_color: "#CC8800"
|
||||
motorway_color: "#FFAA33"
|
||||
primary_border_color: "#AA8800"
|
||||
primary_color: "#FFDD66"
|
||||
|
@ -1449,11 +1448,21 @@ roads:
|
|||
border_color: secondary_border_color
|
||||
priority: 41.8
|
||||
color: secondary_color
|
||||
- tags: {highway: secondary_link}
|
||||
default_width: 9
|
||||
border_color: secondary_border_color
|
||||
priority: 41.8
|
||||
color: secondary_color
|
||||
- tags: {highway: tertiary}
|
||||
default_width: 7
|
||||
border_color: tertiary_border_color
|
||||
priority: 41.7
|
||||
color: tertiary_color
|
||||
- tags: {highway: tertiary_link}
|
||||
default_width: 7
|
||||
border_color: tertiary_border_color
|
||||
priority: 41.7
|
||||
color: tertiary_color
|
||||
- tags: {highway: unclassified}
|
||||
default_width: 5
|
||||
border_color: road_border_color
|
||||
|
|
|
@ -9,6 +9,7 @@ from map_machine.osm_reader import (
|
|||
OSMReader,
|
||||
OSMRelation,
|
||||
OSMWay,
|
||||
parse_levels,
|
||||
)
|
||||
|
||||
__author__ = "Sergey Vartanov"
|
||||
|
@ -104,3 +105,19 @@ def test_relation() -> None:
|
|||
assert len(relation.members) == 1
|
||||
assert relation.members[0].type_ == "way"
|
||||
assert relation.members[0].ref == 2
|
||||
|
||||
|
||||
def test_parse_levels() -> None:
|
||||
"""Test level parsing."""
|
||||
assert parse_levels("1") == [1]
|
||||
assert parse_levels("-1") == [-1]
|
||||
assert parse_levels("1.5") == [1.5]
|
||||
assert parse_levels("1,5") == [1.5]
|
||||
|
||||
|
||||
def test_parse_levels_list() -> None:
|
||||
"""Test list of levels parsing."""
|
||||
assert parse_levels("0;1") == [0, 1]
|
||||
assert parse_levels("0;2") == [0, 2]
|
||||
assert parse_levels("0;2.5") == [0, 2.5]
|
||||
assert parse_levels("0;2,5") == [0, 2.5]
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue