mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 13:06:25 +02:00
Support Overpass, overlaps, tree circumference.
This commit is contained in:
parent
b8289467b9
commit
acf0ee45d9
5 changed files with 147 additions and 104 deletions
|
@ -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)
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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))):
|
||||||
|
|
|
@ -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>",
|
||||||
|
|
|
@ -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")
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue