""" Figures displayed on the map. """ from typing import Optional import numpy as np from colour import Color from svgwrite import Drawing from map_machine.geometry.flinger import Flinger from map_machine.osm.osm_reader import OSMNode, Tagged from map_machine.scheme import LineStyle, Scheme __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) self.inners: list[list[OSMNode]] = list(map(make_clockwise, inners)) self.outers: list[list[OSMNode]] = list( map(make_counter_clockwise, 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 class Tree(Tagged): """Tree on the map.""" def __init__( self, tags: dict[str, str], coordinates: np.ndarray, point: np.ndarray ) -> None: super().__init__(tags) self.coordinates: np.ndarray = coordinates self.point: np.ndarray = point def draw(self, svg: Drawing, flinger: Flinger, scheme: Scheme) -> None: """Draw crown and trunk.""" scale: float = flinger.get_scale(self.coordinates) diameter_crown: Optional[float] = self.get_float("diameter_crown") radius: float if diameter_crown is not None: radius = float(self.tags["diameter_crown"]) / 2.0 else: radius = 2.0 color: Color = scheme.get_color("evergreen_color") svg.add(svg.circle(self.point, radius * scale, fill=color, opacity=0.3)) circumference: Optional[float] = self.get_float("circumference") if circumference is not None: radius: float = circumference / 2.0 / np.pi svg.add(svg.circle(self.point, radius * scale, fill="#B89A74")) 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) -> str: """Construct SVG path commands from nodes.""" return Polyline( [flinger.fling(node.coordinates) + shift for node in nodes] ).get_path()