mirror of
https://github.com/enzet/map-machine.git
synced 2025-04-28 17:57:11 +02:00
157 lines
4.3 KiB
Python
157 lines
4.3 KiB
Python
"""Figures displayed on the map."""
|
|
import numpy as np
|
|
|
|
from map_machine.geometry.flinger import Flinger
|
|
from map_machine.osm.osm_reader import OSMNode, Tagged
|
|
from map_machine.scheme import LineStyle
|
|
|
|
__author__ = "Sergey Vartanov"
|
|
__email__ = "me@enzet.ru"
|
|
|
|
from map_machine.geometry.vector import Polyline
|
|
|
|
|
|
class Figure(Tagged):
|
|
"""Some figure on the map: way or area."""
|
|
|
|
def __init__(
|
|
self,
|
|
tags: dict[str, str],
|
|
inners: list[list[OSMNode]],
|
|
outers: list[list[OSMNode]],
|
|
) -> None:
|
|
super().__init__(tags)
|
|
|
|
if inners and outers:
|
|
self.inners: list[list[OSMNode]] = list(map(make_clockwise, inners))
|
|
self.outers: list[list[OSMNode]] = list(
|
|
map(make_counter_clockwise, outers)
|
|
)
|
|
else:
|
|
self.inners = inners
|
|
self.outers = outers
|
|
|
|
def get_path(
|
|
self, flinger: Flinger, offset: np.ndarray = np.array((0.0, 0.0))
|
|
) -> str:
|
|
"""
|
|
Get SVG path commands.
|
|
|
|
:param flinger: converter for geo coordinates
|
|
:param offset: offset vector
|
|
"""
|
|
path: str = ""
|
|
|
|
for outer_nodes in self.outers:
|
|
path += f"{get_path(outer_nodes, offset, flinger)} "
|
|
|
|
for inner_nodes in self.inners:
|
|
path += f"{get_path(inner_nodes, offset, flinger)} "
|
|
|
|
return path
|
|
|
|
|
|
class StyledFigure(Figure):
|
|
"""Figure with stroke and fill style."""
|
|
|
|
def __init__(
|
|
self,
|
|
tags: dict[str, str],
|
|
inners: list[list[OSMNode]],
|
|
outers: list[list[OSMNode]],
|
|
line_style: LineStyle,
|
|
) -> None:
|
|
super().__init__(tags, inners, outers)
|
|
self.line_style: LineStyle = line_style
|
|
|
|
def get_path(
|
|
self,
|
|
flinger: Flinger,
|
|
offset: np.ndarray = np.array((0.0, 0.0)),
|
|
) -> str:
|
|
"""
|
|
Get SVG path commands.
|
|
|
|
:param flinger: converter for geo coordinates
|
|
:param offset: offset vector
|
|
"""
|
|
path: str = ""
|
|
|
|
for outer_nodes in self.outers:
|
|
commands: str = get_path(
|
|
outer_nodes, offset, flinger, self.line_style.parallel_offset
|
|
)
|
|
path += f"{commands} "
|
|
|
|
for inner_nodes in self.inners:
|
|
commands: str = get_path(
|
|
inner_nodes, offset, flinger, self.line_style.parallel_offset
|
|
)
|
|
path += f"{commands} "
|
|
|
|
return path
|
|
|
|
def get_layer(self) -> float:
|
|
"""
|
|
Get figure layer value or 0 if it is not specified.
|
|
|
|
TODO: support values separated by "," or ";".
|
|
"""
|
|
try:
|
|
if "layer" in self.tags:
|
|
return float(self.tags["layer"])
|
|
except ValueError:
|
|
return 0.0
|
|
return 0.0
|
|
|
|
def __lt__(self, other: "StyledFigure") -> bool:
|
|
"""Compare figures based on priority and layer."""
|
|
if self.get_layer() != other.get_layer():
|
|
return self.get_layer() < other.get_layer()
|
|
|
|
return self.line_style.priority < other.line_style.priority
|
|
|
|
|
|
def is_clockwise(polygon: list[OSMNode]) -> bool:
|
|
"""
|
|
Return true if polygon nodes are in clockwise order.
|
|
|
|
:param polygon: list of OpenStreetMap nodes
|
|
"""
|
|
count: float = 0.0
|
|
for index, node in enumerate(polygon):
|
|
next_index: int = 0 if index == len(polygon) - 1 else index + 1
|
|
count += (polygon[next_index].coordinates[0] - node.coordinates[0]) * (
|
|
polygon[next_index].coordinates[1] + node.coordinates[1]
|
|
)
|
|
return count >= 0.0
|
|
|
|
|
|
def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
|
"""
|
|
Make polygon nodes clockwise.
|
|
|
|
:param polygon: list of OpenStreetMap nodes
|
|
"""
|
|
return polygon if is_clockwise(polygon) else list(reversed(polygon))
|
|
|
|
|
|
def make_counter_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
|
"""
|
|
Make polygon nodes counter-clockwise.
|
|
|
|
:param polygon: list of OpenStreetMap nodes
|
|
"""
|
|
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
|
|
|
|
|
|
def get_path(
|
|
nodes: list[OSMNode],
|
|
shift: np.ndarray,
|
|
flinger: Flinger,
|
|
parallel_offset: float = 0.0,
|
|
) -> str:
|
|
"""Construct SVG path commands from nodes."""
|
|
return Polyline(
|
|
[flinger.fling(node.coordinates) + shift for node in nodes]
|
|
).get_path(parallel_offset)
|