Issue #26: add preliminary fast overlap algorithm.

This commit is contained in:
Sergey Vartanov 2020-09-30 02:39:10 +03:00
parent 4e5367b8f9
commit d664364140
4 changed files with 157 additions and 85 deletions

View file

@ -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,6 +376,9 @@ class Constructor:
elif member.role == "outer":
if member.ref in self.map_.way_map:
outer_ways.append(self.map_.way_map[member.ref])
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)

View file

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

View file

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

View file

@ -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)
if painted:
self.y += 16
left: float = -(len(self.icon_set.extra_icons) - 1) * 8
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("&quot;", '"')
text = text.replace("&amp;", '&')
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