mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-23 14:06:23 +02:00
Fix building height computing.
Add parsing for min_heigth and building:min_level tags; fix shadow generation.
This commit is contained in:
parent
f6d44b97bd
commit
41da1d73b8
4 changed files with 82 additions and 31 deletions
|
@ -148,14 +148,15 @@ class Constructor:
|
|||
self.buildings: List[Building] = []
|
||||
self.roads: List[Road] = []
|
||||
|
||||
self.levels: Set[float] = {0.5, 1.0}
|
||||
self.heights: Set[float] = {2, 4}
|
||||
|
||||
def add_building(self, building: Building) -> None:
|
||||
"""
|
||||
Add building and update levels.
|
||||
"""
|
||||
self.buildings.append(building)
|
||||
self.levels.add(building.get_levels())
|
||||
self.heights.add(building.height)
|
||||
self.heights.add(building.min_height)
|
||||
|
||||
def construct(self) -> None:
|
||||
"""
|
||||
|
@ -216,7 +217,7 @@ class Constructor:
|
|||
|
||||
line_styles: List[LineStyle] = self.scheme.get_style(line.tags, scale)
|
||||
|
||||
if "building" in line.tags:
|
||||
if "building:part" in line.tags or "building" in line.tags:
|
||||
self.add_building(
|
||||
Building(line.tags, inners, outers, self.flinger, self.scheme)
|
||||
)
|
||||
|
|
|
@ -80,14 +80,24 @@ class Building(Figure):
|
|||
|
||||
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
|
||||
self.height: float = 8.0
|
||||
self.min_height: float = 0.0
|
||||
|
||||
levels: Optional[str] = self.get_float("building:levels")
|
||||
if levels:
|
||||
self.height = max(8.0, float(levels) * 2.5)
|
||||
|
||||
levels: Optional[str] = self.get_float("building:min_level")
|
||||
if levels:
|
||||
self.min_height = max(8.0, float(levels) * 2.5)
|
||||
|
||||
height: Optional[str] = self.get_length("height")
|
||||
if height:
|
||||
self.height = max(8.0, height)
|
||||
|
||||
height: Optional[str] = self.get_length("min_height")
|
||||
if height:
|
||||
self.min_height = max(8.0, height)
|
||||
|
||||
|
||||
class StyledFigure(Figure):
|
||||
|
|
|
@ -160,40 +160,49 @@ class Painter:
|
|||
self.flinger.get_scale(node.coordinates),
|
||||
fill="#B89A74"))
|
||||
|
||||
def draw_buildings(self, constructor) -> None:
|
||||
def draw_buildings(self, constructor: Constructor) -> None:
|
||||
"""
|
||||
Draw buildings: shade, walls, and roof.
|
||||
"""
|
||||
# Draw shade.
|
||||
|
||||
building_shade: Group = Group(opacity=0.1)
|
||||
length: float = self.flinger.get_scale()
|
||||
scale: float = self.flinger.get_scale() / 3.0
|
||||
for building in constructor.buildings:
|
||||
shift = np.array((length * building.get_levels(), 0))
|
||||
shift_1 = np.array((scale * building.min_height, 0))
|
||||
shift_2 = np.array((scale * building.height, 0))
|
||||
commands: str = building.get_path(self.flinger, shift_1)
|
||||
path = Path(
|
||||
d=commands, fill="#000000", stroke="#000000", stroke_width=1
|
||||
)
|
||||
building_shade.add(path)
|
||||
for nodes in building.inners + building.outers:
|
||||
for i in range(len(nodes) - 1): # type: int
|
||||
flung_1 = self.flinger.fling(nodes[i].coordinates)
|
||||
flung_2 = self.flinger.fling(nodes[i + 1].coordinates)
|
||||
building_shade.add(Path(
|
||||
("M", flung_1, "L", flung_2, np.add(flung_2, shift),
|
||||
np.add(flung_1, shift), "Z"),
|
||||
("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"),
|
||||
fill="#000000", stroke="#000000", stroke_width=1))
|
||||
self.svg.add(building_shade)
|
||||
|
||||
# Draw buildings.
|
||||
previous_level: float = 0
|
||||
level_height: float = self.flinger.get_scale()
|
||||
level_count: int = len(constructor.levels)
|
||||
for index, level in enumerate(sorted(constructor.levels)):
|
||||
ui.progress_bar(
|
||||
index, level_count, step=1, text="Drawing buildings")
|
||||
|
||||
previous_height: float = 0
|
||||
count: int = len(constructor.heights)
|
||||
for index, height in enumerate(sorted(constructor.heights)):
|
||||
ui.progress_bar(index, count, step=1, text="Drawing buildings")
|
||||
fill: Color()
|
||||
for way in constructor.buildings:
|
||||
if way.get_levels() < level:
|
||||
if way.height < height or way.min_height > height:
|
||||
continue
|
||||
shift_1 = [0, -previous_level * level_height]
|
||||
shift_2 = [0, -level * level_height]
|
||||
shift_1 = [0, -previous_height * scale]
|
||||
shift_2 = [0, -height * scale]
|
||||
for segment in way.parts:
|
||||
if level == 0.5:
|
||||
if height == 2:
|
||||
fill = Color("#AAAAAA")
|
||||
elif level == 1:
|
||||
elif height == 4:
|
||||
fill = Color("#C3C3C3")
|
||||
else:
|
||||
color_part: float = 0.8 + segment.angle * 0.2
|
||||
|
@ -211,16 +220,16 @@ class Painter:
|
|||
# Draw building roofs.
|
||||
|
||||
for way in constructor.buildings:
|
||||
if way.get_levels() == level:
|
||||
shift = np.array([0, -way.get_levels() * level_height])
|
||||
if way.height == height:
|
||||
shift = np.array([0, -way.height * scale])
|
||||
path_commands: str = way.get_path(self.flinger, shift)
|
||||
path = Path(d=path_commands, opacity=1)
|
||||
path.update(way.line_style.style)
|
||||
path.update({"stroke-linejoin": "round"})
|
||||
self.svg.add(path)
|
||||
|
||||
previous_level = level
|
||||
ui.progress_bar(-1, level_count, step=1, text="Drawing buildings")
|
||||
previous_height = height
|
||||
ui.progress_bar(-1, count, step=1, text="Drawing buildings")
|
||||
|
||||
def draw_direction(self, constructor) -> None:
|
||||
"""
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
Reading OpenStreetMap data from XML file.
|
||||
"""
|
||||
import json
|
||||
import re
|
||||
import xml.etree.ElementTree as ET
|
||||
from dataclasses import dataclass
|
||||
from datetime import datetime
|
||||
|
@ -17,6 +18,16 @@ __email__ = "me@enzet.ru"
|
|||
|
||||
OSM_TIME_PATTERN: str = "%Y-%m-%dT%H:%M:%SZ"
|
||||
|
||||
METERS_PATTERN = re.compile("(?P<value>\\d*\\.?\\d*)( )?m")
|
||||
KILOMETERS_PATTERN = re.compile("(?P<value>\\d*\\.?\\d*)( )?km")
|
||||
|
||||
|
||||
def parse_float(string: str) -> Optional[float]:
|
||||
try:
|
||||
return float(string)
|
||||
except (TypeError, ValueError):
|
||||
return None
|
||||
|
||||
|
||||
class Tagged:
|
||||
"""
|
||||
|
@ -37,6 +48,26 @@ class Tagged:
|
|||
return self.tags[key]
|
||||
return None
|
||||
|
||||
def get_float(self, key: str) -> Optional[float]:
|
||||
if key in self.tags:
|
||||
return parse_float(self.tags[key])
|
||||
return None
|
||||
|
||||
def get_length(self, key: str) -> Optional[float]:
|
||||
"""
|
||||
Get length in meters.
|
||||
"""
|
||||
if key in self.tags:
|
||||
value: str = self.tags[key]
|
||||
matcher = METERS_PATTERN.match(value)
|
||||
if matcher:
|
||||
return parse_float(matcher.group("value"))
|
||||
matcher = KILOMETERS_PATTERN.match(value)
|
||||
if matcher:
|
||||
return parse_float(matcher.group("value")) * 1000
|
||||
return self.get_float(key)
|
||||
return None
|
||||
|
||||
|
||||
class OSMNode(Tagged):
|
||||
"""
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue