Issue #20: fix compass points parsing; refactor.

This commit is contained in:
Sergey Vartanov 2020-09-09 23:54:15 +03:00
parent b443a59c49
commit 6d938a1248
13 changed files with 193 additions and 166 deletions

View file

@ -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

View file

@ -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

View file

@ -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:]

View file

@ -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))

View file

@ -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])

View file

@ -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)

View 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"))

View file

@ -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

View file

@ -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",

View file

@ -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
View 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]))

View file

@ -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
View file

@ -0,0 +1,3 @@
def test_main():
assert True