mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-03 12:16:42 +02:00
195 lines
5.5 KiB
Python
195 lines
5.5 KiB
Python
"""
|
|
Road shape drawing.
|
|
"""
|
|
from dataclasses import dataclass
|
|
from typing import List
|
|
|
|
import numpy as np
|
|
import svgwrite
|
|
from shapely.geometry import LineString, Point
|
|
|
|
from roentgen.flinger import Flinger, angle, turn_by_angle, norm
|
|
from roentgen.osm_reader import OSMNode
|
|
|
|
|
|
@dataclass
|
|
class RoadPart:
|
|
"""
|
|
Line part of the road.
|
|
"""
|
|
|
|
def __init__(
|
|
self,
|
|
point_1: np.array,
|
|
point_2: np.array,
|
|
left_offset: float,
|
|
right_offset: float,
|
|
lanes: List[float],
|
|
):
|
|
self.point_1: np.array = point_1
|
|
self.point_2: np.array = point_2
|
|
self.left_offset: float = left_offset
|
|
self.right_offset: float = right_offset
|
|
self.lanes: List[float] = lanes
|
|
|
|
self.turned = norm(
|
|
turn_by_angle(self.point_2 - self.point_1, np.pi / 2)
|
|
)
|
|
self.right = self.turned * self.right_offset
|
|
self.left = -self.turned * self.left_offset
|
|
|
|
self.rm = None
|
|
self.lm = None
|
|
self.rp = None
|
|
self.lp = None
|
|
|
|
@classmethod
|
|
def from_nodes(
|
|
cls,
|
|
node_1: OSMNode,
|
|
node_2: OSMNode,
|
|
flinger: Flinger,
|
|
left_offset: float,
|
|
right_offset: float,
|
|
lanes: List[float],
|
|
) -> "RoadPart":
|
|
"""
|
|
Construct road part from OSM nodes.
|
|
"""
|
|
return cls(
|
|
flinger.fling(node_1.coordinates),
|
|
flinger.fling(node_2.coordinates),
|
|
left_offset,
|
|
right_offset,
|
|
lanes,
|
|
)
|
|
|
|
def update(self):
|
|
"""
|
|
Compute additional points.
|
|
"""
|
|
if self.rp is not None and self.lp is not None:
|
|
self.rm = self.lp + self.right - self.left
|
|
self.lm = self.rp - self.right + self.left
|
|
|
|
def get_angle(self) -> float:
|
|
"""
|
|
Get an angle between line and x axis.
|
|
"""
|
|
return angle(self.point_2 - self.point_1)
|
|
|
|
def draw_debug(self, drawing: svgwrite.Drawing):
|
|
"""
|
|
Draw some debug lines.
|
|
"""
|
|
line = drawing.path(
|
|
("M", self.point_1, "L", self.point_2),
|
|
fill="none",
|
|
stroke="#000000",
|
|
)
|
|
drawing.add(line)
|
|
line = drawing.path(
|
|
("M", self.point_1 + self.right, "L", self.point_2 + self.right),
|
|
fill="none",
|
|
stroke="#FF0000",
|
|
stroke_width=0.5,
|
|
)
|
|
drawing.add(line)
|
|
line = drawing.path(
|
|
("M", self.point_1 + self.left, "L", self.point_2 + self.left),
|
|
fill="none",
|
|
stroke="#0000FF",
|
|
stroke_width=0.5,
|
|
)
|
|
drawing.add(line)
|
|
|
|
if self.rp is not None:
|
|
circle = drawing.circle(self.rp, 2)
|
|
drawing.add(circle)
|
|
if self.lp is not None:
|
|
circle = drawing.circle(self.lp, 2)
|
|
drawing.add(circle)
|
|
if self.rm is not None:
|
|
circle = drawing.circle(self.rm, 2, fill="#FF0000")
|
|
drawing.add(circle)
|
|
if self.lm is not None:
|
|
circle = drawing.circle(self.lm, 2, fill="#0000FF")
|
|
drawing.add(circle)
|
|
|
|
def draw(self, drawing: svgwrite.Drawing):
|
|
"""
|
|
Draw road part.
|
|
"""
|
|
d = [
|
|
"M", self.point_2 + self.right,
|
|
"L", self.point_2 + self.left,
|
|
"L", self.lp,
|
|
"L", self.rp,
|
|
"Z",
|
|
]
|
|
drawing.add(drawing.path(d, fill="#CCCCCC"))
|
|
|
|
d = ["M", self.rm, "L", self.rp, "L", self.lm, "L", self.lp, "Z"]
|
|
drawing.add(drawing.path(d, fill="#C0C0C0"))
|
|
|
|
def draw_lanes(self, drawing: svgwrite.Drawing):
|
|
"""
|
|
Draw lane delimiters.
|
|
"""
|
|
for lane in self.lanes:
|
|
a = self.right - self.turned * lane
|
|
drawing.add(
|
|
drawing.path(
|
|
["M", self.point_2 + a, "L", self.point_1 + a],
|
|
fill="none",
|
|
stroke="#FFFFFF",
|
|
stroke_width=2,
|
|
stroke_dasharray="7,7",
|
|
)
|
|
)
|
|
|
|
|
|
class Intersection:
|
|
"""
|
|
An intersection of the roads, that is described by its parts. All first
|
|
points of the road parts should be the same.
|
|
"""
|
|
|
|
def __init__(self, parts: List[RoadPart]):
|
|
self.parts: List[RoadPart] = sorted(parts, key=lambda x: x.get_angle())
|
|
|
|
for index in range(len(self.parts)):
|
|
next_index: int = 0 if index == len(self.parts) - 1 else index + 1
|
|
part_1 = self.parts[index]
|
|
part_2 = self.parts[next_index]
|
|
line_1 = LineString(
|
|
[part_1.point_1 + part_1.right, part_1.point_2 + part_1.right]
|
|
)
|
|
line_2 = LineString(
|
|
[part_2.point_1 + part_2.left, part_2.point_2 + part_2.left]
|
|
)
|
|
print(line_1)
|
|
print(line_2)
|
|
a = line_1.intersection(line_2)
|
|
print(a)
|
|
if isinstance(a, Point):
|
|
part_1.rp = np.array((a.x, a.y))
|
|
part_2.lp = np.array((a.x, a.y))
|
|
part_1.update()
|
|
part_2.update()
|
|
|
|
def draw(self, drawing: svgwrite.Drawing):
|
|
"""
|
|
Draw all road parts and intersection.
|
|
"""
|
|
for part in self.parts:
|
|
part.draw(drawing)
|
|
for part in self.parts:
|
|
part.draw_lanes(drawing)
|
|
|
|
d = ["M"]
|
|
for part in self.parts:
|
|
d += [part.lp, "L"]
|
|
d[-1] = "Z"
|
|
|
|
drawing.add(drawing.path(d, fill="#BBBBBB"))
|