diff --git a/roentgen/constructor.py b/roentgen/constructor.py index 05c2b78..7c95db3 100644 --- a/roentgen/constructor.py +++ b/roentgen/constructor.py @@ -280,7 +280,7 @@ class Constructor: way_number: int = 0 for way_id in self.map_.way_map: # type: int ui.progress_bar( - way_number, len(self.map_.way_map), + way_number, len(self.map_.way_map), step=10, text="Constructing ways") way_number += 1 way: OSMWay = self.map_.way_map[way_id] @@ -376,9 +376,12 @@ class Constructor: elif member.role == "outer": if member.ref in self.map_.way_map: outer_ways.append(self.map_.way_map[member.ref]) - inners_path: List[List[OSMNode]] = glue(inner_ways) - outers_path: List[List[OSMNode]] = glue(outer_ways) - self.construct_line(relation, inners_path, outers_path) + else: + print(f'Unknown member role "{member.role}".') + if outer_ways: + inners_path: List[List[OSMNode]] = glue(inner_ways) + outers_path: List[List[OSMNode]] = glue(outer_ways) + self.construct_line(relation, inners_path, outers_path) def construct_nodes(self) -> None: """ diff --git a/roentgen/icon.py b/roentgen/icon.py index c81113a..9941338 100644 --- a/roentgen/icon.py +++ b/roentgen/icon.py @@ -6,7 +6,7 @@ Author: Sergey Vartanov (me@enzet.ru). import re import xml.dom.minidom from dataclasses import dataclass -from typing import Dict, Any +from typing import Dict, Any, Set from xml.dom.minidom import Document, Element, Node import numpy as np diff --git a/roentgen/mapper.py b/roentgen/mapper.py index fc7d863..0cd68a9 100644 --- a/roentgen/mapper.py +++ b/roentgen/mapper.py @@ -14,12 +14,12 @@ from colour import Color from svgwrite.container import Group from svgwrite.path import Path from svgwrite.shapes import Rect -from typing import Any, Dict +from typing import Any, Dict, List from roentgen import ui from roentgen.constructor import ( Constructor, Figure, Building, Segment) -from roentgen.point import Point +from roentgen.point import Point, Occupied from roentgen.flinger import Flinger from roentgen.grid import draw_grid from roentgen.icon import Icon, IconExtractor @@ -60,7 +60,7 @@ class Painter: self.icon_extractor = icon_extractor self.scheme: Scheme = scheme - def draw(self, constructor: Constructor, points): + def draw(self, constructor: Constructor): """ Draw map. """ @@ -96,7 +96,7 @@ class Painter: # Draw buildings. previous_level: float = 0 - height: float = self.flinger.get_scale() + level_height: float = self.flinger.get_scale() level_count: int = len(constructor.levels) for index, level in enumerate(sorted(constructor.levels)): @@ -106,8 +106,8 @@ class Painter: for way in constructor.buildings: # type: Building if way.get_levels() < level: continue - shift_1 = [0, -previous_level * height] - shift_2 = [0, -level * height] + shift_1 = [0, -previous_level * level_height] + shift_2 = [0, -level * level_height] for segment in way.parts: # type: Segment if level == 0.5: fill = Color("#AAAAAA") @@ -130,7 +130,7 @@ class Painter: for way in constructor.buildings: # type: Building if way.get_levels() == level: - shift = np.array([0, -way.get_levels() * height]) + shift = np.array([0, -way.get_levels() * level_height]) path_commands: str = way.get_path(self.flinger, shift) path = Path(d=path_commands, opacity=1) path.update(way.line_style.style) @@ -219,6 +219,8 @@ class Painter: # All other points + occupied = Occupied(self.flinger.size[0], self.flinger.size[1]) + nodes = sorted(constructor.nodes, key=lambda x: x.layer) for index, node in enumerate(nodes): # type: int, Point if (node.get_tag("natural") == "tree" and @@ -226,7 +228,7 @@ class Painter: "circumference" in node.tags)): continue ui.progress_bar(index, len(nodes), step=10, text="Drawing nodes") - node.draw_shapes(self.svg) + node.draw_shapes(self.svg, occupied) ui.progress_bar(-1, len(nodes), step=10, text="Drawing nodes") if self.draw_captions == "no": @@ -234,7 +236,8 @@ class Painter: for node in nodes: # type: Point if self.mode not in [CREATION_TIME_MODE, AUTHOR_MODE]: - node.draw_texts(self.svg, self.scheme, self.draw_captions) + node.draw_texts( + self.svg, self.scheme, occupied, self.draw_captions) def check_level_number(tags: Dict[str, Any], level: float): @@ -324,9 +327,6 @@ def main(argv) -> None: map_: Map = osm_reader.map_ - missing_tags = {} - points = [] - scheme: Scheme = Scheme(TAGS_FILE_NAME) min1: np.array = np.array((boundary_box[1], boundary_box[0])) @@ -370,7 +370,8 @@ def main(argv) -> None: draw_captions=options.draw_captions, map_=map_, flinger=flinger, svg=svg, icon_extractor=icon_extractor, scheme=scheme) - painter.draw(constructor, points) + + painter.draw(constructor) print("Writing output SVG...") svg.write(open(options.output_file_name, "w")) diff --git a/roentgen/point.py b/roentgen/point.py index 8420795..7142a90 100644 --- a/roentgen/point.py +++ b/roentgen/point.py @@ -25,57 +25,21 @@ class TextStruct: size: float = 10.0 -def draw_point_shape( - svg: svgwrite.Drawing, icons: List[Icon], point, fill: Color, - tags=None): - """ - Draw one combined icon and its outline. - """ - # Down-cast floats to integers to make icons pixel-perfect. - point = np.array(list(map(int, point))) +class Occupied: + def __init__(self, width: int, height: int): + self.matrix = np.full((int(width), int(height)), False, dtype=bool) + self.width = width + self.height = height - # Draw outlines. + def check(self, point) -> bool: + if 0 <= point[0] < self.width and 0 <= point[1] < self.height: + return self.matrix[point[0], point[1]] == True + return True - for icon in icons: # type: Icon - bright: bool = is_bright(fill) - color: Color = Color("black") if bright else Color("white") - opacity: float = 0.7 if bright else 0.5 - icon.draw(svg, point, color, opacity=opacity, outline=True) - - # Draw icons. - - for icon in icons: # type: Icon - icon.draw(svg, point, fill, tags=tags) - - -def draw_text( - svg: svgwrite.Drawing, text: str, point, fill: Color, - size: float = 10.0, out_fill=Color("white"), out_opacity=1.0, - out_fill_2: Optional[Color] = None, out_opacity_2=1.0): - """ - Drawing text. - - ###### ### outline 2 - #------# --- outline 1 - #| Text |# - #------# - ###### - """ - if out_fill_2: - svg.add(svg.text( - text, point, font_size=size, text_anchor="middle", - font_family=DEFAULT_FONT, fill=out_fill_2.hex, - stroke_linejoin="round", stroke_width=5, - stroke=out_fill_2.hex, opacity=out_opacity_2)) - if out_fill: - svg.add(svg.text( - text, point, font_size=size, text_anchor="middle", - font_family=DEFAULT_FONT, fill=out_fill.hex, - stroke_linejoin="round", stroke_width=3, - stroke=out_fill.hex, opacity=out_opacity)) - svg.add(svg.text( - text, point, font_size=size, text_anchor="middle", - font_family=DEFAULT_FONT, fill=fill.hex)) + def register(self, point) -> None: + if 0 <= point[0] < self.width and 0 <= point[1] < self.height: + self.matrix[point[0], point[1]] = True + assert self.matrix[point[0], point[1]] == True def construct_text( @@ -112,7 +76,7 @@ def construct_text( alt_name += ", " else: alt_name = "" - alt_name += "бывш. " + tags["old_name"] + alt_name += "ex " + tags["old_name"] address: List[str] = get_address(tags, draw_captions) @@ -155,12 +119,19 @@ def construct_text( return texts +def in_range(position, points) -> bool: + return ( + 0 <= position[0] < len(points) and + 0 <= position[1] < len(points[0])) + + class Point(Tagged): """ Object on the map with no dimensional attributes. It may have icons and text. """ + def __init__( self, icon_set: IconSet, tags: Dict[str, str], point: np.array, coordinates: np.array, priority: float = 0, @@ -179,41 +150,138 @@ class Point(Tagged): self.y = 0 - def draw_shapes(self, svg: svgwrite.Drawing): + def draw_shapes(self, svg: svgwrite.Drawing, occupied: Occupied): """ Draw shapes for one node. """ - if self.icon_set.main_icon and (not self.icon_set.main_icon[0].is_default() or self.is_for_node): - draw_point_shape( + painted: bool = False + if (self.icon_set.main_icon and + (not self.icon_set.main_icon[0].is_default() or + self.is_for_node)): + position = self.point + np.array((0, self.y)) + painted: bool = self.draw_point_shape( svg, self.icon_set.main_icon, - self.point + np.array((0, self.y)), self.icon_set.color, + position, self.icon_set.color, occupied, tags=self.tags) - self.y += 16 + if painted: + self.y += 16 + if not self.icon_set.extra_icons or \ + (self.icon_set.main_icon and not painted): + return + + is_place_for_extra: bool = True left: float = -(len(self.icon_set.extra_icons) - 1) * 8 - for shape_ids in self.icon_set.extra_icons: - draw_point_shape( - svg, shape_ids, self.point + np.array((left, self.y)), - Color("#888888")) + if occupied.check( + (int(self.point[0] + left), int(self.point[1] + self.y))): + is_place_for_extra = False + break left += 16 - if self.icon_set.extra_icons: + if is_place_for_extra: + left: float = -(len(self.icon_set.extra_icons) - 1) * 8 + for shape_ids in self.icon_set.extra_icons: + self.draw_point_shape( + svg, shape_ids, self.point + np.array((left, self.y)), + Color("#888888"), occupied) + left += 16 self.y += 16 - def draw_texts(self, svg: svgwrite.Drawing, scheme, draw_captions): + def draw_point_shape( + self, svg: svgwrite.Drawing, icons: List[Icon], position, + fill: Color, occupied, tags=None) -> bool: + """ + Draw one combined icon and its outline. + """ + # Down-cast floats to integers to make icons pixel-perfect. + position = list(map(int, position)) + + if occupied.check(position): + return False + + # Draw outlines. + + for icon in icons: # type: Icon + bright: bool = is_bright(fill) + color: Color = Color("black") if bright else Color("white") + opacity: float = 0.7 if bright else 0.5 + icon.draw(svg, position, color, opacity=opacity, outline=True) + + # Draw icons. + + for icon in icons: # type: Icon + icon.draw(svg, position, fill, tags=tags) + + for i in range(-12, 12): + for j in range(-12, 12): + occupied.register((position[0] + i, position[1] + j)) + + return True + + def draw_texts( + self, svg: svgwrite.Drawing, scheme, occupied: Occupied, + draw_captions): """ Draw all labels. """ - write_tags = construct_text( + text_structures: List[TextStruct] = construct_text( self.tags, self.icon_set.processed, scheme, draw_captions) - for text_struct in write_tags: # type: TextStruct - self.y += text_struct.size + 1 + for text_struct in text_structures: # type: TextStruct text = text_struct.text text = text.replace(""", '"') text = text.replace("&", '&') text = text[:26] + ("..." if len(text) > 26 else "") - draw_text( - svg, text, self.point + np.array((0, self.y - 8)), - text_struct.fill, size=text_struct.size) + self.draw_text( + svg, text, self.point + np.array((0, self.y)), + occupied, text_struct.fill, size=text_struct.size) + + def draw_text( + self, svg: svgwrite.Drawing, text: str, point, occupied: Occupied, + fill: Color, size: float = 10.0, out_fill=Color("white"), + out_opacity=1.0, out_fill_2: Optional[Color] = None, + out_opacity_2=1.0): + """ + Drawing text. + + ###### ### outline 2 + #------# --- outline 1 + #| Text |# + #------# + ###### + """ + length = len(text) * 6 + + is_occupied: bool = False + for i in range(-int(length / 2), int(length/ 2)): + if occupied.check((int(point[0] + i), int(point[1] - 4))): + is_occupied = True + break + + if is_occupied: + return + + for i in range(-int(length / 2), int(length / 2)): + for j in range(-12, 5): + occupied.register((int(point[0] + i), int(point[1] + j))) + # svg.add(svg.rect((point[0] + i, point[1] + j), (1, 1))) + + if out_fill_2: + svg.add(svg.text( + text, point, font_size=size, text_anchor="middle", + font_family=DEFAULT_FONT, fill=out_fill_2.hex, + stroke_linejoin="round", stroke_width=5, + stroke=out_fill_2.hex, opacity=out_opacity_2)) + if out_fill: + svg.add(svg.text( + text, point, font_size=size, text_anchor="middle", + font_family=DEFAULT_FONT, fill=out_fill.hex, + stroke_linejoin="round", stroke_width=3, + stroke=out_fill.hex, opacity=out_opacity)) + svg.add(svg.text( + text, point, font_size=size, text_anchor="middle", + font_family=DEFAULT_FONT, fill=fill.hex)) + + self.y += 11 +