diff --git a/roentgen/constructor.py b/roentgen/constructor.py index 5e3a85f..9c44c16 100644 --- a/roentgen/constructor.py +++ b/roentgen/constructor.py @@ -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) ) diff --git a/roentgen/figure.py b/roentgen/figure.py index 4e407b5..280ab04 100644 --- a/roentgen/figure.py +++ b/roentgen/figure.py @@ -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): diff --git a/roentgen/mapper.py b/roentgen/mapper.py index 4b2f882..792bda1 100644 --- a/roentgen/mapper.py +++ b/roentgen/mapper.py @@ -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: """ diff --git a/roentgen/osm_reader.py b/roentgen/osm_reader.py index 58356db..5e1d473 100644 --- a/roentgen/osm_reader.py +++ b/roentgen/osm_reader.py @@ -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\\d*\\.?\\d*)( )?m") +KILOMETERS_PATTERN = re.compile("(?P\\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): """