Support Overpass, overlaps, tree circumference.

This commit is contained in:
Sergey Vartanov 2020-10-03 00:29:13 +03:00
parent b8289467b9
commit acf0ee45d9
5 changed files with 147 additions and 104 deletions

View file

@ -4,6 +4,7 @@ Icon grid drawing.
Author: Sergey Vartanov (me@enzet.ru). Author: Sergey Vartanov (me@enzet.ru).
""" """
import numpy as np import numpy as np
from colour import Color
from svgwrite import Drawing from svgwrite import Drawing
from typing import List, Dict, Any, Set from typing import List, Dict, Any, Set
@ -11,31 +12,23 @@ from roentgen.icon import Icon, IconExtractor
from roentgen.scheme import Scheme from roentgen.scheme import Scheme
def draw_grid(step: float = 24, columns: int = 16): def draw_all_icons(output_file_name: str, columns: int = 16, step: float = 24):
""" """
Draw all possible icon combinations in grid. Draw all possible icon combinations in grid.
:param step: horizontal and vertical distance between icons :param output_file_name: output SVG file name for icon grid
:param columns: the number of columns in grid :param columns: the number of columns in grid
:param step: horizontal and vertical distance between icons
""" """
tags_file_name: str = "data/tags.yml" tags_file_name: str = "data/tags.yml"
scheme: Scheme = Scheme(tags_file_name) scheme: Scheme = Scheme(tags_file_name)
icons_file_name: str = "icons/icons.svg"
icon_grid_file_name: str = "icon_grid.svg"
width: float = step * columns
point: np.array = np.array((step / 2, step / 2))
to_draw: List[Set[str]] = [] to_draw: List[Set[str]] = []
for element in scheme.icons: # type: Dict[str, Any] for element in scheme.icons: # type: Dict[str, Any]
if "icon" in element: if "icon" in element and set(element["icon"]) not in to_draw:
if set(element["icon"]) not in to_draw:
to_draw.append(set(element["icon"])) to_draw.append(set(element["icon"]))
if "add_icon" in element: if "add_icon" in element and set(element["add_icon"]) not in to_draw:
if set(element["add_icon"]) not in to_draw:
to_draw.append(set(element["add_icon"])) to_draw.append(set(element["add_icon"]))
if "over_icon" not in element: if "over_icon" not in element:
continue continue
@ -59,16 +52,44 @@ def draw_grid(step: float = 24, columns: int = 16):
element["over_icon"]) element["over_icon"])
if (icon_2_id != icon_3_id and icon_2_id != icon_id and if (icon_2_id != icon_3_id and icon_2_id != icon_id and
icon_3_id != icon_id and icon_3_id != icon_id and
(current_set not in to_draw)): current_set not in to_draw):
to_draw.append(current_set) to_draw.append(current_set)
number: int = 0 icons_file_name: str = "icons/icons.svg"
icons: List[List[Icon]] = []
extractor: IconExtractor = IconExtractor(icons_file_name) extractor: IconExtractor = IconExtractor(icons_file_name)
for icons_to_draw in to_draw: specified_ids: Set[str] = set()
for icons_to_draw in to_draw: # type: List[str]
specified_ids |= icons_to_draw
print(
"Icons with no tag specification: \n " +
", ".join(sorted(extractor.icons.keys() - specified_ids)) + ".")
draw_grid(output_file_name, to_draw, extractor, columns, step)
def draw_grid(
file_name: str, combined_icon_ids: List[Set[str]],
extractor: IconExtractor, columns: int = 16, step: float = 24,
color=Color("#444444")):
"""
Draw icons in the form of table
:param file_name: output SVG file name
:param combined_icon_ids: list of set of icon string identifiers
:param extractor: icon extractor that generates icon SVG path commands using
its string identifier
:param columns: number of columns in grid
:param step: horizontal and vertical distance between icons in grid
:return:
"""
point: np.array = np.array((step / 2, step / 2))
width: float = step * columns
number: int = 0
icons: List[List[Icon]] = []
for icons_to_draw in combined_icon_ids: # type: Set[str]
found: bool = False found: bool = False
icon_set: List[Icon] = [] icon_set: List[Icon] = []
for icon_id in icons_to_draw: # type: str for icon_id in icons_to_draw: # type: str
@ -81,19 +102,17 @@ def draw_grid(step: float = 24, columns: int = 16):
number += 1 number += 1
height: int = int(int(number / (width / step) + 1) * step) height: int = int(int(number / (width / step) + 1) * step)
svg: Drawing = Drawing(file_name, (width, height))
svg: Drawing = Drawing(icon_grid_file_name, (width, height))
svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF")) svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF"))
for combined_icon in icons: # type: List[Icon] for combined_icon in icons: # type: List[Icon]
background_color, foreground_color = "#FFFFFF", "#444444" background_color = "#FFFFFF"
svg.add(svg.rect( svg.add(svg.rect(
point - np.array((-10, -10)), (20, 20), point - np.array((-10, -10)), (20, 20),
fill=background_color)) fill=background_color))
for icon in combined_icon: # type: Icon for icon in combined_icon: # type: Icon
path = icon.get_path(svg, point) path = icon.get_path(svg, point)
path.update({"fill": foreground_color}) path.update({"fill": color.hex})
svg.add(path) svg.add(path)
point += np.array((step, 0)) point += np.array((step, 0))
if point[0] > width - 8: if point[0] > width - 8:
@ -103,5 +122,5 @@ def draw_grid(step: float = 24, columns: int = 16):
print(f"Icons: {number}.") print(f"Icons: {number}.")
with open(icon_grid_file_name, "w") as output_file: with open(file_name, "w") as output_file:
svg.write(output_file) svg.write(output_file)

View file

@ -21,10 +21,10 @@ from roentgen.constructor import (
Constructor, Figure, Building, Segment) Constructor, Figure, Building, Segment)
from roentgen.point import Point, Occupied from roentgen.point import Point, Occupied
from roentgen.flinger import Flinger from roentgen.flinger import Flinger
from roentgen.grid import draw_grid from roentgen.grid import draw_all_icons
from roentgen.icon import Icon, IconExtractor from roentgen.icon import Icon, IconExtractor
from roentgen.osm_getter import get_osm from roentgen.osm_getter import get_osm
from roentgen.osm_reader import Map, OSMReader from roentgen.osm_reader import Map, OSMReader, OverpassReader
from roentgen.scheme import Scheme from roentgen.scheme import Scheme
from roentgen.direction import DirectionSet, Sector from roentgen.direction import DirectionSet, Sector
from roentgen.util import MinMax from roentgen.util import MinMax
@ -75,6 +75,31 @@ class Painter:
self.svg.add(path) self.svg.add(path)
ui.progress_bar(-1, 0, text="Drawing ways") ui.progress_bar(-1, 0, text="Drawing ways")
# Trees
for node in constructor.nodes:
if not (node.get_tag("natural") == "tree" and
("diameter_crown" in node.tags or
"circumference" in node.tags)):
continue
if "circumference" in node.tags:
if "diameter_crown" in node.tags:
opacity = 0.7
radius = float(node.tags["diameter_crown"]) / 2
else:
opacity = 0.3
radius = 2
self.svg.add(self.svg.circle(
node.point,
radius * self.flinger.get_scale(node.coordinates),
fill=self.scheme.get_color("evergreen_color"),
opacity=opacity))
self.svg.add(self.svg.circle(
node.point,
float(node.tags["circumference"]) / 2 / np.pi *
self.flinger.get_scale(node.coordinates),
fill="#B89A74"))
# Draw building shade. # Draw building shade.
building_shade: Group = Group(opacity=0.1) building_shade: Group = Group(opacity=0.1)
@ -141,26 +166,6 @@ class Painter:
ui.progress_bar(-1, level_count, step=1, text="Drawing buildings") ui.progress_bar(-1, level_count, step=1, text="Drawing buildings")
# Trees
for node in constructor.nodes:
if not (node.get_tag("natural") == "tree" and
("diameter_crown" in node.tags or
"circumference" in node.tags)):
continue
if "circumference" in node.tags:
self.svg.add(self.svg.circle(
node.point,
float(node.tags["circumference"]) *
self.flinger.get_scale(node.coordinates) / 2,
fill="#AAAA88", opacity=0.3))
if "diameter_crown" in node.tags:
self.svg.add(self.svg.circle(
node.point,
float(node.tags["diameter_crown"]) *
self.flinger.get_scale(node.coordinates) / 2,
fill=self.scheme.get_color("evergreen"), opacity=0.3))
# Directions # Directions
for node in constructor.nodes: # type: Point for node in constructor.nodes: # type: Point
@ -219,7 +224,11 @@ class Painter:
# All other points # All other points
occupied = Occupied(self.flinger.size[0], self.flinger.size[1]) if self.overlap == 0:
occupied = None
else:
occupied = Occupied(
self.flinger.size[0], self.flinger.size[1], self.overlap)
nodes = sorted(constructor.nodes, key=lambda x: x.layer) nodes = sorted(constructor.nodes, key=lambda x: x.layer)
for index, node in enumerate(nodes): # type: int, Point for index, node in enumerate(nodes): # type: int, Point
@ -286,7 +295,7 @@ def main(argv) -> None:
""" """
if len(argv) == 2: if len(argv) == 2:
if argv[1] == "grid": if argv[1] == "grid":
draw_grid() draw_all_icons("icon_grid.svg")
return return
options: argparse.Namespace = ui.parse_options(argv) options: argparse.Namespace = ui.parse_options(argv)
@ -306,6 +315,16 @@ def main(argv) -> None:
ui.error("cannot download OSM data") ui.error("cannot download OSM data")
input_file_name = [os.path.join("map", options.boundary_box + ".osm")] input_file_name = [os.path.join("map", options.boundary_box + ".osm")]
scheme: Scheme = Scheme(TAGS_FILE_NAME)
if input_file_name[0].endswith(".json"):
reader: OverpassReader = OverpassReader()
reader.parse_json_file(input_file_name[0])
map_ = reader.map_
min1 = np.array((map_.boundary_box[0].min_, map_.boundary_box[1].min_))
max1 = np.array((map_.boundary_box[0].max_, map_.boundary_box[1].max_))
else:
boundary_box = list(map( boundary_box = list(map(
lambda x: float(x.replace('m', '-')), options.boundary_box.split(','))) lambda x: float(x.replace('m', '-')), options.boundary_box.split(',')))
@ -327,10 +346,9 @@ def main(argv) -> None:
map_: Map = osm_reader.map_ map_: Map = osm_reader.map_
scheme: Scheme = Scheme(TAGS_FILE_NAME)
min1: np.array = np.array((boundary_box[1], boundary_box[0])) min1: np.array = np.array((boundary_box[1], boundary_box[0]))
max1: np.array = np.array((boundary_box[3], boundary_box[2])) max1: np.array = np.array((boundary_box[3], boundary_box[2]))
flinger: Flinger = Flinger(MinMax(min1, max1), options.scale) flinger: Flinger = Flinger(MinMax(min1, max1), options.scale)
size: np.array = flinger.size size: np.array = flinger.size
@ -362,6 +380,7 @@ def main(argv) -> None:
if options.draw_ways: if options.draw_ways:
constructor.construct_ways() constructor.construct_ways()
constructor.construct_relations() constructor.construct_relations()
if options.mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
constructor.construct_nodes() constructor.construct_nodes()
painter: Painter = Painter( painter: Painter = Painter(

View file

@ -26,10 +26,11 @@ class TextStruct:
class Occupied: class Occupied:
def __init__(self, width: int, height: int): def __init__(self, width: int, height: int, overlap: float):
self.matrix = np.full((int(width), int(height)), False, dtype=bool) self.matrix = np.full((int(width), int(height)), False, dtype=bool)
self.width = width self.width = width
self.height = height self.height = height
self.overlap = overlap
def check(self, point) -> bool: def check(self, point) -> bool:
if 0 <= point[0] < self.width and 0 <= point[1] < self.height: if 0 <= point[0] < self.width and 0 <= point[1] < self.height:
@ -171,10 +172,12 @@ class Point(Tagged):
return return
is_place_for_extra: bool = True is_place_for_extra: bool = True
if occupied:
left: float = -(len(self.icon_set.extra_icons) - 1) * 8 left: float = -(len(self.icon_set.extra_icons) - 1) * 8
for shape_ids in self.icon_set.extra_icons: for shape_ids in self.icon_set.extra_icons:
if occupied.check( if occupied.check(
(int(self.point[0] + left), int(self.point[1] + self.y))): (int(self.point[0] + left),
int(self.point[1] + self.y))):
is_place_for_extra = False is_place_for_extra = False
break break
left += 16 left += 16
@ -197,7 +200,7 @@ class Point(Tagged):
# Down-cast floats to integers to make icons pixel-perfect. # Down-cast floats to integers to make icons pixel-perfect.
position = list(map(int, position)) position = list(map(int, position))
if occupied.check(position): if occupied and occupied.check(position):
return False return False
# Draw outlines. # Draw outlines.
@ -213,8 +216,10 @@ class Point(Tagged):
for icon in icons: # type: Icon for icon in icons: # type: Icon
icon.draw(svg, position, fill, tags=tags) icon.draw(svg, position, fill, tags=tags)
for i in range(-12, 12): if occupied:
for j in range(-12, 12): overlap: int = occupied.overlap
for i in range(-overlap, overlap):
for j in range(-overlap, overlap):
occupied.register((position[0] + i, position[1] + j)) occupied.register((position[0] + i, position[1] + j))
return True return True
@ -240,7 +245,7 @@ class Point(Tagged):
def draw_text( def draw_text(
self, svg: svgwrite.Drawing, text: str, point, occupied: Occupied, self, svg: svgwrite.Drawing, text: str, point, occupied: Occupied,
fill: Color, size: float = 10.0, out_fill=Color("white"), fill: Color, size: float = 10.0, out_fill=Color("white"),
out_opacity=1.0, out_fill_2: Optional[Color] = None, out_opacity=0.5, out_fill_2: Optional[Color] = None,
out_opacity_2=1.0): out_opacity_2=1.0):
""" """
Drawing text. Drawing text.
@ -253,6 +258,7 @@ class Point(Tagged):
""" """
length = len(text) * 6 length = len(text) * 6
if occupied:
is_occupied: bool = False is_occupied: bool = False
for i in range(-int(length / 2), int(length/ 2)): for i in range(-int(length / 2), int(length/ 2)):
if occupied.check((int(point[0] + i), int(point[1] - 4))): if occupied.check((int(point[0] + i), int(point[1] - 4))):

View file

@ -33,8 +33,7 @@ def parse_options(args) -> argparse.Namespace:
"-b", "--boundary-box", "-b", "--boundary-box",
dest="boundary_box", dest="boundary_box",
metavar="<lon1>,<lat1>,<lon2>,<lat2>", metavar="<lon1>,<lat1>,<lon2>,<lat2>",
help="geo boundary box, use space before \"-\" for negative values", help="geo boundary box, use space before \"-\" for negative values")
required=True)
parser.add_argument( parser.add_argument(
"-s", "--scale", "-s", "--scale",
metavar="<float>", metavar="<float>",

View file

@ -2,9 +2,9 @@
Author: Sergey Vartanov (me@enzet.ru). Author: Sergey Vartanov (me@enzet.ru).
""" """
from roentgen.grid import draw_grid from roentgen.grid import draw_all_icons
def test_icons(): def test_icons() -> None:
""" Test grid drawing. """ """ Test grid drawing. """
draw_grid() draw_all_icons("temp.svg")