mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-13 17:16:49 +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
|
||||
|
||||
|
||||
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
|
||||
|
|
|
@ -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)])
|
||||
|
|
|
@ -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
|
||||
|
@ -128,7 +132,7 @@ class Segment:
|
|||
difference: np.array = point_2 - point_1
|
||||
vector: np.array = difference / np.linalg.norm(difference)
|
||||
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"):
|
||||
return (((self.point_1 + self.point_2) / 2)[1] <
|
||||
|
@ -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))
|
||||
|
||||
|
|
|
@ -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]]:
|
||||
|
|
|
@ -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:
|
||||
self.parse(sub_node)
|
||||
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,7 +107,9 @@ class IconExtractor:
|
|||
get_offset(float(matcher.group(2)))))
|
||||
|
||||
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):
|
||||
"""
|
||||
|
|
|
@ -37,15 +37,15 @@ 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
|
||||
"""
|
||||
self.geo_boundaries: MinMax = geo_boundaries
|
||||
self.ratio: float = (
|
||||
osm_zoom_level_to_pixels_per_meter(scale) *
|
||||
EQUATOR_LENGTH / 360)
|
||||
osm_zoom_level_to_pixels_per_meter(scale) *
|
||||
EQUATOR_LENGTH / 360)
|
||||
self.size: np.array = self.ratio * (
|
||||
pseudo_mercator(self.geo_boundaries.max_) -
|
||||
pseudo_mercator(self.geo_boundaries.min_))
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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"))
|
||||
|
|
|
@ -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,20 +42,21 @@ 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",
|
||||
{"bbox": boundary_box}, is_secure=True)
|
||||
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"))
|
||||
|
||||
|
|
|
@ -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,8 +256,7 @@ class OSMReader:
|
|||
if not parse_nodes:
|
||||
if parse_ways or parse_relations:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
break
|
||||
if line[-2] == "/":
|
||||
node: OSMNode = OSMNode().parse_from_xml(line, full)
|
||||
self.map_.add_node(node)
|
||||
|
@ -267,8 +271,7 @@ class OSMReader:
|
|||
if not parse_ways:
|
||||
if parse_relations:
|
||||
continue
|
||||
else:
|
||||
break
|
||||
break
|
||||
if line[-2] == "/":
|
||||
way = OSMWay().parse_from_xml(line, full)
|
||||
self.map_.add_way(way)
|
||||
|
@ -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_
|
||||
|
|
|
@ -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"]
|
||||
|
|
|
@ -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(" ", "")
|
||||
|
|
|
@ -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.
|
||||
"""
|
||||
|
|
|
@ -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]))
|
||||
|
|
|
@ -6,4 +6,5 @@ from roentgen.grid import draw_grid
|
|||
|
||||
|
||||
def test_icons():
|
||||
""" Test grid drawing. """
|
||||
draw_grid()
|
||||
|
|
|
@ -1,3 +1,2 @@
|
|||
def test_main():
|
||||
assert True
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue