mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-01 02:11:58 +02:00
226 lines
7.5 KiB
Python
226 lines
7.5 KiB
Python
"""Buildings on the map."""
|
|
import numpy as np
|
|
import svgwrite
|
|
from colour import Color
|
|
from svgwrite import Drawing
|
|
from svgwrite.container import Group
|
|
from svgwrite.path import Path
|
|
from typing import Dict, List
|
|
|
|
from map_machine.drawing import PathCommands
|
|
from map_machine.figure import Figure
|
|
from map_machine.geometry.flinger import Flinger
|
|
from map_machine.geometry.vector import Segment
|
|
from map_machine.osm.osm_reader import OSMNode
|
|
from map_machine.scheme import Scheme
|
|
|
|
BUILDING_MINIMAL_HEIGHT: float = 8.0
|
|
BUILDING_SCALE: float = 0.33
|
|
LEVEL_HEIGHT: float = 2.5
|
|
SHADE_SCALE: float = 0.4
|
|
|
|
|
|
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)
|
|
|
|
self.is_construction: bool = (
|
|
tags.get("building") == "construction"
|
|
or tags.get("construction") == "yes"
|
|
)
|
|
self.has_walls: bool = tags.get("building") != "roof"
|
|
|
|
if self.is_construction:
|
|
self.fill: Color = scheme.get_color("building_construction_color")
|
|
self.stroke: Color = scheme.get_color(
|
|
"building_construction_border_color"
|
|
)
|
|
else:
|
|
if color := tags.get("roof:colour"):
|
|
self.fill = scheme.get_color(color)
|
|
self.stroke: Color = Color(self.fill)
|
|
self.stroke.set_luminance(self.fill.get_luminance() * 0.85)
|
|
else:
|
|
self.fill: Color = scheme.get_color("building_color")
|
|
self.stroke: Color = scheme.get_color("building_border_color")
|
|
|
|
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
|
|
|
|
self.wall_color: Color
|
|
if self.is_construction:
|
|
self.wall_color = scheme.get_color("wall_construction_color")
|
|
else:
|
|
self.wall_color = scheme.get_color("wall_color")
|
|
|
|
if material := tags.get("building:material"):
|
|
if material in scheme.material_colors:
|
|
self.wall_color = Color(scheme.material_colors[material])
|
|
|
|
if color := tags.get("building:colour"):
|
|
self.wall_color = scheme.get_color(color)
|
|
|
|
if color := tags.get("colour"):
|
|
self.wall_color = scheme.get_color(color)
|
|
|
|
self.wall_bottom_color_1: Color = Color(self.wall_color)
|
|
self.wall_bottom_color_1.set_luminance(
|
|
self.wall_color.get_luminance() * 0.70
|
|
)
|
|
self.wall_bottom_color_2: Color = Color(self.wall_color)
|
|
self.wall_bottom_color_2.set_luminance(
|
|
self.wall_color.get_luminance() * 0.85
|
|
)
|
|
|
|
if levels := self.get_float("building:levels"):
|
|
self.height = BUILDING_MINIMAL_HEIGHT + levels * LEVEL_HEIGHT
|
|
|
|
if levels := self.get_float("building:min_level"):
|
|
self.min_height = BUILDING_MINIMAL_HEIGHT + levels * LEVEL_HEIGHT
|
|
|
|
if height := self.get_length("height"):
|
|
self.height = BUILDING_MINIMAL_HEIGHT + height
|
|
|
|
if height := self.get_length("min_height"):
|
|
self.min_height = BUILDING_MINIMAL_HEIGHT + height
|
|
|
|
def draw(self, svg: Drawing, flinger: Flinger) -> None:
|
|
"""Draw simple building shape."""
|
|
path: Path = Path(
|
|
d=self.get_path(flinger),
|
|
stroke=self.stroke.hex,
|
|
fill=self.fill.hex,
|
|
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() * SHADE_SCALE
|
|
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(
|
|
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."""
|
|
if not self.has_walls:
|
|
return
|
|
|
|
shift_1: np.ndarray = np.array(
|
|
(0.0, -previous_height * scale * BUILDING_SCALE)
|
|
)
|
|
shift_2: np.ndarray = np.array((0.0, -height * scale * BUILDING_SCALE))
|
|
|
|
for segment in self.parts:
|
|
draw_walls(svg, self, segment, height, shift_1, shift_2)
|
|
|
|
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 * BUILDING_SCALE])
|
|
),
|
|
stroke=self.stroke,
|
|
fill="none" if self.is_construction else self.fill.hex,
|
|
stroke_linejoin="round",
|
|
)
|
|
svg.add(path)
|
|
|
|
|
|
def draw_walls(
|
|
svg: svgwrite.Drawing,
|
|
building: Building,
|
|
segment: Segment,
|
|
height: float,
|
|
shift_1: np.ndarray,
|
|
shift_2: np.ndarray,
|
|
):
|
|
"""
|
|
Draw walls for buildings as a quadrangle.
|
|
|
|
Color of the wall is based on illumination.
|
|
"""
|
|
color: Color
|
|
if building.is_construction:
|
|
color_part: float = segment.angle * 0.2
|
|
color = Color(
|
|
rgb=(
|
|
building.wall_color.get_red() + color_part,
|
|
building.wall_color.get_green() + color_part,
|
|
building.wall_color.get_blue() + color_part,
|
|
)
|
|
)
|
|
elif height <= 0.25 / BUILDING_SCALE:
|
|
color = building.wall_bottom_color_1
|
|
elif height <= 0.5 / BUILDING_SCALE:
|
|
color = building.wall_bottom_color_2
|
|
else:
|
|
color_part: float = segment.angle * 0.2 - 0.1
|
|
color = Color(
|
|
rgb=(
|
|
max(min(building.wall_color.get_red() + color_part, 1), 0),
|
|
max(min(building.wall_color.get_green() + color_part, 1), 0),
|
|
max(min(building.wall_color.get_blue() + color_part, 1), 0),
|
|
)
|
|
)
|
|
|
|
command: PathCommands = [
|
|
"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 = Path(
|
|
d=command,
|
|
fill=color.hex,
|
|
stroke=color.hex,
|
|
stroke_width=1,
|
|
stroke_linejoin="round",
|
|
)
|
|
svg.add(path)
|