Issue #20: support viewpoint direction.

This commit is contained in:
Sergey Vartanov 2020-09-06 22:58:41 +03:00
parent be90f4bb09
commit 0405e552e2
5 changed files with 140 additions and 35 deletions

View file

@ -16,6 +16,8 @@ colors:
wood: "B8CC84"
tree: "98AC64"
direction_color: "E0F0FF"
outline_color: "FFFFFF"
beach_color: "F0E0C0"
boundary_color: "880088"

View file

@ -177,12 +177,6 @@ class Constructor:
self.nodes: List[Node] = []
self.ways: List[Way] = []
def color(self, name: str):
"""
Get color from the scheme.
"""
return self.scheme.get_color(name)
def construct_ways(self):
"""
Construct Röntgen ways.
@ -283,7 +277,7 @@ class Constructor:
if key not in ["tags", "no_tags", "layer", "level", "icon"]:
value = element[key]
if isinstance(value, str) and value.endswith("_color"):
value = "#" + self.scheme.get_color(value)
value = self.scheme.get_color(value)
style[key] = value
self.ways.append(
Way(kind, nodes, path, style, layer, 50, levels))
@ -294,6 +288,7 @@ class Constructor:
icon_set, tags, center_point, path, is_for_node=False))
appended = True
"""
if not appended:
style: Dict[str, Any] = {
"fill": "none", "stroke": "#FF0000", "stroke-width": 1}
@ -303,6 +298,7 @@ class Constructor:
icon_set: IconSet = self.scheme.get_icon(tags)
self.nodes.append(Node(
icon_set, tags, center_point, path, is_for_node=False))
"""
def construct_relations(self) -> None:
"""

83
roentgen/direction.py Normal file
View file

@ -0,0 +1,83 @@
"""
Direction tag support.
Author: Sergey Vartanov (me@enzet.ru).
"""
import math
import numpy as np
from typing import Dict, List, Optional, Iterator
DIRECTIONS: Dict[str, np.array] = {
"N": np.array((0, -1)),
"E": np.array((1, 0)),
"W": np.array((-1, 0)),
"S": np.array((0, 1)),
}
def parse_vector(text: str) -> np.array:
"""
Parse vector from text representation: letters N, E, W, S or 360-degree
notation. E.g. NW, 270.
:param text: vector text representation
:return: parsed normalized vector
"""
try:
degree: float = float(text) / 180 * math.pi - math.pi / 2
return np.array((math.cos(degree), math.sin(degree)))
except ValueError as e:
vector: np.array = np.array((0, 0))
for char in text: # type: str
if char not in DIRECTIONS:
return None
vector += DIRECTIONS[char]
return vector / np.linalg.norm(vector)
class Sector:
def __init__(self, text: str):
self.start: Optional[np.array]
self.end: Optional[np.array]
if "-" in text:
parts: List[str] = text.split("-")
self.start = parse_vector(parts[0])
self.end = parse_vector(parts[1])
else:
self.start = parse_vector(text)
self.end = None
def draw(self, center: np.array, radius: float) -> List:
"""
Construct SVG "d" for arc element.
:param center: arc center
:param radius: arc radius
"""
start: np.array = center + radius * self.end
end: np.array = center + radius * self.start
return ["L", start, "A", radius, radius, 0, "0", 0, end]
def __str__(self):
return f"{self.start}-{self.end}"
class DirectionSet:
"""
Describes direction, set of directions.
"""
def __init__(self, text: str):
"""
:param text: direction tag value
"""
self.sectors = list(map(Sector, text.split(";")))
def __str__(self):
return ", ".join(map(str, self.sectors))
def draw(self, center: np.array, radius: float) -> Iterator[str]:
return map(lambda x: x.draw(center, radius), self.sectors)

View file

@ -22,6 +22,7 @@ from roentgen.extract_icon import Icon, IconExtractor
from roentgen.osm_getter import get_osm
from roentgen.osm_reader import Map, OSMReader
from roentgen.scheme import Scheme
from roentgen.direction import DirectionSet
ICONS_FILE_NAME: str = "icons/icons.svg"
TAGS_FILE_NAME: str = "data/tags.yml"
@ -95,7 +96,7 @@ class Painter:
write_tags = self.construct_text(node.tags, node.icon_set.processed)
for text_struct in write_tags:
fill = text_struct["fill"] if "fill" in text_struct else "444444"
fill = text_struct["fill"] if "fill" in text_struct else "#444444"
size = text_struct["size"] if "size" in text_struct else 10
text_y += size + 1
text = text_struct["text"]
@ -113,11 +114,11 @@ class Painter:
text = f"{tag}: {node.tags[tag]}"
self.draw_text(
text, (node.point[0], node.point[1] + text_y + 18),
"734A08")
"#734A08")
text_y += 10
def draw_text(
self, text: str, point, fill, size=10, out_fill="FFFFFF",
self, text: str, point, fill, size=10, out_fill="#FFFFFF",
out_opacity=1.0, out_fill_2=None, out_opacity_2=1.0):
"""
Drawing text.
@ -131,18 +132,18 @@ class Painter:
if out_fill_2:
self.svg.add(Text(
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{out_fill_2}",
font_family="Roboto", fill=out_fill_2,
stroke_linejoin="round", stroke_width=5,
stroke=f"#{out_fill_2}", opacity=out_opacity_2))
stroke=out_fill_2, opacity=out_opacity_2))
if out_fill:
self.svg.add(Text(
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{out_fill}",
font_family="Roboto", fill=out_fill,
stroke_linejoin="round", stroke_width=3,
stroke=f"#{out_fill}", opacity=out_opacity))
stroke=out_fill, opacity=out_opacity))
self.svg.add(Text(
text, point, font_size=size, text_anchor="middle",
font_family="Roboto", fill=f"#{fill}"))
font_family="Roboto", fill=fill))
def construct_text(self, tags, processed):
"""
@ -197,7 +198,7 @@ class Painter:
address.append(tags["addr:housenumber"])
tags.pop("addr:housenumber", None)
if name:
texts.append({"text": name, "fill": "000000"})
texts.append({"text": name, "fill": "#000000"})
if alt_name:
texts.append({"text": "(" + alt_name + ")"})
if address:
@ -223,11 +224,11 @@ class Painter:
if link[-1] == "/":
link = link[:-1]
link = link[:25] + ("..." if len(tags["website"]) > 25 else "")
texts.append({"text": link, "fill": "000088"})
texts.append({"text": link, "fill": "#000088"})
tags.pop("website", None)
for k in ["phone"]:
if k in tags:
texts.append({"text": tags[k], "fill": "444444"})
texts.append({"text": tags[k], "fill": "#444444"})
tags.pop(k)
for tag in tags:
if self.scheme.is_writable(tag) and not (tag in processed):
@ -266,7 +267,7 @@ class Painter:
d=("M", np.add(flung_1, shift_1), "L",
np.add(flung_2, shift_1), np.add(flung_2, shift_2),
np.add(flung_1, shift_2), "Z"),
fill=f"#{color}", stroke=f"#{color}", stroke_width=1))
fill=color, stroke=color, stroke_width=1))
elif way.path:
# TODO: implement
pass
@ -312,9 +313,9 @@ class Painter:
# Building walls
self.draw_building_walls(1, "AAAAAA", ways)
self.draw_building_walls(2, "C3C3C3", ways)
self.draw_building_walls(3, "DDDDDD", ways)
self.draw_building_walls(1, "#AAAAAA", ways)
self.draw_building_walls(2, "#C3C3C3", ways)
self.draw_building_walls(3, "#DDDDDD", ways)
# Building roof
@ -345,6 +346,29 @@ class Painter:
node.point, float(node.tags["diameter_crown"]) * 1.2,
fill="#688C44", stroke="#688C44", opacity=0.3))
# Directions
for node in nodes:
if not ("tourism" in node.tags and
node.tags["tourism"] == "viewpoint" and
"direction" in node.tags):
continue
DIRECTION_RADIUS: int = 50
DIRECTION_COLOR: str = self.scheme.get_color("direction_color")
for d in DirectionSet(node.tags["direction"])\
.draw(node.point, DIRECTION_RADIUS):
gradient = self.svg.defs.add(self.svg.radialGradient(
center=node.point, r=DIRECTION_RADIUS,
gradientUnits="userSpaceOnUse"))
gradient\
.add_stop_color(0, DIRECTION_COLOR, opacity=0)\
.add_stop_color(1, DIRECTION_COLOR, opacity=0.7)
self.svg.add(self.svg.path(
d=["M", node.point] + d + ["L", node.point, "Z"],
fill=gradient.get_paint_server()))
# All other nodes
nodes = sorted(nodes, key=lambda x: x.layer)
@ -382,12 +406,12 @@ class Painter:
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
path = icon.get_path(self.svg, point)
path.update({"fill": f"#{fill}"})
path.update({"fill": fill})
path.set_desc(title=title)
self.svg.add(path)
def draw_point_outline(
self, icon: Icon, point, fill, mode="default", size=16):
self, icon: Icon, point, fill, mode="default"):
point = np.array(list(map(lambda x: int(x), point)))
@ -395,18 +419,18 @@ class Painter:
stroke_width = 2.2
outline_fill = self.scheme.get_color("outline_color")
if mode not in [AUTHOR_MODE, CREATION_TIME_MODE]:
r = int(fill[0:2], 16)
g = int(fill[2:4], 16)
b = int(fill[4:6], 16)
r = int(fill[1:3], 16)
g = int(fill[3:5], 16)
b = int(fill[5:7], 16)
Y = 0.2126 * r + 0.7152 * g + 0.0722 * b
if Y > 200:
outline_fill = "000000"
outline_fill = "#000000"
opacity = 0.7
path = icon.get_path(self.svg, point)
path.update({
"fill": f"#{outline_fill}", "opacity": opacity,
"stroke": f"#{outline_fill}", "stroke-width": stroke_width,
"fill": outline_fill, "opacity": opacity,
"stroke": outline_fill, "stroke-width": stroke_width,
"stroke-linejoin": "round"})
self.svg.add(path)

View file

@ -10,7 +10,7 @@ from typing import Any, Dict, List, Optional, Set
from roentgen.extract_icon import DEFAULT_SHAPE_ID
DEFAULT_COLOR: str = "444444"
DEFAULT_COLOR: str = "#444444"
class IconSet:
@ -68,12 +68,12 @@ class Scheme:
"""
Return color if the color is in scheme, otherwise return default color.
:return: 6-digit color specification without "#"
:return: 6-digit color specification with "#"
"""
if color in self.colors:
return self.colors[color]
return "#" + self.colors[color]
if color.startswith("#"):
return color[1:]
return color
print(f"No color {color}.")
@ -158,7 +158,7 @@ class Scheme:
for key in matcher["tags"].keys():
processed.add(key)
if "color" in matcher:
fill = self.colors[matcher["color"]]
fill = self.get_color(matcher["color"])
for key in matcher["tags"].keys():
processed.add(key)