mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-02 19:56:39 +02:00
Refactor buildings and craters.
This commit is contained in:
parent
0f632087e4
commit
1b035300df
5 changed files with 293 additions and 266 deletions
|
@ -11,11 +11,11 @@ import numpy as np
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
|
||||||
from map_machine.color import get_gradient_color
|
from map_machine.color import get_gradient_color
|
||||||
|
from map_machine.feature.building import Building
|
||||||
|
from map_machine.feature.crater import Crater
|
||||||
|
from map_machine.feature.direction import DirectionSector
|
||||||
from map_machine.feature.road import Road, Roads
|
from map_machine.feature.road import Road, Roads
|
||||||
from map_machine.figure import (
|
from map_machine.figure import (
|
||||||
Building,
|
|
||||||
Crater,
|
|
||||||
DirectionSector,
|
|
||||||
StyledFigure,
|
StyledFigure,
|
||||||
Tree,
|
Tree,
|
||||||
)
|
)
|
||||||
|
|
146
map_machine/feature/building.py
Normal file
146
map_machine/feature/building.py
Normal file
|
@ -0,0 +1,146 @@
|
||||||
|
"""
|
||||||
|
Buildings on the map.
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
from colour import Color
|
||||||
|
from svgwrite import Drawing
|
||||||
|
from svgwrite.container import Group
|
||||||
|
from svgwrite.path import Path
|
||||||
|
from typing import Any, Optional
|
||||||
|
|
||||||
|
from map_machine.drawing import PathCommands
|
||||||
|
from map_machine.feature.direction import Segment
|
||||||
|
from map_machine.figure import Figure
|
||||||
|
from map_machine.geometry.flinger import Flinger
|
||||||
|
from map_machine.osm.osm_reader import OSMNode
|
||||||
|
from map_machine.scheme import Scheme, LineStyle
|
||||||
|
|
||||||
|
BUILDING_HEIGHT_SCALE: float = 2.5
|
||||||
|
BUILDING_MINIMAL_HEIGHT: float = 8.0
|
||||||
|
|
||||||
|
|
||||||
|
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,
|
||||||
|
) -> None:
|
||||||
|
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 = LineStyle(style)
|
||||||
|
self.parts: list[Segment] = []
|
||||||
|
|
||||||
|
for nodes in self.inners + self.outers:
|
||||||
|
for i in range(len(nodes) - 1):
|
||||||
|
flung_1: np.ndarray = flinger.fling(nodes[i].coordinates)
|
||||||
|
flung_2: np.ndarray = flinger.fling(nodes[i + 1].coordinates)
|
||||||
|
self.parts.append(Segment(flung_1, flung_2))
|
||||||
|
|
||||||
|
self.parts = sorted(self.parts)
|
||||||
|
|
||||||
|
self.height: float = BUILDING_MINIMAL_HEIGHT
|
||||||
|
self.min_height: float = 0.0
|
||||||
|
|
||||||
|
levels: Optional[str] = self.get_float("building:levels")
|
||||||
|
if levels:
|
||||||
|
self.height = float(levels) * BUILDING_HEIGHT_SCALE
|
||||||
|
|
||||||
|
levels: Optional[str] = self.get_float("building:min_level")
|
||||||
|
if levels:
|
||||||
|
self.min_height = float(levels) * BUILDING_HEIGHT_SCALE
|
||||||
|
|
||||||
|
height: Optional[float] = self.get_length("height")
|
||||||
|
if height:
|
||||||
|
self.height = height
|
||||||
|
|
||||||
|
height: Optional[float] = self.get_length("min_height")
|
||||||
|
if height:
|
||||||
|
self.min_height = height
|
||||||
|
|
||||||
|
def draw(self, svg: Drawing, flinger: Flinger) -> None:
|
||||||
|
"""Draw simple building shape."""
|
||||||
|
path: Path = Path(d=self.get_path(flinger))
|
||||||
|
path.update(self.line_style.style)
|
||||||
|
path.update({"stroke-linejoin": "round"})
|
||||||
|
svg.add(path)
|
||||||
|
|
||||||
|
def draw_shade(self, building_shade: Group, flinger: Flinger) -> None:
|
||||||
|
"""Draw shade casted by the building."""
|
||||||
|
scale: float = flinger.get_scale() / 3.0
|
||||||
|
shift_1: np.ndarray = np.array((scale * self.min_height, 0.0))
|
||||||
|
shift_2: np.ndarray = np.array((scale * self.height, 0.0))
|
||||||
|
commands: str = self.get_path(flinger, shift_1)
|
||||||
|
path: Path = Path(
|
||||||
|
d=commands, fill="#000000", stroke="#000000", stroke_width=1.0
|
||||||
|
)
|
||||||
|
building_shade.add(path)
|
||||||
|
for nodes in self.inners + self.outers:
|
||||||
|
for i in range(len(nodes) - 1):
|
||||||
|
flung_1 = flinger.fling(nodes[i].coordinates)
|
||||||
|
flung_2 = flinger.fling(nodes[i + 1].coordinates)
|
||||||
|
command: PathCommands = [
|
||||||
|
"M",
|
||||||
|
np.add(flung_1, shift_1),
|
||||||
|
"L",
|
||||||
|
np.add(flung_2, shift_1),
|
||||||
|
np.add(flung_2, shift_2),
|
||||||
|
np.add(flung_1, shift_2),
|
||||||
|
"Z",
|
||||||
|
]
|
||||||
|
path: Path = Path(
|
||||||
|
command, fill="#000000", stroke="#000000", stroke_width=1.0
|
||||||
|
)
|
||||||
|
building_shade.add(path)
|
||||||
|
|
||||||
|
def draw_walls(
|
||||||
|
self, svg: Drawing, height: float, previous_height: float, scale: float
|
||||||
|
) -> None:
|
||||||
|
"""Draw building walls."""
|
||||||
|
shift_1: np.ndarray = np.array((0.0, -previous_height * scale))
|
||||||
|
shift_2: np.ndarray = np.array((0.0, -height * scale))
|
||||||
|
for segment in self.parts:
|
||||||
|
fill: Color
|
||||||
|
if height == 2.0:
|
||||||
|
fill = Color("#AAAAAA")
|
||||||
|
elif height == 4.0:
|
||||||
|
fill = Color("#C3C3C3")
|
||||||
|
else:
|
||||||
|
color_part: float = 0.8 + segment.angle * 0.2
|
||||||
|
fill = Color(rgb=(color_part, color_part, color_part))
|
||||||
|
|
||||||
|
command = (
|
||||||
|
"M",
|
||||||
|
segment.point_1 + shift_1,
|
||||||
|
"L",
|
||||||
|
segment.point_2 + shift_1,
|
||||||
|
segment.point_2 + shift_2,
|
||||||
|
segment.point_1 + shift_2,
|
||||||
|
segment.point_1 + shift_1,
|
||||||
|
"Z",
|
||||||
|
)
|
||||||
|
path: Path = svg.path(
|
||||||
|
d=command,
|
||||||
|
fill=fill.hex,
|
||||||
|
stroke=fill.hex,
|
||||||
|
stroke_width=1,
|
||||||
|
stroke_linejoin="round",
|
||||||
|
)
|
||||||
|
svg.add(path)
|
||||||
|
|
||||||
|
def draw_roof(self, svg: Drawing, flinger: Flinger, scale: float) -> None:
|
||||||
|
"""Draw building roof."""
|
||||||
|
path: Path = Path(
|
||||||
|
d=self.get_path(flinger, np.array([0.0, -self.height * scale]))
|
||||||
|
)
|
||||||
|
path.update(self.line_style.style)
|
||||||
|
path.update({"stroke-linejoin": "round"})
|
||||||
|
svg.add(path)
|
46
map_machine/feature/crater.py
Normal file
46
map_machine/feature/crater.py
Normal file
|
@ -0,0 +1,46 @@
|
||||||
|
"""
|
||||||
|
Crater on the map.
|
||||||
|
"""
|
||||||
|
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 Tagged
|
||||||
|
|
||||||
|
|
||||||
|
class Crater(Tagged):
|
||||||
|
"""Volcano or impact crater 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) -> None:
|
||||||
|
"""Draw crater ridge."""
|
||||||
|
scale: float = flinger.get_scale(self.coordinates)
|
||||||
|
assert "diameter" in self.tags
|
||||||
|
radius: float = float(self.tags["diameter"]) / 2.0
|
||||||
|
radial_gradient = svg.radialGradient(
|
||||||
|
center=self.point + np.array((0.0, radius * scale / 7.0)),
|
||||||
|
r=radius * scale,
|
||||||
|
gradientUnits="userSpaceOnUse",
|
||||||
|
)
|
||||||
|
color: Color = Color("#000000")
|
||||||
|
gradient = svg.defs.add(radial_gradient)
|
||||||
|
(
|
||||||
|
gradient
|
||||||
|
.add_stop_color(0.0, color.hex, opacity=0.2)
|
||||||
|
.add_stop_color(0.7, color.hex, opacity=0.2)
|
||||||
|
.add_stop_color(1.0, color.hex, opacity=1.0)
|
||||||
|
) # fmt: skip
|
||||||
|
circle = svg.circle(
|
||||||
|
self.point,
|
||||||
|
radius * scale,
|
||||||
|
fill=gradient.get_funciri(),
|
||||||
|
opacity=0.2,
|
||||||
|
)
|
||||||
|
svg.add(circle)
|
|
@ -1,12 +1,17 @@
|
||||||
"""
|
"""
|
||||||
Direction tag support.
|
Direction tag support.
|
||||||
"""
|
"""
|
||||||
|
from colour import Color
|
||||||
|
from svgwrite import Drawing
|
||||||
|
from svgwrite.gradients import RadialGradient
|
||||||
|
from svgwrite.path import Path
|
||||||
from typing import Iterator, Optional
|
from typing import Iterator, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from portolan import middle
|
from portolan import middle
|
||||||
|
|
||||||
from map_machine.drawing import PathCommands
|
from map_machine.drawing import PathCommands
|
||||||
|
from map_machine.osm.osm_reader import Tagged
|
||||||
|
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
@ -155,3 +160,95 @@ class DirectionSet:
|
||||||
if result == [False] * len(result):
|
if result == [False] * len(result):
|
||||||
return False
|
return False
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
class DirectionSector(Tagged):
|
||||||
|
"""Sector that represents direction."""
|
||||||
|
|
||||||
|
def __init__(self, tags: dict[str, str], point: np.ndarray) -> None:
|
||||||
|
super().__init__(tags)
|
||||||
|
self.point: np.ndarray = point
|
||||||
|
|
||||||
|
def draw(self, svg: Drawing, scheme) -> None:
|
||||||
|
"""Draw gradient sector."""
|
||||||
|
angle: Optional[float] = None
|
||||||
|
is_revert_gradient: bool = False
|
||||||
|
direction: str
|
||||||
|
direction_radius: float
|
||||||
|
direction_color: Color
|
||||||
|
|
||||||
|
if self.get_tag("man_made") == "surveillance":
|
||||||
|
direction = self.get_tag("camera:direction")
|
||||||
|
if "camera:angle" in self.tags:
|
||||||
|
angle = float(self.get_tag("camera:angle"))
|
||||||
|
if "angle" in self.tags:
|
||||||
|
angle = float(self.get_tag("angle"))
|
||||||
|
direction_radius = 50.0
|
||||||
|
direction_color = scheme.get_color("direction_camera_color")
|
||||||
|
elif self.get_tag("traffic_sign") == "stop":
|
||||||
|
direction = self.get_tag("direction")
|
||||||
|
direction_radius = 25.0
|
||||||
|
direction_color = Color("red")
|
||||||
|
else:
|
||||||
|
direction = self.get_tag("direction")
|
||||||
|
direction_radius = 50.0
|
||||||
|
direction_color = scheme.get_color("direction_view_color")
|
||||||
|
is_revert_gradient = True
|
||||||
|
|
||||||
|
if not direction:
|
||||||
|
return
|
||||||
|
|
||||||
|
point: np.ndarray = (self.point.astype(int)).astype(float)
|
||||||
|
|
||||||
|
paths: Iterator[PathCommands]
|
||||||
|
if angle is not None:
|
||||||
|
paths = [Sector(direction, angle).draw(point, direction_radius)]
|
||||||
|
else:
|
||||||
|
paths = DirectionSet(direction).draw(point, direction_radius)
|
||||||
|
|
||||||
|
for path in paths:
|
||||||
|
radial_gradient: RadialGradient = svg.radialGradient(
|
||||||
|
center=point,
|
||||||
|
r=direction_radius,
|
||||||
|
gradientUnits="userSpaceOnUse",
|
||||||
|
)
|
||||||
|
gradient: RadialGradient = svg.defs.add(radial_gradient)
|
||||||
|
|
||||||
|
if is_revert_gradient:
|
||||||
|
(
|
||||||
|
gradient
|
||||||
|
.add_stop_color(0.0, direction_color.hex, opacity=0.0)
|
||||||
|
.add_stop_color(1.0, direction_color.hex, opacity=0.7)
|
||||||
|
) # fmt: skip
|
||||||
|
else:
|
||||||
|
(
|
||||||
|
gradient
|
||||||
|
.add_stop_color(0.0, direction_color.hex, opacity=0.4)
|
||||||
|
.add_stop_color(1.0, direction_color.hex, opacity=0.0)
|
||||||
|
) # fmt: skip
|
||||||
|
|
||||||
|
path_element: Path = svg.path(
|
||||||
|
d=["M", point] + path + ["L", point, "Z"],
|
||||||
|
fill=gradient.get_funciri(),
|
||||||
|
)
|
||||||
|
svg.add(path_element)
|
||||||
|
|
||||||
|
|
||||||
|
class Segment:
|
||||||
|
"""Closed line segment."""
|
||||||
|
|
||||||
|
def __init__(self, point_1: np.ndarray, point_2: np.ndarray) -> None:
|
||||||
|
self.point_1: np.ndarray = point_1
|
||||||
|
self.point_2: np.ndarray = point_2
|
||||||
|
|
||||||
|
difference: np.ndarray = point_2 - point_1
|
||||||
|
vector: np.ndarray = difference / np.linalg.norm(difference)
|
||||||
|
self.angle: float = (
|
||||||
|
np.arccos(np.dot(vector, np.array((0.0, 1.0)))) / np.pi
|
||||||
|
)
|
||||||
|
|
||||||
|
def __lt__(self, other: "Segment") -> bool:
|
||||||
|
return (
|
||||||
|
((self.point_1 + self.point_2) / 2.0)[1]
|
||||||
|
< ((other.point_1 + other.point_2) / 2.0)[1]
|
||||||
|
) # fmt: skip
|
||||||
|
|
|
@ -1,17 +1,12 @@
|
||||||
"""
|
"""
|
||||||
Figures displayed on the map.
|
Figures displayed on the map.
|
||||||
"""
|
"""
|
||||||
from typing import Any, Iterator, Optional
|
from typing import Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from colour import Color
|
from colour import Color
|
||||||
from svgwrite import Drawing
|
from svgwrite import Drawing
|
||||||
from svgwrite.container import Group
|
|
||||||
from svgwrite.gradients import RadialGradient
|
|
||||||
from svgwrite.path import Path
|
|
||||||
|
|
||||||
from map_machine.drawing import PathCommands
|
|
||||||
from map_machine.feature.direction import DirectionSet, Sector
|
|
||||||
from map_machine.geometry.flinger import Flinger
|
from map_machine.geometry.flinger import Flinger
|
||||||
from map_machine.osm.osm_reader import OSMNode, Tagged
|
from map_machine.osm.osm_reader import OSMNode, Tagged
|
||||||
from map_machine.scheme import LineStyle, Scheme
|
from map_machine.scheme import LineStyle, Scheme
|
||||||
|
@ -21,9 +16,6 @@ __email__ = "me@enzet.ru"
|
||||||
|
|
||||||
from map_machine.geometry.vector import Polyline
|
from map_machine.geometry.vector import Polyline
|
||||||
|
|
||||||
BUILDING_HEIGHT_SCALE: float = 2.5
|
|
||||||
BUILDING_MINIMAL_HEIGHT: float = 8.0
|
|
||||||
|
|
||||||
|
|
||||||
class Figure(Tagged):
|
class Figure(Tagged):
|
||||||
"""Some figure on the map: way or area."""
|
"""Some figure on the map: way or area."""
|
||||||
|
@ -61,133 +53,6 @@ class Figure(Tagged):
|
||||||
return path
|
return path
|
||||||
|
|
||||||
|
|
||||||
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,
|
|
||||||
) -> None:
|
|
||||||
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 = LineStyle(style)
|
|
||||||
self.parts: list[Segment] = []
|
|
||||||
|
|
||||||
for nodes in self.inners + self.outers:
|
|
||||||
for i in range(len(nodes) - 1):
|
|
||||||
flung_1: np.ndarray = flinger.fling(nodes[i].coordinates)
|
|
||||||
flung_2: np.ndarray = flinger.fling(nodes[i + 1].coordinates)
|
|
||||||
self.parts.append(Segment(flung_1, flung_2))
|
|
||||||
|
|
||||||
self.parts = sorted(self.parts)
|
|
||||||
|
|
||||||
self.height: float = BUILDING_MINIMAL_HEIGHT
|
|
||||||
self.min_height: float = 0.0
|
|
||||||
|
|
||||||
levels: Optional[str] = self.get_float("building:levels")
|
|
||||||
if levels:
|
|
||||||
self.height = float(levels) * BUILDING_HEIGHT_SCALE
|
|
||||||
|
|
||||||
levels: Optional[str] = self.get_float("building:min_level")
|
|
||||||
if levels:
|
|
||||||
self.min_height = float(levels) * BUILDING_HEIGHT_SCALE
|
|
||||||
|
|
||||||
height: Optional[float] = self.get_length("height")
|
|
||||||
if height:
|
|
||||||
self.height = height
|
|
||||||
|
|
||||||
height: Optional[float] = self.get_length("min_height")
|
|
||||||
if height:
|
|
||||||
self.min_height = height
|
|
||||||
|
|
||||||
def draw(self, svg: Drawing, flinger: Flinger) -> None:
|
|
||||||
"""Draw simple building shape."""
|
|
||||||
path: Path = Path(d=self.get_path(flinger))
|
|
||||||
path.update(self.line_style.style)
|
|
||||||
path.update({"stroke-linejoin": "round"})
|
|
||||||
svg.add(path)
|
|
||||||
|
|
||||||
def draw_shade(self, building_shade: Group, flinger: Flinger) -> None:
|
|
||||||
"""Draw shade casted by the building."""
|
|
||||||
scale: float = flinger.get_scale() / 3.0
|
|
||||||
shift_1: np.ndarray = np.array((scale * self.min_height, 0))
|
|
||||||
shift_2: np.ndarray = np.array((scale * self.height, 0))
|
|
||||||
commands: str = self.get_path(flinger, shift_1)
|
|
||||||
path: Path = Path(
|
|
||||||
d=commands, fill="#000000", stroke="#000000", stroke_width=1
|
|
||||||
)
|
|
||||||
building_shade.add(path)
|
|
||||||
for nodes in self.inners + self.outers:
|
|
||||||
for i in range(len(nodes) - 1):
|
|
||||||
flung_1 = flinger.fling(nodes[i].coordinates)
|
|
||||||
flung_2 = flinger.fling(nodes[i + 1].coordinates)
|
|
||||||
command: PathCommands = [
|
|
||||||
"M",
|
|
||||||
np.add(flung_1, shift_1),
|
|
||||||
"L",
|
|
||||||
np.add(flung_2, shift_1),
|
|
||||||
np.add(flung_2, shift_2),
|
|
||||||
np.add(flung_1, shift_2),
|
|
||||||
"Z",
|
|
||||||
]
|
|
||||||
path: Path = Path(
|
|
||||||
command, fill="#000000", stroke="#000000", stroke_width=1
|
|
||||||
)
|
|
||||||
building_shade.add(path)
|
|
||||||
|
|
||||||
def draw_walls(
|
|
||||||
self, svg: Drawing, height: float, previous_height: float, scale: float
|
|
||||||
) -> None:
|
|
||||||
"""Draw building walls."""
|
|
||||||
shift_1: np.ndarray = np.array((0, -previous_height * scale))
|
|
||||||
shift_2: np.ndarray = np.array((0, -height * scale))
|
|
||||||
for segment in self.parts:
|
|
||||||
fill: Color
|
|
||||||
if height == 2:
|
|
||||||
fill = Color("#AAAAAA")
|
|
||||||
elif height == 4:
|
|
||||||
fill = Color("#C3C3C3")
|
|
||||||
else:
|
|
||||||
color_part: float = 0.8 + segment.angle * 0.2
|
|
||||||
fill = Color(rgb=(color_part, color_part, color_part))
|
|
||||||
|
|
||||||
command = (
|
|
||||||
"M",
|
|
||||||
segment.point_1 + shift_1,
|
|
||||||
"L",
|
|
||||||
segment.point_2 + shift_1,
|
|
||||||
segment.point_2 + shift_2,
|
|
||||||
segment.point_1 + shift_2,
|
|
||||||
segment.point_1 + shift_1,
|
|
||||||
"Z",
|
|
||||||
)
|
|
||||||
path: Path = svg.path(
|
|
||||||
d=command,
|
|
||||||
fill=fill.hex,
|
|
||||||
stroke=fill.hex,
|
|
||||||
stroke_width=1,
|
|
||||||
stroke_linejoin="round",
|
|
||||||
)
|
|
||||||
svg.add(path)
|
|
||||||
|
|
||||||
def draw_roof(self, svg: Drawing, flinger: Flinger, scale: float) -> None:
|
|
||||||
"""Draw building roof."""
|
|
||||||
path: Path = Path(
|
|
||||||
d=self.get_path(flinger, np.array([0, -self.height * scale]))
|
|
||||||
)
|
|
||||||
path.update(self.line_style.style)
|
|
||||||
path.update({"stroke-linejoin": "round"})
|
|
||||||
svg.add(path)
|
|
||||||
|
|
||||||
|
|
||||||
class StyledFigure(Figure):
|
class StyledFigure(Figure):
|
||||||
"""Figure with stroke and fill style."""
|
"""Figure with stroke and fill style."""
|
||||||
|
|
||||||
|
@ -202,43 +67,6 @@ class StyledFigure(Figure):
|
||||||
self.line_style: LineStyle = line_style
|
self.line_style: LineStyle = line_style
|
||||||
|
|
||||||
|
|
||||||
class Crater(Tagged):
|
|
||||||
"""Volcano or impact crater 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) -> None:
|
|
||||||
"""Draw crater ridge."""
|
|
||||||
scale: float = flinger.get_scale(self.coordinates)
|
|
||||||
assert "diameter" in self.tags
|
|
||||||
radius: float = float(self.tags["diameter"]) / 2.0
|
|
||||||
radial_gradient = svg.radialGradient(
|
|
||||||
center=self.point + np.array((0, radius * scale / 7)),
|
|
||||||
r=radius * scale,
|
|
||||||
gradientUnits="userSpaceOnUse",
|
|
||||||
)
|
|
||||||
color: Color = Color("#000000")
|
|
||||||
gradient = svg.defs.add(radial_gradient)
|
|
||||||
(
|
|
||||||
gradient
|
|
||||||
.add_stop_color(0, color.hex, opacity=0.2)
|
|
||||||
.add_stop_color(0.7, color.hex, opacity=0.2)
|
|
||||||
.add_stop_color(1, color.hex, opacity=1)
|
|
||||||
) # fmt: skip
|
|
||||||
circle = svg.circle(
|
|
||||||
self.point,
|
|
||||||
radius * scale,
|
|
||||||
fill=gradient.get_funciri(),
|
|
||||||
opacity=0.2,
|
|
||||||
)
|
|
||||||
svg.add(circle)
|
|
||||||
|
|
||||||
|
|
||||||
class Tree(Tagged):
|
class Tree(Tagged):
|
||||||
"""Tree on the map."""
|
"""Tree on the map."""
|
||||||
|
|
||||||
|
@ -269,96 +97,6 @@ class Tree(Tagged):
|
||||||
svg.add(svg.circle(self.point, radius * scale, fill="#B89A74"))
|
svg.add(svg.circle(self.point, radius * scale, fill="#B89A74"))
|
||||||
|
|
||||||
|
|
||||||
class DirectionSector(Tagged):
|
|
||||||
"""Sector that represents direction."""
|
|
||||||
|
|
||||||
def __init__(self, tags: dict[str, str], point: np.ndarray) -> None:
|
|
||||||
super().__init__(tags)
|
|
||||||
self.point: np.ndarray = point
|
|
||||||
|
|
||||||
def draw(self, svg: Drawing, scheme: Scheme) -> None:
|
|
||||||
"""Draw gradient sector."""
|
|
||||||
angle: Optional[float] = None
|
|
||||||
is_revert_gradient: bool = False
|
|
||||||
direction: str
|
|
||||||
direction_radius: float
|
|
||||||
direction_color: Color
|
|
||||||
|
|
||||||
if self.get_tag("man_made") == "surveillance":
|
|
||||||
direction = self.get_tag("camera:direction")
|
|
||||||
if "camera:angle" in self.tags:
|
|
||||||
angle = float(self.get_tag("camera:angle"))
|
|
||||||
if "angle" in self.tags:
|
|
||||||
angle = float(self.get_tag("angle"))
|
|
||||||
direction_radius = 50
|
|
||||||
direction_color = scheme.get_color("direction_camera_color")
|
|
||||||
elif self.get_tag("traffic_sign") == "stop":
|
|
||||||
direction = self.get_tag("direction")
|
|
||||||
direction_radius = 25
|
|
||||||
direction_color = Color("red")
|
|
||||||
else:
|
|
||||||
direction = self.get_tag("direction")
|
|
||||||
direction_radius = 50
|
|
||||||
direction_color = scheme.get_color("direction_view_color")
|
|
||||||
is_revert_gradient = True
|
|
||||||
|
|
||||||
if not direction:
|
|
||||||
return
|
|
||||||
|
|
||||||
point: np.ndarray = (self.point.astype(int)).astype(float)
|
|
||||||
|
|
||||||
paths: Iterator[PathCommands]
|
|
||||||
if angle is not None:
|
|
||||||
paths = [Sector(direction, angle).draw(point, direction_radius)]
|
|
||||||
else:
|
|
||||||
paths = DirectionSet(direction).draw(point, direction_radius)
|
|
||||||
|
|
||||||
for path in paths:
|
|
||||||
radial_gradient: RadialGradient = svg.radialGradient(
|
|
||||||
center=point,
|
|
||||||
r=direction_radius,
|
|
||||||
gradientUnits="userSpaceOnUse",
|
|
||||||
)
|
|
||||||
gradient: RadialGradient = svg.defs.add(radial_gradient)
|
|
||||||
|
|
||||||
if is_revert_gradient:
|
|
||||||
(
|
|
||||||
gradient
|
|
||||||
.add_stop_color(0, direction_color.hex, opacity=0)
|
|
||||||
.add_stop_color(1, direction_color.hex, opacity=0.7)
|
|
||||||
) # fmt: skip
|
|
||||||
else:
|
|
||||||
(
|
|
||||||
gradient
|
|
||||||
.add_stop_color(0, direction_color.hex, opacity=0.4)
|
|
||||||
.add_stop_color(1, direction_color.hex, opacity=0)
|
|
||||||
) # fmt: skip
|
|
||||||
|
|
||||||
path_element: Path = svg.path(
|
|
||||||
d=["M", point] + path + ["L", point, "Z"],
|
|
||||||
fill=gradient.get_funciri(),
|
|
||||||
)
|
|
||||||
svg.add(path_element)
|
|
||||||
|
|
||||||
|
|
||||||
class Segment:
|
|
||||||
"""Closed line segment."""
|
|
||||||
|
|
||||||
def __init__(self, point_1: np.ndarray, point_2: np.ndarray) -> None:
|
|
||||||
self.point_1: np.ndarray = point_1
|
|
||||||
self.point_2: np.ndarray = point_2
|
|
||||||
|
|
||||||
difference: np.ndarray = point_2 - point_1
|
|
||||||
vector: np.ndarray = 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
|
|
||||||
|
|
||||||
|
|
||||||
def is_clockwise(polygon: list[OSMNode]) -> bool:
|
def is_clockwise(polygon: list[OSMNode]) -> bool:
|
||||||
"""
|
"""
|
||||||
Return true if polygon nodes are in clockwise order.
|
Return true if polygon nodes are in clockwise order.
|
||||||
|
|
Loading…
Add table
Reference in a new issue