mirror of
https://github.com/enzet/map-machine.git
synced 2025-05-21 04:56:24 +02:00
Issue #88: backport to Python 3.8.
This commit is contained in:
parent
e9b1b499bf
commit
75128537da
30 changed files with 303 additions and 267 deletions
4
.github/workflows/test.yml
vendored
4
.github/workflows/test.yml
vendored
|
@ -11,10 +11,10 @@ jobs:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v2
|
- uses: actions/checkout@v2
|
||||||
- name: Set up Python 3.9
|
- name: Set up Python 3.8
|
||||||
uses: actions/setup-python@v2
|
uses: actions/setup-python@v2
|
||||||
with:
|
with:
|
||||||
python-version: 3.9
|
python-version: 3.8
|
||||||
- name: Install dependencies
|
- name: Install dependencies
|
||||||
run: |
|
run: |
|
||||||
python -m pip install --upgrade pip
|
python -m pip install --upgrade pip
|
||||||
|
|
|
@ -120,7 +120,7 @@ Every way and node displayed with the random color picked for each author with `
|
||||||
Installation
|
Installation
|
||||||
------------
|
------------
|
||||||
|
|
||||||
Requirements: Python 3.9.
|
Requirements: Python 3.8.
|
||||||
|
|
||||||
To install all packages, run:
|
To install all packages, run:
|
||||||
|
|
||||||
|
|
|
@ -13,5 +13,5 @@ echo "Lint with Flake8..."
|
||||||
flake8 \
|
flake8 \
|
||||||
--max-line-length=80 \
|
--max-line-length=80 \
|
||||||
--ignore=E203,W503,ANN002,ANN003,ANN101,ANN102 \
|
--ignore=E203,W503,ANN002,ANN003,ANN101,ANN102 \
|
||||||
--exclude=work,precommit.py,tests/test_road.py \
|
--exclude=work,precommit.py,tests/test_road.py,python3.8 \
|
||||||
|| { echo "FAIL"; exit 1; }
|
|| { echo "FAIL"; exit 1; }
|
||||||
|
|
|
@ -168,7 +168,7 @@ Every way and node displayed with the random color picked for each author with \
|
||||||
|
|
||||||
\2 {Installation} {installation}
|
\2 {Installation} {installation}
|
||||||
|
|
||||||
Requirements\: Python 3.9/* or higher*/.
|
Requirements\: Python 3.8/* or higher*/.
|
||||||
|
|
||||||
To install all packages, run\:
|
To install all packages, run\:
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Color utility.
|
Color utility.
|
||||||
"""
|
"""
|
||||||
from typing import Any
|
from typing import Any, List
|
||||||
|
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
|
||||||
|
@ -22,7 +22,7 @@ def is_bright(color: Color) -> bool:
|
||||||
|
|
||||||
|
|
||||||
def get_gradient_color(
|
def get_gradient_color(
|
||||||
value: Any, bounds: MinMax, colors: list[Color]
|
value: Any, bounds: MinMax, colors: List[Color]
|
||||||
) -> Color:
|
) -> Color:
|
||||||
"""
|
"""
|
||||||
Get color from the color scale for the value.
|
Get color from the color scale for the value.
|
||||||
|
@ -32,7 +32,7 @@ def get_gradient_color(
|
||||||
:param colors: color scale
|
:param colors: color scale
|
||||||
"""
|
"""
|
||||||
color_length: int = len(colors) - 1
|
color_length: int = len(colors) - 1
|
||||||
scale: list[Color] = colors + [Color("black")]
|
scale: List[Color] = colors + [Color("black")]
|
||||||
|
|
||||||
range_coefficient: float = (
|
range_coefficient: float = (
|
||||||
0 if bounds.is_empty() else (value - bounds.min_) / bounds.delta()
|
0 if bounds.is_empty() else (value - bounds.min_) / bounds.delta()
|
||||||
|
|
|
@ -4,7 +4,7 @@ Construct Map Machine nodes and ways.
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from hashlib import sha256
|
from hashlib import sha256
|
||||||
from typing import Any, Iterator, Optional, Union
|
from typing import Any, Dict, Iterator, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
@ -46,7 +46,7 @@ __author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
DEBUG: bool = False
|
DEBUG: bool = False
|
||||||
TIME_COLOR_SCALE: list[Color] = [
|
TIME_COLOR_SCALE: List[Color] = [
|
||||||
Color("#581845"),
|
Color("#581845"),
|
||||||
Color("#900C3F"),
|
Color("#900C3F"),
|
||||||
Color("#C70039"),
|
Color("#C70039"),
|
||||||
|
@ -57,7 +57,7 @@ TIME_COLOR_SCALE: list[Color] = [
|
||||||
|
|
||||||
|
|
||||||
def line_center(
|
def line_center(
|
||||||
nodes: list[OSMNode], flinger: Flinger
|
nodes: List[OSMNode], flinger: Flinger
|
||||||
) -> (np.ndarray, np.ndarray):
|
) -> (np.ndarray, np.ndarray):
|
||||||
"""
|
"""
|
||||||
Get geometric center of nodes set.
|
Get geometric center of nodes set.
|
||||||
|
@ -65,7 +65,7 @@ def line_center(
|
||||||
:param nodes: node list
|
:param nodes: node list
|
||||||
:param flinger: flinger that remap geo positions
|
:param flinger: flinger that remap geo positions
|
||||||
"""
|
"""
|
||||||
boundary: list[MinMax] = [MinMax(), MinMax()]
|
boundary: List[MinMax] = [MinMax(), MinMax()]
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
boundary[0].update(node.coordinates[0])
|
boundary[0].update(node.coordinates[0])
|
||||||
|
@ -95,14 +95,14 @@ def get_time_color(time: Optional[datetime], boundaries: MinMax) -> Color:
|
||||||
return get_gradient_color(time, boundaries, TIME_COLOR_SCALE)
|
return get_gradient_color(time, boundaries, TIME_COLOR_SCALE)
|
||||||
|
|
||||||
|
|
||||||
def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
|
def glue(ways: List[OSMWay]) -> List[List[OSMNode]]:
|
||||||
"""
|
"""
|
||||||
Try to glue ways that share nodes.
|
Try to glue ways that share nodes.
|
||||||
|
|
||||||
:param ways: ways to glue
|
:param ways: ways to glue
|
||||||
"""
|
"""
|
||||||
result: list[list[OSMNode]] = []
|
result: List[List[OSMNode]] = []
|
||||||
to_process: set[tuple[OSMNode]] = set()
|
to_process: Set[Tuple[OSMNode]] = set()
|
||||||
|
|
||||||
for way in ways:
|
for way in ways:
|
||||||
if way.is_cycle():
|
if way.is_cycle():
|
||||||
|
@ -111,9 +111,9 @@ def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
|
||||||
to_process.add(tuple(way.nodes))
|
to_process.add(tuple(way.nodes))
|
||||||
|
|
||||||
while to_process:
|
while to_process:
|
||||||
nodes: list[OSMNode] = list(to_process.pop())
|
nodes: List[OSMNode] = list(to_process.pop())
|
||||||
glued: Optional[list[OSMNode]] = None
|
glued: Optional[List[OSMNode]] = None
|
||||||
other_nodes: Optional[tuple[OSMNode]] = None
|
other_nodes: Optional[Tuple[OSMNode]] = None
|
||||||
|
|
||||||
for other_nodes in to_process:
|
for other_nodes in to_process:
|
||||||
glued = try_to_glue(nodes, list(other_nodes))
|
glued = try_to_glue(nodes, list(other_nodes))
|
||||||
|
@ -132,14 +132,14 @@ def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
|
||||||
return result
|
return result
|
||||||
|
|
||||||
|
|
||||||
def is_cycle(nodes: list[OSMNode]) -> bool:
|
def is_cycle(nodes: List[OSMNode]) -> bool:
|
||||||
"""Is way a cycle way or an area boundary."""
|
"""Is way a cycle way or an area boundary."""
|
||||||
return nodes[0] == nodes[-1]
|
return nodes[0] == nodes[-1]
|
||||||
|
|
||||||
|
|
||||||
def try_to_glue(
|
def try_to_glue(
|
||||||
nodes: list[OSMNode], other: list[OSMNode]
|
nodes: List[OSMNode], other: List[OSMNode]
|
||||||
) -> Optional[list[OSMNode]]:
|
) -> Optional[List[OSMNode]]:
|
||||||
"""Create new combined way if ways share endpoints."""
|
"""Create new combined way if ways share endpoints."""
|
||||||
if nodes[0] == other[0]:
|
if nodes[0] == other[0]:
|
||||||
return list(reversed(other[1:])) + nodes
|
return list(reversed(other[1:])) + nodes
|
||||||
|
@ -182,15 +182,15 @@ class Constructor:
|
||||||
x, float(self.configuration.level)
|
x, float(self.configuration.level)
|
||||||
)
|
)
|
||||||
|
|
||||||
self.points: list[Point] = []
|
self.points: List[Point] = []
|
||||||
self.figures: list[StyledFigure] = []
|
self.figures: List[StyledFigure] = []
|
||||||
self.buildings: list[Building] = []
|
self.buildings: List[Building] = []
|
||||||
self.roads: Roads = Roads()
|
self.roads: Roads = Roads()
|
||||||
self.trees: list[Tree] = []
|
self.trees: List[Tree] = []
|
||||||
self.craters: list[Crater] = []
|
self.craters: List[Crater] = []
|
||||||
self.direction_sectors: list[DirectionSector] = []
|
self.direction_sectors: List[DirectionSector] = []
|
||||||
|
|
||||||
self.heights: set[float] = {2, 4}
|
self.heights: Set[float] = {2, 4}
|
||||||
|
|
||||||
def add_building(self, building: Building) -> None:
|
def add_building(self, building: Building) -> None:
|
||||||
"""Add building and update levels."""
|
"""Add building and update levels."""
|
||||||
|
@ -221,8 +221,8 @@ class Constructor:
|
||||||
def construct_line(
|
def construct_line(
|
||||||
self,
|
self,
|
||||||
line: Union[OSMWay, OSMRelation],
|
line: Union[OSMWay, OSMRelation],
|
||||||
inners: list[list[OSMNode]],
|
inners: List[List[OSMNode]],
|
||||||
outers: list[list[OSMNode]],
|
outers: List[List[OSMNode]],
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Way or relation construction.
|
Way or relation construction.
|
||||||
|
@ -265,7 +265,7 @@ class Constructor:
|
||||||
)
|
)
|
||||||
return
|
return
|
||||||
|
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
|
|
||||||
recolor: Optional[Color] = None
|
recolor: Optional[Color] = None
|
||||||
|
|
||||||
|
@ -275,11 +275,11 @@ class Constructor:
|
||||||
recolor = self.scheme.get_color(line.tags[color_tag_key])
|
recolor = self.scheme.get_color(line.tags[color_tag_key])
|
||||||
processed.add(color_tag_key)
|
processed.add(color_tag_key)
|
||||||
|
|
||||||
line_styles: list[LineStyle] = self.scheme.get_style(line.tags)
|
line_styles: List[LineStyle] = self.scheme.get_style(line.tags)
|
||||||
|
|
||||||
for line_style in line_styles:
|
for line_style in line_styles:
|
||||||
if recolor is not None:
|
if recolor is not None:
|
||||||
new_style: dict[str, Union[float, int, str]] = dict(
|
new_style: Dict[str, Union[float, int, str]] = dict(
|
||||||
line_style.style
|
line_style.style
|
||||||
)
|
)
|
||||||
new_style["stroke"] = recolor.hex
|
new_style["stroke"] = recolor.hex
|
||||||
|
@ -302,7 +302,7 @@ class Constructor:
|
||||||
self.extractor, line.tags, processed, self.configuration
|
self.extractor, line.tags, processed, self.configuration
|
||||||
)
|
)
|
||||||
if icon_set is not None:
|
if icon_set is not None:
|
||||||
labels: list[Label] = self.scheme.construct_text(
|
labels: List[Label] = self.scheme.construct_text(
|
||||||
line.tags, "all", processed
|
line.tags, "all", processed
|
||||||
)
|
)
|
||||||
point: Point = Point(
|
point: Point = Point(
|
||||||
|
@ -319,7 +319,7 @@ class Constructor:
|
||||||
|
|
||||||
if not line_styles:
|
if not line_styles:
|
||||||
if DEBUG:
|
if DEBUG:
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": "none",
|
"fill": "none",
|
||||||
"stroke": Color("red").hex,
|
"stroke": Color("red").hex,
|
||||||
"stroke-width": 1,
|
"stroke-width": 1,
|
||||||
|
@ -329,7 +329,7 @@ class Constructor:
|
||||||
)
|
)
|
||||||
self.figures.append(figure)
|
self.figures.append(figure)
|
||||||
|
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
|
|
||||||
priority: int
|
priority: int
|
||||||
icon_set: IconSet
|
icon_set: IconSet
|
||||||
|
@ -340,7 +340,7 @@ class Constructor:
|
||||||
self.configuration,
|
self.configuration,
|
||||||
)
|
)
|
||||||
if icon_set is not None:
|
if icon_set is not None:
|
||||||
labels: list[Label] = self.scheme.construct_text(
|
labels: List[Label] = self.scheme.construct_text(
|
||||||
line.tags, "all", processed
|
line.tags, "all", processed
|
||||||
)
|
)
|
||||||
point: Point = Point(
|
point: Point = Point(
|
||||||
|
@ -358,12 +358,12 @@ class Constructor:
|
||||||
def draw_special_mode(
|
def draw_special_mode(
|
||||||
self,
|
self,
|
||||||
line: Union[OSMWay, OSMRelation],
|
line: Union[OSMWay, OSMRelation],
|
||||||
inners: list[list[OSMNode]],
|
inners: List[List[OSMNode]],
|
||||||
outers: list[list[OSMNode]],
|
outers: List[List[OSMNode]],
|
||||||
color: Color,
|
color: Color,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add figure for special mode: time or author."""
|
"""Add figure for special mode: time or author."""
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": "none",
|
"fill": "none",
|
||||||
"stroke": color.hex,
|
"stroke": color.hex,
|
||||||
"stroke-width": 1,
|
"stroke-width": 1,
|
||||||
|
@ -376,13 +376,13 @@ class Constructor:
|
||||||
"""Construct Map Machine ways from OSM relations."""
|
"""Construct Map Machine ways from OSM relations."""
|
||||||
for relation_id in self.osm_data.relations:
|
for relation_id in self.osm_data.relations:
|
||||||
relation: OSMRelation = self.osm_data.relations[relation_id]
|
relation: OSMRelation = self.osm_data.relations[relation_id]
|
||||||
tags: dict[str, str] = relation.tags
|
tags: Dict[str, str] = relation.tags
|
||||||
if not self.check_level(tags):
|
if not self.check_level(tags):
|
||||||
continue
|
continue
|
||||||
if "type" not in tags or tags["type"] != "multipolygon":
|
if "type" not in tags or tags["type"] != "multipolygon":
|
||||||
continue
|
continue
|
||||||
inner_ways: list[OSMWay] = []
|
inner_ways: List[OSMWay] = []
|
||||||
outer_ways: list[OSMWay] = []
|
outer_ways: List[OSMWay] = []
|
||||||
for member in relation.members:
|
for member in relation.members:
|
||||||
if member.type_ == "way":
|
if member.type_ == "way":
|
||||||
if member.role == "inner":
|
if member.role == "inner":
|
||||||
|
@ -394,8 +394,8 @@ class Constructor:
|
||||||
else:
|
else:
|
||||||
logging.warning(f'Unknown member role "{member.role}".')
|
logging.warning(f'Unknown member role "{member.role}".')
|
||||||
if outer_ways:
|
if outer_ways:
|
||||||
inners_path: list[list[OSMNode]] = glue(inner_ways)
|
inners_path: List[List[OSMNode]] = glue(inner_ways)
|
||||||
outers_path: list[list[OSMNode]] = glue(outer_ways)
|
outers_path: List[List[OSMNode]] = glue(outer_ways)
|
||||||
self.construct_line(relation, inners_path, outers_path)
|
self.construct_line(relation, inners_path, outers_path)
|
||||||
|
|
||||||
def construct_nodes(self) -> None:
|
def construct_nodes(self) -> None:
|
||||||
|
@ -414,11 +414,11 @@ class Constructor:
|
||||||
|
|
||||||
def construct_node(self, node: OSMNode) -> None:
|
def construct_node(self, node: OSMNode) -> None:
|
||||||
"""Draw one node."""
|
"""Draw one node."""
|
||||||
tags: dict[str, str] = node.tags
|
tags: Dict[str, str] = node.tags
|
||||||
if not self.check_level(tags):
|
if not self.check_level(tags):
|
||||||
return
|
return
|
||||||
|
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
|
|
||||||
flung: np.ndarray = self.flinger.fling(node.coordinates)
|
flung: np.ndarray = self.flinger.fling(node.coordinates)
|
||||||
|
|
||||||
|
@ -455,7 +455,7 @@ class Constructor:
|
||||||
)
|
)
|
||||||
if icon_set is None:
|
if icon_set is None:
|
||||||
return
|
return
|
||||||
labels: list[Label] = self.scheme.construct_text(tags, "all", processed)
|
labels: List[Label] = self.scheme.construct_text(tags, "all", processed)
|
||||||
self.scheme.process_ignored(tags, processed)
|
self.scheme.process_ignored(tags, processed)
|
||||||
|
|
||||||
if node.get_tag("natural") == "tree" and (
|
if node.get_tag("natural") == "tree" and (
|
||||||
|
@ -483,7 +483,7 @@ class Constructor:
|
||||||
self.points.append(point)
|
self.points.append(point)
|
||||||
|
|
||||||
|
|
||||||
def check_level_number(tags: dict[str, Any], level: float) -> bool:
|
def check_level_number(tags: Dict[str, Any], level: float) -> bool:
|
||||||
"""Check if element described by tags is no the specified level."""
|
"""Check if element described by tags is no the specified level."""
|
||||||
if "level" in tags:
|
if "level" in tags:
|
||||||
if level not in parse_levels(tags["level"]):
|
if level not in parse_levels(tags["level"]):
|
||||||
|
@ -493,7 +493,7 @@ def check_level_number(tags: dict[str, Any], level: float) -> bool:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
|
|
||||||
def check_level_overground(tags: dict[str, Any]) -> bool:
|
def check_level_overground(tags: Dict[str, Any]) -> bool:
|
||||||
"""Check if element described by tags is overground."""
|
"""Check if element described by tags is overground."""
|
||||||
if "level" in tags:
|
if "level" in tags:
|
||||||
try:
|
try:
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Direction tag support.
|
Direction tag support.
|
||||||
"""
|
"""
|
||||||
from typing import Iterator, Optional
|
from typing import Iterator, List, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from portolan import middle
|
from portolan import middle
|
||||||
|
@ -65,7 +65,7 @@ class Sector:
|
||||||
self.main_direction: Optional[np.ndarray] = None
|
self.main_direction: Optional[np.ndarray] = None
|
||||||
|
|
||||||
if "-" in text:
|
if "-" in text:
|
||||||
parts: list[str] = text.split("-")
|
parts: List[str] = text.split("-")
|
||||||
self.start = parse_vector(parts[0])
|
self.start = parse_vector(parts[0])
|
||||||
self.end = parse_vector(parts[1])
|
self.end = parse_vector(parts[1])
|
||||||
self.main_direction = (self.start + self.end) / 2
|
self.main_direction = (self.start + self.end) / 2
|
||||||
|
@ -152,7 +152,7 @@ class DirectionSet:
|
||||||
:return: true if direction is right, false if direction is left, and
|
:return: true if direction is right, false if direction is left, and
|
||||||
None otherwise.
|
None otherwise.
|
||||||
"""
|
"""
|
||||||
result: list[bool] = [x.is_right() for x in self.sectors]
|
result: List[bool] = [x.is_right() for x in self.sectors]
|
||||||
if result == [True] * len(result):
|
if result == [True] * len(result):
|
||||||
return True
|
return True
|
||||||
if result == [False] * len(result):
|
if result == [False] * len(result):
|
||||||
|
|
|
@ -3,7 +3,7 @@ Drawing utility.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import List, Optional, Union
|
||||||
|
|
||||||
import cairo
|
import cairo
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -18,7 +18,7 @@ from svgwrite.text import Text
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
PathCommands = list[Union[float, str, np.ndarray]]
|
PathCommands = List[Union[float, str, np.ndarray]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -75,7 +75,7 @@ class Drawing:
|
||||||
"""Draw rectangle."""
|
"""Draw rectangle."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||||
"""Draw line."""
|
"""Draw line."""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
@ -117,7 +117,7 @@ class SVGDrawing(Drawing):
|
||||||
style.update_svg_element(rectangle)
|
style.update_svg_element(rectangle)
|
||||||
self.image.add(rectangle)
|
self.image.add(rectangle)
|
||||||
|
|
||||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||||
"""Draw line."""
|
"""Draw line."""
|
||||||
commands: PathCommands = ["M"]
|
commands: PathCommands = ["M"]
|
||||||
for point in points:
|
for point in points:
|
||||||
|
@ -169,7 +169,7 @@ class PNGDrawing(Drawing):
|
||||||
self.context.rectangle(point_1[0], point_1[1], size[0], size[1])
|
self.context.rectangle(point_1[0], point_1[1], size[0], size[1])
|
||||||
style.draw_png_stroke(self.context)
|
style.draw_png_stroke(self.context)
|
||||||
|
|
||||||
def line(self, points: list[np.ndarray], style: Style) -> None:
|
def line(self, points: List[np.ndarray], style: Style) -> None:
|
||||||
"""Draw line."""
|
"""Draw line."""
|
||||||
if style.fill is not None:
|
if style.fill is not None:
|
||||||
self.context.move_to(float(points[0][0]), float(points[0][1]))
|
self.context.move_to(float(points[0][0]), float(points[0][1]))
|
||||||
|
@ -283,7 +283,7 @@ class PNGDrawing(Drawing):
|
||||||
|
|
||||||
def parse_path(path: str) -> PathCommands:
|
def parse_path(path: str) -> PathCommands:
|
||||||
"""Parse path command from text representation into list."""
|
"""Parse path command from text representation into list."""
|
||||||
parts: list[str] = path.split(" ")
|
parts: List[str] = path.split(" ")
|
||||||
result: PathCommands = []
|
result: PathCommands = []
|
||||||
command: str = "M"
|
command: str = "M"
|
||||||
index: int = 0
|
index: int = 0
|
||||||
|
@ -296,7 +296,7 @@ def parse_path(path: str) -> PathCommands:
|
||||||
result.append(float(part))
|
result.append(float(part))
|
||||||
else:
|
else:
|
||||||
if "," in part:
|
if "," in part:
|
||||||
elements: list[str] = part.split(",")
|
elements: List[str] = part.split(",")
|
||||||
result.append(np.array(list(map(float, elements))))
|
result.append(np.array(list(map(float, elements))))
|
||||||
else:
|
else:
|
||||||
result.append(np.array((float(part), float(parts[index + 1]))))
|
result.append(np.array((float(part), float(parts[index + 1]))))
|
||||||
|
|
|
@ -4,6 +4,7 @@ Drawing separate map elements.
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
@ -33,17 +34,17 @@ def draw_element(options: argparse.Namespace) -> None:
|
||||||
target = "area"
|
target = "area"
|
||||||
tags_description = options.area
|
tags_description = options.area
|
||||||
|
|
||||||
tags: dict[str, str] = dict(
|
tags: Dict[str, str] = dict(
|
||||||
[x.split("=") for x in tags_description.split(",")]
|
[x.split("=") for x in tags_description.split(",")]
|
||||||
)
|
)
|
||||||
scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
|
scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
|
||||||
extractor: ShapeExtractor = ShapeExtractor(
|
extractor: ShapeExtractor = ShapeExtractor(
|
||||||
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
|
||||||
)
|
)
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
icon, priority = scheme.get_icon(extractor, tags, processed)
|
icon, priority = scheme.get_icon(extractor, tags, processed)
|
||||||
is_for_node: bool = target == "node"
|
is_for_node: bool = target == "node"
|
||||||
labels: list[Label] = scheme.construct_text(tags, "all", processed)
|
labels: List[Label] = scheme.construct_text(tags, "all", processed)
|
||||||
point: Point = Point(
|
point: Point = Point(
|
||||||
icon,
|
icon,
|
||||||
labels,
|
labels,
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Figures displayed on the map.
|
Figures displayed on the map.
|
||||||
"""
|
"""
|
||||||
from typing import Any, Iterator, Optional
|
from typing import Any, Dict, Iterator, List, Optional
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
@ -31,14 +31,14 @@ class Figure(Tagged):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
inners: list[list[OSMNode]],
|
inners: List[List[OSMNode]],
|
||||||
outers: list[list[OSMNode]],
|
outers: List[List[OSMNode]],
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags)
|
super().__init__(tags)
|
||||||
|
|
||||||
self.inners: list[list[OSMNode]] = list(map(make_clockwise, inners))
|
self.inners: List[List[OSMNode]] = list(map(make_clockwise, inners))
|
||||||
self.outers: list[list[OSMNode]] = list(
|
self.outers: List[List[OSMNode]] = list(
|
||||||
map(make_counter_clockwise, outers)
|
map(make_counter_clockwise, outers)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -69,20 +69,20 @@ class Building(Figure):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
inners: list[list[OSMNode]],
|
inners: List[List[OSMNode]],
|
||||||
outers: list[list[OSMNode]],
|
outers: List[List[OSMNode]],
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
scheme: Scheme,
|
scheme: Scheme,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags, inners, outers)
|
super().__init__(tags, inners, outers)
|
||||||
|
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": scheme.get_color("building_color").hex,
|
"fill": scheme.get_color("building_color").hex,
|
||||||
"stroke": scheme.get_color("building_border_color").hex,
|
"stroke": scheme.get_color("building_border_color").hex,
|
||||||
}
|
}
|
||||||
self.line_style: LineStyle = LineStyle(style)
|
self.line_style: LineStyle = LineStyle(style)
|
||||||
self.parts: list[Segment] = []
|
self.parts: List[Segment] = []
|
||||||
|
|
||||||
for nodes in self.inners + self.outers:
|
for nodes in self.inners + self.outers:
|
||||||
for i in range(len(nodes) - 1):
|
for i in range(len(nodes) - 1):
|
||||||
|
@ -198,9 +198,9 @@ class StyledFigure(Figure):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
inners: list[list[OSMNode]],
|
inners: List[List[OSMNode]],
|
||||||
outers: list[list[OSMNode]],
|
outers: List[List[OSMNode]],
|
||||||
line_style: LineStyle,
|
line_style: LineStyle,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags, inners, outers)
|
super().__init__(tags, inners, outers)
|
||||||
|
@ -213,7 +213,7 @@ class Crater(Tagged):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, tags: dict[str, str], coordinates: np.ndarray, point: np.ndarray
|
self, tags: Dict[str, str], coordinates: np.ndarray, point: np.ndarray
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags)
|
super().__init__(tags)
|
||||||
self.coordinates: np.ndarray = coordinates
|
self.coordinates: np.ndarray = coordinates
|
||||||
|
@ -252,7 +252,7 @@ class Tree(Tagged):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, tags: dict[str, str], coordinates: np.ndarray, point: np.ndarray
|
self, tags: Dict[str, str], coordinates: np.ndarray, point: np.ndarray
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags)
|
super().__init__(tags)
|
||||||
self.coordinates: np.ndarray = coordinates
|
self.coordinates: np.ndarray = coordinates
|
||||||
|
@ -279,7 +279,7 @@ class DirectionSector(Tagged):
|
||||||
Sector that represents direction.
|
Sector that represents direction.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, tags: dict[str, str], point: np.ndarray) -> None:
|
def __init__(self, tags: Dict[str, str], point: np.ndarray) -> None:
|
||||||
super().__init__(tags)
|
super().__init__(tags)
|
||||||
self.point: np.ndarray = point
|
self.point: np.ndarray = point
|
||||||
|
|
||||||
|
@ -366,7 +366,7 @@ class Segment:
|
||||||
) # fmt: skip
|
) # fmt: skip
|
||||||
|
|
||||||
|
|
||||||
def is_clockwise(polygon: list[OSMNode]) -> bool:
|
def is_clockwise(polygon: List[OSMNode]) -> bool:
|
||||||
"""
|
"""
|
||||||
Return true if polygon nodes are in clockwise order.
|
Return true if polygon nodes are in clockwise order.
|
||||||
|
|
||||||
|
@ -381,7 +381,7 @@ def is_clockwise(polygon: list[OSMNode]) -> bool:
|
||||||
return count >= 0
|
return count >= 0
|
||||||
|
|
||||||
|
|
||||||
def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
|
||||||
"""
|
"""
|
||||||
Make polygon nodes clockwise.
|
Make polygon nodes clockwise.
|
||||||
|
|
||||||
|
@ -390,7 +390,7 @@ def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
||||||
return polygon if is_clockwise(polygon) else list(reversed(polygon))
|
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]:
|
||||||
"""
|
"""
|
||||||
Make polygon nodes counter-clockwise.
|
Make polygon nodes counter-clockwise.
|
||||||
|
|
||||||
|
@ -399,7 +399,7 @@ def make_counter_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
|
||||||
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
|
return polygon if not is_clockwise(polygon) else list(reversed(polygon))
|
||||||
|
|
||||||
|
|
||||||
def get_path(nodes: list[OSMNode], shift: np.ndarray, flinger: Flinger) -> str:
|
def get_path(nodes: List[OSMNode], shift: np.ndarray, flinger: Flinger) -> str:
|
||||||
"""Construct SVG path commands from nodes."""
|
"""Construct SVG path commands from nodes."""
|
||||||
return Polyline(
|
return Polyline(
|
||||||
[flinger.fling(x.coordinates) + shift for x in nodes]
|
[flinger.fling(x.coordinates) + shift for x in nodes]
|
||||||
|
|
|
@ -4,7 +4,7 @@ Icon grid drawing.
|
||||||
import logging
|
import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, Union
|
from typing import Dict, List, Optional, Set, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
@ -25,7 +25,7 @@ class IconCollection:
|
||||||
Collection of icons.
|
Collection of icons.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
icons: list[Icon]
|
icons: List[Icon]
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_scheme(
|
def from_scheme(
|
||||||
|
@ -48,11 +48,11 @@ class IconCollection:
|
||||||
tags
|
tags
|
||||||
:param add_all: create icons from all possible shapes including parts
|
:param add_all: create icons from all possible shapes including parts
|
||||||
"""
|
"""
|
||||||
icons: list[Icon] = []
|
icons: List[Icon] = []
|
||||||
|
|
||||||
def add() -> Icon:
|
def add() -> Icon:
|
||||||
"""Construct icon and add it to the list."""
|
"""Construct icon and add it to the list."""
|
||||||
specifications: list[ShapeSpecification] = [
|
specifications: List[ShapeSpecification] = [
|
||||||
scheme.get_shape_specification(x, extractor)
|
scheme.get_shape_specification(x, extractor)
|
||||||
for x in current_set
|
for x in current_set
|
||||||
]
|
]
|
||||||
|
@ -63,7 +63,7 @@ class IconCollection:
|
||||||
|
|
||||||
return constructed_icon
|
return constructed_icon
|
||||||
|
|
||||||
current_set: list[Union[str, dict[str, str]]]
|
current_set: List[Union[str, Dict[str, str]]]
|
||||||
|
|
||||||
for matcher in scheme.node_matchers:
|
for matcher in scheme.node_matchers:
|
||||||
matcher: NodeMatcher
|
matcher: NodeMatcher
|
||||||
|
@ -83,7 +83,7 @@ class IconCollection:
|
||||||
continue
|
continue
|
||||||
for icon_id in matcher.under_icon:
|
for icon_id in matcher.under_icon:
|
||||||
for icon_2_id in matcher.with_icon:
|
for icon_2_id in matcher.with_icon:
|
||||||
current_set: list[str] = (
|
current_set: List[str] = (
|
||||||
[icon_id] + [icon_2_id] + matcher.over_icon
|
[icon_id] + [icon_2_id] + matcher.over_icon
|
||||||
)
|
)
|
||||||
add()
|
add()
|
||||||
|
@ -102,7 +102,7 @@ class IconCollection:
|
||||||
):
|
):
|
||||||
add()
|
add()
|
||||||
|
|
||||||
specified_ids: set[str] = set()
|
specified_ids: Set[str] = set()
|
||||||
|
|
||||||
for icon in icons:
|
for icon in icons:
|
||||||
specified_ids |= set(icon.get_shape_ids())
|
specified_ids |= set(icon.get_shape_ids())
|
||||||
|
|
|
@ -6,7 +6,7 @@ import logging
|
||||||
import re
|
import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, List, Optional, Set
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from xml.etree.ElementTree import Element
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
|
@ -47,13 +47,13 @@ class Shape:
|
||||||
id_: str # shape identifier
|
id_: str # shape identifier
|
||||||
name: Optional[str] = None # icon description
|
name: Optional[str] = None # icon description
|
||||||
is_right_directed: Optional[bool] = None
|
is_right_directed: Optional[bool] = None
|
||||||
emojis: set[str] = field(default_factory=set)
|
emojis: Set[str] = field(default_factory=set)
|
||||||
is_part: bool = False
|
is_part: bool = False
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_structure(
|
def from_structure(
|
||||||
cls,
|
cls,
|
||||||
structure: dict[str, Any],
|
structure: Dict[str, Any],
|
||||||
path: str,
|
path: str,
|
||||||
offset: np.ndarray,
|
offset: np.ndarray,
|
||||||
id_: str,
|
id_: str,
|
||||||
|
@ -105,7 +105,7 @@ class Shape:
|
||||||
:param offset: additional offset
|
:param offset: additional offset
|
||||||
:param scale: scale resulting image
|
:param scale: scale resulting image
|
||||||
"""
|
"""
|
||||||
transformations: list[str] = []
|
transformations: List[str] = []
|
||||||
shift: np.ndarray = point + offset
|
shift: np.ndarray = point + offset
|
||||||
|
|
||||||
transformations.append(f"translate({shift[0]},{shift[1]})")
|
transformations.append(f"translate({shift[0]},{shift[1]})")
|
||||||
|
@ -138,7 +138,7 @@ def verify_sketch_element(element: Element, id_: str) -> bool:
|
||||||
if "style" not in element.attrib or not element.attrib["style"]:
|
if "style" not in element.attrib or not element.attrib["style"]:
|
||||||
return True
|
return True
|
||||||
|
|
||||||
style: dict[str, str] = dict(
|
style: Dict[str, str] = dict(
|
||||||
[x.split(":") for x in element.attrib["style"].split(";")]
|
[x.split(":") for x in element.attrib["style"].split(";")]
|
||||||
)
|
)
|
||||||
if (
|
if (
|
||||||
|
@ -180,8 +180,8 @@ class ShapeExtractor:
|
||||||
:param svg_file_name: input SVG file name with icons. File may contain
|
:param svg_file_name: input SVG file name with icons. File may contain
|
||||||
any other irrelevant graphics.
|
any other irrelevant graphics.
|
||||||
"""
|
"""
|
||||||
self.shapes: dict[str, Shape] = {}
|
self.shapes: Dict[str, Shape] = {}
|
||||||
self.configuration: dict[str, Any] = json.load(
|
self.configuration: Dict[str, Any] = json.load(
|
||||||
configuration_file_name.open()
|
configuration_file_name.open()
|
||||||
)
|
)
|
||||||
root: Element = ElementTree.parse(svg_file_name).getroot()
|
root: Element = ElementTree.parse(svg_file_name).getroot()
|
||||||
|
@ -229,7 +229,7 @@ class ShapeExtractor:
|
||||||
name = child_node.text
|
name = child_node.text
|
||||||
break
|
break
|
||||||
|
|
||||||
configuration: dict[str, Any] = (
|
configuration: Dict[str, Any] = (
|
||||||
self.configuration[id_] if id_ in self.configuration else {}
|
self.configuration[id_] if id_ in self.configuration else {}
|
||||||
)
|
)
|
||||||
self.shapes[id_] = Shape.from_structure(
|
self.shapes[id_] = Shape.from_structure(
|
||||||
|
@ -271,7 +271,7 @@ class ShapeSpecification:
|
||||||
self,
|
self,
|
||||||
svg: BaseElement,
|
svg: BaseElement,
|
||||||
point: np.ndarray,
|
point: np.ndarray,
|
||||||
tags: dict[str, Any] = None,
|
tags: Dict[str, Any] = None,
|
||||||
outline: bool = False,
|
outline: bool = False,
|
||||||
outline_opacity: float = 1.0,
|
outline_opacity: float = 1.0,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -299,7 +299,7 @@ class ShapeSpecification:
|
||||||
bright: bool = is_bright(self.color)
|
bright: bool = is_bright(self.color)
|
||||||
color: Color = Color("black") if bright else Color("white")
|
color: Color = Color("black") if bright else Color("white")
|
||||||
|
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": color.hex,
|
"fill": color.hex,
|
||||||
"stroke": color.hex,
|
"stroke": color.hex,
|
||||||
"stroke-width": 2.2,
|
"stroke-width": 2.2,
|
||||||
|
@ -330,14 +330,14 @@ class Icon:
|
||||||
Icon that consists of (probably) multiple shapes.
|
Icon that consists of (probably) multiple shapes.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
shape_specifications: list[ShapeSpecification]
|
shape_specifications: List[ShapeSpecification]
|
||||||
opacity: float = 1.0
|
opacity: float = 1.0
|
||||||
|
|
||||||
def get_shape_ids(self) -> list[str]:
|
def get_shape_ids(self) -> List[str]:
|
||||||
"""Get all shape identifiers in the icon."""
|
"""Get all shape identifiers in the icon."""
|
||||||
return [x.shape.id_ for x in self.shape_specifications]
|
return [x.shape.id_ for x in self.shape_specifications]
|
||||||
|
|
||||||
def get_names(self) -> list[str]:
|
def get_names(self) -> List[str]:
|
||||||
"""Get all shape names in the icon."""
|
"""Get all shape names in the icon."""
|
||||||
return [
|
return [
|
||||||
(x.shape.name if x.shape.name else "unknown")
|
(x.shape.name if x.shape.name else "unknown")
|
||||||
|
@ -348,7 +348,7 @@ class Icon:
|
||||||
self,
|
self,
|
||||||
svg: svgwrite.Drawing,
|
svg: svgwrite.Drawing,
|
||||||
point: np.ndarray,
|
point: np.ndarray,
|
||||||
tags: dict[str, Any] = None,
|
tags: Dict[str, Any] = None,
|
||||||
outline: bool = False,
|
outline: bool = False,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -420,7 +420,7 @@ class Icon:
|
||||||
shape_specification.color = color
|
shape_specification.color = color
|
||||||
|
|
||||||
def add_specifications(
|
def add_specifications(
|
||||||
self, specifications: list[ShapeSpecification]
|
self, specifications: List[ShapeSpecification]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Add shape specifications to the icon."""
|
"""Add shape specifications to the icon."""
|
||||||
self.shape_specifications += specifications
|
self.shape_specifications += specifications
|
||||||
|
@ -443,8 +443,8 @@ class IconSet:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
main_icon: Icon
|
main_icon: Icon
|
||||||
extra_icons: list[Icon]
|
extra_icons: List[Icon]
|
||||||
|
|
||||||
# Tag keys that were processed to create icon set (other tag keys should be
|
# Tag keys that were processed to create icon set (other tag keys should be
|
||||||
# displayed by text or ignored)
|
# displayed by text or ignored)
|
||||||
processed: set[str]
|
processed: Set[str]
|
||||||
|
|
|
@ -4,7 +4,7 @@ MapCSS scheme creation.
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional, TextIO
|
from typing import Dict, List, Optional, TextIO
|
||||||
|
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
|
||||||
|
@ -84,8 +84,8 @@ class MapCSSWriter:
|
||||||
self.add_icons_for_lifecycle: bool = add_icons_for_lifecycle
|
self.add_icons_for_lifecycle: bool = add_icons_for_lifecycle
|
||||||
self.icon_directory_name: str = icon_directory_name
|
self.icon_directory_name: str = icon_directory_name
|
||||||
|
|
||||||
self.point_matchers: list[Matcher] = scheme.node_matchers
|
self.point_matchers: List[Matcher] = scheme.node_matchers
|
||||||
self.line_matchers: list[Matcher] = scheme.way_matchers
|
self.line_matchers: List[Matcher] = scheme.way_matchers
|
||||||
|
|
||||||
def add_selector(
|
def add_selector(
|
||||||
self,
|
self,
|
||||||
|
@ -103,7 +103,7 @@ class MapCSSWriter:
|
||||||
:param opacity: icon opacity
|
:param opacity: icon opacity
|
||||||
:return: string representation of selector
|
:return: string representation of selector
|
||||||
"""
|
"""
|
||||||
elements: dict[str, str] = {}
|
elements: Dict[str, str] = {}
|
||||||
|
|
||||||
clean_shapes = matcher.get_clean_shapes()
|
clean_shapes = matcher.get_clean_shapes()
|
||||||
if clean_shapes:
|
if clean_shapes:
|
||||||
|
@ -116,7 +116,7 @@ class MapCSSWriter:
|
||||||
if opacity is not None:
|
if opacity is not None:
|
||||||
elements["icon-opacity"] = f"{opacity:.2f}"
|
elements["icon-opacity"] = f"{opacity:.2f}"
|
||||||
|
|
||||||
style: dict[str, str] = matcher.get_style()
|
style: Dict[str, str] = matcher.get_style()
|
||||||
if style:
|
if style:
|
||||||
if "fill" in style:
|
if "fill" in style:
|
||||||
elements["fill-color"] = style["fill"]
|
elements["fill-color"] = style["fill"]
|
||||||
|
|
|
@ -4,7 +4,7 @@ Simple OpenStreetMap renderer.
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Iterator, Optional
|
from typing import Dict, Iterator, List, Optional, Set
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
@ -57,7 +57,7 @@ class Map:
|
||||||
self.svg.add(
|
self.svg.add(
|
||||||
Rect((0, 0), self.flinger.size, fill=self.background_color)
|
Rect((0, 0), self.flinger.size, fill=self.background_color)
|
||||||
)
|
)
|
||||||
ways: list[StyledFigure] = sorted(
|
ways: List[StyledFigure] = sorted(
|
||||||
constructor.figures, key=lambda x: x.line_style.priority
|
constructor.figures, key=lambda x: x.line_style.priority
|
||||||
)
|
)
|
||||||
ways_length: int = len(ways)
|
ways_length: int = len(ways)
|
||||||
|
@ -94,7 +94,7 @@ class Map:
|
||||||
self.configuration.overlap,
|
self.configuration.overlap,
|
||||||
)
|
)
|
||||||
|
|
||||||
nodes: list[Point] = sorted(
|
nodes: List[Point] = sorted(
|
||||||
constructor.points, key=lambda x: -x.priority
|
constructor.points, key=lambda x: -x.priority
|
||||||
)
|
)
|
||||||
steps: int = len(nodes)
|
steps: int = len(nodes)
|
||||||
|
@ -156,7 +156,7 @@ class Map:
|
||||||
|
|
||||||
def draw_roads(self, roads: Iterator[Road]) -> None:
|
def draw_roads(self, roads: Iterator[Road]) -> None:
|
||||||
"""Draw road as simple SVG path."""
|
"""Draw road as simple SVG path."""
|
||||||
nodes: dict[OSMNode, set[RoadPart]] = {}
|
nodes: Dict[OSMNode, Set[RoadPart]] = {}
|
||||||
|
|
||||||
for road in roads:
|
for road in roads:
|
||||||
for index in range(len(road.outers[0]) - 1):
|
for index in range(len(road.outers[0]) - 1):
|
||||||
|
@ -177,7 +177,7 @@ class Map:
|
||||||
nodes[node_2].add(part_2)
|
nodes[node_2].add(part_2)
|
||||||
|
|
||||||
for node in nodes:
|
for node in nodes:
|
||||||
parts: set[RoadPart] = nodes[node]
|
parts: Set[RoadPart] = nodes[node]
|
||||||
if len(parts) < 4:
|
if len(parts) < 4:
|
||||||
continue
|
continue
|
||||||
intersection: Intersection = Intersection(list(parts))
|
intersection: Intersection = Intersection(list(parts))
|
||||||
|
@ -197,7 +197,7 @@ def ui(arguments: argparse.Namespace) -> None:
|
||||||
cache_path.mkdir(parents=True, exist_ok=True)
|
cache_path.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
boundary_box: Optional[BoundaryBox] = None
|
boundary_box: Optional[BoundaryBox] = None
|
||||||
input_file_names: list[Path] = []
|
input_file_names: List[Path] = []
|
||||||
|
|
||||||
if arguments.input_file_names:
|
if arguments.input_file_names:
|
||||||
input_file_names = list(map(Path, arguments.input_file_names))
|
input_file_names = list(map(Path, arguments.input_file_names))
|
||||||
|
|
|
@ -4,7 +4,7 @@ Moire markup extension for Map Machine.
|
||||||
import argparse
|
import argparse
|
||||||
from abc import ABC
|
from abc import ABC
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Union
|
from typing import Any, Dict, List, Union
|
||||||
|
|
||||||
from moire.default import Default, DefaultHTML, DefaultMarkdown, DefaultWiki
|
from moire.default import Default, DefaultHTML, DefaultMarkdown, DefaultWiki
|
||||||
from moire.moire import Tag
|
from moire.moire import Tag
|
||||||
|
@ -17,7 +17,7 @@ from map_machine.workspace import workspace
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
Arguments = list[Any]
|
Arguments = List[Any]
|
||||||
Code = Union[str, Tag, list]
|
Code = Union[str, Tag, list]
|
||||||
|
|
||||||
PREFIX: str = "https://wiki.openstreetmap.org/wiki/"
|
PREFIX: str = "https://wiki.openstreetmap.org/wiki/"
|
||||||
|
@ -50,13 +50,13 @@ class ArgumentParser(argparse.ArgumentParser):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, *args, **kwargs) -> None:
|
def __init__(self, *args, **kwargs) -> None:
|
||||||
self.arguments: list[dict[str, Any]] = []
|
self.arguments: List[Dict[str, Any]] = []
|
||||||
super(ArgumentParser, self).__init__(*args, **kwargs)
|
super(ArgumentParser, self).__init__(*args, **kwargs)
|
||||||
|
|
||||||
def add_argument(self, *args, **kwargs) -> None:
|
def add_argument(self, *args, **kwargs) -> None:
|
||||||
"""Just store argument with options."""
|
"""Just store argument with options."""
|
||||||
super(ArgumentParser, self).add_argument(*args, **kwargs)
|
super(ArgumentParser, self).add_argument(*args, **kwargs)
|
||||||
argument: dict[str, Any] = {"arguments": [x for x in args]}
|
argument: Dict[str, Any] = {"arguments": [x for x in args]}
|
||||||
|
|
||||||
for key in kwargs:
|
for key in kwargs:
|
||||||
argument[key] = kwargs[key]
|
argument[key] = kwargs[key]
|
||||||
|
|
|
@ -5,6 +5,7 @@ import logging
|
||||||
import time
|
import time
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import Dict
|
||||||
|
|
||||||
import urllib3
|
import urllib3
|
||||||
|
|
||||||
|
@ -48,7 +49,7 @@ def get_osm(
|
||||||
return content.decode("utf-8")
|
return content.decode("utf-8")
|
||||||
|
|
||||||
|
|
||||||
def get_data(address: str, parameters: dict[str, str]) -> bytes:
|
def get_data(address: str, parameters: Dict[str, str]) -> bytes:
|
||||||
"""
|
"""
|
||||||
Construct Internet page URL and get its descriptor.
|
Construct Internet page URL and get its descriptor.
|
||||||
|
|
||||||
|
|
|
@ -7,7 +7,7 @@ import re
|
||||||
from dataclasses import dataclass, field
|
from dataclasses import dataclass, field
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, List, Optional, Set
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from xml.etree.ElementTree import Element
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
|
@ -27,7 +27,7 @@ MILES_PATTERN: re.Pattern = re.compile("^(?P<value>\\d*\\.?\\d*)\\s*mi$")
|
||||||
|
|
||||||
|
|
||||||
# See https://wiki.openstreetmap.org/wiki/Lifecycle_prefix#Stages_of_decay
|
# See https://wiki.openstreetmap.org/wiki/Lifecycle_prefix#Stages_of_decay
|
||||||
STAGES_OF_DECAY: list[str] = [
|
STAGES_OF_DECAY: List[str] = [
|
||||||
"disused",
|
"disused",
|
||||||
"abandoned",
|
"abandoned",
|
||||||
"ruins",
|
"ruins",
|
||||||
|
@ -47,7 +47,7 @@ def parse_float(string: str) -> Optional[float]:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
def parse_levels(string: str) -> list[float]:
|
def parse_levels(string: str) -> List[float]:
|
||||||
"""Parse string representation of level sequence value."""
|
"""Parse string representation of level sequence value."""
|
||||||
# TODO: add `-` parsing
|
# TODO: add `-` parsing
|
||||||
try:
|
try:
|
||||||
|
@ -63,7 +63,7 @@ class Tagged:
|
||||||
Something with tags (string to string mapping).
|
Something with tags (string to string mapping).
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tags: dict[str, str]
|
tags: Dict[str, str]
|
||||||
|
|
||||||
def get_tag(self, key: str) -> Optional[str]:
|
def get_tag(self, key: str) -> Optional[str]:
|
||||||
"""
|
"""
|
||||||
|
@ -127,7 +127,7 @@ class OSMNode(Tagged):
|
||||||
def from_xml_structure(cls, element: Element) -> "OSMNode":
|
def from_xml_structure(cls, element: Element) -> "OSMNode":
|
||||||
"""Parse node from OSM XML `<node>` element."""
|
"""Parse node from OSM XML `<node>` element."""
|
||||||
attributes = element.attrib
|
attributes = element.attrib
|
||||||
tags: dict[str, str] = dict(
|
tags: Dict[str, str] = dict(
|
||||||
[(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"]
|
[(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"]
|
||||||
)
|
)
|
||||||
return cls(
|
return cls(
|
||||||
|
@ -144,7 +144,7 @@ class OSMNode(Tagged):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_from_structure(cls, structure: dict[str, Any]) -> "OSMNode":
|
def parse_from_structure(cls, structure: Dict[str, Any]) -> "OSMNode":
|
||||||
"""
|
"""
|
||||||
Parse node from Overpass-like structure.
|
Parse node from Overpass-like structure.
|
||||||
|
|
||||||
|
@ -169,7 +169,7 @@ class OSMWay(Tagged):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id_: int
|
id_: int
|
||||||
nodes: Optional[list[OSMNode]] = field(default_factory=list)
|
nodes: Optional[List[OSMNode]] = field(default_factory=list)
|
||||||
visible: Optional[str] = None
|
visible: Optional[str] = None
|
||||||
changeset: Optional[str] = None
|
changeset: Optional[str] = None
|
||||||
timestamp: Optional[datetime] = None
|
timestamp: Optional[datetime] = None
|
||||||
|
@ -178,11 +178,11 @@ class OSMWay(Tagged):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_xml_structure(
|
def from_xml_structure(
|
||||||
cls, element: Element, nodes: dict[int, OSMNode]
|
cls, element: Element, nodes: Dict[int, OSMNode]
|
||||||
) -> "OSMWay":
|
) -> "OSMWay":
|
||||||
"""Parse way from OSM XML `<way>` element."""
|
"""Parse way from OSM XML `<way>` element."""
|
||||||
attributes = element.attrib
|
attributes = element.attrib
|
||||||
tags: dict[str, str] = dict(
|
tags: Dict[str, str] = dict(
|
||||||
[(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"]
|
[(x.attrib["k"], x.attrib["v"]) for x in element if x.tag == "tag"]
|
||||||
)
|
)
|
||||||
return cls(
|
return cls(
|
||||||
|
@ -200,7 +200,7 @@ class OSMWay(Tagged):
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_from_structure(
|
def parse_from_structure(
|
||||||
cls, structure: dict[str, Any], nodes: dict[int, OSMNode]
|
cls, structure: Dict[str, Any], nodes: Dict[int, OSMNode]
|
||||||
) -> "OSMWay":
|
) -> "OSMWay":
|
||||||
"""
|
"""
|
||||||
Parse way from Overpass-like structure.
|
Parse way from Overpass-like structure.
|
||||||
|
@ -242,7 +242,7 @@ class OSMRelation(Tagged):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id_: int
|
id_: int
|
||||||
members: Optional[list[OSMMember]]
|
members: Optional[List[OSMMember]]
|
||||||
visible: Optional[str] = None
|
visible: Optional[str] = None
|
||||||
changeset: Optional[str] = None
|
changeset: Optional[str] = None
|
||||||
timestamp: Optional[datetime] = None
|
timestamp: Optional[datetime] = None
|
||||||
|
@ -253,8 +253,8 @@ class OSMRelation(Tagged):
|
||||||
def from_xml_structure(cls, element: Element) -> "OSMRelation":
|
def from_xml_structure(cls, element: Element) -> "OSMRelation":
|
||||||
"""Parse relation from OSM XML `<relation>` element."""
|
"""Parse relation from OSM XML `<relation>` element."""
|
||||||
attributes = element.attrib
|
attributes = element.attrib
|
||||||
members: list[OSMMember] = []
|
members: List[OSMMember] = []
|
||||||
tags: dict[str, str] = {}
|
tags: Dict[str, str] = {}
|
||||||
for subelement in element:
|
for subelement in element:
|
||||||
if subelement.tag == "member":
|
if subelement.tag == "member":
|
||||||
subattributes = subelement.attrib
|
subattributes = subelement.attrib
|
||||||
|
@ -281,7 +281,7 @@ class OSMRelation(Tagged):
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def parse_from_structure(cls, structure: dict[str, Any]) -> "OSMRelation":
|
def parse_from_structure(cls, structure: Dict[str, Any]) -> "OSMRelation":
|
||||||
"""
|
"""
|
||||||
Parse relation from Overpass-like structure.
|
Parse relation from Overpass-like structure.
|
||||||
|
|
||||||
|
@ -311,12 +311,12 @@ class OSMData:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.nodes: dict[int, OSMNode] = {}
|
self.nodes: Dict[int, OSMNode] = {}
|
||||||
self.ways: dict[int, OSMWay] = {}
|
self.ways: Dict[int, OSMWay] = {}
|
||||||
self.relations: dict[int, OSMRelation] = {}
|
self.relations: Dict[int, OSMRelation] = {}
|
||||||
|
|
||||||
self.authors: set[str] = set()
|
self.authors: Set[str] = set()
|
||||||
self.levels: set[float] = set()
|
self.levels: Set[float] = set()
|
||||||
self.time: MinMax = MinMax()
|
self.time: MinMax = MinMax()
|
||||||
self.view_box: Optional[BoundaryBox] = None
|
self.view_box: Optional[BoundaryBox] = None
|
||||||
self.equator_length: float = 40_075_017.0
|
self.equator_length: float = 40_075_017.0
|
||||||
|
@ -364,8 +364,8 @@ class OSMData:
|
||||||
with file_name.open() as input_file:
|
with file_name.open() as input_file:
|
||||||
structure = json.load(input_file)
|
structure = json.load(input_file)
|
||||||
|
|
||||||
node_map: dict[int, OSMNode] = {}
|
node_map: Dict[int, OSMNode] = {}
|
||||||
way_map: dict[int, OSMWay] = {}
|
way_map: Dict[int, OSMWay] = {}
|
||||||
|
|
||||||
for element in structure["elements"]:
|
for element in structure["elements"]:
|
||||||
if element["type"] == "node":
|
if element["type"] == "node":
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
"""
|
"""
|
||||||
Point: node representation on the map.
|
Point: node representation on the map.
|
||||||
"""
|
"""
|
||||||
from typing import Optional
|
from typing import Dict, List, Optional, Set
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
@ -53,9 +53,9 @@ class Point(Tagged):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
icon_set: IconSet,
|
icon_set: IconSet,
|
||||||
labels: list[Label],
|
labels: List[Label],
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
processed: set[str],
|
processed: Set[str],
|
||||||
point: np.ndarray,
|
point: np.ndarray,
|
||||||
priority: float = 0,
|
priority: float = 0,
|
||||||
is_for_node: bool = True,
|
is_for_node: bool = True,
|
||||||
|
@ -67,8 +67,8 @@ class Point(Tagged):
|
||||||
assert point is not None
|
assert point is not None
|
||||||
|
|
||||||
self.icon_set: IconSet = icon_set
|
self.icon_set: IconSet = icon_set
|
||||||
self.labels: list[Label] = labels
|
self.labels: List[Label] = labels
|
||||||
self.processed: set[str] = processed
|
self.processed: Set[str] = processed
|
||||||
self.point: np.ndarray = point
|
self.point: np.ndarray = point
|
||||||
self.priority: float = priority
|
self.priority: float = priority
|
||||||
self.layer: float = 0
|
self.layer: float = 0
|
||||||
|
@ -92,7 +92,7 @@ class Point(Tagged):
|
||||||
return
|
return
|
||||||
|
|
||||||
position: np.ndarray = self.point + np.array((0, self.y))
|
position: np.ndarray = self.point + np.array((0, self.y))
|
||||||
tags: Optional[dict[str, str]] = (
|
tags: Optional[Dict[str, str]] = (
|
||||||
self.tags if self.add_tooltips else None
|
self.tags if self.add_tooltips else None
|
||||||
)
|
)
|
||||||
self.main_icon_painted: bool = self.draw_point_shape(
|
self.main_icon_painted: bool = self.draw_point_shape(
|
||||||
|
@ -135,7 +135,7 @@ class Point(Tagged):
|
||||||
icon: Icon,
|
icon: Icon,
|
||||||
position: np.ndarray,
|
position: np.ndarray,
|
||||||
occupied: Occupied,
|
occupied: Occupied,
|
||||||
tags: Optional[dict[str, str]] = None,
|
tags: Optional[Dict[str, str]] = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""Draw one combined icon and its outline."""
|
"""Draw one combined icon and its outline."""
|
||||||
# Down-cast floats to integers to make icons pixel-perfect.
|
# Down-cast floats to integers to make icons pixel-perfect.
|
||||||
|
@ -166,7 +166,7 @@ class Point(Tagged):
|
||||||
label_mode: str = LabelMode.MAIN,
|
label_mode: str = LabelMode.MAIN,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Draw all labels."""
|
"""Draw all labels."""
|
||||||
labels: list[Label]
|
labels: List[Label]
|
||||||
|
|
||||||
if label_mode == LabelMode.MAIN:
|
if label_mode == LabelMode.MAIN:
|
||||||
labels = self.labels[:1]
|
labels = self.labels[:1]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
WIP: road shape drawing.
|
WIP: road shape drawing.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any, Optional
|
from typing import Any, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import svgwrite
|
import svgwrite
|
||||||
|
@ -62,7 +62,7 @@ class RoadPart:
|
||||||
self,
|
self,
|
||||||
point_1: np.ndarray,
|
point_1: np.ndarray,
|
||||||
point_2: np.ndarray,
|
point_2: np.ndarray,
|
||||||
lanes: list[Lane],
|
lanes: List[Lane],
|
||||||
scale: float,
|
scale: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
|
@ -72,7 +72,7 @@ class RoadPart:
|
||||||
"""
|
"""
|
||||||
self.point_1: np.ndarray = point_1
|
self.point_1: np.ndarray = point_1
|
||||||
self.point_2: np.ndarray = point_2
|
self.point_2: np.ndarray = point_2
|
||||||
self.lanes: list[Lane] = lanes
|
self.lanes: List[Lane] = lanes
|
||||||
if lanes:
|
if lanes:
|
||||||
self.width = sum(map(lambda x: x.get_width(scale), lanes))
|
self.width = sum(map(lambda x: x.get_width(scale), lanes))
|
||||||
else:
|
else:
|
||||||
|
@ -284,8 +284,8 @@ class Intersection:
|
||||||
points of the road parts should be the same.
|
points of the road parts should be the same.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, parts: list[RoadPart]) -> None:
|
def __init__(self, parts: List[RoadPart]) -> None:
|
||||||
self.parts: list[RoadPart] = sorted(parts, key=lambda x: x.get_angle())
|
self.parts: List[RoadPart] = sorted(parts, key=lambda x: x.get_angle())
|
||||||
|
|
||||||
for index in range(len(self.parts)):
|
for index in range(len(self.parts)):
|
||||||
next_index: int = 0 if index == len(self.parts) - 1 else index + 1
|
next_index: int = 0 if index == len(self.parts) - 1 else index + 1
|
||||||
|
@ -368,20 +368,20 @@ class Road(Tagged):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
nodes: list[OSMNode],
|
nodes: List[OSMNode],
|
||||||
matcher: RoadMatcher,
|
matcher: RoadMatcher,
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(tags)
|
super().__init__(tags)
|
||||||
self.nodes: list[OSMNode] = nodes
|
self.nodes: List[OSMNode] = nodes
|
||||||
self.matcher: RoadMatcher = matcher
|
self.matcher: RoadMatcher = matcher
|
||||||
|
|
||||||
self.line: Polyline = Polyline(
|
self.line: Polyline = Polyline(
|
||||||
[flinger.fling(x.coordinates) for x in self.nodes]
|
[flinger.fling(x.coordinates) for x in self.nodes]
|
||||||
)
|
)
|
||||||
self.width: Optional[float] = matcher.default_width
|
self.width: Optional[float] = matcher.default_width
|
||||||
self.lanes: list[Lane] = []
|
self.lanes: List[Lane] = []
|
||||||
|
|
||||||
if "lanes" in tags:
|
if "lanes" in tags:
|
||||||
try:
|
try:
|
||||||
|
@ -391,7 +391,7 @@ class Road(Tagged):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
if "width:lanes" in tags:
|
if "width:lanes" in tags:
|
||||||
widths: list[float] = list(
|
widths: List[float] = list(
|
||||||
map(float, tags["width:lanes"].split("|"))
|
map(float, tags["width:lanes"].split("|"))
|
||||||
)
|
)
|
||||||
if len(widths) == len(self.lanes):
|
if len(widths) == len(self.lanes):
|
||||||
|
@ -434,7 +434,7 @@ class Road(Tagged):
|
||||||
scale: float = flinger.get_scale(self.nodes[0].coordinates)
|
scale: float = flinger.get_scale(self.nodes[0].coordinates)
|
||||||
path_commands: str = self.line.get_path()
|
path_commands: str = self.line.get_path()
|
||||||
path: Path = Path(d=path_commands)
|
path: Path = Path(d=path_commands)
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": "none",
|
"fill": "none",
|
||||||
"stroke": color.hex,
|
"stroke": color.hex,
|
||||||
"stroke-linecap": "butt",
|
"stroke-linecap": "butt",
|
||||||
|
@ -454,7 +454,7 @@ class Road(Tagged):
|
||||||
-self.width / 2 + index * self.width / len(self.lanes)
|
-self.width / 2 + index * self.width / len(self.lanes)
|
||||||
)
|
)
|
||||||
path: Path = Path(d=self.line.get_path(parallel_offset))
|
path: Path = Path(d=self.line.get_path(parallel_offset))
|
||||||
style: dict[str, Any] = {
|
style: Dict[str, Any] = {
|
||||||
"fill": "none",
|
"fill": "none",
|
||||||
"stroke": color.hex,
|
"stroke": color.hex,
|
||||||
"stroke-linejoin": "round",
|
"stroke-linejoin": "round",
|
||||||
|
@ -467,7 +467,7 @@ class Road(Tagged):
|
||||||
|
|
||||||
def get_curve_points(
|
def get_curve_points(
|
||||||
road: Road, scale: float, center: np.ndarray, road_end: np.ndarray
|
road: Road, scale: float, center: np.ndarray, road_end: np.ndarray
|
||||||
) -> list[np.ndarray]:
|
) -> List[np.ndarray]:
|
||||||
"""
|
"""
|
||||||
:param road: road segment
|
:param road: road segment
|
||||||
:param scale: current zoom scale
|
:param scale: current zoom scale
|
||||||
|
@ -492,11 +492,11 @@ class Connector:
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connections: list[tuple[Road, int]],
|
connections: List[Tuple[Road, int]],
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
scale: float,
|
scale: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
self.connections: list[tuple[Road, int]] = connections
|
self.connections: List[Tuple[Road, int]] = connections
|
||||||
self.road_1: Road = connections[0][0]
|
self.road_1: Road = connections[0][0]
|
||||||
self.index_1: int = connections[0][1]
|
self.index_1: int = connections[0][1]
|
||||||
|
|
||||||
|
@ -520,7 +520,7 @@ class SimpleConnector(Connector):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connections: list[tuple[Road, int]],
|
connections: List[Tuple[Road, int]],
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
scale: float,
|
scale: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -558,7 +558,7 @@ class ComplexConnector(Connector):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connections: list[tuple[Road, int]],
|
connections: List[Tuple[Road, int]],
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
scale: float,
|
scale: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -574,10 +574,10 @@ class ComplexConnector(Connector):
|
||||||
node: OSMNode = self.road_1.nodes[self.index_1]
|
node: OSMNode = self.road_1.nodes[self.index_1]
|
||||||
point: np.ndarray = flinger.fling(node.coordinates)
|
point: np.ndarray = flinger.fling(node.coordinates)
|
||||||
|
|
||||||
points_1: list[np.ndarray] = get_curve_points(
|
points_1: List[np.ndarray] = get_curve_points(
|
||||||
self.road_1, scale, point, self.road_1.line.points[self.index_1]
|
self.road_1, scale, point, self.road_1.line.points[self.index_1]
|
||||||
)
|
)
|
||||||
points_2: list[np.ndarray] = get_curve_points(
|
points_2: List[np.ndarray] = get_curve_points(
|
||||||
self.road_2, scale, point, self.road_2.line.points[self.index_2]
|
self.road_2, scale, point, self.road_2.line.points[self.index_2]
|
||||||
)
|
)
|
||||||
# fmt: off
|
# fmt: off
|
||||||
|
@ -626,7 +626,7 @@ class SimpleIntersection(Connector):
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
connections: list[tuple[Road, int]],
|
connections: List[Tuple[Road, int]],
|
||||||
flinger: Flinger,
|
flinger: Flinger,
|
||||||
scale: float,
|
scale: float,
|
||||||
) -> None:
|
) -> None:
|
||||||
|
@ -661,8 +661,8 @@ class Roads:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self) -> None:
|
def __init__(self) -> None:
|
||||||
self.roads: list[Road] = []
|
self.roads: List[Road] = []
|
||||||
self.connections: dict[int, list[tuple[Road, int]]] = {}
|
self.connections: Dict[int, List[Tuple[Road, int]]] = {}
|
||||||
|
|
||||||
def append(self, road: Road) -> None:
|
def append(self, road: Road) -> None:
|
||||||
"""Add road and update connections."""
|
"""Add road and update connections."""
|
||||||
|
@ -679,8 +679,8 @@ class Roads:
|
||||||
return
|
return
|
||||||
|
|
||||||
scale: float = flinger.get_scale(self.roads[0].nodes[0].coordinates)
|
scale: float = flinger.get_scale(self.roads[0].nodes[0].coordinates)
|
||||||
layered_roads: dict[float, list[Road]] = {}
|
layered_roads: Dict[float, List[Road]] = {}
|
||||||
layered_connectors: dict[float, list[Connector]] = {}
|
layered_connectors: Dict[float, List[Connector]] = {}
|
||||||
|
|
||||||
for road in self.roads:
|
for road in self.roads:
|
||||||
if road.layer not in layered_roads:
|
if road.layer not in layered_roads:
|
||||||
|
@ -688,7 +688,7 @@ class Roads:
|
||||||
layered_roads[road.layer].append(road)
|
layered_roads[road.layer].append(road)
|
||||||
|
|
||||||
for id_ in self.connections:
|
for id_ in self.connections:
|
||||||
connected: list[tuple[Road, int]] = self.connections[id_]
|
connected: List[Tuple[Road, int]] = self.connections[id_]
|
||||||
connector: Connector
|
connector: Connector
|
||||||
|
|
||||||
if len(self.connections[id_]) == 2:
|
if len(self.connections[id_]) == 2:
|
||||||
|
@ -706,10 +706,10 @@ class Roads:
|
||||||
layered_connectors[connector.layer].append(connector)
|
layered_connectors[connector.layer].append(connector)
|
||||||
|
|
||||||
for layer in sorted(layered_roads.keys()):
|
for layer in sorted(layered_roads.keys()):
|
||||||
roads: list[Road] = sorted(
|
roads: List[Road] = sorted(
|
||||||
layered_roads[layer], key=lambda x: x.matcher.priority
|
layered_roads[layer], key=lambda x: x.matcher.priority
|
||||||
)
|
)
|
||||||
connectors: list[Connector]
|
connectors: List[Connector]
|
||||||
if layer in layered_connectors:
|
if layer in layered_connectors:
|
||||||
connectors = layered_connectors[layer]
|
connectors = layered_connectors[layer]
|
||||||
else:
|
else:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import logging
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Optional, Union
|
from typing import Any, Dict, List, Optional, Set, Tuple, Union
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
import yaml
|
import yaml
|
||||||
|
@ -27,7 +27,7 @@ from map_machine.text import Label, get_address, get_text
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
IconDescription = list[Union[str, dict[str, str]]]
|
IconDescription = List[Union[str, Dict[str, str]]]
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -36,7 +36,7 @@ class LineStyle:
|
||||||
SVG line style and its priority.
|
SVG line style and its priority.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
style: dict[str, Union[int, float, str]]
|
style: Dict[str, Union[int, float, str]]
|
||||||
priority: float = 0.0
|
priority: float = 0.0
|
||||||
|
|
||||||
|
|
||||||
|
@ -54,7 +54,7 @@ class MatchingType(Enum):
|
||||||
def is_matched_tag(
|
def is_matched_tag(
|
||||||
matcher_tag_key: str,
|
matcher_tag_key: str,
|
||||||
matcher_tag_value: Union[str, list],
|
matcher_tag_value: Union[str, list],
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
) -> MatchingType:
|
) -> MatchingType:
|
||||||
"""
|
"""
|
||||||
Check whether element tags contradict tag matcher.
|
Check whether element tags contradict tag matcher.
|
||||||
|
@ -90,7 +90,7 @@ def get_selector(key: str, value: str, prefix: str = "") -> str:
|
||||||
return f'[{key}="{value}"]'
|
return f'[{key}="{value}"]'
|
||||||
|
|
||||||
|
|
||||||
def match_location(restrictions: dict[str, str], country: str) -> bool:
|
def match_location(restrictions: Dict[str, str], country: str) -> bool:
|
||||||
"""Check whether country is matched by location restrictions."""
|
"""Check whether country is matched by location restrictions."""
|
||||||
if "exclude" in restrictions and country in restrictions["exclude"]:
|
if "exclude" in restrictions and country in restrictions["exclude"]:
|
||||||
return False
|
return False
|
||||||
|
@ -109,11 +109,11 @@ class Matcher:
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, structure: dict[str, Any], group: Optional[dict[str, Any]] = None
|
self, structure: Dict[str, Any], group: Optional[Dict[str, Any]] = None
|
||||||
) -> None:
|
) -> None:
|
||||||
self.tags: dict[str, str] = structure["tags"]
|
self.tags: Dict[str, str] = structure["tags"]
|
||||||
|
|
||||||
self.exception: dict[str, str] = {}
|
self.exception: Dict[str, str] = {}
|
||||||
if "exception" in structure:
|
if "exception" in structure:
|
||||||
self.exception = structure["exception"]
|
self.exception = structure["exception"]
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ class Matcher:
|
||||||
if "replace_shapes" in structure:
|
if "replace_shapes" in structure:
|
||||||
self.replace_shapes = structure["replace_shapes"]
|
self.replace_shapes = structure["replace_shapes"]
|
||||||
|
|
||||||
self.location_restrictions: dict[str, str] = {}
|
self.location_restrictions: Dict[str, str] = {}
|
||||||
if "location_restrictions" in structure:
|
if "location_restrictions" in structure:
|
||||||
self.location_restrictions = structure["location_restrictions"]
|
self.location_restrictions = structure["location_restrictions"]
|
||||||
|
|
||||||
|
@ -137,7 +137,7 @@ class Matcher:
|
||||||
|
|
||||||
def is_matched(
|
def is_matched(
|
||||||
self,
|
self,
|
||||||
tags: dict[str, str],
|
tags: Dict[str, str],
|
||||||
configuration: Optional[MapConfiguration] = None,
|
configuration: Optional[MapConfiguration] = None,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
|
@ -186,11 +186,11 @@ class Matcher:
|
||||||
[get_selector(x, y, prefix) for (x, y) in self.tags.items()]
|
[get_selector(x, y, prefix) for (x, y) in self.tags.items()]
|
||||||
)
|
)
|
||||||
|
|
||||||
def get_clean_shapes(self) -> Optional[list[str]]:
|
def get_clean_shapes(self) -> Optional[List[str]]:
|
||||||
"""Get list of shape identifiers for shapes."""
|
"""Get list of shape identifiers for shapes."""
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def get_style(self) -> dict[str, Any]:
|
def get_style(self) -> Dict[str, Any]:
|
||||||
"""Return way SVG style."""
|
"""Return way SVG style."""
|
||||||
return {}
|
return {}
|
||||||
|
|
||||||
|
@ -201,7 +201,7 @@ class NodeMatcher(Matcher):
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self, structure: dict[str, Any], group: dict[str, Any]
|
self, structure: Dict[str, Any], group: Dict[str, Any]
|
||||||
) -> None:
|
) -> None:
|
||||||
# Dictionary with tag keys and values, value lists, or "*"
|
# Dictionary with tag keys and values, value lists, or "*"
|
||||||
super().__init__(structure, group)
|
super().__init__(structure, group)
|
||||||
|
@ -238,7 +238,7 @@ class NodeMatcher(Matcher):
|
||||||
if "with_icon" in structure:
|
if "with_icon" in structure:
|
||||||
self.with_icon = structure["with_icon"]
|
self.with_icon = structure["with_icon"]
|
||||||
|
|
||||||
def get_clean_shapes(self) -> Optional[list[str]]:
|
def get_clean_shapes(self) -> Optional[List[str]]:
|
||||||
"""Get list of shape identifiers for shapes."""
|
"""Get list of shape identifiers for shapes."""
|
||||||
if not self.shapes:
|
if not self.shapes:
|
||||||
return None
|
return None
|
||||||
|
@ -250,11 +250,11 @@ class WayMatcher(Matcher):
|
||||||
Special tag matcher for ways.
|
Special tag matcher for ways.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, structure: dict[str, Any], scheme: "Scheme") -> None:
|
def __init__(self, structure: Dict[str, Any], scheme: "Scheme") -> None:
|
||||||
super().__init__(structure)
|
super().__init__(structure)
|
||||||
self.style: dict[str, Any] = {"fill": "none"}
|
self.style: Dict[str, Any] = {"fill": "none"}
|
||||||
if "style" in structure:
|
if "style" in structure:
|
||||||
style: dict[str, Any] = structure["style"]
|
style: Dict[str, Any] = structure["style"]
|
||||||
for key in style:
|
for key in style:
|
||||||
if str(style[key]).endswith("_color"):
|
if str(style[key]).endswith("_color"):
|
||||||
self.style[key] = scheme.get_color(style[key]).hex.upper()
|
self.style[key] = scheme.get_color(style[key]).hex.upper()
|
||||||
|
@ -264,7 +264,7 @@ class WayMatcher(Matcher):
|
||||||
if "priority" in structure:
|
if "priority" in structure:
|
||||||
self.priority = structure["priority"]
|
self.priority = structure["priority"]
|
||||||
|
|
||||||
def get_style(self) -> dict[str, Any]:
|
def get_style(self) -> Dict[str, Any]:
|
||||||
"""Return way SVG style."""
|
"""Return way SVG style."""
|
||||||
return self.style
|
return self.style
|
||||||
|
|
||||||
|
@ -274,7 +274,7 @@ class RoadMatcher(Matcher):
|
||||||
Special tag matcher for highways.
|
Special tag matcher for highways.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, structure: dict[str, Any], scheme: "Scheme") -> None:
|
def __init__(self, structure: Dict[str, Any], scheme: "Scheme") -> None:
|
||||||
super().__init__(structure)
|
super().__init__(structure)
|
||||||
self.border_color: Color = Color(
|
self.border_color: Color = Color(
|
||||||
scheme.get_color(structure["border_color"])
|
scheme.get_color(structure["border_color"])
|
||||||
|
@ -287,7 +287,7 @@ class RoadMatcher(Matcher):
|
||||||
if "priority" in structure:
|
if "priority" in structure:
|
||||||
self.priority = structure["priority"]
|
self.priority = structure["priority"]
|
||||||
|
|
||||||
def get_priority(self, tags: dict[str, str]) -> float:
|
def get_priority(self, tags: Dict[str, str]) -> float:
|
||||||
layer: float = 0
|
layer: float = 0
|
||||||
if "layer" in tags:
|
if "layer" in tags:
|
||||||
layer = float(tags.get("layer"))
|
layer = float(tags.get("layer"))
|
||||||
|
@ -307,33 +307,33 @@ class Scheme:
|
||||||
specification
|
specification
|
||||||
"""
|
"""
|
||||||
with file_name.open() as input_file:
|
with file_name.open() as input_file:
|
||||||
content: dict[str, Any] = yaml.load(
|
content: Dict[str, Any] = yaml.load(
|
||||||
input_file.read(), Loader=yaml.FullLoader
|
input_file.read(), Loader=yaml.FullLoader
|
||||||
)
|
)
|
||||||
self.node_matchers: list[NodeMatcher] = []
|
self.node_matchers: List[NodeMatcher] = []
|
||||||
for group in content["node_icons"]:
|
for group in content["node_icons"]:
|
||||||
for element in group["tags"]:
|
for element in group["tags"]:
|
||||||
self.node_matchers.append(NodeMatcher(element, group))
|
self.node_matchers.append(NodeMatcher(element, group))
|
||||||
|
|
||||||
self.colors: dict[str, str] = content["colors"]
|
self.colors: Dict[str, str] = content["colors"]
|
||||||
self.material_colors: dict[str, str] = content["material_colors"]
|
self.material_colors: Dict[str, str] = content["material_colors"]
|
||||||
|
|
||||||
self.way_matchers: list[WayMatcher] = [
|
self.way_matchers: List[WayMatcher] = [
|
||||||
WayMatcher(x, self) for x in content["ways"]
|
WayMatcher(x, self) for x in content["ways"]
|
||||||
]
|
]
|
||||||
self.road_matchers: list[RoadMatcher] = [
|
self.road_matchers: List[RoadMatcher] = [
|
||||||
RoadMatcher(x, self) for x in content["roads"]
|
RoadMatcher(x, self) for x in content["roads"]
|
||||||
]
|
]
|
||||||
self.area_matchers: list[Matcher] = [
|
self.area_matchers: List[Matcher] = [
|
||||||
Matcher(x) for x in content["area_tags"]
|
Matcher(x) for x in content["area_tags"]
|
||||||
]
|
]
|
||||||
self.tags_to_write: list[str] = content["tags_to_write"]
|
self.tags_to_write: List[str] = content["tags_to_write"]
|
||||||
self.prefix_to_write: list[str] = content["prefix_to_write"]
|
self.prefix_to_write: List[str] = content["prefix_to_write"]
|
||||||
self.tags_to_skip: list[str] = content["tags_to_skip"]
|
self.tags_to_skip: List[str] = content["tags_to_skip"]
|
||||||
self.prefix_to_skip: list[str] = content["prefix_to_skip"]
|
self.prefix_to_skip: List[str] = content["prefix_to_skip"]
|
||||||
|
|
||||||
# Storage for created icon sets.
|
# Storage for created icon sets.
|
||||||
self.cache: dict[str, tuple[IconSet, int]] = {}
|
self.cache: Dict[str, Tuple[IconSet, int]] = {}
|
||||||
|
|
||||||
def get_color(self, color: str) -> Color:
|
def get_color(self, color: str) -> Color:
|
||||||
"""
|
"""
|
||||||
|
@ -384,10 +384,10 @@ class Scheme:
|
||||||
def get_icon(
|
def get_icon(
|
||||||
self,
|
self,
|
||||||
extractor: ShapeExtractor,
|
extractor: ShapeExtractor,
|
||||||
tags: dict[str, Any],
|
tags: Dict[str, Any],
|
||||||
processed: set[str],
|
processed: Set[str],
|
||||||
configuration: MapConfiguration = MapConfiguration(),
|
configuration: MapConfiguration = MapConfiguration(),
|
||||||
) -> tuple[Optional[IconSet], int]:
|
) -> Tuple[Optional[IconSet], int]:
|
||||||
"""
|
"""
|
||||||
Construct icon set.
|
Construct icon set.
|
||||||
|
|
||||||
|
@ -404,7 +404,7 @@ class Scheme:
|
||||||
return self.cache[tags_hash]
|
return self.cache[tags_hash]
|
||||||
|
|
||||||
main_icon: Optional[Icon] = None
|
main_icon: Optional[Icon] = None
|
||||||
extra_icons: list[Icon] = []
|
extra_icons: List[Icon] = []
|
||||||
priority: int = 0
|
priority: int = 0
|
||||||
|
|
||||||
index: int = 0
|
index: int = 0
|
||||||
|
@ -419,7 +419,7 @@ class Scheme:
|
||||||
and not matcher.check_zoom_level(configuration.zoom_level)
|
and not matcher.check_zoom_level(configuration.zoom_level)
|
||||||
):
|
):
|
||||||
return None, 0
|
return None, 0
|
||||||
matcher_tags: set[str] = set(matcher.tags.keys())
|
matcher_tags: Set[str] = set(matcher.tags.keys())
|
||||||
priority = len(self.node_matchers) - index
|
priority = len(self.node_matchers) - index
|
||||||
if not matcher.draw:
|
if not matcher.draw:
|
||||||
processed |= matcher_tags
|
processed |= matcher_tags
|
||||||
|
@ -492,7 +492,7 @@ class Scheme:
|
||||||
|
|
||||||
return returned, priority
|
return returned, priority
|
||||||
|
|
||||||
def get_style(self, tags: dict[str, Any]) -> list[LineStyle]:
|
def get_style(self, tags: Dict[str, Any]) -> List[LineStyle]:
|
||||||
"""Get line style based on tags and scale."""
|
"""Get line style based on tags and scale."""
|
||||||
line_styles = []
|
line_styles = []
|
||||||
|
|
||||||
|
@ -504,7 +504,7 @@ class Scheme:
|
||||||
|
|
||||||
return line_styles
|
return line_styles
|
||||||
|
|
||||||
def get_road(self, tags: dict[str, Any]) -> Optional[RoadMatcher]:
|
def get_road(self, tags: Dict[str, Any]) -> Optional[RoadMatcher]:
|
||||||
"""Get road matcher if tags are matched."""
|
"""Get road matcher if tags are matched."""
|
||||||
for matcher in self.road_matchers:
|
for matcher in self.road_matchers:
|
||||||
if not matcher.is_matched(tags):
|
if not matcher.is_matched(tags):
|
||||||
|
@ -513,10 +513,10 @@ class Scheme:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def construct_text(
|
def construct_text(
|
||||||
self, tags: dict[str, str], draw_captions: str, processed: set[str]
|
self, tags: Dict[str, str], draw_captions: str, processed: Set[str]
|
||||||
) -> list[Label]:
|
) -> List[Label]:
|
||||||
"""Construct labels for not processed tags."""
|
"""Construct labels for not processed tags."""
|
||||||
texts: list[Label] = []
|
texts: List[Label] = []
|
||||||
|
|
||||||
name = None
|
name = None
|
||||||
alt_name = None
|
alt_name = None
|
||||||
|
@ -542,7 +542,7 @@ class Scheme:
|
||||||
alt_name = ""
|
alt_name = ""
|
||||||
alt_name += "ex " + tags["old_name"]
|
alt_name += "ex " + tags["old_name"]
|
||||||
|
|
||||||
address: list[str] = get_address(tags, draw_captions, processed)
|
address: List[str] = get_address(tags, draw_captions, processed)
|
||||||
|
|
||||||
if name:
|
if name:
|
||||||
texts.append(Label(name, Color("black")))
|
texts.append(Label(name, Color("black")))
|
||||||
|
@ -587,7 +587,7 @@ class Scheme:
|
||||||
texts.append(Label(tags[tag]))
|
texts.append(Label(tags[tag]))
|
||||||
return texts
|
return texts
|
||||||
|
|
||||||
def is_area(self, tags: dict[str, str]) -> bool:
|
def is_area(self, tags: Dict[str, str]) -> bool:
|
||||||
"""Check whether way described by tags is area."""
|
"""Check whether way described by tags is area."""
|
||||||
for matcher in self.area_matchers:
|
for matcher in self.area_matchers:
|
||||||
if matcher.is_matched(tags):
|
if matcher.is_matched(tags):
|
||||||
|
@ -595,7 +595,7 @@ class Scheme:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def process_ignored(
|
def process_ignored(
|
||||||
self, tags: dict[str, str], processed: set[str]
|
self, tags: Dict[str, str], processed: Set[str]
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Mark all ignored tag as processed.
|
Mark all ignored tag as processed.
|
||||||
|
@ -607,7 +607,7 @@ class Scheme:
|
||||||
|
|
||||||
def get_shape_specification(
|
def get_shape_specification(
|
||||||
self,
|
self,
|
||||||
structure: Union[str, dict[str, Any]],
|
structure: Union[str, Dict[str, Any]],
|
||||||
extractor: ShapeExtractor,
|
extractor: ShapeExtractor,
|
||||||
color: Color = DEFAULT_COLOR,
|
color: Color = DEFAULT_COLOR,
|
||||||
) -> ShapeSpecification:
|
) -> ShapeSpecification:
|
||||||
|
|
|
@ -5,7 +5,7 @@ import argparse
|
||||||
import logging
|
import logging
|
||||||
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
from http.server import HTTPServer, SimpleHTTPRequestHandler
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import cairosvg
|
import cairosvg
|
||||||
|
|
||||||
|
@ -28,14 +28,14 @@ class _Handler(SimpleHTTPRequestHandler):
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
request: bytes,
|
request: bytes,
|
||||||
client_address: tuple[str, int],
|
client_address: Tuple[str, int],
|
||||||
server: HTTPServer,
|
server: HTTPServer,
|
||||||
) -> None:
|
) -> None:
|
||||||
super().__init__(request, client_address, server)
|
super().__init__(request, client_address, server)
|
||||||
|
|
||||||
def do_GET(self) -> None:
|
def do_GET(self) -> None:
|
||||||
"""Serve a GET request."""
|
"""Serve a GET request."""
|
||||||
parts: list[str] = self.path.split("/")
|
parts: List[str] = self.path.split("/")
|
||||||
if not (len(parts) == 5 and not parts[0] and parts[1] == "tiles"):
|
if not (len(parts) == 5 and not parts[0] and parts[1] == "tiles"):
|
||||||
return
|
return
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,7 @@ import json
|
||||||
import logging
|
import logging
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from map_machine import (
|
from map_machine import (
|
||||||
__author__,
|
__author__,
|
||||||
|
@ -54,7 +55,7 @@ class TaginfoProjectFile:
|
||||||
):
|
):
|
||||||
key: str = list(matcher.tags.keys())[0]
|
key: str = list(matcher.tags.keys())[0]
|
||||||
value: str = matcher.tags[key]
|
value: str = matcher.tags[key]
|
||||||
ids: list[str] = [
|
ids: List[str] = [
|
||||||
(x if isinstance(x, str) else x["shape"])
|
(x if isinstance(x, str) else x["shape"])
|
||||||
for x in matcher.shapes
|
for x in matcher.shapes
|
||||||
]
|
]
|
||||||
|
|
|
@ -2,7 +2,7 @@
|
||||||
OSM address tag processing.
|
OSM address tag processing.
|
||||||
"""
|
"""
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from typing import Any
|
from typing import Any, Dict, List, Set
|
||||||
|
|
||||||
from colour import Color
|
from colour import Color
|
||||||
|
|
||||||
|
@ -25,8 +25,8 @@ class Label:
|
||||||
|
|
||||||
|
|
||||||
def get_address(
|
def get_address(
|
||||||
tags: dict[str, Any], draw_captions_mode: str, processed: set[str]
|
tags: Dict[str, Any], draw_captions_mode: str, processed: Set[str]
|
||||||
) -> list[str]:
|
) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Construct address text list from the tags.
|
Construct address text list from the tags.
|
||||||
|
|
||||||
|
@ -34,7 +34,7 @@ def get_address(
|
||||||
:param draw_captions_mode: captions mode ("all", "main", or "no")
|
:param draw_captions_mode: captions mode ("all", "main", or "no")
|
||||||
:param processed: set of processed tag keys
|
:param processed: set of processed tag keys
|
||||||
"""
|
"""
|
||||||
address: list[str] = []
|
address: List[str] = []
|
||||||
|
|
||||||
if draw_captions_mode == "address":
|
if draw_captions_mode == "address":
|
||||||
if "addr:postcode" in tags:
|
if "addr:postcode" in tags:
|
||||||
|
@ -80,10 +80,10 @@ def format_frequency(value: str) -> str:
|
||||||
return f"{value} "
|
return f"{value} "
|
||||||
|
|
||||||
|
|
||||||
def get_text(tags: dict[str, Any], processed: set[str]) -> list[Label]:
|
def get_text(tags: Dict[str, Any], processed: Set[str]) -> List[Label]:
|
||||||
"""Get text representation of writable tags."""
|
"""Get text representation of writable tags."""
|
||||||
texts: list[Label] = []
|
texts: List[Label] = []
|
||||||
values: list[str] = []
|
values: List[str] = []
|
||||||
|
|
||||||
if "voltage:primary" in tags:
|
if "voltage:primary" in tags:
|
||||||
values.append(tags["voltage:primary"])
|
values.append(tags["voltage:primary"])
|
||||||
|
|
|
@ -8,7 +8,7 @@ import logging
|
||||||
import sys
|
import sys
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Optional
|
from typing import List, Optional, Tuple
|
||||||
|
|
||||||
import cairosvg
|
import cairosvg
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
@ -72,7 +72,7 @@ class Tile:
|
||||||
lat_deg: np.ndarray = np.degrees(lat_rad)
|
lat_deg: np.ndarray = np.degrees(lat_rad)
|
||||||
return np.array((lat_deg, lon_deg))
|
return np.array((lat_deg, lon_deg))
|
||||||
|
|
||||||
def get_boundary_box(self) -> tuple[np.ndarray, np.ndarray]:
|
def get_boundary_box(self) -> Tuple[np.ndarray, np.ndarray]:
|
||||||
"""
|
"""
|
||||||
Get geographical boundary box of the tile: north-west and south-east
|
Get geographical boundary box of the tile: north-west and south-east
|
||||||
points.
|
points.
|
||||||
|
@ -191,11 +191,11 @@ class Tile:
|
||||||
cairosvg.svg2png(file_obj=input_file, write_to=str(output_path))
|
cairosvg.svg2png(file_obj=input_file, write_to=str(output_path))
|
||||||
logging.info(f"SVG file is rasterized to {output_path}.")
|
logging.info(f"SVG file is rasterized to {output_path}.")
|
||||||
|
|
||||||
def subdivide(self, zoom_level: int) -> list["Tile"]:
|
def subdivide(self, zoom_level: int) -> List["Tile"]:
|
||||||
"""Get subtiles of the tile."""
|
"""Get subtiles of the tile."""
|
||||||
assert zoom_level >= self.zoom_level
|
assert zoom_level >= self.zoom_level
|
||||||
|
|
||||||
tiles: list["Tile"] = []
|
tiles: List["Tile"] = []
|
||||||
n: int = 2 ** (zoom_level - self.zoom_level)
|
n: int = 2 ** (zoom_level - self.zoom_level)
|
||||||
for i in range(n):
|
for i in range(n):
|
||||||
for j in range(n):
|
for j in range(n):
|
||||||
|
@ -214,7 +214,7 @@ class Tiles:
|
||||||
Collection of tiles.
|
Collection of tiles.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
tiles: list[Tile]
|
tiles: List[Tile]
|
||||||
tile_1: Tile # Left top tile.
|
tile_1: Tile # Left top tile.
|
||||||
tile_2: Tile # Right bottom tile.
|
tile_2: Tile # Right bottom tile.
|
||||||
zoom_level: int # OpenStreetMap zoom level.
|
zoom_level: int # OpenStreetMap zoom level.
|
||||||
|
@ -230,7 +230,7 @@ class Tiles:
|
||||||
:param boundary_box: area to be covered by tiles
|
:param boundary_box: area to be covered by tiles
|
||||||
:param zoom_level: zoom level in OpenStreetMap terminology
|
:param zoom_level: zoom level in OpenStreetMap terminology
|
||||||
"""
|
"""
|
||||||
tiles: list[Tile] = []
|
tiles: List[Tile] = []
|
||||||
tile_1: Tile = Tile.from_coordinates(
|
tile_1: Tile = Tile.from_coordinates(
|
||||||
boundary_box.get_left_top(), zoom_level
|
boundary_box.get_left_top(), zoom_level
|
||||||
)
|
)
|
||||||
|
@ -327,7 +327,7 @@ class Tiles:
|
||||||
for tile in self.tiles:
|
for tile in self.tiles:
|
||||||
x: int = tile.x - self.tile_1.x
|
x: int = tile.x - self.tile_1.x
|
||||||
y: int = tile.y - self.tile_1.y
|
y: int = tile.y - self.tile_1.y
|
||||||
area: tuple[int, int, int, int] = (
|
area: Tuple[int, int, int, int] = (
|
||||||
x * TILE_WIDTH,
|
x * TILE_WIDTH,
|
||||||
y * TILE_HEIGHT,
|
y * TILE_HEIGHT,
|
||||||
(x + 1) * TILE_WIDTH,
|
(x + 1) * TILE_WIDTH,
|
||||||
|
@ -413,7 +413,7 @@ class Tiles:
|
||||||
|
|
||||||
def subdivide(self, zoom_level: int) -> "Tiles":
|
def subdivide(self, zoom_level: int) -> "Tiles":
|
||||||
"""Get subtiles from tiles."""
|
"""Get subtiles from tiles."""
|
||||||
tiles: list[Tile] = []
|
tiles: List[Tile] = []
|
||||||
for tile in self.tiles:
|
for tile in self.tiles:
|
||||||
tiles += tile.subdivide(zoom_level)
|
tiles += tile.subdivide(zoom_level)
|
||||||
return Tiles(
|
return Tiles(
|
||||||
|
@ -429,9 +429,9 @@ class ScaleConfigurationException(Exception):
|
||||||
"""Wrong configuration format."""
|
"""Wrong configuration format."""
|
||||||
|
|
||||||
|
|
||||||
def parse_zoom_level(zoom_level_specification: str) -> list[int]:
|
def parse_zoom_level(zoom_level_specification: str) -> List[int]:
|
||||||
"""Parse zoom level specification."""
|
"""Parse zoom level specification."""
|
||||||
parts: list[str]
|
parts: List[str]
|
||||||
if "," in zoom_level_specification:
|
if "," in zoom_level_specification:
|
||||||
parts = zoom_level_specification.split(",")
|
parts = zoom_level_specification.split(",")
|
||||||
else:
|
else:
|
||||||
|
@ -444,7 +444,7 @@ def parse_zoom_level(zoom_level_specification: str) -> list[int]:
|
||||||
raise ScaleConfigurationException("Scale is too big.")
|
raise ScaleConfigurationException("Scale is too big.")
|
||||||
return parsed_zoom_level
|
return parsed_zoom_level
|
||||||
|
|
||||||
result: list[int] = []
|
result: List[int] = []
|
||||||
for part in parts:
|
for part in parts:
|
||||||
if "-" in part:
|
if "-" in part:
|
||||||
from_, to = part.split("-")
|
from_, to = part.split("-")
|
||||||
|
@ -463,7 +463,7 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
"""Simple user interface for tile generation."""
|
"""Simple user interface for tile generation."""
|
||||||
directory: Path = workspace.get_tile_path()
|
directory: Path = workspace.get_tile_path()
|
||||||
|
|
||||||
zoom_levels: list[int] = parse_zoom_level(options.zoom)
|
zoom_levels: List[int] = parse_zoom_level(options.zoom)
|
||||||
min_zoom_level: int = min(zoom_levels)
|
min_zoom_level: int = min(zoom_levels)
|
||||||
|
|
||||||
if options.input_file_name:
|
if options.input_file_name:
|
||||||
|
@ -478,7 +478,7 @@ def ui(options: argparse.Namespace) -> None:
|
||||||
)
|
)
|
||||||
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
tiles.draw(directory, Path(options.cache), configuration, osm_data)
|
||||||
elif options.coordinates:
|
elif options.coordinates:
|
||||||
coordinates: list[float] = list(
|
coordinates: List[float] = list(
|
||||||
map(float, options.coordinates.strip().split(","))
|
map(float, options.coordinates.strip().split(","))
|
||||||
)
|
)
|
||||||
min_tile: Tile = Tile.from_coordinates(
|
min_tile: Tile = Tile.from_coordinates(
|
||||||
|
|
|
@ -3,6 +3,7 @@ Command-line user interface.
|
||||||
"""
|
"""
|
||||||
import argparse
|
import argparse
|
||||||
import sys
|
import sys
|
||||||
|
from typing import Dict, List
|
||||||
|
|
||||||
from map_machine import __version__
|
from map_machine import __version__
|
||||||
from map_machine.map_configuration import BuildingMode, DrawingMode, LabelMode
|
from map_machine.map_configuration import BuildingMode, DrawingMode, LabelMode
|
||||||
|
@ -14,7 +15,7 @@ __email__ = "me@enzet.ru"
|
||||||
BOXES: str = " ▏▎▍▌▋▊▉"
|
BOXES: str = " ▏▎▍▌▋▊▉"
|
||||||
BOXES_LENGTH: int = len(BOXES)
|
BOXES_LENGTH: int = len(BOXES)
|
||||||
|
|
||||||
COMMANDS: dict[str, list[str]] = {
|
COMMANDS: Dict[str, List[str]] = {
|
||||||
"render": ["render", "-b", "10.000,20.000,10.001,20.001"],
|
"render": ["render", "-b", "10.000,20.000,10.001,20.001"],
|
||||||
"render_with_tooltips": [
|
"render_with_tooltips": [
|
||||||
"render",
|
"render",
|
||||||
|
@ -29,7 +30,7 @@ COMMANDS: dict[str, list[str]] = {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
def parse_arguments(args: list[str]) -> argparse.Namespace:
|
def parse_arguments(args: List[str]) -> argparse.Namespace:
|
||||||
"""Parse Map Machine command-line arguments."""
|
"""Parse Map Machine command-line arguments."""
|
||||||
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
parser: argparse.ArgumentParser = argparse.ArgumentParser(
|
||||||
description="Map Machine. OpenStreetMap renderer with custom icon set"
|
description="Map Machine. OpenStreetMap renderer with custom icon set"
|
||||||
|
@ -117,9 +118,15 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--show-tooltips",
|
"--show-tooltips",
|
||||||
help="add tooltips with tags for icons in SVG files",
|
help="add tooltips with tags for icons in SVG files",
|
||||||
action=argparse.BooleanOptionalAction,
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-show-tooltips",
|
||||||
|
dest="show_tooltips",
|
||||||
|
help="don't add tooltips with tags for icons in SVG files",
|
||||||
|
action="store_false",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--country",
|
"--country",
|
||||||
help="two-letter code (ISO 3166-1 alpha-2) of country, that should be "
|
help="two-letter code (ISO 3166-1 alpha-2) of country, that should be "
|
||||||
|
@ -256,24 +263,42 @@ def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None:
|
||||||
"""Add arguments for mapcss command."""
|
"""Add arguments for mapcss command."""
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--icons",
|
"--icons",
|
||||||
action=argparse.BooleanOptionalAction,
|
action="store_true",
|
||||||
default=True,
|
default=True,
|
||||||
help="add icons for nodes and areas",
|
help="add icons for nodes and areas",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-icons",
|
||||||
|
dest="icons",
|
||||||
|
action="store_false",
|
||||||
|
help="don't add icons for nodes and areas",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--ways",
|
"--ways",
|
||||||
action=argparse.BooleanOptionalAction,
|
action="store_true",
|
||||||
default=False,
|
default=False,
|
||||||
help="add style for ways and relations",
|
help="add style for ways and relations",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-ways",
|
||||||
|
dest="ways",
|
||||||
|
action="store_false",
|
||||||
|
help="don't add style for ways and relations",
|
||||||
|
)
|
||||||
parser.add_argument(
|
parser.add_argument(
|
||||||
"--lifecycle",
|
"--lifecycle",
|
||||||
action=argparse.BooleanOptionalAction,
|
action="store_true",
|
||||||
default=True,
|
default=True,
|
||||||
help="add icons for lifecycle tags; be careful: this will increase the "
|
help="add icons for lifecycle tags; be careful: this will increase the "
|
||||||
f"number of node and area selectors by {len(STAGES_OF_DECAY) + 1} "
|
f"number of node and area selectors by {len(STAGES_OF_DECAY) + 1} "
|
||||||
f"times",
|
f"times",
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
"--no-lifecycle",
|
||||||
|
dest="lifecycle",
|
||||||
|
action="store_false",
|
||||||
|
help="don't add icons for lifecycle tags",
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def progress_bar(
|
def progress_bar(
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Vector utility.
|
Vector utility.
|
||||||
"""
|
"""
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
|
@ -45,12 +47,12 @@ class Polyline:
|
||||||
List of connected points.
|
List of connected points.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, points: list[np.ndarray]) -> None:
|
def __init__(self, points: List[np.ndarray]) -> None:
|
||||||
self.points: list[np.ndarray] = points
|
self.points: List[np.ndarray] = points
|
||||||
|
|
||||||
def get_path(self, parallel_offset: float = 0) -> str:
|
def get_path(self, parallel_offset: float = 0) -> str:
|
||||||
"""Construct SVG path commands."""
|
"""Construct SVG path commands."""
|
||||||
points: list[np.ndarray]
|
points: List[np.ndarray]
|
||||||
try:
|
try:
|
||||||
points = (
|
points = (
|
||||||
LineString(self.points).parallel_offset(parallel_offset).coords
|
LineString(self.points).parallel_offset(parallel_offset).coords
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -49,7 +49,7 @@ setup(
|
||||||
"scheme/default.yml",
|
"scheme/default.yml",
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
python_requires=">=3.9",
|
python_requires=">=3.8",
|
||||||
install_requires=[
|
install_requires=[
|
||||||
"CairoSVG>=2.5.0",
|
"CairoSVG>=2.5.0",
|
||||||
"colour>=0.1.5",
|
"colour>=0.1.5",
|
||||||
|
|
|
@ -7,13 +7,15 @@ from subprocess import PIPE, Popen
|
||||||
__author__ = "Sergey Vartanov"
|
__author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
|
from typing import List
|
||||||
|
|
||||||
from xml.etree import ElementTree
|
from xml.etree import ElementTree
|
||||||
from xml.etree.ElementTree import Element
|
from xml.etree.ElementTree import Element
|
||||||
|
|
||||||
from map_machine.ui import COMMANDS
|
from map_machine.ui import COMMANDS
|
||||||
|
|
||||||
|
|
||||||
def error_run(arguments: list[str], message: bytes) -> None:
|
def error_run(arguments: List[str], message: bytes) -> None:
|
||||||
"""Run command that should fail and check error message."""
|
"""Run command that should fail and check error message."""
|
||||||
p = Popen(["map-machine"] + arguments, stderr=PIPE)
|
p = Popen(["map-machine"] + arguments, stderr=PIPE)
|
||||||
_, error = p.communicate()
|
_, error = p.communicate()
|
||||||
|
@ -21,7 +23,7 @@ def error_run(arguments: list[str], message: bytes) -> None:
|
||||||
assert error == message
|
assert error == message
|
||||||
|
|
||||||
|
|
||||||
def run(arguments: list[str], message: bytes) -> None:
|
def run(arguments: List[str], message: bytes) -> None:
|
||||||
"""Run command that should fail and check error message."""
|
"""Run command that should fail and check error message."""
|
||||||
p = Popen(["map-machine"] + arguments, stderr=PIPE)
|
p = Popen(["map-machine"] + arguments, stderr=PIPE)
|
||||||
_, error = p.communicate()
|
_, error = p.communicate()
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Test icon generation for nodes.
|
Test icon generation for nodes.
|
||||||
"""
|
"""
|
||||||
|
from typing import Dict, Set
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
|
|
||||||
from map_machine.grid import IconCollection
|
from map_machine.grid import IconCollection
|
||||||
|
@ -32,9 +34,9 @@ def test_icons_by_name(init_collection: IconCollection) -> None:
|
||||||
init_collection.draw_icons(workspace.get_icons_by_name_path(), by_name=True)
|
init_collection.draw_icons(workspace.get_icons_by_name_path(), by_name=True)
|
||||||
|
|
||||||
|
|
||||||
def get_icon(tags: dict[str, str]) -> IconSet:
|
def get_icon(tags: Dict[str, str]) -> IconSet:
|
||||||
"""Construct icon from tags."""
|
"""Construct icon from tags."""
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
|
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
|
||||||
return icon
|
return icon
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
"""
|
"""
|
||||||
Test label generation for nodes.
|
Test label generation for nodes.
|
||||||
"""
|
"""
|
||||||
|
from typing import Dict, List, Set
|
||||||
|
|
||||||
from map_machine.text import Label
|
from map_machine.text import Label
|
||||||
from tests import SCHEME
|
from tests import SCHEME
|
||||||
|
|
||||||
|
@ -8,9 +10,9 @@ __author__ = "Sergey Vartanov"
|
||||||
__email__ = "me@enzet.ru"
|
__email__ = "me@enzet.ru"
|
||||||
|
|
||||||
|
|
||||||
def construct_labels(tags: dict[str, str]) -> list[Label]:
|
def construct_labels(tags: Dict[str, str]) -> List[Label]:
|
||||||
"""Construct labels from OSM node tags."""
|
"""Construct labels from OSM node tags."""
|
||||||
processed: set[str] = set()
|
processed: Set[str] = set()
|
||||||
return SCHEME.construct_text(tags, "all", processed)
|
return SCHEME.construct_text(tags, "all", processed)
|
||||||
|
|
||||||
|
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue