Fix code style.

This commit is contained in:
Sergey Vartanov 2020-09-24 23:55:04 +03:00
parent c9fe5e55b6
commit 2667ee9d02
16 changed files with 228 additions and 240 deletions

View file

@ -6,8 +6,13 @@ Author: Sergey Vartanov (me@enzet.ru).
from typing import List, Any, Dict
def get_address(tags: Dict[str, Any], draw_captions_mode: str):
def get_address(tags: Dict[str, Any], draw_captions_mode: str) -> List[str]:
"""
Construct address text list from the tags.
:param tags: OSM node, way or relation tags
:param draw_captions_mode: captions mode ("all", "main", or "no")
"""
address: List[str] = []
if draw_captions_mode != "main":
@ -30,3 +35,5 @@ def get_address(tags: Dict[str, Any], draw_captions_mode: str):
if "addr:housenumber" in tags:
address.append(tags["addr:housenumber"])
tags.pop("addr:housenumber", None)
return address

View file

@ -1,3 +1,8 @@
"""
Röntgen project. Color utility.
Author: Sergey Vartanov (me@enzet.ru)
"""
from typing import Any, List
from colour import Color
@ -10,9 +15,8 @@ def is_bright(color: Color) -> bool:
Is color bright enough to have black outline instead of white.
"""
return (
0.2126 * color.red * 256 +
0.7152 * color.green * 256 +
0.0722 * color.blue * 256 > 200)
0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue
> 0.78125)
def get_gradient_color(value: Any, bounds: MinMax, colors: List[Color]):
@ -30,9 +34,9 @@ def get_gradient_color(value: Any, bounds: MinMax, colors: List[Color]):
0 if bounds.max_ == bounds.min_ else
(value - bounds.min_) / (bounds.max_ - bounds.min_))
coefficient = min(1.0, max(0.0, coefficient))
m: int = int(coefficient * color_length)
color_coefficient = (coefficient - m / color_length) * color_length
index: int = int(coefficient * color_length)
color_coefficient = (coefficient - index / color_length) * color_length
return Color(rgb=[
scale[m].rgb[i] + color_coefficient *
(scale[m + 1].rgb[i] - scale[m].rgb[i]) for i in range(3)])
scale[index].rgb[i] + color_coefficient *
(scale[index + 1].rgb[i] - scale[index].rgb[i]) for i in range(3)])

View file

@ -3,21 +3,22 @@ Construct Röntgen nodes and ways.
Author: Sergey Vartanov (me@enzet.ru).
"""
import numpy as np
from colour import Color
from dataclasses import dataclass
from datetime import datetime
from hashlib import sha256
from typing import Any, Dict, List, Optional, Set
from colour import Color
import numpy as np
from roentgen import ui
from roentgen.color import get_gradient_color
from roentgen.extract_icon import DEFAULT_SMALL_SHAPE_ID
from roentgen.flinger import Flinger
from roentgen.osm_reader import Map, OSMMember, OSMRelation, OSMWay, OSMNode, \
Tagged
from roentgen.osm_reader import (
Map, OSMMember, OSMRelation, OSMWay, OSMNode, Tagged)
from roentgen.scheme import IconSet, Scheme
from roentgen.util import MinMax
from roentgen.color import get_gradient_color
DEBUG: bool = False
TIME_COLOR_SCALE: List[Color] = [
@ -27,31 +28,35 @@ TIME_COLOR_SCALE: List[Color] = [
def is_clockwise(polygon: List[OSMNode]) -> bool:
"""
Are polygon nodes are in clockwise order.
Return true if polygon nodes are in clockwise order.
:param polygon: list of OpenStreetMap nodes
"""
count: float = 0
for index in range(len(polygon)): # type: int
for index, node in enumerate(polygon): # type: int, OSMNode
next_index: int = 0 if index == len(polygon) - 1 else index + 1
count += (
(polygon[next_index].coordinates[0] -
polygon[index].coordinates[0]) *
(polygon[next_index].coordinates[1] +
polygon[index].coordinates[1]))
(polygon[next_index].coordinates[0] - node.coordinates[0]) *
(polygon[next_index].coordinates[1] + node.coordinates[1]))
return count >= 0
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
if is_clockwise(polygon):
return polygon
else:
return list(reversed(polygon))
"""
Make polygon nodes clockwise.
:param polygon: list of OpenStreetMap nodes
"""
return polygon if is_clockwise(polygon) else list(reversed(polygon))
def make_counter_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
if not is_clockwise(polygon):
return polygon
else:
return list(reversed(polygon))
"""
Make polygon nodes counter-clockwise.
:param polygon: list of OpenStreetMap nodes
"""
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
class Point(Tagged):
@ -74,11 +79,6 @@ class Point(Tagged):
self.layer: float = 0
self.is_for_node: bool = is_for_node
def get_tag(self, key: str):
if key in self.tags:
return self.tags[key]
return None
class Figure(Tagged):
"""
@ -107,6 +107,7 @@ class Figure(Tagged):
"""
Get SVG path commands.
:param flinger: convertor for geo coordinates
:param shift: shift vector
"""
path: str = ""
@ -121,6 +122,9 @@ class Figure(Tagged):
class Segment:
"""
Line segment.
"""
def __init__(self, point_1: np.array, point_2: np.array):
self.point_1 = point_1
self.point_2 = point_2
@ -142,6 +146,7 @@ class Building(Figure):
def __init__(
self, tags: Dict[str, str], inners, outers, flinger: Flinger,
style: Dict[str, Any], layer: float):
super().__init__(tags, inners, outers, style, layer)
self.parts = []
@ -154,23 +159,24 @@ class Building(Figure):
self.parts = sorted(self.parts)
def get_levels(self):
def get_levels(self) -> float:
"""
Get building level number.
"""
try:
return max(3, float(self.get_tag("building:levels")))
return max(3.0, float(self.get_tag("building:levels")))
except (ValueError, TypeError):
return 3
@dataclass
class TextStruct:
"""
Some label on the map with attributes.
"""
def __init__(
self, text: str, fill: Color = Color("#444444"), size: float = 10):
self.text = text
self.fill = fill
self.size = size
text: str
fill: Color = Color("#444444")
size: float = 10
def line_center(nodes: List[OSMNode], flinger: Flinger) -> np.array:
@ -202,6 +208,9 @@ def get_user_color(text: str, seed: str) -> Color:
def get_time_color(time: Optional[datetime], boundaries: MinMax) -> Color:
"""
Generate color based on time.
:param time: current element creation time
:param boundaries: minimum and maximum element creation time on the map
"""
return get_gradient_color(time, boundaries, TIME_COLOR_SCALE)
@ -244,11 +253,11 @@ def glue(ways: List[OSMWay]) -> List[List[OSMNode]]:
def get_path(nodes: List[OSMNode], shift: np.array, flinger: Flinger) -> str:
"""
Construct SVG path from nodes.
Construct SVG path commands from nodes.
"""
path = ""
prev_node = None
for node in nodes:
path: str = ""
prev_node: Optional[OSMNode] = None
for node in nodes: # type: OSMNode
flung = flinger.fling(node.coordinates) + shift
path += ("L" if prev_node else "M") + f" {flung[0]},{flung[1]} "
prev_node = node
@ -280,11 +289,14 @@ class Constructor:
self.levels: Set[float] = {0.5, 1}
def add_building(self, building: Building):
def add_building(self, building: Building) -> None:
"""
Add building and update levels.
"""
self.buildings.append(building)
self.levels.add(building.get_levels())
def construct_ways(self):
def construct_ways(self) -> None:
"""
Construct Röntgen ways.
"""
@ -303,33 +315,22 @@ class Constructor:
def construct_way(
self, way: Optional[OSMWay], tags: Dict[str, Any],
inners, outers) -> None:
inners: List[List[OSMNode]], outers: List[List[OSMNode]]) -> None:
"""
Way construction.
:param way: OSM way
:param tags: way tag dictionary
:param inners: list of polygons that compose inner boundary
:param outers: list of polygons that compose outer boundary
"""
layer: float = 0
# level: float = 0
#
# if "layer" in tags:
# layer = get_float(tags["layer"])
# if "level" in tags:
# try:
# levels = list(map(float, tags["level"].split(";")))
# level = sum(levels) / len(levels)
# except ValueError:
# pass
# layer = 100 * level + 0.01 * layer
center_point, center_coordinates = None, None
if way:
center_point, center_coordinates = \
line_center(way.nodes, self.flinger)
nodes = way.nodes
if way is not None:
center_point, center_coordinates = (
line_center(way.nodes, self.flinger))
if self.mode == "user-coloring":
if not way:
@ -417,8 +418,8 @@ class Constructor:
"stroke-width": 1}
self.figures.append(Figure(
tags, inners, outers, style, layer))
if center_point is not None and (way.is_cycle() or
"area" in tags and tags["area"]):
if (center_point is not None and
way.is_cycle() and "area" in tags and tags["area"]):
icon_set: IconSet = self.scheme.get_icon(tags)
self.nodes.append(Point(
icon_set, tags, center_point, center_coordinates,
@ -480,7 +481,7 @@ class Constructor:
if self.mode == "user-coloring":
icon_set.color = get_user_color(node.user, self.seed)
if self.mode == "time":
icon_set.color = get_time_color(node.timestamp)
icon_set.color = get_time_color(node.timestamp, self.map_.time)
self.nodes.append(Point(icon_set, tags, flung, node.coordinates))

View file

@ -43,7 +43,7 @@ def parse_vector(text: str) -> Optional[np.array]:
return None
def rotation_matrix(angle):
def rotation_matrix(angle) -> np.array:
"""
Get a matrix to rotate 2D vector by the angle.
@ -100,7 +100,7 @@ class Sector:
return ["L", start, "A", radius, radius, 0, "0", 0, end]
def __str__(self):
def __str__(self) -> str:
return f"{self.start}-{self.end}"
@ -115,7 +115,7 @@ class DirectionSet:
"""
self.sectors: Iterator[Optional[Sector]] = map(Sector, text.split(";"))
def __str__(self):
def __str__(self) -> str:
return ", ".join(map(str, self.sectors))
def draw(self, center: np.array, radius: float) -> Iterator[List[Path]]:

View file

@ -6,6 +6,7 @@ Author: Sergey Vartanov (me@enzet.ru).
import re
import xml.dom.minidom
from typing import Dict
from xml.dom.minidom import Element, Node
import numpy as np
from svgwrite import Drawing
@ -14,6 +15,7 @@ from roentgen import ui
DEFAULT_SHAPE_ID: str = "default"
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
STANDARD_INKSCAPE_ID: str = "(path|rect)\\d*"
GRID_STEP: int = 16
@ -71,25 +73,26 @@ class IconExtractor:
content = xml.dom.minidom.parse(input_file)
for element in content.childNodes:
if element.nodeName == "svg":
for node in element.childNodes:
if node.nodeName in ["g", "path"]:
for node in element.childNodes: # type: Node
if isinstance(node, Element):
self.parse(node)
def parse(self, node) -> None:
def parse(self, node: Element) -> None:
"""
Extract icon paths into a map.
:param node: XML node that contains icon
"""
if node.nodeName != "path":
if node.nodeName == "g":
for sub_node in node.childNodes:
if isinstance(sub_node, Element):
self.parse(sub_node)
return
if "id" in node.attributes.keys() and \
"d" in node.attributes.keys() and \
node.attributes["id"].value:
path = node.attributes["d"].value
if ("id" in node.attributes.keys() and
"d" in node.attributes.keys() and
node.attributes["id"].value):
path: str = node.attributes["d"].value
matcher = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
if not matcher:
ui.error(f"invalid path: {path}")
@ -104,6 +107,8 @@ class IconExtractor:
get_offset(float(matcher.group(2)))))
id_: str = node.attributes["id"].value
matcher = re.match(STANDARD_INKSCAPE_ID, id_)
if not matcher:
self.icons[id_] = Icon(node.attributes["d"].value, point, id_)
def get_path(self, id_: str) -> (Icon, bool):

View file

@ -37,7 +37,7 @@ class Flinger:
"""
Convert geo coordinates into SVG position points.
"""
def __init__(self, geo_boundaries: MinMax, scale: float = 1000):
def __init__(self, geo_boundaries: MinMax, scale: float = 18):
"""
:param geo_boundaries: minimum and maximum latitude and longitude
:param scale: OSM zoom level

View file

@ -5,11 +5,10 @@ Author: Sergey Vartanov (me@enzet.ru).
"""
import numpy as np
from svgwrite import Drawing
import yaml
from typing import List, Dict, Any, Set
from roentgen.extract_icon import Icon, IconExtractor
from typing import List
from roentgen.scheme import Scheme
def draw_grid(step: float = 24, columns: int = 16):
@ -19,19 +18,19 @@ def draw_grid(step: float = 24, columns: int = 16):
: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: str = "data/tags.yml"
scheme = yaml.load(open(tags_file_name), Loader=yaml.FullLoader)
scheme: Scheme = Scheme(tags_file_name)
icons_file_name = "icons/icons.svg"
icon_grid_file_name = "icon_grid.svg"
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 = []
to_draw: List[Set[str]] = []
for element in scheme["nodes"]:
for element in scheme.nodes: # type: Dict[str, Any]
if "icon" in element:
if set(element["icon"]) not in to_draw:
to_draw.append(set(element["icon"]))
@ -41,23 +40,26 @@ def draw_grid(step: float = 24, columns: int = 16):
if "over_icon" not in element:
continue
if "under_icon" in element:
for icon in element["under_icon"]:
current_set = set([icon] + element["over_icon"])
for icon_id in element["under_icon"]: # type: str
current_set = set([icon_id] + element["over_icon"])
if current_set not in to_draw:
to_draw.append(current_set)
if not ("under_icon" in element and "with_icon" in element):
continue
for icon in element["under_icon"]:
for icon2 in element["with_icon"]:
current_set = set([icon] + [icon2] + element["over_icon"])
for icon_id in element["under_icon"]: # type: str
for icon_2_id in element["with_icon"]: # type: str
current_set: Set[str] = set(
[icon_id] + [icon_2_id] + element["over_icon"])
if current_set not in to_draw:
to_draw.append(current_set)
for icon2 in element["with_icon"]:
for icon3 in element["with_icon"]:
current_set = \
set([icon] + [icon2] + [icon3] + element["over_icon"])
if icon2 != icon3 and icon2 != icon and icon3 != icon and \
(current_set not in to_draw):
for icon_2_id in element["with_icon"]: # type: str
for icon_3_id in element["with_icon"]: # type: str
current_set = set(
[icon_id] + [icon_2_id] + [icon_3_id] +
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)):
to_draw.append(current_set)
number: int = 0
@ -70,8 +72,8 @@ def draw_grid(step: float = 24, columns: int = 16):
found: bool = False
icon_set: List[Icon] = []
for icon_id in icons_to_draw: # type: str
icon, got = extractor.get_path(icon_id)
assert got
icon, extracted = extractor.get_path(icon_id) # type: Icon, bool
assert extracted, f"no icon with ID {icon_id}"
icon_set.append(icon)
found = True
if found:
@ -84,13 +86,13 @@ def draw_grid(step: float = 24, columns: int = 16):
svg.add(svg.rect((0, 0), (width, height), fill="#FFFFFF"))
for icon in icons:
for combined_icon in icons: # type: List[Icon]
background_color, foreground_color = "#FFFFFF", "#444444"
svg.add(svg.rect(
point - np.array((-10, -10)), (20, 20),
fill=background_color))
for i in icon: # type: Icon
path = i.get_path(svg, point)
for icon in combined_icon: # type: Icon
path = icon.get_path(svg, point)
path.update({"fill": foreground_color})
svg.add(path)
point += np.array((step, 0))

View file

@ -3,6 +3,8 @@ Simple OpenStreetMap renderer.
Author: Sergey Vartanov (me@enzet.ru).
"""
import argparse
import numpy as np
import os
import svgwrite
@ -17,7 +19,8 @@ from typing import Any, Dict, List, Optional
from roentgen import ui
from roentgen.address import get_address
from roentgen.constructor import Constructor, Point, Figure, TextStruct, Building
from roentgen.constructor import (
Constructor, Point, Figure, TextStruct, Building, Segment)
from roentgen.flinger import Flinger
from roentgen.grid import draw_grid
from roentgen.extract_icon import Icon, IconExtractor
@ -150,7 +153,7 @@ class Painter:
text, point, font_size=size, text_anchor="middle",
font_family=DEFAULT_FONT, fill=fill.hex))
def construct_text(self, tags, processed):
def construct_text(self, tags, processed) -> List[TextStruct]:
"""
Construct labels for not processed tags.
"""
@ -185,7 +188,7 @@ class Painter:
alt_name = ""
alt_name += "бывш. " + tags["old_name"]
address = get_address(tags, self.draw_captions)
address: List[str] = get_address(tags, self.draw_captions)
if name:
texts.append(TextStruct(name, Color("black")))
@ -233,11 +236,11 @@ class Painter:
ways_length: int = len(ways)
for index, way in enumerate(ways): # type: Figure
ui.progress_bar(index, ways_length, step=10, text="Drawing ways")
path: str = way.get_path(self.flinger)
if path:
p = Path(d=path)
p.update(way.style)
self.svg.add(p)
path_commands: str = way.get_path(self.flinger)
if path_commands:
path = Path(d=path_commands)
path.update(way.style)
self.svg.add(path)
ui.progress_bar(-1, 0, text="Drawing ways")
# Draw building shade.
@ -262,15 +265,18 @@ class Painter:
previous_level: float = 0
height: float = self.flinger.get_scale()
level_count: int = len(constructor.levels)
for level in sorted(constructor.levels):
for index, level in enumerate(sorted(constructor.levels)):
ui.progress_bar(
index, level_count, step=1, text="Drawing buildings")
fill: Color()
for way in constructor.buildings: # type: Building
if way.get_levels() < level:
continue
shift_1 = [0, -previous_level * height]
shift_2 = [0, -level * height]
for segment in way.parts:
for segment in way.parts: # type: Segment
if level == 0.5:
fill = Color("#AAAAAA")
elif level == 1:
@ -280,26 +286,29 @@ class Painter:
fill = Color(rgb=(color_part, color_part, color_part))
self.svg.add(self.svg.path(
d=("M", np.add(segment.point_1, shift_1), "L",
np.add(segment.point_2, shift_1),
np.add(segment.point_2, shift_2),
np.add(segment.point_1, shift_2),
np.add(segment.point_1, shift_1), "Z"),
d=("M", segment.point_1 + shift_1, "L",
segment.point_2 + shift_1,
segment.point_2 + shift_2,
segment.point_1 + shift_2,
segment.point_1 + shift_1, "Z"),
fill=fill.hex, stroke=fill.hex, stroke_width=1,
stroke_linejoin="round"))
# Draw building roof.
# Draw building roofs.
for way in constructor.buildings: # type: Building
if way.get_levels() == level:
shift = np.array([0, -way.get_levels() * height])
path: str = way.get_path(self.flinger, shift)
p = Path(d=path, opacity=1)
p.update(way.style)
p.update({"stroke-linejoin": "round"})
self.svg.add(p)
path_commands: str = way.get_path(self.flinger, shift)
path = Path(d=path_commands, opacity=1)
path.update(way.style)
path.update({"stroke-linejoin": "round"})
self.svg.add(path)
previous_level = level
ui.progress_bar(-1, level_count, step=1, text="Drawing buildings")
# Trees
for node in constructor.nodes:
@ -412,7 +421,7 @@ class Painter:
self, icon: Icon, point: (float, float), fill: Color,
tags: Dict[str, str] = None) -> None:
point = np.array(list(map(lambda x: int(x), point)))
point = np.array(list(map(int, point)))
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
path: svgwrite.path.Path = icon.get_path(self.svg, point)
@ -423,7 +432,7 @@ class Painter:
def draw_point_outline(
self, icon: Icon, point, fill: Color, mode="default"):
point = np.array(list(map(lambda x: int(x), point)))
point = np.array(list(map(int, point)))
opacity: float = 0.5
stroke_width: float = 2.2
@ -479,13 +488,13 @@ def check_level_overground(tags: Dict[str, Any]):
return True
def main(argv):
def main(argv) -> None:
if len(argv) == 2:
if argv[1] == "grid":
draw_grid()
return
options = ui.parse_options(argv)
options: argparse.Namespace = ui.parse_options(argv)
if not options:
sys.exit(1)
@ -570,9 +579,6 @@ def main(argv):
scheme=scheme)
painter.draw(constructor, points)
if options.show_index:
draw_index(flinger, map_, max1, min1, svg)
print("Writing output SVG...")
svg.write(open(options.output_file_name, "w"))
print("Done.")
@ -584,48 +590,3 @@ def main(argv):
missing_tags_file.write(
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
missing_tags_file.close()
def draw_index(flinger, map_, max1, min1, svg):
print(min1[1], max1[1])
print(min1[0], max1[0])
lon_step = 0.001
lat_step = 0.001
matrix = []
lat_number = int((max1[0] - min1[0]) / lat_step) + 1
lon_number = int((max1[1] - min1[1]) / 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[0] - min1[0]) / lat_step)
j = int((node[1] - min1[1]) / 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.nodes)
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[0] - min1[0]) / lat_step)
j = int((node[1] - min1[1]) / lon_step)
if (0 <= i < lat_number) and (0 <= j < lon_number):
matrix[i][j] += len(way.nodes) / float(
len(way.nodes))
for i in range(lat_number):
for j in range(lon_number):
t1 = flinger.fling(np.array((
min1[0] + i * lat_step, min1[1] + j * lon_step)))
t2 = flinger.fling(np.array((
min1[0] + (i + 1) * lat_step,
min1[1] + (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

@ -6,11 +6,11 @@ Author: Sergey Vartanov (me@enzet.ru).
import os
import re
import time
from typing import Dict, Optional
import urllib
import urllib3
from typing import Dict, Optional
from roentgen.ui import error
@ -26,12 +26,14 @@ def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
if not to_update and os.path.isfile(result_file_name):
return open(result_file_name).read()
matcher = re.match("(?P<left>[0-9.-]*),(?P<bottom>[0-9.-]*)," +
"(?P<right>[0-9.-]*),(?P<top>[0-9.-]*)", boundary_box)
matcher = re.match(
"(?P<left>[0-9.-]*),(?P<bottom>[0-9.-]*)," +
"(?P<right>[0-9.-]*),(?P<top>[0-9.-]*)",
boundary_box)
if not matcher:
error("invalid boundary box")
return
return None
try:
left = float(matcher.group("left"))
@ -40,19 +42,20 @@ def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
top = float(matcher.group("top"))
except ValueError:
error("parsing boundary box")
return
return None
if left >= right:
error("negative horizontal boundary")
return
return None
if bottom >= top:
error("negative vertical boundary")
return
return None
if right - left > 0.5 or top - bottom > 0.5:
error("box too big")
return
return None
content = get_data("api.openstreetmap.org/api/0.6/map",
content = get_data(
"api.openstreetmap.org/api/0.6/map",
{"bbox": boundary_box}, is_secure=True)
open(result_file_name, "w+").write(content.decode("utf-8"))

View file

@ -3,6 +3,8 @@ Reading OpenStreetMap data from XML file.
Author: Sergey Vartanov (me@enzet.ru).
"""
from dataclasses import dataclass
import numpy as np
from datetime import datetime
from typing import Dict, List, Optional, Set, Union
@ -115,20 +117,21 @@ class OSMWay(Tagged):
"""
return self.nodes[0] == self.nodes[-1]
def try_to_glue(self, other: "OSMWay"):
def try_to_glue(self, other: "OSMWay") -> Optional["OSMWay"]:
"""
Create new combined way if ways share endpoints.
"""
if self.nodes[0] == other.nodes[0]:
return OSMWay(nodes=list(reversed(other.nodes[1:])) + self.nodes)
elif self.nodes[0] == other.nodes[-1]:
if self.nodes[0] == other.nodes[-1]:
return OSMWay(nodes=other.nodes[:-1] + self.nodes)
elif self.nodes[-1] == other.nodes[-1]:
if self.nodes[-1] == other.nodes[-1]:
return OSMWay(nodes=self.nodes + list(reversed(other.nodes[:-1])))
elif self.nodes[-1] == other.nodes[0]:
if self.nodes[-1] == other.nodes[0]:
return OSMWay(nodes=self.nodes + other.nodes[1:])
return None
def __repr__(self):
def __repr__(self) -> str:
return f"Way <{self.id_}> {self.nodes}"
@ -175,6 +178,8 @@ def get_value(key: str, text: str):
value = text[end_index:text.find('"', end_index)]
return value
return None
class Map:
"""
@ -243,7 +248,7 @@ class OSMReader:
line = line.strip()
line_number += 1
progress_bar(line_number, lines_number)
progress_bar(line_number, lines_number, text="Parsing")
# Node parsing.
@ -251,7 +256,6 @@ class OSMReader:
if not parse_nodes:
if parse_ways or parse_relations:
continue
else:
break
if line[-2] == "/":
node: OSMNode = OSMNode().parse_from_xml(line, full)
@ -267,7 +271,6 @@ class OSMReader:
if not parse_ways:
if parse_relations:
continue
else:
break
if line[-2] == "/":
way = OSMWay().parse_from_xml(line, full)
@ -293,15 +296,15 @@ class OSMReader:
# Elements parsing.
elif line.startswith("<tag"):
k = get_value("k", line)
v = get_value("v", line)
element.tags[k] = v
key: str = get_value("k", line)
value = get_value("v", line)
element.tags[key] = value
elif line.startswith("<nd"):
element.nodes.append(
self.map_.node_map[int(get_value("ref", line))])
elif line.startswith("<member"):
element.members.append(OSMMember(line))
progress_bar(-1, lines_number) # Complete progress bar.
progress_bar(-1, lines_number, text="Parsing")
return self.map_

View file

@ -7,6 +7,7 @@ import copy
import yaml
from colour import Color
from dataclasses import dataclass
from typing import Any, Dict, List, Optional, Set
from roentgen.extract_icon import DEFAULT_SHAPE_ID
@ -14,23 +15,17 @@ from roentgen.extract_icon import DEFAULT_SHAPE_ID
DEFAULT_COLOR: Color = Color("#444444")
@dataclass
class IconSet:
"""
Node representation: icons and color.
"""
def __init__(
self, icons: List[List[str]], color: Color, processed: Set[str],
is_default: bool):
"""
:param icons: list of lists of shape identifiers
:param color: fill color of all icons
:param processed: tag keys that were processed to create icon set (other
tag keys should be displayed by text or ignored)
"""
self.icons: List[List[str]] = icons
self.color: Color = color
self.processed: Set[str] = processed
self.is_default = is_default
icons: List[List[str]] # list of lists of shape identifiers
color: Color # fill color of all icons
# tag keys that were processed to create icon set (other
# tag keys should be displayed by text or ignored)
processed: Set[str]
is_default: bool
class Scheme:
@ -44,8 +39,9 @@ class Scheme:
:param file_name: scheme file name with tags, colors, and tag key
specification
"""
content: Dict[str, Any] = \
yaml.load(open(file_name).read(), Loader=yaml.FullLoader)
with open(file_name) as input_file:
content: Dict[str, Any] = yaml.load(
input_file.read(), Loader=yaml.FullLoader)
self.nodes: List[Dict[str, Any]] = content["nodes"]
self.ways: List[Dict[str, Any]] = content["ways"]

View file

@ -10,7 +10,7 @@ BOXES: List[str] = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
BOXES_LENGTH: int = len(BOXES)
def parse_options(args):
def parse_options(args) -> argparse.Namespace:
"""
Parse Röntgen command-line options.
"""
@ -70,14 +70,6 @@ def parse_options(args):
dest="overlap",
default=12,
type=int)
parser.add_argument(
"--show-index",
dest="show_index",
action="store_true")
parser.add_argument(
"--no-show-index",
dest="show_index",
action="store_false")
parser.add_argument(
"--mode",
default="normal")
@ -88,7 +80,7 @@ def parse_options(args):
"--level",
default=None)
arguments = parser.parse_args(args[1:])
arguments: argparse.Namespace = parser.parse_args(args[1:])
if arguments.boundary_box:
arguments.boundary_box = arguments.boundary_box.replace(" ", "")

View file

@ -1,25 +1,34 @@
"""
Röntgen utility file.
Author: Sergey Vartanov (me@enzet.ru).
"""
from dataclasses import dataclass
from typing import Any
@dataclass
class MinMax:
"""
Minimum and maximum.
"""
def __init__(self, min_=None, max_=None):
self.min_ = min_
self.max_ = max_
min_: Any = None
max_: Any = None
def update(self, value):
def update(self, value: Any) -> None:
"""
Update minimum and maximum with new value.
"""
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_
def delta(self):
def delta(self) -> Any:
"""
Difference between maximum and minimum.
"""
return self.max_ - self.min_
def center(self):
def center(self) -> Any:
"""
Get middle point between minimum and maximum.
"""

View file

@ -9,22 +9,27 @@ from roentgen.direction import parse_vector
def test_compass_points_1():
""" Test north direction. """
assert np.allclose(parse_vector("N"), np.array([0, -1]))
def test_compass_points_2():
""" Test north-west direction. """
root: np.float64 = -np.sqrt(2) / 2
assert np.allclose(parse_vector("NW"), np.array([root, root]))
def test_compass_points_3():
""" Test south-south-west direction. """
assert np.allclose(
parse_vector("SSW"), np.array([-0.38268343, 0.92387953]))
def test_wrong():
def test_invalid():
""" Test invalid direction representation string. """
assert not parse_vector("O")
def test_degree():
""" Test east direction. """
assert np.allclose(parse_vector("90"), np.array([1, 0]))

View file

@ -6,4 +6,5 @@ from roentgen.grid import draw_grid
def test_icons():
""" Test grid drawing. """
draw_grid()

View file

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