mirror of
https://github.com/enzet/map-machine.git
synced 2025-04-30 02:37:24 +02:00
Issue #20: fix compass points parsing; refactor.
This commit is contained in:
parent
b443a59c49
commit
6d938a1248
13 changed files with 193 additions and 166 deletions
|
@ -1,4 +1,4 @@
|
||||||
language: python
|
language: python
|
||||||
install:
|
install:
|
||||||
- pip install -r requirements.txt
|
- pip install -r requirements.txt
|
||||||
script: pytest -v test.py
|
script: pytest -v
|
||||||
|
|
|
@ -1,4 +1,5 @@
|
||||||
svgwrite
|
|
||||||
numpy>=1.18.1
|
numpy>=1.18.1
|
||||||
PyYAML>=4.2b1
|
portolan
|
||||||
|
pyyaml>=4.2b1
|
||||||
|
svgwrite
|
||||||
urllib3>=1.25.6
|
urllib3>=1.25.6
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
from hashlib import sha256
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
from hashlib import sha256
|
||||||
from typing import Any, Dict, List, Optional, Set
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
|
@ -30,6 +29,11 @@ class Node:
|
||||||
self.layer = 0
|
self.layer = 0
|
||||||
self.is_for_node = is_for_node
|
self.is_for_node = is_for_node
|
||||||
|
|
||||||
|
def get_tag(self, key: str):
|
||||||
|
if key in self.tags:
|
||||||
|
return self.tags[key]
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Way:
|
class Way:
|
||||||
"""
|
"""
|
||||||
|
@ -95,13 +99,12 @@ def get_user_color(text: str, seed: str):
|
||||||
return "#" + "0" * (6 - len(h)) + h
|
return "#" + "0" * (6 - len(h)) + h
|
||||||
|
|
||||||
|
|
||||||
def get_time_color(time):
|
def get_time_color(time: datetime):
|
||||||
"""
|
"""
|
||||||
Generate color based on time.
|
Generate color based on time.
|
||||||
"""
|
"""
|
||||||
if not time:
|
if not time:
|
||||||
return "#000000"
|
return "#000000"
|
||||||
time = datetime.strptime(time, "%Y-%m-%dT%H:%M:%SZ")
|
|
||||||
delta = (datetime.now() - time).total_seconds()
|
delta = (datetime.now() - time).total_seconds()
|
||||||
time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:]
|
time_color = hex(0xFF - min(0xFF, int(delta / 500000.)))[2:]
|
||||||
i_time_color = hex(min(0xFF, int(delta / 500000.)))[2:]
|
i_time_color = hex(min(0xFF, int(delta / 500000.)))[2:]
|
||||||
|
|
|
@ -3,42 +3,51 @@ Direction tag support.
|
||||||
|
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
import math
|
from typing import Iterator, List, Optional, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from portolan import middle
|
||||||
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:
|
def parse_vector(text: str) -> np.array:
|
||||||
"""
|
"""
|
||||||
Parse vector from text representation: letters N, E, W, S or 360-degree
|
Parse vector from text representation: compass points or 360-degree
|
||||||
notation. E.g. NW, 270.
|
notation. E.g. "NW", "270".
|
||||||
|
|
||||||
:param text: vector text representation
|
:param text: vector text representation
|
||||||
:return: parsed normalized vector
|
:return: parsed normalized vector
|
||||||
"""
|
"""
|
||||||
|
def degree_to_radian(degree: float):
|
||||||
|
""" Convert value in degrees to radians. """
|
||||||
|
return degree / 180 * np.pi - np.pi / 2
|
||||||
|
|
||||||
try:
|
try:
|
||||||
degree: float = float(text) / 180 * math.pi - math.pi / 2
|
radians: float = degree_to_radian(float(text))
|
||||||
return np.array((math.cos(degree), math.sin(degree)))
|
return np.array((np.cos(radians), np.sin(radians)))
|
||||||
except ValueError as e:
|
except ValueError:
|
||||||
vector: np.array = np.array((0, 0))
|
radians: float = degree_to_radian(middle(text))
|
||||||
for char in text: # type: str
|
return np.array((np.cos(radians), np.sin(radians)))
|
||||||
if char not in DIRECTIONS:
|
|
||||||
return None
|
|
||||||
vector += DIRECTIONS[char]
|
def rotation_matrix(angle):
|
||||||
return vector / np.linalg.norm(vector)
|
"""
|
||||||
|
Get a matrix to rotate 2D vector by the angle.
|
||||||
|
|
||||||
|
:param angle: angle in radians
|
||||||
|
"""
|
||||||
|
return np.array([
|
||||||
|
[np.cos(angle), np.sin(angle)],
|
||||||
|
[-np.sin(angle), np.cos(angle)]])
|
||||||
|
|
||||||
|
|
||||||
class Sector:
|
class Sector:
|
||||||
|
"""
|
||||||
|
Sector described by two vectors.
|
||||||
|
"""
|
||||||
def __init__(self, text: str):
|
def __init__(self, text: str):
|
||||||
|
"""
|
||||||
|
:param text: sector text representation. E.g. "70-210", "N-NW"
|
||||||
|
"""
|
||||||
self.start: Optional[np.array]
|
self.start: Optional[np.array]
|
||||||
self.end: Optional[np.array]
|
self.end: Optional[np.array]
|
||||||
|
|
||||||
|
@ -47,16 +56,23 @@ class Sector:
|
||||||
self.start = parse_vector(parts[0])
|
self.start = parse_vector(parts[0])
|
||||||
self.end = parse_vector(parts[1])
|
self.end = parse_vector(parts[1])
|
||||||
else:
|
else:
|
||||||
self.start = parse_vector(text)
|
vector = parse_vector(text)
|
||||||
self.end = None
|
angle = np.pi / 12
|
||||||
|
self.start = np.dot(rotation_matrix(angle), vector)
|
||||||
|
self.end = np.dot(rotation_matrix(-angle), vector)
|
||||||
|
|
||||||
def draw(self, center: np.array, radius: float) -> List:
|
def draw(self, center: np.array, radius: float) \
|
||||||
|
-> Optional[List[Union[float, str, np.array]]]:
|
||||||
"""
|
"""
|
||||||
Construct SVG "d" for arc element.
|
Construct SVG path commands for arc element.
|
||||||
|
|
||||||
:param center: arc center
|
:param center: arc center point
|
||||||
:param radius: arc radius
|
:param radius: arc radius
|
||||||
|
:return: SVG path commands
|
||||||
"""
|
"""
|
||||||
|
if self.start is None or self.end is None:
|
||||||
|
return None
|
||||||
|
|
||||||
start: np.array = center + radius * self.end
|
start: np.array = center + radius * self.end
|
||||||
end: np.array = center + radius * self.start
|
end: np.array = center + radius * self.start
|
||||||
|
|
||||||
|
@ -79,5 +95,14 @@ class DirectionSet:
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return ", ".join(map(str, self.sectors))
|
return ", ".join(map(str, self.sectors))
|
||||||
|
|
||||||
def draw(self, center: np.array, radius: float) -> Iterator[str]:
|
def draw(self, center: np.array, radius: float) -> Iterator[List]:
|
||||||
return map(lambda x: x.draw(center, radius), self.sectors)
|
"""
|
||||||
|
Construct SVG "d" for arc elements.
|
||||||
|
|
||||||
|
:param center: center point of all arcs
|
||||||
|
:param radius: radius of all arcs
|
||||||
|
:return: list of "d" values
|
||||||
|
"""
|
||||||
|
return filter(
|
||||||
|
lambda x: x is not None,
|
||||||
|
map(lambda x: x.draw(center, radius), self.sectors))
|
||||||
|
|
|
@ -5,6 +5,8 @@ import math
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
|
from roentgen.util import MinMax
|
||||||
|
|
||||||
|
|
||||||
def get_ratio(maximum, minimum, ratio: float = 1):
|
def get_ratio(maximum, minimum, ratio: float = 1):
|
||||||
return (maximum[0] - minimum[0]) * ratio / (maximum[1] - minimum[1])
|
return (maximum[0] - minimum[0]) * ratio / (maximum[1] - minimum[1])
|
||||||
|
|
|
@ -1,10 +1,10 @@
|
||||||
"""
|
"""
|
||||||
|
Icon grid drawing.
|
||||||
|
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
from svgwrite import Drawing
|
||||||
import random
|
|
||||||
import svgwrite
|
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from roentgen.extract_icon import Icon, IconExtractor
|
from roentgen.extract_icon import Icon, IconExtractor
|
||||||
|
@ -12,32 +12,21 @@ from roentgen.extract_icon import Icon, IconExtractor
|
||||||
from typing import List
|
from typing import List
|
||||||
|
|
||||||
|
|
||||||
def draw_grid():
|
def draw_grid(step: float = 24, columns: int = 16):
|
||||||
|
"""
|
||||||
|
Draw all possible icon combinations in grid.
|
||||||
|
|
||||||
|
:param step: horizontal and vertical distance between icons
|
||||||
|
:param columns: the number of columns in grid
|
||||||
|
"""
|
||||||
tags_file_name = "data/tags.yml"
|
tags_file_name = "data/tags.yml"
|
||||||
|
|
||||||
scheme = yaml.load(open(tags_file_name), Loader=yaml.FullLoader)
|
scheme = yaml.load(open(tags_file_name), Loader=yaml.FullLoader)
|
||||||
|
|
||||||
icons_file_name = "icons/icons.svg"
|
icons_file_name = "icons/icons.svg"
|
||||||
icon_grid_file_name = "icon_grid.svg"
|
icon_grid_file_name = "icon_grid.svg"
|
||||||
icon_colors_file_name = "data/icon_colors"
|
|
||||||
|
|
||||||
icon_colors = [("FFFFFF", "444444")]
|
|
||||||
|
|
||||||
if os.path.isfile(icon_colors_file_name):
|
|
||||||
icon_colors_file = open(icon_colors_file_name)
|
|
||||||
for line in icon_colors_file.read().split("\n"):
|
|
||||||
background_color = \
|
|
||||||
hex(int(line[0:3]))[2:] + hex(int(line[3:6]))[2:] + \
|
|
||||||
hex(int(line[6:9]))[2:]
|
|
||||||
foreground_color = \
|
|
||||||
hex(int(line[10:13]))[2:] + hex(int(line[13:16]))[2:] + \
|
|
||||||
hex(int(line[16:19]))[2:]
|
|
||||||
icon_colors.append((background_color, foreground_color))
|
|
||||||
|
|
||||||
step: float = 24
|
|
||||||
|
|
||||||
width: float = 24 * 16
|
|
||||||
|
|
||||||
|
width: float = step * columns
|
||||||
point: np.array = np.array((step / 2, step / 2))
|
point: np.array = np.array((step / 2, step / 2))
|
||||||
|
|
||||||
to_draw = []
|
to_draw = []
|
||||||
|
@ -89,20 +78,20 @@ def draw_grid():
|
||||||
icons.append(icon_set)
|
icons.append(icon_set)
|
||||||
number += 1
|
number += 1
|
||||||
|
|
||||||
height = int(number / (width / step) + 1) * step
|
height: int = int(int(number / (width / step) + 1) * step)
|
||||||
|
|
||||||
svg = svgwrite.Drawing(icon_grid_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 icon in icons:
|
for icon in icons:
|
||||||
background_color, foreground_color = random.choice(icon_colors)
|
background_color, foreground_color = "#FFFFFF", "#444444"
|
||||||
svg.add(svg.rect(
|
svg.add(svg.rect(
|
||||||
point - np.array((-10, -10)), (20, 20),
|
point - np.array((-10, -10)), (20, 20),
|
||||||
fill=f"#{background_color}"))
|
fill=background_color))
|
||||||
for i in icon: # type: Icon
|
for i in icon: # type: Icon
|
||||||
path = i.get_path(svg, point)
|
path = i.get_path(svg, point)
|
||||||
path.update({"fill": f"#{foreground_color}"})
|
path.update({"fill": foreground_color})
|
||||||
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:
|
||||||
|
@ -112,4 +101,5 @@ def draw_grid():
|
||||||
|
|
||||||
print(f"Icons: {number}.")
|
print(f"Icons: {number}.")
|
||||||
|
|
||||||
svg.write(open(icon_grid_file_name, "w"))
|
with open(icon_grid_file_name, "w") as output_file:
|
||||||
|
svg.write(output_file)
|
||||||
|
|
|
@ -15,6 +15,7 @@ from svgwrite.text import Text
|
||||||
from typing import Dict, List
|
from typing import Dict, List
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
|
from roentgen.address import get_address
|
||||||
from roentgen.constructor import Constructor, get_path, Node, Way
|
from roentgen.constructor import Constructor, get_path, Node, Way
|
||||||
from roentgen.flinger import GeoFlinger, Geo
|
from roentgen.flinger import GeoFlinger, Geo
|
||||||
from roentgen.grid import draw_grid
|
from roentgen.grid import draw_grid
|
||||||
|
@ -39,7 +40,7 @@ class Painter:
|
||||||
"""
|
"""
|
||||||
def __init__(
|
def __init__(
|
||||||
self, show_missing_tags: bool, overlap: int, draw_nodes: bool,
|
self, show_missing_tags: bool, overlap: int, draw_nodes: bool,
|
||||||
mode: str, draw_captions: bool, map_: Map, flinger: GeoFlinger,
|
mode: str, draw_captions: str, map_: Map, flinger: GeoFlinger,
|
||||||
svg: svgwrite.Drawing, icon_extractor: IconExtractor,
|
svg: svgwrite.Drawing, icon_extractor: IconExtractor,
|
||||||
scheme: Scheme):
|
scheme: Scheme):
|
||||||
|
|
||||||
|
@ -47,7 +48,7 @@ class Painter:
|
||||||
self.overlap: int = overlap
|
self.overlap: int = overlap
|
||||||
self.draw_nodes: bool = draw_nodes
|
self.draw_nodes: bool = draw_nodes
|
||||||
self.mode: str = mode
|
self.mode: str = mode
|
||||||
self.draw_captions = draw_captions
|
self.draw_captions: str = draw_captions
|
||||||
|
|
||||||
self.map_: Map = map_
|
self.map_: Map = map_
|
||||||
self.flinger: GeoFlinger = flinger
|
self.flinger: GeoFlinger = flinger
|
||||||
|
@ -150,7 +151,6 @@ class Painter:
|
||||||
Construct labels for not processed tags.
|
Construct labels for not processed tags.
|
||||||
"""
|
"""
|
||||||
texts = []
|
texts = []
|
||||||
address: List[str] = []
|
|
||||||
name = None
|
name = None
|
||||||
alt_name = None
|
alt_name = None
|
||||||
if "name" in tags:
|
if "name" in tags:
|
||||||
|
@ -179,24 +179,9 @@ class Painter:
|
||||||
else:
|
else:
|
||||||
alt_name = ""
|
alt_name = ""
|
||||||
alt_name += "бывш. " + tags["old_name"]
|
alt_name += "бывш. " + tags["old_name"]
|
||||||
if "addr:postcode" in tags and self.draw_captions != "main":
|
|
||||||
address.append(tags["addr:postcode"])
|
address = get_address(tags, self.draw_captions)
|
||||||
tags.pop("addr:postcode", None)
|
|
||||||
if "addr:country" in tags and self.draw_captions != "main":
|
|
||||||
address.append(tags["addr:country"])
|
|
||||||
tags.pop("addr:country", None)
|
|
||||||
if "addr:city" in tags and self.draw_captions != "main":
|
|
||||||
address.append(tags["addr:city"])
|
|
||||||
tags.pop("addr:city", None)
|
|
||||||
if "addr:street" in tags and self.draw_captions != "main":
|
|
||||||
street = tags["addr:street"]
|
|
||||||
if street.startswith("улица "):
|
|
||||||
street = "ул. " + street[len("улица "):]
|
|
||||||
address.append(street)
|
|
||||||
tags.pop("addr:street", None)
|
|
||||||
if "addr:housenumber" in tags:
|
|
||||||
address.append(tags["addr:housenumber"])
|
|
||||||
tags.pop("addr:housenumber", None)
|
|
||||||
if name:
|
if name:
|
||||||
texts.append({"text": name, "fill": "#000000"})
|
texts.append({"text": name, "fill": "#000000"})
|
||||||
if alt_name:
|
if alt_name:
|
||||||
|
@ -338,8 +323,7 @@ class Painter:
|
||||||
# Trees
|
# Trees
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
if not("natural" in node.tags and
|
if not(node.get_tag("natural") == "tree" and
|
||||||
node.tags["natural"] == "tree" and
|
|
||||||
"diameter_crown" in node.tags):
|
"diameter_crown" in node.tags):
|
||||||
continue
|
continue
|
||||||
self.svg.add(Circle(
|
self.svg.add(Circle(
|
||||||
|
@ -348,23 +332,27 @@ class Painter:
|
||||||
|
|
||||||
# Directions
|
# Directions
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes: # type: Node
|
||||||
if not ("tourism" in node.tags and
|
direction = None
|
||||||
node.tags["tourism"] == "viewpoint" and
|
if node.get_tag("tourism") == "viewpoint":
|
||||||
"direction" in node.tags):
|
direction = node.get_tag("direction")
|
||||||
|
if node.get_tag("man_made") == "surveillance":
|
||||||
|
direction = node.get_tag("camera:direction")
|
||||||
|
|
||||||
|
if not direction:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
DIRECTION_RADIUS: int = 50
|
DIRECTION_RADIUS: int = 25
|
||||||
DIRECTION_COLOR: str = self.scheme.get_color("direction_color")
|
DIRECTION_COLOR: str = self.scheme.get_color("direction_color")
|
||||||
|
|
||||||
for path in DirectionSet(node.tags["direction"])\
|
for path in DirectionSet(direction)\
|
||||||
.draw(node.point, DIRECTION_RADIUS):
|
.draw(node.point, DIRECTION_RADIUS):
|
||||||
gradient = self.svg.defs.add(self.svg.radialGradient(
|
gradient = self.svg.defs.add(self.svg.radialGradient(
|
||||||
center=node.point, r=DIRECTION_RADIUS,
|
center=node.point, r=DIRECTION_RADIUS,
|
||||||
gradientUnits="userSpaceOnUse"))
|
gradientUnits="userSpaceOnUse"))
|
||||||
gradient\
|
gradient\
|
||||||
.add_stop_color(0, DIRECTION_COLOR, opacity=0)\
|
.add_stop_color(0, DIRECTION_COLOR, opacity=0)\
|
||||||
.add_stop_color(1, DIRECTION_COLOR, opacity=0.7)
|
.add_stop_color(1, DIRECTION_COLOR, opacity=0.4)
|
||||||
self.svg.add(self.svg.path(
|
self.svg.add(self.svg.path(
|
||||||
d=["M", node.point] + path + ["L", node.point, "Z"],
|
d=["M", node.point] + path + ["L", node.point, "Z"],
|
||||||
fill=gradient.get_paint_server()))
|
fill=gradient.get_paint_server()))
|
||||||
|
@ -372,12 +360,14 @@ class Painter:
|
||||||
# All other nodes
|
# All other nodes
|
||||||
|
|
||||||
nodes = sorted(nodes, key=lambda x: x.layer)
|
nodes = sorted(nodes, key=lambda x: x.layer)
|
||||||
for node in nodes: # type: Node
|
for index, node in enumerate(nodes): # type: int, Node
|
||||||
if "natural" in node.tags and \
|
if "natural" in node.tags and \
|
||||||
node.tags["natural"] == "tree" and \
|
node.tags["natural"] == "tree" and \
|
||||||
"diameter_crown" in node.tags:
|
"diameter_crown" in node.tags:
|
||||||
continue
|
continue
|
||||||
|
ui.progress_bar(index, len(nodes), step=10)
|
||||||
self.draw_shapes(node, points)
|
self.draw_shapes(node, points)
|
||||||
|
ui.progress_bar(-1, len(nodes), step=10)
|
||||||
|
|
||||||
if self.draw_captions == "no":
|
if self.draw_captions == "no":
|
||||||
return
|
return
|
||||||
|
@ -405,7 +395,7 @@ class Painter:
|
||||||
point = np.array(list(map(lambda x: int(x), point)))
|
point = np.array(list(map(lambda x: int(x), point)))
|
||||||
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
|
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
|
||||||
|
|
||||||
path = icon.get_path(self.svg, point)
|
path: svgwrite.path.Path = icon.get_path(self.svg, point)
|
||||||
path.update({"fill": fill})
|
path.update({"fill": fill})
|
||||||
path.set_desc(title=title)
|
path.set_desc(title=title)
|
||||||
self.svg.add(path)
|
self.svg.add(path)
|
||||||
|
@ -518,15 +508,14 @@ def main():
|
||||||
min1 = Geo(boundary_box[1], boundary_box[0])
|
min1 = Geo(boundary_box[1], boundary_box[0])
|
||||||
max1 = Geo(boundary_box[3], boundary_box[2])
|
max1 = Geo(boundary_box[3], boundary_box[2])
|
||||||
|
|
||||||
authors = {}
|
|
||||||
missing_tags = {}
|
missing_tags = {}
|
||||||
points = []
|
points = []
|
||||||
|
|
||||||
scheme = Scheme(TAGS_FILE_NAME, COLORS_FILE_NAME)
|
scheme: Scheme = Scheme(TAGS_FILE_NAME, COLORS_FILE_NAME)
|
||||||
|
|
||||||
flinger = GeoFlinger(min1, max1, [0, 0], [w, h])
|
flinger: GeoFlinger = GeoFlinger(min1, max1, [0, 0], [w, h])
|
||||||
|
|
||||||
icon_extractor = IconExtractor(ICONS_FILE_NAME)
|
icon_extractor: IconExtractor = IconExtractor(ICONS_FILE_NAME)
|
||||||
|
|
||||||
def check_level(x):
|
def check_level(x):
|
||||||
""" Draw objects on all levels. """
|
""" Draw objects on all levels. """
|
||||||
|
@ -544,14 +533,14 @@ def main():
|
||||||
""" Draw objects on the specified level. """
|
""" Draw objects on the specified level. """
|
||||||
return not check_level_number(x, float(options.level))
|
return not check_level_number(x, float(options.level))
|
||||||
|
|
||||||
constructor = Constructor(
|
constructor: Constructor = Constructor(
|
||||||
check_level, options.mode, options.seed, map_, flinger, scheme)
|
check_level, options.mode, options.seed, map_, flinger, scheme)
|
||||||
if options.draw_ways:
|
if options.draw_ways:
|
||||||
constructor.construct_ways()
|
constructor.construct_ways()
|
||||||
constructor.construct_relations()
|
constructor.construct_relations()
|
||||||
constructor.construct_nodes()
|
constructor.construct_nodes()
|
||||||
|
|
||||||
painter = Painter(
|
painter: Painter = Painter(
|
||||||
show_missing_tags=options.show_missing_tags, overlap=options.overlap,
|
show_missing_tags=options.show_missing_tags, overlap=options.overlap,
|
||||||
draw_nodes=options.draw_nodes, mode=options.mode,
|
draw_nodes=options.draw_nodes, mode=options.mode,
|
||||||
draw_captions=options.draw_captions,
|
draw_captions=options.draw_captions,
|
||||||
|
@ -569,57 +558,11 @@ def main():
|
||||||
(w - flinger.space[0], 0), (flinger.space[0], h), fill="#FFFFFF"))
|
(w - flinger.space[0], 0), (flinger.space[0], h), fill="#FFFFFF"))
|
||||||
|
|
||||||
if options.show_index:
|
if options.show_index:
|
||||||
print(min1.lon, max1.lon)
|
draw_index(flinger, map_, max1, min1, svg)
|
||||||
print(min1.lat, max1.lat)
|
|
||||||
|
|
||||||
lon_step = 0.001
|
|
||||||
lat_step = 0.001
|
|
||||||
|
|
||||||
matrix = []
|
|
||||||
|
|
||||||
lat_number = int((max1.lat - min1.lat) / lat_step) + 1
|
|
||||||
lon_number = int((max1.lon - min1.lon) / lon_step) + 1
|
|
||||||
|
|
||||||
for i in range(lat_number):
|
|
||||||
row = []
|
|
||||||
for j in range(lon_number):
|
|
||||||
row.append(0)
|
|
||||||
matrix.append(row)
|
|
||||||
|
|
||||||
for node_id in map_.node_map:
|
|
||||||
node = map_.node_map[node_id]
|
|
||||||
i = int((node.lat - min1.lat) / lat_step)
|
|
||||||
j = int((node.lon - min1.lon) / lon_step)
|
|
||||||
if (0 <= i < lat_number) and (0 <= j < lon_number):
|
|
||||||
matrix[i][j] += 1
|
|
||||||
if "tags" in node:
|
|
||||||
matrix[i][j] += len(node.tags)
|
|
||||||
|
|
||||||
for way_id in map_.way_map:
|
|
||||||
way = map_.way_map[way_id]
|
|
||||||
if "tags" in way:
|
|
||||||
for node_id in way.nodes:
|
|
||||||
node = map_.node_map[node_id]
|
|
||||||
i = int((node.lat - min1.lat) / lat_step)
|
|
||||||
j = int((node.lon - min1.lon) / lon_step)
|
|
||||||
if (0 <= i < lat_number) and (0 <= j < lon_number):
|
|
||||||
matrix[i][j] += len(way.tags) / float(
|
|
||||||
len(way.nodes))
|
|
||||||
|
|
||||||
for i in range(lat_number):
|
|
||||||
for j in range(lon_number):
|
|
||||||
t1 = flinger.fling(Geo(
|
|
||||||
min1.lat + i * lat_step, min1.lon + j * lon_step))
|
|
||||||
t2 = flinger.fling(Geo(
|
|
||||||
min1.lat + (i + 1) * lat_step,
|
|
||||||
min1.lon + (j + 1) * lon_step))
|
|
||||||
svg.add(Text(
|
|
||||||
str(int(matrix[i][j])),
|
|
||||||
(((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40),
|
|
||||||
font_size=80, fill="440000",
|
|
||||||
opacity=0.1, align="center"))
|
|
||||||
|
|
||||||
|
print("Writing output SVG...")
|
||||||
svg.write(open(options.output_file_name, "w"))
|
svg.write(open(options.output_file_name, "w"))
|
||||||
|
print("Done")
|
||||||
|
|
||||||
top_missing_tags = \
|
top_missing_tags = \
|
||||||
sorted(missing_tags.keys(), key=lambda x: -missing_tags[x])
|
sorted(missing_tags.keys(), key=lambda x: -missing_tags[x])
|
||||||
|
@ -629,6 +572,47 @@ def main():
|
||||||
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
|
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
|
||||||
missing_tags_file.close()
|
missing_tags_file.close()
|
||||||
|
|
||||||
top_authors = sorted(authors.keys(), key=lambda x: -authors[x])
|
|
||||||
for author in top_authors:
|
def draw_index(flinger, map_, max1, min1, svg):
|
||||||
print(f"{author}: {authors[author]}")
|
print(min1.lon, max1.lon)
|
||||||
|
print(min1.lat, max1.lat)
|
||||||
|
lon_step = 0.001
|
||||||
|
lat_step = 0.001
|
||||||
|
matrix = []
|
||||||
|
lat_number = int((max1.lat - min1.lat) / lat_step) + 1
|
||||||
|
lon_number = int((max1.lon - min1.lon) / lon_step) + 1
|
||||||
|
for i in range(lat_number):
|
||||||
|
row = []
|
||||||
|
for j in range(lon_number):
|
||||||
|
row.append(0)
|
||||||
|
matrix.append(row)
|
||||||
|
for node_id in map_.node_map: # type: int
|
||||||
|
node = map_.node_map[node_id]
|
||||||
|
i = int((node.lat - min1.lat) / lat_step)
|
||||||
|
j = int((node.lon - min1.lon) / lon_step)
|
||||||
|
if (0 <= i < lat_number) and (0 <= j < lon_number):
|
||||||
|
matrix[i][j] += 1
|
||||||
|
if "tags" in node:
|
||||||
|
matrix[i][j] += len(node.tags)
|
||||||
|
for way_id in map_.way_map: # type: int
|
||||||
|
way = map_.way_map[way_id]
|
||||||
|
if "tags" in way:
|
||||||
|
for node_id in way.nodes:
|
||||||
|
node = map_.node_map[node_id]
|
||||||
|
i = int((node.lat - min1.lat) / lat_step)
|
||||||
|
j = int((node.lon - min1.lon) / lon_step)
|
||||||
|
if (0 <= i < lat_number) and (0 <= j < lon_number):
|
||||||
|
matrix[i][j] += len(way.tags) / float(
|
||||||
|
len(way.nodes))
|
||||||
|
for i in range(lat_number):
|
||||||
|
for j in range(lon_number):
|
||||||
|
t1 = flinger.fling(Geo(
|
||||||
|
min1.lat + i * lat_step, min1.lon + j * lon_step))
|
||||||
|
t2 = flinger.fling(Geo(
|
||||||
|
min1.lat + (i + 1) * lat_step,
|
||||||
|
min1.lon + (j + 1) * lon_step))
|
||||||
|
svg.add(Text(
|
||||||
|
str(int(matrix[i][j])),
|
||||||
|
(((t1 + t2) * 0.5)[0], ((t1 + t2) * 0.5)[1] + 40),
|
||||||
|
font_size=80, fill="440000",
|
||||||
|
opacity=0.1, align="center"))
|
||||||
|
|
|
@ -3,6 +3,7 @@ Reading OpenStreetMap data from XML file.
|
||||||
|
|
||||||
Author: Sergey Vartanov
|
Author: Sergey Vartanov
|
||||||
"""
|
"""
|
||||||
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional
|
from typing import Dict, List, Optional
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
|
@ -61,7 +62,7 @@ class OSMWay:
|
||||||
self.visible: Optional[str] = None
|
self.visible: Optional[str] = None
|
||||||
self.changeset: Optional[str] = None
|
self.changeset: Optional[str] = None
|
||||||
self.user: Optional[str] = None
|
self.user: Optional[str] = None
|
||||||
self.timestamp: Optional[str] = None
|
self.timestamp: Optional[datetime] = None
|
||||||
self.uid: Optional[str] = None
|
self.uid: Optional[str] = None
|
||||||
|
|
||||||
def parse_from_xml(self, text: str, is_full: bool = False) -> "OSMWay":
|
def parse_from_xml(self, text: str, is_full: bool = False) -> "OSMWay":
|
||||||
|
@ -76,7 +77,8 @@ class OSMWay:
|
||||||
if is_full:
|
if is_full:
|
||||||
self.visible = get_value("visible", text)
|
self.visible = get_value("visible", text)
|
||||||
self.changeset = get_value("changeset", text)
|
self.changeset = get_value("changeset", text)
|
||||||
self.timestamp = get_value("timestamp", text)
|
self.timestamp = datetime.strptime(
|
||||||
|
get_value("timestamp", text), "%Y-%m-%dT%H:%M:%SZ")
|
||||||
self.user = get_value("user", text)
|
self.user = get_value("user", text)
|
||||||
self.uid = get_value("uid", text)
|
self.uid = get_value("uid", text)
|
||||||
|
|
||||||
|
@ -190,8 +192,7 @@ class OSMReader:
|
||||||
if not parse_nodes:
|
if not parse_nodes:
|
||||||
if parse_ways or parse_relations:
|
if parse_ways or parse_relations:
|
||||||
continue
|
continue
|
||||||
else:
|
break
|
||||||
break
|
|
||||||
if line[-3] == "/":
|
if line[-3] == "/":
|
||||||
node: OSMNode = OSMNode().parse_from_xml(line[7:-3], full)
|
node: OSMNode = OSMNode().parse_from_xml(line[7:-3], full)
|
||||||
self.map_.node_map[node.id_] = node
|
self.map_.node_map[node.id_] = node
|
||||||
|
@ -206,8 +207,7 @@ class OSMReader:
|
||||||
if not parse_ways:
|
if not parse_ways:
|
||||||
if parse_relations:
|
if parse_relations:
|
||||||
continue
|
continue
|
||||||
else:
|
break
|
||||||
break
|
|
||||||
if line[-3] == '/':
|
if line[-3] == '/':
|
||||||
way = OSMWay().parse_from_xml(line[6:-3], full)
|
way = OSMWay().parse_from_xml(line[6:-3], full)
|
||||||
self.map_.way_map[way.id_] = way
|
self.map_.way_map[way.id_] = way
|
||||||
|
|
|
@ -39,7 +39,8 @@ def parse_options(args):
|
||||||
"-s", "--size",
|
"-s", "--size",
|
||||||
metavar="<width>,<height>",
|
metavar="<width>,<height>",
|
||||||
help="output SVG file size in pixels",
|
help="output SVG file size in pixels",
|
||||||
dest="size")
|
dest="size",
|
||||||
|
required=True)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"-nn", "--no-draw-nodes",
|
"-nn", "--no-draw-nodes",
|
||||||
dest="draw_nodes",
|
dest="draw_nodes",
|
||||||
|
|
|
@ -5,4 +5,4 @@ class MinMax:
|
||||||
|
|
||||||
def add(self, value):
|
def add(self, value):
|
||||||
self.min_ = value if not self.min_ or value < self.min_ else self.min_
|
self.min_ = value if not self.min_ or value < self.min_ else self.min_
|
||||||
self.max_ = value if not self.max_ or value > self.max_ else self.max_
|
self.max_ = value if not self.max_ or value > self.max_ else self.max_
|
||||||
|
|
22
test/test_direction.py
Normal file
22
test/test_direction.py
Normal file
|
@ -0,0 +1,22 @@
|
||||||
|
"""
|
||||||
|
Test direction processing.
|
||||||
|
|
||||||
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
|
"""
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
|
from roentgen.direction import parse_vector
|
||||||
|
|
||||||
|
|
||||||
|
def test_compass_points_1():
|
||||||
|
assert np.allclose(parse_vector("N"), np.array([0, -1]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_compass_points_2():
|
||||||
|
root: np.float64 = -np.sqrt(2) / 2
|
||||||
|
assert np.allclose(parse_vector("NW"), np.array([root, root]))
|
||||||
|
|
||||||
|
|
||||||
|
def test_compass_points_3():
|
||||||
|
assert np.allclose(
|
||||||
|
parse_vector("SSW"), np.array([-0.38268343, 0.92387953]))
|
|
@ -2,10 +2,6 @@
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import yaml
|
|
||||||
|
|
||||||
from roentgen.flinger import map_
|
from roentgen.flinger import map_
|
||||||
from roentgen.grid import draw_grid
|
from roentgen.grid import draw_grid
|
||||||
|
|
3
test_main.py
Normal file
3
test_main.py
Normal file
|
@ -0,0 +1,3 @@
|
||||||
|
def test_main():
|
||||||
|
assert True
|
||||||
|
|
Loading…
Add table
Reference in a new issue