Fix building height computing.

Add parsing for min_heigth and building:min_level tags; fix shadow
generation.
This commit is contained in:
Sergey Vartanov 2021-06-23 01:09:01 +03:00
parent f6d44b97bd
commit 41da1d73b8
4 changed files with 82 additions and 31 deletions

View file

@ -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)
)

View file

@ -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):

View file

@ -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:
"""

View file

@ -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):
"""