Issue #88: backport to Python 3.8.

This commit is contained in:
Sergey Vartanov 2021-09-21 09:40:05 +03:00
parent e9b1b499bf
commit 75128537da
30 changed files with 303 additions and 267 deletions

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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