Issue #45: add lane parsing; refactor figure.

This commit is contained in:
Sergey Vartanov 2021-06-03 04:39:05 +03:00
parent b63a0ed397
commit 735f19428d
5 changed files with 233 additions and 206 deletions

View file

@ -11,6 +11,7 @@ from colour import Color
from roentgen import ui
from roentgen.color import get_gradient_color
from roentgen.figure import Building, StyledFigure, Road
from roentgen.flinger import Flinger
# fmt: off
@ -21,200 +22,22 @@ from roentgen.osm_reader import (
Map, OSMMember, OSMNode, OSMRelation, OSMWay, Tagged
)
from roentgen.point import Point
from roentgen.scheme import DEFAULT_COLOR, LineStyle, RoadMatcher, Scheme
from roentgen.scheme import DEFAULT_COLOR, LineStyle, Scheme
from roentgen.util import MinMax
# fmt: on
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEBUG: bool = False
TIME_COLOR_SCALE: List[Color] = [
Color("#581845"), Color("#900C3F"), Color("#C70039"),
Color("#FF5733"), Color("#FFC300"), Color("#DAF7A6"),
Color("#581845"),
Color("#900C3F"),
Color("#C70039"),
Color("#FF5733"),
Color("#FFC300"),
Color("#DAF7A6"),
]
# fmt: on
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
for index, node in enumerate(polygon): # type: int, OSMNode
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
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))
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]],
):
super().__init__()
self.tags: Dict[str, str] = tags
self.inners: List[List[OSMNode]] = []
self.outers: List[List[OSMNode]] = []
for inner_nodes in inners:
self.inners.append(make_clockwise(inner_nodes))
for outer_nodes in outers:
self.outers.append(make_counter_clockwise(outer_nodes))
def get_path(
self, flinger: Flinger, shift: np.array = np.array((0, 0))
) -> str:
"""
Get SVG path commands.
:param flinger: converter for geo coordinates
:param shift: shift vector
"""
path: str = ""
for outer_nodes in self.outers:
path += f"{get_path(outer_nodes, shift, flinger)} "
for inner_nodes in self.inners:
path += f"{get_path(inner_nodes, shift, 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,
):
super().__init__(tags, inners, outers)
self.line_style = line_style
class Segment:
"""
Line segment.
"""
def __init__(self, point_1: np.array, point_2: np.array):
self.point_1 = point_1
self.point_2 = point_2
difference: np.array = point_2 - point_1
vector: np.array = difference / np.linalg.norm(difference)
self.angle: float = np.arccos(np.dot(vector, np.array((0, 1)))) / np.pi
def __lt__(self, other: "Segment") -> bool:
return (
((self.point_1 + self.point_2) / 2)[1]
< ((other.point_1 + other.point_2) / 2)[1]
) # fmt: skip
class Building(Figure):
"""
Building on the map.
"""
def __init__(
self,
tags: Dict[str, str],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
flinger: Flinger,
scheme: Scheme,
):
super().__init__(tags, inners, outers)
style: Dict[str, Any] = {
"fill": scheme.get_color("building_color").hex,
"stroke": scheme.get_color("building_border_color").hex,
}
self.line_style = LineStyle(style)
self.parts = []
for nodes in self.inners + self.outers:
for i in range(len(nodes) - 1):
flung_1: np.array = flinger.fling(nodes[i].coordinates)
flung_2: np.array = flinger.fling(nodes[i + 1].coordinates)
self.parts.append(Segment(flung_1, flung_2))
self.parts = sorted(self.parts)
def get_levels(self) -> float:
"""
Get building level number.
"""
try:
return max(3.0, float(self.get_tag("building:levels")))
except (ValueError, TypeError):
return 3
class Road(Figure):
"""
Road or track on the map.
"""
def __init__(
self,
tags: Dict[str, str],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
matcher: RoadMatcher,
):
super().__init__(tags, inners, outers)
self.matcher: RoadMatcher = matcher
self.width: Optional[float] = None
self.lanes: int = 1
if "lanes" in tags:
try:
self.width = float(tags["lanes"]) * 3.7
self.lanes = float(tags["lanes"])
except ValueError:
pass
if "width" in tags:
try:
self.width = float(tags["width"])
except ValueError:
pass
def line_center(nodes: List[OSMNode], flinger: Flinger) -> np.array:
@ -290,23 +113,6 @@ def glue(ways: List[OSMWay]) -> List[List[OSMNode]]:
return result
def get_path(nodes: List[OSMNode], shift: np.array, flinger: Flinger) -> str:
"""
Construct SVG path commands from nodes.
"""
path: str = ""
prev_node: Optional[OSMNode] = None
for node in nodes: # type: OSMNode
flung = flinger.fling(node.coordinates) + shift
path += ("L" if prev_node else "M") + f" {flung[0]},{flung[1]} "
prev_node = node
if nodes[0] == nodes[-1]:
path += "Z"
else:
path = path[:-1]
return path
def is_cycle(nodes) -> bool:
"""
Is way a cycle way or an area boundary.