mirror of
https://github.com/enzet/map-machine.git
synced 2025-06-05 20:31:51 +02:00
Issue #26: add preliminary fast overlap algorithm.
This commit is contained in:
parent
4e5367b8f9
commit
d664364140
4 changed files with 157 additions and 85 deletions
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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(""", '"')
|
||||
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
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue