mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 04:56:24 +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).
|
||||
"""
|
||||
import numpy as np
|
||||
from colour import Color
|
||||
from svgwrite import Drawing
|
||||
from typing import List, Dict, Any, Set
|
||||
|
||||
|
@ -11,32 +12,24 @@ 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:
|
||||
to_draw.append(set(element["icon"]))
|
||||
if "add_icon" in element:
|
||||
if set(element["add_icon"]) not in to_draw:
|
||||
to_draw.append(set(element["add_icon"]))
|
||||
if "icon" in element and set(element["icon"]) not in to_draw:
|
||||
to_draw.append(set(element["icon"]))
|
||||
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
|
||||
if "under_icon" in element:
|
||||
|
@ -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)
|
||||
|
|
|
@ -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,31 +315,40 @@ def main(argv) -> None:
|
|||
ui.error("cannot download OSM data")
|
||||
input_file_name = [os.path.join("map", options.boundary_box + ".osm")]
|
||||
|
||||
boundary_box = list(map(
|
||||
lambda x: float(x.replace('m', '-')), options.boundary_box.split(',')))
|
||||
|
||||
full = False # Full keys getting
|
||||
|
||||
if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]:
|
||||
full = True
|
||||
|
||||
osm_reader = OSMReader()
|
||||
|
||||
for file_name in input_file_name:
|
||||
if not os.path.isfile(file_name):
|
||||
print("Fatal: no such file: " + file_name + ".")
|
||||
sys.exit(1)
|
||||
|
||||
osm_reader.parse_osm_file(
|
||||
file_name, parse_ways=options.draw_ways,
|
||||
parse_relations=options.draw_ways, full=full)
|
||||
|
||||
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]))
|
||||
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(',')))
|
||||
|
||||
full = False # Full keys getting
|
||||
|
||||
if options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]:
|
||||
full = True
|
||||
|
||||
osm_reader = OSMReader()
|
||||
|
||||
for file_name in input_file_name:
|
||||
if not os.path.isfile(file_name):
|
||||
print("Fatal: no such file: " + file_name + ".")
|
||||
sys.exit(1)
|
||||
|
||||
osm_reader.parse_osm_file(
|
||||
file_name, parse_ways=options.draw_ways,
|
||||
parse_relations=options.draw_ways, full=full)
|
||||
|
||||
map_: Map = osm_reader.map_
|
||||
|
||||
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,7 +380,8 @@ def main(argv) -> None:
|
|||
if options.draw_ways:
|
||||
constructor.construct_ways()
|
||||
constructor.construct_relations()
|
||||
constructor.construct_nodes()
|
||||
if options.mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
|
||||
constructor.construct_nodes()
|
||||
|
||||
painter: Painter = Painter(
|
||||
show_missing_tags=options.show_missing_tags, overlap=options.overlap,
|
||||
|
|
|
@ -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,13 +172,15 @@ class Point(Tagged):
|
|||
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:
|
||||
if occupied.check(
|
||||
(int(self.point[0] + left), int(self.point[1] + self.y))):
|
||||
is_place_for_extra = False
|
||||
break
|
||||
left += 16
|
||||
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))):
|
||||
is_place_for_extra = False
|
||||
break
|
||||
left += 16
|
||||
|
||||
if is_place_for_extra:
|
||||
left: float = -(len(self.icon_set.extra_icons) - 1) * 8
|
||||
|
@ -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,9 +216,11 @@ 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):
|
||||
occupied.register((position[0] + i, position[1] + j))
|
||||
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,19 +258,20 @@ class Point(Tagged):
|
|||
"""
|
||||
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 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))):
|
||||
is_occupied = True
|
||||
break
|
||||
|
||||
if is_occupied:
|
||||
return
|
||||
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)))
|
||||
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(
|
||||
|
|
|
@ -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>",
|
||||
|
|
|
@ -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")
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue