mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-14 01:26:51 +02:00
Fix code style.
This commit is contained in:
parent
c9fe5e55b6
commit
2667ee9d02
16 changed files with 228 additions and 240 deletions
|
@ -6,8 +6,13 @@ Author: Sergey Vartanov (me@enzet.ru).
|
||||||
from typing import List, Any, Dict
|
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] = []
|
address: List[str] = []
|
||||||
|
|
||||||
if draw_captions_mode != "main":
|
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:
|
if "addr:housenumber" in tags:
|
||||||
address.append(tags["addr:housenumber"])
|
address.append(tags["addr:housenumber"])
|
||||||
tags.pop("addr:housenumber", None)
|
tags.pop("addr:housenumber", None)
|
||||||
|
|
||||||
|
return address
|
||||||
|
|
|
@ -1,3 +1,8 @@
|
||||||
|
"""
|
||||||
|
Röntgen project. Color utility.
|
||||||
|
|
||||||
|
Author: Sergey Vartanov (me@enzet.ru)
|
||||||
|
"""
|
||||||
from typing import Any, List
|
from typing import Any, List
|
||||||
|
|
||||||
from colour import Color
|
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.
|
Is color bright enough to have black outline instead of white.
|
||||||
"""
|
"""
|
||||||
return (
|
return (
|
||||||
0.2126 * color.red * 256 +
|
0.2126 * color.red + 0.7152 * color.green + 0.0722 * color.blue
|
||||||
0.7152 * color.green * 256 +
|
> 0.78125)
|
||||||
0.0722 * color.blue * 256 > 200)
|
|
||||||
|
|
||||||
|
|
||||||
def get_gradient_color(value: Any, bounds: MinMax, colors: List[Color]):
|
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
|
0 if bounds.max_ == bounds.min_ else
|
||||||
(value - bounds.min_) / (bounds.max_ - bounds.min_))
|
(value - bounds.min_) / (bounds.max_ - bounds.min_))
|
||||||
coefficient = min(1.0, max(0.0, coefficient))
|
coefficient = min(1.0, max(0.0, coefficient))
|
||||||
m: int = int(coefficient * color_length)
|
index: int = int(coefficient * color_length)
|
||||||
color_coefficient = (coefficient - m / color_length) * color_length
|
color_coefficient = (coefficient - index / color_length) * color_length
|
||||||
|
|
||||||
return Color(rgb=[
|
return Color(rgb=[
|
||||||
scale[m].rgb[i] + color_coefficient *
|
scale[index].rgb[i] + color_coefficient *
|
||||||
(scale[m + 1].rgb[i] - scale[m].rgb[i]) for i in range(3)])
|
(scale[index + 1].rgb[i] - scale[index].rgb[i]) for i in range(3)])
|
||||||
|
|
|
@ -3,21 +3,22 @@ Construct Röntgen nodes and ways.
|
||||||
|
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
from dataclasses import dataclass
|
||||||
|
|
||||||
from colour import Color
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from typing import Any, Dict, List, Optional, Set
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
|
from colour import Color
|
||||||
|
import numpy as np
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
|
from roentgen.color import get_gradient_color
|
||||||
from roentgen.extract_icon import DEFAULT_SMALL_SHAPE_ID
|
from roentgen.extract_icon import DEFAULT_SMALL_SHAPE_ID
|
||||||
from roentgen.flinger import Flinger
|
from roentgen.flinger import Flinger
|
||||||
from roentgen.osm_reader import Map, OSMMember, OSMRelation, OSMWay, OSMNode, \
|
from roentgen.osm_reader import (
|
||||||
Tagged
|
Map, OSMMember, OSMRelation, OSMWay, OSMNode, Tagged)
|
||||||
from roentgen.scheme import IconSet, Scheme
|
from roentgen.scheme import IconSet, Scheme
|
||||||
from roentgen.util import MinMax
|
from roentgen.util import MinMax
|
||||||
from roentgen.color import get_gradient_color
|
|
||||||
|
|
||||||
DEBUG: bool = False
|
DEBUG: bool = False
|
||||||
TIME_COLOR_SCALE: List[Color] = [
|
TIME_COLOR_SCALE: List[Color] = [
|
||||||
|
@ -27,31 +28,35 @@ TIME_COLOR_SCALE: List[Color] = [
|
||||||
|
|
||||||
def is_clockwise(polygon: List[OSMNode]) -> bool:
|
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
|
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
|
next_index: int = 0 if index == len(polygon) - 1 else index + 1
|
||||||
count += (
|
count += (
|
||||||
(polygon[next_index].coordinates[0] -
|
(polygon[next_index].coordinates[0] - node.coordinates[0]) *
|
||||||
polygon[index].coordinates[0]) *
|
(polygon[next_index].coordinates[1] + node.coordinates[1]))
|
||||||
(polygon[next_index].coordinates[1] +
|
|
||||||
polygon[index].coordinates[1]))
|
|
||||||
return count >= 0
|
return count >= 0
|
||||||
|
|
||||||
|
|
||||||
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
||||||
if is_clockwise(polygon):
|
"""
|
||||||
return polygon
|
Make polygon nodes clockwise.
|
||||||
else:
|
|
||||||
return list(reversed(polygon))
|
: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]:
|
def make_counter_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
||||||
if not is_clockwise(polygon):
|
"""
|
||||||
return polygon
|
Make polygon nodes counter-clockwise.
|
||||||
else:
|
|
||||||
return list(reversed(polygon))
|
:param polygon: list of OpenStreetMap nodes
|
||||||
|
"""
|
||||||
|
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
|
||||||
|
|
||||||
|
|
||||||
class Point(Tagged):
|
class Point(Tagged):
|
||||||
|
@ -74,11 +79,6 @@ class Point(Tagged):
|
||||||
self.layer: float = 0
|
self.layer: float = 0
|
||||||
self.is_for_node: bool = is_for_node
|
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):
|
class Figure(Tagged):
|
||||||
"""
|
"""
|
||||||
|
@ -107,6 +107,7 @@ class Figure(Tagged):
|
||||||
"""
|
"""
|
||||||
Get SVG path commands.
|
Get SVG path commands.
|
||||||
|
|
||||||
|
:param flinger: convertor for geo coordinates
|
||||||
:param shift: shift vector
|
:param shift: shift vector
|
||||||
"""
|
"""
|
||||||
path: str = ""
|
path: str = ""
|
||||||
|
@ -121,6 +122,9 @@ class Figure(Tagged):
|
||||||
|
|
||||||
|
|
||||||
class Segment:
|
class Segment:
|
||||||
|
"""
|
||||||
|
Line segment.
|
||||||
|
"""
|
||||||
def __init__(self, point_1: np.array, point_2: np.array):
|
def __init__(self, point_1: np.array, point_2: np.array):
|
||||||
self.point_1 = point_1
|
self.point_1 = point_1
|
||||||
self.point_2 = point_2
|
self.point_2 = point_2
|
||||||
|
@ -128,7 +132,7 @@ class Segment:
|
||||||
difference: np.array = point_2 - point_1
|
difference: np.array = point_2 - point_1
|
||||||
vector: np.array = difference / np.linalg.norm(difference)
|
vector: np.array = difference / np.linalg.norm(difference)
|
||||||
self.angle: float = (
|
self.angle: float = (
|
||||||
np.arccos(np.dot(vector, np.array((0, 1)))) / np.pi)
|
np.arccos(np.dot(vector, np.array((0, 1)))) / np.pi)
|
||||||
|
|
||||||
def __lt__(self, other: "Segment"):
|
def __lt__(self, other: "Segment"):
|
||||||
return (((self.point_1 + self.point_2) / 2)[1] <
|
return (((self.point_1 + self.point_2) / 2)[1] <
|
||||||
|
@ -142,6 +146,7 @@ class Building(Figure):
|
||||||
def __init__(
|
def __init__(
|
||||||
self, tags: Dict[str, str], inners, outers, flinger: Flinger,
|
self, tags: Dict[str, str], inners, outers, flinger: Flinger,
|
||||||
style: Dict[str, Any], layer: float):
|
style: Dict[str, Any], layer: float):
|
||||||
|
|
||||||
super().__init__(tags, inners, outers, style, layer)
|
super().__init__(tags, inners, outers, style, layer)
|
||||||
|
|
||||||
self.parts = []
|
self.parts = []
|
||||||
|
@ -154,23 +159,24 @@ class Building(Figure):
|
||||||
|
|
||||||
self.parts = sorted(self.parts)
|
self.parts = sorted(self.parts)
|
||||||
|
|
||||||
|
def get_levels(self) -> float:
|
||||||
def get_levels(self):
|
"""
|
||||||
|
Get building level number.
|
||||||
|
"""
|
||||||
try:
|
try:
|
||||||
return max(3, float(self.get_tag("building:levels")))
|
return max(3.0, float(self.get_tag("building:levels")))
|
||||||
except (ValueError, TypeError):
|
except (ValueError, TypeError):
|
||||||
return 3
|
return 3
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class TextStruct:
|
class TextStruct:
|
||||||
"""
|
"""
|
||||||
Some label on the map with attributes.
|
Some label on the map with attributes.
|
||||||
"""
|
"""
|
||||||
def __init__(
|
text: str
|
||||||
self, text: str, fill: Color = Color("#444444"), size: float = 10):
|
fill: Color = Color("#444444")
|
||||||
self.text = text
|
size: float = 10
|
||||||
self.fill = fill
|
|
||||||
self.size = size
|
|
||||||
|
|
||||||
|
|
||||||
def line_center(nodes: List[OSMNode], flinger: Flinger) -> np.array:
|
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:
|
def get_time_color(time: Optional[datetime], boundaries: MinMax) -> Color:
|
||||||
"""
|
"""
|
||||||
Generate color based on time.
|
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)
|
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:
|
def get_path(nodes: List[OSMNode], shift: np.array, flinger: Flinger) -> str:
|
||||||
"""
|
"""
|
||||||
Construct SVG path from nodes.
|
Construct SVG path commands from nodes.
|
||||||
"""
|
"""
|
||||||
path = ""
|
path: str = ""
|
||||||
prev_node = None
|
prev_node: Optional[OSMNode] = None
|
||||||
for node in nodes:
|
for node in nodes: # type: OSMNode
|
||||||
flung = flinger.fling(node.coordinates) + shift
|
flung = flinger.fling(node.coordinates) + shift
|
||||||
path += ("L" if prev_node else "M") + f" {flung[0]},{flung[1]} "
|
path += ("L" if prev_node else "M") + f" {flung[0]},{flung[1]} "
|
||||||
prev_node = node
|
prev_node = node
|
||||||
|
@ -280,11 +289,14 @@ class Constructor:
|
||||||
|
|
||||||
self.levels: Set[float] = {0.5, 1}
|
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.buildings.append(building)
|
||||||
self.levels.add(building.get_levels())
|
self.levels.add(building.get_levels())
|
||||||
|
|
||||||
def construct_ways(self):
|
def construct_ways(self) -> None:
|
||||||
"""
|
"""
|
||||||
Construct Röntgen ways.
|
Construct Röntgen ways.
|
||||||
"""
|
"""
|
||||||
|
@ -303,33 +315,22 @@ class Constructor:
|
||||||
|
|
||||||
def construct_way(
|
def construct_way(
|
||||||
self, way: Optional[OSMWay], tags: Dict[str, Any],
|
self, way: Optional[OSMWay], tags: Dict[str, Any],
|
||||||
inners, outers) -> None:
|
inners: List[List[OSMNode]], outers: List[List[OSMNode]]) -> None:
|
||||||
"""
|
"""
|
||||||
Way construction.
|
Way construction.
|
||||||
|
|
||||||
:param way: OSM way
|
:param way: OSM way
|
||||||
:param tags: way tag dictionary
|
: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
|
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
|
center_point, center_coordinates = None, None
|
||||||
|
|
||||||
if way:
|
if way is not None:
|
||||||
center_point, center_coordinates = \
|
center_point, center_coordinates = (
|
||||||
line_center(way.nodes, self.flinger)
|
line_center(way.nodes, self.flinger))
|
||||||
nodes = way.nodes
|
|
||||||
|
|
||||||
if self.mode == "user-coloring":
|
if self.mode == "user-coloring":
|
||||||
if not way:
|
if not way:
|
||||||
|
@ -417,8 +418,8 @@ class Constructor:
|
||||||
"stroke-width": 1}
|
"stroke-width": 1}
|
||||||
self.figures.append(Figure(
|
self.figures.append(Figure(
|
||||||
tags, inners, outers, style, layer))
|
tags, inners, outers, style, layer))
|
||||||
if center_point is not None and (way.is_cycle() or
|
if (center_point is not None and
|
||||||
"area" in tags and tags["area"]):
|
way.is_cycle() and "area" in tags and tags["area"]):
|
||||||
icon_set: IconSet = self.scheme.get_icon(tags)
|
icon_set: IconSet = self.scheme.get_icon(tags)
|
||||||
self.nodes.append(Point(
|
self.nodes.append(Point(
|
||||||
icon_set, tags, center_point, center_coordinates,
|
icon_set, tags, center_point, center_coordinates,
|
||||||
|
@ -480,7 +481,7 @@ class Constructor:
|
||||||
if self.mode == "user-coloring":
|
if self.mode == "user-coloring":
|
||||||
icon_set.color = get_user_color(node.user, self.seed)
|
icon_set.color = get_user_color(node.user, self.seed)
|
||||||
if self.mode == "time":
|
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))
|
self.nodes.append(Point(icon_set, tags, flung, node.coordinates))
|
||||||
|
|
||||||
|
|
|
@ -43,7 +43,7 @@ def parse_vector(text: str) -> Optional[np.array]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def rotation_matrix(angle):
|
def rotation_matrix(angle) -> np.array:
|
||||||
"""
|
"""
|
||||||
Get a matrix to rotate 2D vector by the angle.
|
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]
|
return ["L", start, "A", radius, radius, 0, "0", 0, end]
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return f"{self.start}-{self.end}"
|
return f"{self.start}-{self.end}"
|
||||||
|
|
||||||
|
|
||||||
|
@ -115,7 +115,7 @@ class DirectionSet:
|
||||||
"""
|
"""
|
||||||
self.sectors: Iterator[Optional[Sector]] = map(Sector, text.split(";"))
|
self.sectors: Iterator[Optional[Sector]] = map(Sector, text.split(";"))
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self) -> str:
|
||||||
return ", ".join(map(str, self.sectors))
|
return ", ".join(map(str, self.sectors))
|
||||||
|
|
||||||
def draw(self, center: np.array, radius: float) -> Iterator[List[Path]]:
|
def draw(self, center: np.array, radius: float) -> Iterator[List[Path]]:
|
||||||
|
|
|
@ -6,6 +6,7 @@ Author: Sergey Vartanov (me@enzet.ru).
|
||||||
import re
|
import re
|
||||||
import xml.dom.minidom
|
import xml.dom.minidom
|
||||||
from typing import Dict
|
from typing import Dict
|
||||||
|
from xml.dom.minidom import Element, Node
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from svgwrite import Drawing
|
from svgwrite import Drawing
|
||||||
|
@ -14,6 +15,7 @@ from roentgen import ui
|
||||||
|
|
||||||
DEFAULT_SHAPE_ID: str = "default"
|
DEFAULT_SHAPE_ID: str = "default"
|
||||||
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
|
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
|
||||||
|
STANDARD_INKSCAPE_ID: str = "(path|rect)\\d*"
|
||||||
|
|
||||||
GRID_STEP: int = 16
|
GRID_STEP: int = 16
|
||||||
|
|
||||||
|
@ -71,25 +73,26 @@ class IconExtractor:
|
||||||
content = xml.dom.minidom.parse(input_file)
|
content = xml.dom.minidom.parse(input_file)
|
||||||
for element in content.childNodes:
|
for element in content.childNodes:
|
||||||
if element.nodeName == "svg":
|
if element.nodeName == "svg":
|
||||||
for node in element.childNodes:
|
for node in element.childNodes: # type: Node
|
||||||
if node.nodeName in ["g", "path"]:
|
if isinstance(node, Element):
|
||||||
self.parse(node)
|
self.parse(node)
|
||||||
|
|
||||||
def parse(self, node) -> None:
|
def parse(self, node: Element) -> None:
|
||||||
"""
|
"""
|
||||||
Extract icon paths into a map.
|
Extract icon paths into a map.
|
||||||
|
|
||||||
:param node: XML node that contains icon
|
:param node: XML node that contains icon
|
||||||
"""
|
"""
|
||||||
if node.nodeName != "path":
|
if node.nodeName == "g":
|
||||||
for sub_node in node.childNodes:
|
for sub_node in node.childNodes:
|
||||||
self.parse(sub_node)
|
if isinstance(sub_node, Element):
|
||||||
|
self.parse(sub_node)
|
||||||
return
|
return
|
||||||
|
|
||||||
if "id" in node.attributes.keys() and \
|
if ("id" in node.attributes.keys() and
|
||||||
"d" in node.attributes.keys() and \
|
"d" in node.attributes.keys() and
|
||||||
node.attributes["id"].value:
|
node.attributes["id"].value):
|
||||||
path = node.attributes["d"].value
|
path: str = node.attributes["d"].value
|
||||||
matcher = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
|
matcher = re.match("[Mm] ([0-9.e-]*)[, ]([0-9.e-]*)", path)
|
||||||
if not matcher:
|
if not matcher:
|
||||||
ui.error(f"invalid path: {path}")
|
ui.error(f"invalid path: {path}")
|
||||||
|
@ -104,7 +107,9 @@ class IconExtractor:
|
||||||
get_offset(float(matcher.group(2)))))
|
get_offset(float(matcher.group(2)))))
|
||||||
|
|
||||||
id_: str = node.attributes["id"].value
|
id_: str = node.attributes["id"].value
|
||||||
self.icons[id_] = Icon(node.attributes["d"].value, point, id_)
|
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):
|
def get_path(self, id_: str) -> (Icon, bool):
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -37,15 +37,15 @@ class Flinger:
|
||||||
"""
|
"""
|
||||||
Convert geo coordinates into SVG position points.
|
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 geo_boundaries: minimum and maximum latitude and longitude
|
||||||
:param scale: OSM zoom level
|
:param scale: OSM zoom level
|
||||||
"""
|
"""
|
||||||
self.geo_boundaries: MinMax = geo_boundaries
|
self.geo_boundaries: MinMax = geo_boundaries
|
||||||
self.ratio: float = (
|
self.ratio: float = (
|
||||||
osm_zoom_level_to_pixels_per_meter(scale) *
|
osm_zoom_level_to_pixels_per_meter(scale) *
|
||||||
EQUATOR_LENGTH / 360)
|
EQUATOR_LENGTH / 360)
|
||||||
self.size: np.array = self.ratio * (
|
self.size: np.array = self.ratio * (
|
||||||
pseudo_mercator(self.geo_boundaries.max_) -
|
pseudo_mercator(self.geo_boundaries.max_) -
|
||||||
pseudo_mercator(self.geo_boundaries.min_))
|
pseudo_mercator(self.geo_boundaries.min_))
|
||||||
|
|
|
@ -5,11 +5,10 @@ Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from svgwrite import Drawing
|
from svgwrite import Drawing
|
||||||
import yaml
|
from typing import List, Dict, Any, Set
|
||||||
|
|
||||||
from roentgen.extract_icon import Icon, IconExtractor
|
from roentgen.extract_icon import Icon, IconExtractor
|
||||||
|
from roentgen.scheme import Scheme
|
||||||
from typing import List
|
|
||||||
|
|
||||||
|
|
||||||
def draw_grid(step: float = 24, columns: int = 16):
|
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 step: horizontal and vertical distance between icons
|
||||||
:param columns: the number of columns in grid
|
: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"
|
icons_file_name: str = "icons/icons.svg"
|
||||||
icon_grid_file_name = "icon_grid.svg"
|
icon_grid_file_name: str = "icon_grid.svg"
|
||||||
|
|
||||||
width: float = step * columns
|
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: List[Set[str]] = []
|
||||||
|
|
||||||
for element in scheme["nodes"]:
|
for element in scheme.nodes: # type: Dict[str, Any]
|
||||||
if "icon" in element:
|
if "icon" in element:
|
||||||
if set(element["icon"]) not in to_draw:
|
if set(element["icon"]) not in to_draw:
|
||||||
to_draw.append(set(element["icon"]))
|
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:
|
if "over_icon" not in element:
|
||||||
continue
|
continue
|
||||||
if "under_icon" in element:
|
if "under_icon" in element:
|
||||||
for icon in element["under_icon"]:
|
for icon_id in element["under_icon"]: # type: str
|
||||||
current_set = set([icon] + element["over_icon"])
|
current_set = set([icon_id] + element["over_icon"])
|
||||||
if current_set not in to_draw:
|
if current_set not in to_draw:
|
||||||
to_draw.append(current_set)
|
to_draw.append(current_set)
|
||||||
if not ("under_icon" in element and "with_icon" in element):
|
if not ("under_icon" in element and "with_icon" in element):
|
||||||
continue
|
continue
|
||||||
for icon in element["under_icon"]:
|
for icon_id in element["under_icon"]: # type: str
|
||||||
for icon2 in element["with_icon"]:
|
for icon_2_id in element["with_icon"]: # type: str
|
||||||
current_set = set([icon] + [icon2] + element["over_icon"])
|
current_set: Set[str] = set(
|
||||||
|
[icon_id] + [icon_2_id] + element["over_icon"])
|
||||||
if current_set not in to_draw:
|
if current_set not in to_draw:
|
||||||
to_draw.append(current_set)
|
to_draw.append(current_set)
|
||||||
for icon2 in element["with_icon"]:
|
for icon_2_id in element["with_icon"]: # type: str
|
||||||
for icon3 in element["with_icon"]:
|
for icon_3_id in element["with_icon"]: # type: str
|
||||||
current_set = \
|
current_set = set(
|
||||||
set([icon] + [icon2] + [icon3] + element["over_icon"])
|
[icon_id] + [icon_2_id] + [icon_3_id] +
|
||||||
if icon2 != icon3 and icon2 != icon and icon3 != icon and \
|
element["over_icon"])
|
||||||
(current_set not in to_draw):
|
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)
|
to_draw.append(current_set)
|
||||||
|
|
||||||
number: int = 0
|
number: int = 0
|
||||||
|
@ -70,8 +72,8 @@ def draw_grid(step: float = 24, columns: int = 16):
|
||||||
found: bool = False
|
found: bool = False
|
||||||
icon_set: List[Icon] = []
|
icon_set: List[Icon] = []
|
||||||
for icon_id in icons_to_draw: # type: str
|
for icon_id in icons_to_draw: # type: str
|
||||||
icon, got = extractor.get_path(icon_id)
|
icon, extracted = extractor.get_path(icon_id) # type: Icon, bool
|
||||||
assert got
|
assert extracted, f"no icon with ID {icon_id}"
|
||||||
icon_set.append(icon)
|
icon_set.append(icon)
|
||||||
found = True
|
found = True
|
||||||
if found:
|
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"))
|
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"
|
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=background_color))
|
fill=background_color))
|
||||||
for i in icon: # type: Icon
|
for icon in combined_icon: # type: Icon
|
||||||
path = i.get_path(svg, point)
|
path = icon.get_path(svg, point)
|
||||||
path.update({"fill": foreground_color})
|
path.update({"fill": foreground_color})
|
||||||
svg.add(path)
|
svg.add(path)
|
||||||
point += np.array((step, 0))
|
point += np.array((step, 0))
|
||||||
|
|
|
@ -3,6 +3,8 @@ Simple OpenStreetMap renderer.
|
||||||
|
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
|
import argparse
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import os
|
import os
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
@ -17,7 +19,8 @@ from typing import Any, Dict, List, Optional
|
||||||
|
|
||||||
from roentgen import ui
|
from roentgen import ui
|
||||||
from roentgen.address import get_address
|
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.flinger import Flinger
|
||||||
from roentgen.grid import draw_grid
|
from roentgen.grid import draw_grid
|
||||||
from roentgen.extract_icon import Icon, IconExtractor
|
from roentgen.extract_icon import Icon, IconExtractor
|
||||||
|
@ -150,7 +153,7 @@ class Painter:
|
||||||
text, point, font_size=size, text_anchor="middle",
|
text, point, font_size=size, text_anchor="middle",
|
||||||
font_family=DEFAULT_FONT, fill=fill.hex))
|
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.
|
Construct labels for not processed tags.
|
||||||
"""
|
"""
|
||||||
|
@ -185,7 +188,7 @@ class Painter:
|
||||||
alt_name = ""
|
alt_name = ""
|
||||||
alt_name += "бывш. " + tags["old_name"]
|
alt_name += "бывш. " + tags["old_name"]
|
||||||
|
|
||||||
address = get_address(tags, self.draw_captions)
|
address: List[str] = get_address(tags, self.draw_captions)
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
texts.append(TextStruct(name, Color("black")))
|
texts.append(TextStruct(name, Color("black")))
|
||||||
|
@ -233,11 +236,11 @@ class Painter:
|
||||||
ways_length: int = len(ways)
|
ways_length: int = len(ways)
|
||||||
for index, way in enumerate(ways): # type: Figure
|
for index, way in enumerate(ways): # type: Figure
|
||||||
ui.progress_bar(index, ways_length, step=10, text="Drawing ways")
|
ui.progress_bar(index, ways_length, step=10, text="Drawing ways")
|
||||||
path: str = way.get_path(self.flinger)
|
path_commands: str = way.get_path(self.flinger)
|
||||||
if path:
|
if path_commands:
|
||||||
p = Path(d=path)
|
path = Path(d=path_commands)
|
||||||
p.update(way.style)
|
path.update(way.style)
|
||||||
self.svg.add(p)
|
self.svg.add(path)
|
||||||
ui.progress_bar(-1, 0, text="Drawing ways")
|
ui.progress_bar(-1, 0, text="Drawing ways")
|
||||||
|
|
||||||
# Draw building shade.
|
# Draw building shade.
|
||||||
|
@ -262,15 +265,18 @@ class Painter:
|
||||||
|
|
||||||
previous_level: float = 0
|
previous_level: float = 0
|
||||||
height: float = self.flinger.get_scale()
|
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()
|
fill: Color()
|
||||||
for way in constructor.buildings: # type: Building
|
for way in constructor.buildings: # type: Building
|
||||||
if way.get_levels() < level:
|
if way.get_levels() < level:
|
||||||
continue
|
continue
|
||||||
shift_1 = [0, -previous_level * height]
|
shift_1 = [0, -previous_level * height]
|
||||||
shift_2 = [0, -level * height]
|
shift_2 = [0, -level * height]
|
||||||
for segment in way.parts:
|
for segment in way.parts: # type: Segment
|
||||||
if level == 0.5:
|
if level == 0.5:
|
||||||
fill = Color("#AAAAAA")
|
fill = Color("#AAAAAA")
|
||||||
elif level == 1:
|
elif level == 1:
|
||||||
|
@ -280,26 +286,29 @@ class Painter:
|
||||||
fill = Color(rgb=(color_part, color_part, color_part))
|
fill = Color(rgb=(color_part, color_part, color_part))
|
||||||
|
|
||||||
self.svg.add(self.svg.path(
|
self.svg.add(self.svg.path(
|
||||||
d=("M", np.add(segment.point_1, shift_1), "L",
|
d=("M", segment.point_1 + shift_1, "L",
|
||||||
np.add(segment.point_2, shift_1),
|
segment.point_2 + shift_1,
|
||||||
np.add(segment.point_2, shift_2),
|
segment.point_2 + shift_2,
|
||||||
np.add(segment.point_1, shift_2),
|
segment.point_1 + shift_2,
|
||||||
np.add(segment.point_1, shift_1), "Z"),
|
segment.point_1 + shift_1, "Z"),
|
||||||
fill=fill.hex, stroke=fill.hex, stroke_width=1,
|
fill=fill.hex, stroke=fill.hex, stroke_width=1,
|
||||||
stroke_linejoin="round"))
|
stroke_linejoin="round"))
|
||||||
|
|
||||||
# Draw building roof.
|
# Draw building roofs.
|
||||||
|
|
||||||
|
for way in constructor.buildings: # type: Building
|
||||||
if way.get_levels() == level:
|
if way.get_levels() == level:
|
||||||
shift = np.array([0, -way.get_levels() * height])
|
shift = np.array([0, -way.get_levels() * height])
|
||||||
path: str = way.get_path(self.flinger, shift)
|
path_commands: str = way.get_path(self.flinger, shift)
|
||||||
p = Path(d=path, opacity=1)
|
path = Path(d=path_commands, opacity=1)
|
||||||
p.update(way.style)
|
path.update(way.style)
|
||||||
p.update({"stroke-linejoin": "round"})
|
path.update({"stroke-linejoin": "round"})
|
||||||
self.svg.add(p)
|
self.svg.add(path)
|
||||||
|
|
||||||
previous_level = level
|
previous_level = level
|
||||||
|
|
||||||
|
ui.progress_bar(-1, level_count, step=1, text="Drawing buildings")
|
||||||
|
|
||||||
# Trees
|
# Trees
|
||||||
|
|
||||||
for node in constructor.nodes:
|
for node in constructor.nodes:
|
||||||
|
@ -412,7 +421,7 @@ class Painter:
|
||||||
self, icon: Icon, point: (float, float), fill: Color,
|
self, icon: Icon, point: (float, float), fill: Color,
|
||||||
tags: Dict[str, str] = None) -> None:
|
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))
|
title: str = "\n".join(map(lambda x: x + ": " + tags[x], tags))
|
||||||
|
|
||||||
path: svgwrite.path.Path = icon.get_path(self.svg, point)
|
path: svgwrite.path.Path = icon.get_path(self.svg, point)
|
||||||
|
@ -423,7 +432,7 @@ class Painter:
|
||||||
def draw_point_outline(
|
def draw_point_outline(
|
||||||
self, icon: Icon, point, fill: Color, mode="default"):
|
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
|
opacity: float = 0.5
|
||||||
stroke_width: float = 2.2
|
stroke_width: float = 2.2
|
||||||
|
@ -479,13 +488,13 @@ def check_level_overground(tags: Dict[str, Any]):
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def main(argv):
|
def main(argv) -> None:
|
||||||
if len(argv) == 2:
|
if len(argv) == 2:
|
||||||
if argv[1] == "grid":
|
if argv[1] == "grid":
|
||||||
draw_grid()
|
draw_grid()
|
||||||
return
|
return
|
||||||
|
|
||||||
options = ui.parse_options(argv)
|
options: argparse.Namespace = ui.parse_options(argv)
|
||||||
|
|
||||||
if not options:
|
if not options:
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
|
@ -570,9 +579,6 @@ def main(argv):
|
||||||
scheme=scheme)
|
scheme=scheme)
|
||||||
painter.draw(constructor, points)
|
painter.draw(constructor, points)
|
||||||
|
|
||||||
if options.show_index:
|
|
||||||
draw_index(flinger, map_, max1, min1, svg)
|
|
||||||
|
|
||||||
print("Writing output SVG...")
|
print("Writing output SVG...")
|
||||||
svg.write(open(options.output_file_name, "w"))
|
svg.write(open(options.output_file_name, "w"))
|
||||||
print("Done.")
|
print("Done.")
|
||||||
|
@ -584,48 +590,3 @@ def main(argv):
|
||||||
missing_tags_file.write(
|
missing_tags_file.write(
|
||||||
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
|
f'- {{tag: "{tag}", count: {missing_tags[tag]}}}\n')
|
||||||
missing_tags_file.close()
|
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"))
|
|
||||||
|
|
|
@ -6,11 +6,11 @@ Author: Sergey Vartanov (me@enzet.ru).
|
||||||
import os
|
import os
|
||||||
import re
|
import re
|
||||||
import time
|
import time
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
import urllib
|
import urllib
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
from typing import Dict, Optional
|
|
||||||
|
|
||||||
from roentgen.ui import error
|
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):
|
if not to_update and os.path.isfile(result_file_name):
|
||||||
return open(result_file_name).read()
|
return open(result_file_name).read()
|
||||||
|
|
||||||
matcher = re.match("(?P<left>[0-9.-]*),(?P<bottom>[0-9.-]*)," +
|
matcher = re.match(
|
||||||
"(?P<right>[0-9.-]*),(?P<top>[0-9.-]*)", boundary_box)
|
"(?P<left>[0-9.-]*),(?P<bottom>[0-9.-]*)," +
|
||||||
|
"(?P<right>[0-9.-]*),(?P<top>[0-9.-]*)",
|
||||||
|
boundary_box)
|
||||||
|
|
||||||
if not matcher:
|
if not matcher:
|
||||||
error("invalid boundary box")
|
error("invalid boundary box")
|
||||||
return
|
return None
|
||||||
|
|
||||||
try:
|
try:
|
||||||
left = float(matcher.group("left"))
|
left = float(matcher.group("left"))
|
||||||
|
@ -40,20 +42,21 @@ def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
|
||||||
top = float(matcher.group("top"))
|
top = float(matcher.group("top"))
|
||||||
except ValueError:
|
except ValueError:
|
||||||
error("parsing boundary box")
|
error("parsing boundary box")
|
||||||
return
|
return None
|
||||||
|
|
||||||
if left >= right:
|
if left >= right:
|
||||||
error("negative horizontal boundary")
|
error("negative horizontal boundary")
|
||||||
return
|
return None
|
||||||
if bottom >= top:
|
if bottom >= top:
|
||||||
error("negative vertical boundary")
|
error("negative vertical boundary")
|
||||||
return
|
return None
|
||||||
if right - left > 0.5 or top - bottom > 0.5:
|
if right - left > 0.5 or top - bottom > 0.5:
|
||||||
error("box too big")
|
error("box too big")
|
||||||
return
|
return None
|
||||||
|
|
||||||
content = get_data("api.openstreetmap.org/api/0.6/map",
|
content = get_data(
|
||||||
{"bbox": boundary_box}, is_secure=True)
|
"api.openstreetmap.org/api/0.6/map",
|
||||||
|
{"bbox": boundary_box}, is_secure=True)
|
||||||
|
|
||||||
open(result_file_name, "w+").write(content.decode("utf-8"))
|
open(result_file_name, "w+").write(content.decode("utf-8"))
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,8 @@ Reading OpenStreetMap data from XML file.
|
||||||
|
|
||||||
Author: Sergey Vartanov (me@enzet.ru).
|
Author: Sergey Vartanov (me@enzet.ru).
|
||||||
"""
|
"""
|
||||||
|
from dataclasses import dataclass
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from typing import Dict, List, Optional, Set, Union
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
@ -115,20 +117,21 @@ class OSMWay(Tagged):
|
||||||
"""
|
"""
|
||||||
return self.nodes[0] == self.nodes[-1]
|
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.
|
Create new combined way if ways share endpoints.
|
||||||
"""
|
"""
|
||||||
if self.nodes[0] == other.nodes[0]:
|
if self.nodes[0] == other.nodes[0]:
|
||||||
return OSMWay(nodes=list(reversed(other.nodes[1:])) + self.nodes)
|
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)
|
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])))
|
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 OSMWay(nodes=self.nodes + other.nodes[1:])
|
||||||
|
return None
|
||||||
|
|
||||||
def __repr__(self):
|
def __repr__(self) -> str:
|
||||||
return f"Way <{self.id_}> {self.nodes}"
|
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)]
|
value = text[end_index:text.find('"', end_index)]
|
||||||
return value
|
return value
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
class Map:
|
class Map:
|
||||||
"""
|
"""
|
||||||
|
@ -243,7 +248,7 @@ class OSMReader:
|
||||||
line = line.strip()
|
line = line.strip()
|
||||||
|
|
||||||
line_number += 1
|
line_number += 1
|
||||||
progress_bar(line_number, lines_number)
|
progress_bar(line_number, lines_number, text="Parsing")
|
||||||
|
|
||||||
# Node parsing.
|
# Node parsing.
|
||||||
|
|
||||||
|
@ -251,8 +256,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[-2] == "/":
|
if line[-2] == "/":
|
||||||
node: OSMNode = OSMNode().parse_from_xml(line, full)
|
node: OSMNode = OSMNode().parse_from_xml(line, full)
|
||||||
self.map_.add_node(node)
|
self.map_.add_node(node)
|
||||||
|
@ -267,8 +271,7 @@ class OSMReader:
|
||||||
if not parse_ways:
|
if not parse_ways:
|
||||||
if parse_relations:
|
if parse_relations:
|
||||||
continue
|
continue
|
||||||
else:
|
break
|
||||||
break
|
|
||||||
if line[-2] == "/":
|
if line[-2] == "/":
|
||||||
way = OSMWay().parse_from_xml(line, full)
|
way = OSMWay().parse_from_xml(line, full)
|
||||||
self.map_.add_way(way)
|
self.map_.add_way(way)
|
||||||
|
@ -293,15 +296,15 @@ class OSMReader:
|
||||||
# Elements parsing.
|
# Elements parsing.
|
||||||
|
|
||||||
elif line.startswith("<tag"):
|
elif line.startswith("<tag"):
|
||||||
k = get_value("k", line)
|
key: str = get_value("k", line)
|
||||||
v = get_value("v", line)
|
value = get_value("v", line)
|
||||||
element.tags[k] = v
|
element.tags[key] = value
|
||||||
elif line.startswith("<nd"):
|
elif line.startswith("<nd"):
|
||||||
element.nodes.append(
|
element.nodes.append(
|
||||||
self.map_.node_map[int(get_value("ref", line))])
|
self.map_.node_map[int(get_value("ref", line))])
|
||||||
elif line.startswith("<member"):
|
elif line.startswith("<member"):
|
||||||
element.members.append(OSMMember(line))
|
element.members.append(OSMMember(line))
|
||||||
|
|
||||||
progress_bar(-1, lines_number) # Complete progress bar.
|
progress_bar(-1, lines_number, text="Parsing")
|
||||||
|
|
||||||
return self.map_
|
return self.map_
|
||||||
|
|
|
@ -7,6 +7,7 @@ import copy
|
||||||
import yaml
|
import yaml
|
||||||
|
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
from dataclasses import dataclass
|
||||||
from typing import Any, Dict, List, Optional, Set
|
from typing import Any, Dict, List, Optional, Set
|
||||||
|
|
||||||
from roentgen.extract_icon import DEFAULT_SHAPE_ID
|
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")
|
DEFAULT_COLOR: Color = Color("#444444")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass
|
||||||
class IconSet:
|
class IconSet:
|
||||||
"""
|
"""
|
||||||
Node representation: icons and color.
|
Node representation: icons and color.
|
||||||
"""
|
"""
|
||||||
def __init__(
|
icons: List[List[str]] # list of lists of shape identifiers
|
||||||
self, icons: List[List[str]], color: Color, processed: Set[str],
|
color: Color # fill color of all icons
|
||||||
is_default: bool):
|
# tag keys that were processed to create icon set (other
|
||||||
"""
|
# tag keys should be displayed by text or ignored)
|
||||||
:param icons: list of lists of shape identifiers
|
processed: Set[str]
|
||||||
:param color: fill color of all icons
|
is_default: bool
|
||||||
: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
|
|
||||||
|
|
||||||
|
|
||||||
class Scheme:
|
class Scheme:
|
||||||
|
@ -44,8 +39,9 @@ class Scheme:
|
||||||
:param file_name: scheme file name with tags, colors, and tag key
|
:param file_name: scheme file name with tags, colors, and tag key
|
||||||
specification
|
specification
|
||||||
"""
|
"""
|
||||||
content: Dict[str, Any] = \
|
with open(file_name) as input_file:
|
||||||
yaml.load(open(file_name).read(), Loader=yaml.FullLoader)
|
content: Dict[str, Any] = yaml.load(
|
||||||
|
input_file.read(), Loader=yaml.FullLoader)
|
||||||
|
|
||||||
self.nodes: List[Dict[str, Any]] = content["nodes"]
|
self.nodes: List[Dict[str, Any]] = content["nodes"]
|
||||||
self.ways: List[Dict[str, Any]] = content["ways"]
|
self.ways: List[Dict[str, Any]] = content["ways"]
|
||||||
|
|
|
@ -10,7 +10,7 @@ BOXES: List[str] = [" ", "▏", "▎", "▍", "▌", "▋", "▊", "▉"]
|
||||||
BOXES_LENGTH: int = len(BOXES)
|
BOXES_LENGTH: int = len(BOXES)
|
||||||
|
|
||||||
|
|
||||||
def parse_options(args):
|
def parse_options(args) -> argparse.Namespace:
|
||||||
"""
|
"""
|
||||||
Parse Röntgen command-line options.
|
Parse Röntgen command-line options.
|
||||||
"""
|
"""
|
||||||
|
@ -70,14 +70,6 @@ def parse_options(args):
|
||||||
dest="overlap",
|
dest="overlap",
|
||||||
default=12,
|
default=12,
|
||||||
type=int)
|
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(
|
parser.add_argument(
|
||||||
"--mode",
|
"--mode",
|
||||||
default="normal")
|
default="normal")
|
||||||
|
@ -88,7 +80,7 @@ def parse_options(args):
|
||||||
"--level",
|
"--level",
|
||||||
default=None)
|
default=None)
|
||||||
|
|
||||||
arguments = parser.parse_args(args[1:])
|
arguments: argparse.Namespace = parser.parse_args(args[1:])
|
||||||
|
|
||||||
if arguments.boundary_box:
|
if arguments.boundary_box:
|
||||||
arguments.boundary_box = arguments.boundary_box.replace(" ", "")
|
arguments.boundary_box = arguments.boundary_box.replace(" ", "")
|
||||||
|
|
|
@ -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:
|
class MinMax:
|
||||||
"""
|
"""
|
||||||
Minimum and maximum.
|
Minimum and maximum.
|
||||||
"""
|
"""
|
||||||
def __init__(self, min_=None, max_=None):
|
min_: Any = None
|
||||||
self.min_ = min_
|
max_: Any = None
|
||||||
self.max_ = max_
|
|
||||||
|
|
||||||
def update(self, value):
|
def update(self, value: Any) -> None:
|
||||||
"""
|
"""
|
||||||
Update minimum and maximum with new value.
|
Update minimum and maximum with new 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_
|
||||||
|
|
||||||
def delta(self):
|
def delta(self) -> Any:
|
||||||
"""
|
"""
|
||||||
Difference between maximum and minimum.
|
Difference between maximum and minimum.
|
||||||
"""
|
"""
|
||||||
return self.max_ - self.min_
|
return self.max_ - self.min_
|
||||||
|
|
||||||
def center(self):
|
def center(self) -> Any:
|
||||||
"""
|
"""
|
||||||
Get middle point between minimum and maximum.
|
Get middle point between minimum and maximum.
|
||||||
"""
|
"""
|
||||||
|
|
|
@ -9,22 +9,27 @@ from roentgen.direction import parse_vector
|
||||||
|
|
||||||
|
|
||||||
def test_compass_points_1():
|
def test_compass_points_1():
|
||||||
|
""" Test north direction. """
|
||||||
assert np.allclose(parse_vector("N"), np.array([0, -1]))
|
assert np.allclose(parse_vector("N"), np.array([0, -1]))
|
||||||
|
|
||||||
|
|
||||||
def test_compass_points_2():
|
def test_compass_points_2():
|
||||||
|
""" Test north-west direction. """
|
||||||
root: np.float64 = -np.sqrt(2) / 2
|
root: np.float64 = -np.sqrt(2) / 2
|
||||||
assert np.allclose(parse_vector("NW"), np.array([root, root]))
|
assert np.allclose(parse_vector("NW"), np.array([root, root]))
|
||||||
|
|
||||||
|
|
||||||
def test_compass_points_3():
|
def test_compass_points_3():
|
||||||
|
""" Test south-south-west direction. """
|
||||||
assert np.allclose(
|
assert np.allclose(
|
||||||
parse_vector("SSW"), np.array([-0.38268343, 0.92387953]))
|
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")
|
assert not parse_vector("O")
|
||||||
|
|
||||||
|
|
||||||
def test_degree():
|
def test_degree():
|
||||||
|
""" Test east direction. """
|
||||||
assert np.allclose(parse_vector("90"), np.array([1, 0]))
|
assert np.allclose(parse_vector("90"), np.array([1, 0]))
|
||||||
|
|
|
@ -6,4 +6,5 @@ from roentgen.grid import draw_grid
|
||||||
|
|
||||||
|
|
||||||
def test_icons():
|
def test_icons():
|
||||||
|
""" Test grid drawing. """
|
||||||
draw_grid()
|
draw_grid()
|
||||||
|
|
|
@ -1,3 +1,2 @@
|
||||||
def test_main():
|
def test_main():
|
||||||
assert True
|
assert True
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue