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).
"""
import numpy as np
from colour import Color
from svgwrite import Drawing
from typing import List, Dict, Any, Set
@ -11,31 +12,23 @@ from roentgen.icon import Icon, IconExtractor
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.
: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 step: horizontal and vertical distance between icons
"""
tags_file_name: str = "data/tags.yml"
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]] = []
for element in scheme.icons: # type: Dict[str, Any]
if "icon" in element:
if set(element["icon"]) not in to_draw:
if "icon" in element and set(element["icon"]) not in to_draw:
to_draw.append(set(element["icon"]))
if "add_icon" in element:
if set(element["add_icon"]) not in to_draw:
if "add_icon" in element and set(element["add_icon"]) not in to_draw:
to_draw.append(set(element["add_icon"]))
if "over_icon" not in element:
continue
@ -59,16 +52,44 @@ def draw_grid(step: float = 24, columns: int = 16):
element["over_icon"])
if (icon_2_id != icon_3_id and icon_2_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)
number: int = 0
icons: List[List[Icon]] = []
icons_file_name: str = "icons/icons.svg"
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
icon_set: List[Icon] = []
for icon_id in icons_to_draw: # type: str
@ -81,19 +102,17 @@ def draw_grid(step: float = 24, columns: int = 16):
number += 1
height: int = int(int(number / (width / step) + 1) * step)
svg: Drawing = Drawing(icon_grid_file_name, (width, height))
svg: Drawing = Drawing(file_name, (width, height))
svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF"))
for combined_icon in icons: # type: List[Icon]
background_color, foreground_color = "#FFFFFF", "#444444"
background_color = "#FFFFFF"
svg.add(svg.rect(
point - np.array((-10, -10)), (20, 20),
fill=background_color))
for icon in combined_icon: # type: Icon
path = icon.get_path(svg, point)
path.update({"fill": foreground_color})
path.update({"fill": color.hex})
svg.add(path)
point += np.array((step, 0))
if point[0] > width - 8:
@ -103,5 +122,5 @@ def draw_grid(step: float = 24, columns: int = 16):
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)

View file

@ -21,10 +21,10 @@ from roentgen.constructor import (
Constructor, Figure, Building, Segment)
from roentgen.point import Point, Occupied
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.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.direction import DirectionSet, Sector
from roentgen.util import MinMax
@ -75,6 +75,31 @@ class Painter:
self.svg.add(path)
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.
building_shade: Group = Group(opacity=0.1)
@ -141,26 +166,6 @@ class Painter:
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
for node in constructor.nodes: # type: Point
@ -219,7 +224,11 @@ class Painter:
# 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)
for index, node in enumerate(nodes): # type: int, Point
@ -286,7 +295,7 @@ def main(argv) -> None:
"""
if len(argv) == 2:
if argv[1] == "grid":
draw_grid()
draw_all_icons("icon_grid.svg")
return
options: argparse.Namespace = ui.parse_options(argv)
@ -306,6 +315,16 @@ def main(argv) -> None:
ui.error("cannot download OSM data")
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(
lambda x: float(x.replace('m', '-')), options.boundary_box.split(',')))
@ -327,10 +346,9 @@ def main(argv) -> None:
map_: Map = osm_reader.map_
scheme: Scheme = Scheme(TAGS_FILE_NAME)
min1: np.array = np.array((boundary_box[1], boundary_box[0]))
max1: np.array = np.array((boundary_box[3], boundary_box[2]))
flinger: Flinger = Flinger(MinMax(min1, max1), options.scale)
size: np.array = flinger.size
@ -362,6 +380,7 @@ def main(argv) -> None:
if options.draw_ways:
constructor.construct_ways()
constructor.construct_relations()
if options.mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
constructor.construct_nodes()
painter: Painter = Painter(

View file

@ -26,10 +26,11 @@ class TextStruct:
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.width = width
self.height = height
self.overlap = overlap
def check(self, point) -> bool:
if 0 <= point[0] < self.width and 0 <= point[1] < self.height:
@ -171,10 +172,12 @@ class Point(Tagged):
return
is_place_for_extra: bool = True
if occupied:
left: float = -(len(self.icon_set.extra_icons) - 1) * 8
for shape_ids in self.icon_set.extra_icons:
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
break
left += 16
@ -197,7 +200,7 @@ class Point(Tagged):
# Down-cast floats to integers to make icons pixel-perfect.
position = list(map(int, position))
if occupied.check(position):
if occupied and occupied.check(position):
return False
# Draw outlines.
@ -213,8 +216,10 @@ class Point(Tagged):
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):
if occupied:
overlap: int = occupied.overlap
for i in range(-overlap, overlap):
for j in range(-overlap, overlap):
occupied.register((position[0] + i, position[1] + j))
return True
@ -240,7 +245,7 @@ class Point(Tagged):
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=0.5, out_fill_2: Optional[Color] = None,
out_opacity_2=1.0):
"""
Drawing text.
@ -253,6 +258,7 @@ class Point(Tagged):
"""
length = len(text) * 6
if occupied:
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))):

View file

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

View file

@ -2,9 +2,9 @@
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. """
draw_grid()
draw_all_icons("temp.svg")