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
steps:
- uses: actions/checkout@v2
- name: Set up Python 3.9
- name: Set up Python 3.8
uses: actions/setup-python@v2
with:
python-version: 3.9
python-version: 3.8
- name: Install dependencies
run: |
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
------------
Requirements: Python 3.9.
Requirements: Python 3.8.
To install all packages, run:

View file

@ -13,5 +13,5 @@ echo "Lint with Flake8..."
flake8 \
--max-line-length=80 \
--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; }

View file

@ -168,7 +168,7 @@ Every way and node displayed with the random color picked for each author with \
\2 {Installation} {installation}
Requirements\: Python 3.9/* or higher*/.
Requirements\: Python 3.8/* or higher*/.
To install all packages, run\:

View file

@ -1,7 +1,7 @@
"""
Color utility.
"""
from typing import Any
from typing import Any, List
from colour import Color
@ -22,7 +22,7 @@ def is_bright(color: Color) -> bool:
def get_gradient_color(
value: Any, bounds: MinMax, colors: list[Color]
value: Any, bounds: MinMax, colors: List[Color]
) -> Color:
"""
Get color from the color scale for the value.
@ -32,7 +32,7 @@ def get_gradient_color(
:param colors: color scale
"""
color_length: int = len(colors) - 1
scale: list[Color] = colors + [Color("black")]
scale: List[Color] = colors + [Color("black")]
range_coefficient: float = (
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
from datetime import datetime
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
from colour import Color
@ -46,7 +46,7 @@ __author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEBUG: bool = False
TIME_COLOR_SCALE: list[Color] = [
TIME_COLOR_SCALE: List[Color] = [
Color("#581845"),
Color("#900C3F"),
Color("#C70039"),
@ -57,7 +57,7 @@ TIME_COLOR_SCALE: list[Color] = [
def line_center(
nodes: list[OSMNode], flinger: Flinger
nodes: List[OSMNode], flinger: Flinger
) -> (np.ndarray, np.ndarray):
"""
Get geometric center of nodes set.
@ -65,7 +65,7 @@ def line_center(
:param nodes: node list
:param flinger: flinger that remap geo positions
"""
boundary: list[MinMax] = [MinMax(), MinMax()]
boundary: List[MinMax] = [MinMax(), MinMax()]
for node in nodes:
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)
def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
def glue(ways: List[OSMWay]) -> List[List[OSMNode]]:
"""
Try to glue ways that share nodes.
:param ways: ways to glue
"""
result: list[list[OSMNode]] = []
to_process: set[tuple[OSMNode]] = set()
result: List[List[OSMNode]] = []
to_process: Set[Tuple[OSMNode]] = set()
for way in ways:
if way.is_cycle():
@ -111,9 +111,9 @@ def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
to_process.add(tuple(way.nodes))
while to_process:
nodes: list[OSMNode] = list(to_process.pop())
glued: Optional[list[OSMNode]] = None
other_nodes: Optional[tuple[OSMNode]] = None
nodes: List[OSMNode] = list(to_process.pop())
glued: Optional[List[OSMNode]] = None
other_nodes: Optional[Tuple[OSMNode]] = None
for other_nodes in to_process:
glued = try_to_glue(nodes, list(other_nodes))
@ -132,14 +132,14 @@ def glue(ways: list[OSMWay]) -> list[list[OSMNode]]:
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."""
return nodes[0] == nodes[-1]
def try_to_glue(
nodes: list[OSMNode], other: list[OSMNode]
) -> Optional[list[OSMNode]]:
nodes: List[OSMNode], other: List[OSMNode]
) -> Optional[List[OSMNode]]:
"""Create new combined way if ways share endpoints."""
if nodes[0] == other[0]:
return list(reversed(other[1:])) + nodes
@ -182,15 +182,15 @@ class Constructor:
x, float(self.configuration.level)
)
self.points: list[Point] = []
self.figures: list[StyledFigure] = []
self.buildings: list[Building] = []
self.points: List[Point] = []
self.figures: List[StyledFigure] = []
self.buildings: List[Building] = []
self.roads: Roads = Roads()
self.trees: list[Tree] = []
self.craters: list[Crater] = []
self.direction_sectors: list[DirectionSector] = []
self.trees: List[Tree] = []
self.craters: List[Crater] = []
self.direction_sectors: List[DirectionSector] = []
self.heights: set[float] = {2, 4}
self.heights: Set[float] = {2, 4}
def add_building(self, building: Building) -> None:
"""Add building and update levels."""
@ -221,8 +221,8 @@ class Constructor:
def construct_line(
self,
line: Union[OSMWay, OSMRelation],
inners: list[list[OSMNode]],
outers: list[list[OSMNode]],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
) -> None:
"""
Way or relation construction.
@ -265,7 +265,7 @@ class Constructor:
)
return
processed: set[str] = set()
processed: Set[str] = set()
recolor: Optional[Color] = None
@ -275,11 +275,11 @@ class Constructor:
recolor = self.scheme.get_color(line.tags[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:
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
)
new_style["stroke"] = recolor.hex
@ -302,7 +302,7 @@ class Constructor:
self.extractor, line.tags, processed, self.configuration
)
if icon_set is not None:
labels: list[Label] = self.scheme.construct_text(
labels: List[Label] = self.scheme.construct_text(
line.tags, "all", processed
)
point: Point = Point(
@ -319,7 +319,7 @@ class Constructor:
if not line_styles:
if DEBUG:
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": "none",
"stroke": Color("red").hex,
"stroke-width": 1,
@ -329,7 +329,7 @@ class Constructor:
)
self.figures.append(figure)
processed: set[str] = set()
processed: Set[str] = set()
priority: int
icon_set: IconSet
@ -340,7 +340,7 @@ class Constructor:
self.configuration,
)
if icon_set is not None:
labels: list[Label] = self.scheme.construct_text(
labels: List[Label] = self.scheme.construct_text(
line.tags, "all", processed
)
point: Point = Point(
@ -358,12 +358,12 @@ class Constructor:
def draw_special_mode(
self,
line: Union[OSMWay, OSMRelation],
inners: list[list[OSMNode]],
outers: list[list[OSMNode]],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
color: Color,
) -> None:
"""Add figure for special mode: time or author."""
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": "none",
"stroke": color.hex,
"stroke-width": 1,
@ -376,13 +376,13 @@ class Constructor:
"""Construct Map Machine ways from OSM relations."""
for relation_id in self.osm_data.relations:
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):
continue
if "type" not in tags or tags["type"] != "multipolygon":
continue
inner_ways: list[OSMWay] = []
outer_ways: list[OSMWay] = []
inner_ways: List[OSMWay] = []
outer_ways: List[OSMWay] = []
for member in relation.members:
if member.type_ == "way":
if member.role == "inner":
@ -394,8 +394,8 @@ class Constructor:
else:
logging.warning(f'Unknown member role "{member.role}".')
if outer_ways:
inners_path: list[list[OSMNode]] = glue(inner_ways)
outers_path: list[list[OSMNode]] = glue(outer_ways)
inners_path: List[List[OSMNode]] = glue(inner_ways)
outers_path: List[List[OSMNode]] = glue(outer_ways)
self.construct_line(relation, inners_path, outers_path)
def construct_nodes(self) -> None:
@ -414,11 +414,11 @@ class Constructor:
def construct_node(self, node: OSMNode) -> None:
"""Draw one node."""
tags: dict[str, str] = node.tags
tags: Dict[str, str] = node.tags
if not self.check_level(tags):
return
processed: set[str] = set()
processed: Set[str] = set()
flung: np.ndarray = self.flinger.fling(node.coordinates)
@ -455,7 +455,7 @@ class Constructor:
)
if icon_set is None:
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)
if node.get_tag("natural") == "tree" and (
@ -483,7 +483,7 @@ class Constructor:
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."""
if "level" in tags:
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
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."""
if "level" in tags:
try:

View file

@ -1,7 +1,7 @@
"""
Direction tag support.
"""
from typing import Iterator, Optional
from typing import Iterator, List, Optional
import numpy as np
from portolan import middle
@ -65,7 +65,7 @@ class Sector:
self.main_direction: Optional[np.ndarray] = None
if "-" in text:
parts: list[str] = text.split("-")
parts: List[str] = text.split("-")
self.start = parse_vector(parts[0])
self.end = parse_vector(parts[1])
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
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):
return True
if result == [False] * len(result):

View file

@ -3,7 +3,7 @@ Drawing utility.
"""
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Union
from typing import List, Optional, Union
import cairo
import numpy as np
@ -18,7 +18,7 @@ from svgwrite.text import Text
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
PathCommands = list[Union[float, str, np.ndarray]]
PathCommands = List[Union[float, str, np.ndarray]]
@dataclass
@ -75,7 +75,7 @@ class Drawing:
"""Draw rectangle."""
raise NotImplementedError
def line(self, points: list[np.ndarray], style: Style) -> None:
def line(self, points: List[np.ndarray], style: Style) -> None:
"""Draw line."""
raise NotImplementedError
@ -117,7 +117,7 @@ class SVGDrawing(Drawing):
style.update_svg_element(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."""
commands: PathCommands = ["M"]
for point in points:
@ -169,7 +169,7 @@ class PNGDrawing(Drawing):
self.context.rectangle(point_1[0], point_1[1], size[0], size[1])
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."""
if style.fill is not None:
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:
"""Parse path command from text representation into list."""
parts: list[str] = path.split(" ")
parts: List[str] = path.split(" ")
result: PathCommands = []
command: str = "M"
index: int = 0
@ -296,7 +296,7 @@ def parse_path(path: str) -> PathCommands:
result.append(float(part))
else:
if "," in part:
elements: list[str] = part.split(",")
elements: List[str] = part.split(",")
result.append(np.array(list(map(float, elements))))
else:
result.append(np.array((float(part), float(parts[index + 1]))))

View file

@ -4,6 +4,7 @@ Drawing separate map elements.
import argparse
import logging
from pathlib import Path
from typing import Dict, List, Set
import numpy as np
import svgwrite
@ -33,17 +34,17 @@ def draw_element(options: argparse.Namespace) -> None:
target = "area"
tags_description = options.area
tags: dict[str, str] = dict(
tags: Dict[str, str] = dict(
[x.split("=") for x in tags_description.split(",")]
)
scheme: Scheme = Scheme(workspace.DEFAULT_SCHEME_PATH)
extractor: ShapeExtractor = ShapeExtractor(
workspace.ICONS_PATH, workspace.ICONS_CONFIG_PATH
)
processed: set[str] = set()
processed: Set[str] = set()
icon, priority = scheme.get_icon(extractor, tags, processed)
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(
icon,
labels,

View file

@ -1,7 +1,7 @@
"""
Figures displayed on the map.
"""
from typing import Any, Iterator, Optional
from typing import Any, Dict, Iterator, List, Optional
import numpy as np
from colour import Color
@ -31,14 +31,14 @@ class Figure(Tagged):
def __init__(
self,
tags: dict[str, str],
inners: list[list[OSMNode]],
outers: list[list[OSMNode]],
tags: Dict[str, str],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
) -> None:
super().__init__(tags)
self.inners: list[list[OSMNode]] = list(map(make_clockwise, inners))
self.outers: list[list[OSMNode]] = list(
self.inners: List[List[OSMNode]] = list(map(make_clockwise, inners))
self.outers: List[List[OSMNode]] = list(
map(make_counter_clockwise, outers)
)
@ -69,20 +69,20 @@ class Building(Figure):
def __init__(
self,
tags: dict[str, str],
inners: list[list[OSMNode]],
outers: list[list[OSMNode]],
tags: Dict[str, str],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
flinger: Flinger,
scheme: Scheme,
) -> None:
super().__init__(tags, inners, outers)
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": scheme.get_color("building_color").hex,
"stroke": scheme.get_color("building_border_color").hex,
}
self.line_style: LineStyle = LineStyle(style)
self.parts: list[Segment] = []
self.parts: List[Segment] = []
for nodes in self.inners + self.outers:
for i in range(len(nodes) - 1):
@ -198,9 +198,9 @@ class StyledFigure(Figure):
def __init__(
self,
tags: dict[str, str],
inners: list[list[OSMNode]],
outers: list[list[OSMNode]],
tags: Dict[str, str],
inners: List[List[OSMNode]],
outers: List[List[OSMNode]],
line_style: LineStyle,
) -> None:
super().__init__(tags, inners, outers)
@ -213,7 +213,7 @@ class Crater(Tagged):
"""
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:
super().__init__(tags)
self.coordinates: np.ndarray = coordinates
@ -252,7 +252,7 @@ class Tree(Tagged):
"""
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:
super().__init__(tags)
self.coordinates: np.ndarray = coordinates
@ -279,7 +279,7 @@ class DirectionSector(Tagged):
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)
self.point: np.ndarray = point
@ -366,7 +366,7 @@ class Segment:
) # 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.
@ -381,7 +381,7 @@ def is_clockwise(polygon: list[OSMNode]) -> bool:
return count >= 0
def make_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
def make_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
"""
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))
def make_counter_clockwise(polygon: list[OSMNode]) -> list[OSMNode]:
def make_counter_clockwise(polygon: List[OSMNode]) -> List[OSMNode]:
"""
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))
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."""
return Polyline(
[flinger.fling(x.coordinates) + shift for x in nodes]

View file

@ -4,7 +4,7 @@ Icon grid drawing.
import logging
from dataclasses import dataclass
from pathlib import Path
from typing import Optional, Union
from typing import Dict, List, Optional, Set, Union
import numpy as np
from colour import Color
@ -25,7 +25,7 @@ class IconCollection:
Collection of icons.
"""
icons: list[Icon]
icons: List[Icon]
@classmethod
def from_scheme(
@ -48,11 +48,11 @@ class IconCollection:
tags
:param add_all: create icons from all possible shapes including parts
"""
icons: list[Icon] = []
icons: List[Icon] = []
def add() -> Icon:
"""Construct icon and add it to the list."""
specifications: list[ShapeSpecification] = [
specifications: List[ShapeSpecification] = [
scheme.get_shape_specification(x, extractor)
for x in current_set
]
@ -63,7 +63,7 @@ class IconCollection:
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:
matcher: NodeMatcher
@ -83,7 +83,7 @@ class IconCollection:
continue
for icon_id in matcher.under_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
)
add()
@ -102,7 +102,7 @@ class IconCollection:
):
add()
specified_ids: set[str] = set()
specified_ids: Set[str] = set()
for icon in icons:
specified_ids |= set(icon.get_shape_ids())

View file

@ -6,7 +6,7 @@ import logging
import re
from dataclasses import dataclass, field
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.ElementTree import Element
@ -47,13 +47,13 @@ class Shape:
id_: str # shape identifier
name: Optional[str] = None # icon description
is_right_directed: Optional[bool] = None
emojis: set[str] = field(default_factory=set)
emojis: Set[str] = field(default_factory=set)
is_part: bool = False
@classmethod
def from_structure(
cls,
structure: dict[str, Any],
structure: Dict[str, Any],
path: str,
offset: np.ndarray,
id_: str,
@ -105,7 +105,7 @@ class Shape:
:param offset: additional offset
:param scale: scale resulting image
"""
transformations: list[str] = []
transformations: List[str] = []
shift: np.ndarray = point + offset
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"]:
return True
style: dict[str, str] = dict(
style: Dict[str, str] = dict(
[x.split(":") for x in element.attrib["style"].split(";")]
)
if (
@ -180,8 +180,8 @@ class ShapeExtractor:
:param svg_file_name: input SVG file name with icons. File may contain
any other irrelevant graphics.
"""
self.shapes: dict[str, Shape] = {}
self.configuration: dict[str, Any] = json.load(
self.shapes: Dict[str, Shape] = {}
self.configuration: Dict[str, Any] = json.load(
configuration_file_name.open()
)
root: Element = ElementTree.parse(svg_file_name).getroot()
@ -229,7 +229,7 @@ class ShapeExtractor:
name = child_node.text
break
configuration: dict[str, Any] = (
configuration: Dict[str, Any] = (
self.configuration[id_] if id_ in self.configuration else {}
)
self.shapes[id_] = Shape.from_structure(
@ -271,7 +271,7 @@ class ShapeSpecification:
self,
svg: BaseElement,
point: np.ndarray,
tags: dict[str, Any] = None,
tags: Dict[str, Any] = None,
outline: bool = False,
outline_opacity: float = 1.0,
) -> None:
@ -299,7 +299,7 @@ class ShapeSpecification:
bright: bool = is_bright(self.color)
color: Color = Color("black") if bright else Color("white")
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": color.hex,
"stroke": color.hex,
"stroke-width": 2.2,
@ -330,14 +330,14 @@ class Icon:
Icon that consists of (probably) multiple shapes.
"""
shape_specifications: list[ShapeSpecification]
shape_specifications: List[ShapeSpecification]
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."""
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."""
return [
(x.shape.name if x.shape.name else "unknown")
@ -348,7 +348,7 @@ class Icon:
self,
svg: svgwrite.Drawing,
point: np.ndarray,
tags: dict[str, Any] = None,
tags: Dict[str, Any] = None,
outline: bool = False,
) -> None:
"""
@ -420,7 +420,7 @@ class Icon:
shape_specification.color = color
def add_specifications(
self, specifications: list[ShapeSpecification]
self, specifications: List[ShapeSpecification]
) -> None:
"""Add shape specifications to the icon."""
self.shape_specifications += specifications
@ -443,8 +443,8 @@ class IconSet:
"""
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
# displayed by text or ignored)
processed: set[str]
processed: Set[str]

View file

@ -4,7 +4,7 @@ MapCSS scheme creation.
import argparse
import logging
from pathlib import Path
from typing import Optional, TextIO
from typing import Dict, List, Optional, TextIO
from colour import Color
@ -84,8 +84,8 @@ class MapCSSWriter:
self.add_icons_for_lifecycle: bool = add_icons_for_lifecycle
self.icon_directory_name: str = icon_directory_name
self.point_matchers: list[Matcher] = scheme.node_matchers
self.line_matchers: list[Matcher] = scheme.way_matchers
self.point_matchers: List[Matcher] = scheme.node_matchers
self.line_matchers: List[Matcher] = scheme.way_matchers
def add_selector(
self,
@ -103,7 +103,7 @@ class MapCSSWriter:
:param opacity: icon opacity
:return: string representation of selector
"""
elements: dict[str, str] = {}
elements: Dict[str, str] = {}
clean_shapes = matcher.get_clean_shapes()
if clean_shapes:
@ -116,7 +116,7 @@ class MapCSSWriter:
if opacity is not None:
elements["icon-opacity"] = f"{opacity:.2f}"
style: dict[str, str] = matcher.get_style()
style: Dict[str, str] = matcher.get_style()
if style:
if "fill" in style:
elements["fill-color"] = style["fill"]

View file

@ -4,7 +4,7 @@ Simple OpenStreetMap renderer.
import argparse
import logging
from pathlib import Path
from typing import Iterator, Optional
from typing import Dict, Iterator, List, Optional, Set
import numpy as np
import svgwrite
@ -57,7 +57,7 @@ class Map:
self.svg.add(
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
)
ways_length: int = len(ways)
@ -94,7 +94,7 @@ class Map:
self.configuration.overlap,
)
nodes: list[Point] = sorted(
nodes: List[Point] = sorted(
constructor.points, key=lambda x: -x.priority
)
steps: int = len(nodes)
@ -156,7 +156,7 @@ class Map:
def draw_roads(self, roads: Iterator[Road]) -> None:
"""Draw road as simple SVG path."""
nodes: dict[OSMNode, set[RoadPart]] = {}
nodes: Dict[OSMNode, Set[RoadPart]] = {}
for road in roads:
for index in range(len(road.outers[0]) - 1):
@ -177,7 +177,7 @@ class Map:
nodes[node_2].add(part_2)
for node in nodes:
parts: set[RoadPart] = nodes[node]
parts: Set[RoadPart] = nodes[node]
if len(parts) < 4:
continue
intersection: Intersection = Intersection(list(parts))
@ -197,7 +197,7 @@ def ui(arguments: argparse.Namespace) -> None:
cache_path.mkdir(parents=True, exist_ok=True)
boundary_box: Optional[BoundaryBox] = None
input_file_names: list[Path] = []
input_file_names: List[Path] = []
if 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
from abc import ABC
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.moire import Tag
@ -17,7 +17,7 @@ from map_machine.workspace import workspace
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
Arguments = list[Any]
Arguments = List[Any]
Code = Union[str, Tag, list]
PREFIX: str = "https://wiki.openstreetmap.org/wiki/"
@ -50,13 +50,13 @@ class ArgumentParser(argparse.ArgumentParser):
"""
def __init__(self, *args, **kwargs) -> None:
self.arguments: list[dict[str, Any]] = []
self.arguments: List[Dict[str, Any]] = []
super(ArgumentParser, self).__init__(*args, **kwargs)
def add_argument(self, *args, **kwargs) -> None:
"""Just store argument with options."""
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:
argument[key] = kwargs[key]

View file

@ -5,6 +5,7 @@ import logging
import time
from dataclasses import dataclass
from pathlib import Path
from typing import Dict
import urllib3
@ -48,7 +49,7 @@ def get_osm(
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.

View file

@ -7,7 +7,7 @@ import re
from dataclasses import dataclass, field
from datetime import datetime
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.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
STAGES_OF_DECAY: list[str] = [
STAGES_OF_DECAY: List[str] = [
"disused",
"abandoned",
"ruins",
@ -47,7 +47,7 @@ def parse_float(string: str) -> Optional[float]:
return None
def parse_levels(string: str) -> list[float]:
def parse_levels(string: str) -> List[float]:
"""Parse string representation of level sequence value."""
# TODO: add `-` parsing
try:
@ -63,7 +63,7 @@ class Tagged:
Something with tags (string to string mapping).
"""
tags: dict[str, str]
tags: Dict[str, str]
def get_tag(self, key: str) -> Optional[str]:
"""
@ -127,7 +127,7 @@ class OSMNode(Tagged):
def from_xml_structure(cls, element: Element) -> "OSMNode":
"""Parse node from OSM XML `<node>` element."""
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"]
)
return cls(
@ -144,7 +144,7 @@ class OSMNode(Tagged):
)
@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.
@ -169,7 +169,7 @@ class OSMWay(Tagged):
"""
id_: int
nodes: Optional[list[OSMNode]] = field(default_factory=list)
nodes: Optional[List[OSMNode]] = field(default_factory=list)
visible: Optional[str] = None
changeset: Optional[str] = None
timestamp: Optional[datetime] = None
@ -178,11 +178,11 @@ class OSMWay(Tagged):
@classmethod
def from_xml_structure(
cls, element: Element, nodes: dict[int, OSMNode]
cls, element: Element, nodes: Dict[int, OSMNode]
) -> "OSMWay":
"""Parse way from OSM XML `<way>` element."""
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"]
)
return cls(
@ -200,7 +200,7 @@ class OSMWay(Tagged):
@classmethod
def parse_from_structure(
cls, structure: dict[str, Any], nodes: dict[int, OSMNode]
cls, structure: Dict[str, Any], nodes: Dict[int, OSMNode]
) -> "OSMWay":
"""
Parse way from Overpass-like structure.
@ -242,7 +242,7 @@ class OSMRelation(Tagged):
"""
id_: int
members: Optional[list[OSMMember]]
members: Optional[List[OSMMember]]
visible: Optional[str] = None
changeset: Optional[str] = None
timestamp: Optional[datetime] = None
@ -253,8 +253,8 @@ class OSMRelation(Tagged):
def from_xml_structure(cls, element: Element) -> "OSMRelation":
"""Parse relation from OSM XML `<relation>` element."""
attributes = element.attrib
members: list[OSMMember] = []
tags: dict[str, str] = {}
members: List[OSMMember] = []
tags: Dict[str, str] = {}
for subelement in element:
if subelement.tag == "member":
subattributes = subelement.attrib
@ -281,7 +281,7 @@ class OSMRelation(Tagged):
)
@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.
@ -311,12 +311,12 @@ class OSMData:
"""
def __init__(self) -> None:
self.nodes: dict[int, OSMNode] = {}
self.ways: dict[int, OSMWay] = {}
self.relations: dict[int, OSMRelation] = {}
self.nodes: Dict[int, OSMNode] = {}
self.ways: Dict[int, OSMWay] = {}
self.relations: Dict[int, OSMRelation] = {}
self.authors: set[str] = set()
self.levels: set[float] = set()
self.authors: Set[str] = set()
self.levels: Set[float] = set()
self.time: MinMax = MinMax()
self.view_box: Optional[BoundaryBox] = None
self.equator_length: float = 40_075_017.0
@ -364,8 +364,8 @@ class OSMData:
with file_name.open() as input_file:
structure = json.load(input_file)
node_map: dict[int, OSMNode] = {}
way_map: dict[int, OSMWay] = {}
node_map: Dict[int, OSMNode] = {}
way_map: Dict[int, OSMWay] = {}
for element in structure["elements"]:
if element["type"] == "node":

View file

@ -1,7 +1,7 @@
"""
Point: node representation on the map.
"""
from typing import Optional
from typing import Dict, List, Optional, Set
import numpy as np
import svgwrite
@ -53,9 +53,9 @@ class Point(Tagged):
def __init__(
self,
icon_set: IconSet,
labels: list[Label],
tags: dict[str, str],
processed: set[str],
labels: List[Label],
tags: Dict[str, str],
processed: Set[str],
point: np.ndarray,
priority: float = 0,
is_for_node: bool = True,
@ -67,8 +67,8 @@ class Point(Tagged):
assert point is not None
self.icon_set: IconSet = icon_set
self.labels: list[Label] = labels
self.processed: set[str] = processed
self.labels: List[Label] = labels
self.processed: Set[str] = processed
self.point: np.ndarray = point
self.priority: float = priority
self.layer: float = 0
@ -92,7 +92,7 @@ class Point(Tagged):
return
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.main_icon_painted: bool = self.draw_point_shape(
@ -135,7 +135,7 @@ class Point(Tagged):
icon: Icon,
position: np.ndarray,
occupied: Occupied,
tags: Optional[dict[str, str]] = None,
tags: Optional[Dict[str, str]] = None,
) -> bool:
"""Draw one combined icon and its outline."""
# Down-cast floats to integers to make icons pixel-perfect.
@ -166,7 +166,7 @@ class Point(Tagged):
label_mode: str = LabelMode.MAIN,
) -> None:
"""Draw all labels."""
labels: list[Label]
labels: List[Label]
if label_mode == LabelMode.MAIN:
labels = self.labels[:1]

View file

@ -2,7 +2,7 @@
WIP: road shape drawing.
"""
from dataclasses import dataclass
from typing import Any, Optional
from typing import Any, Dict, List, Optional, Tuple
import numpy as np
import svgwrite
@ -62,7 +62,7 @@ class RoadPart:
self,
point_1: np.ndarray,
point_2: np.ndarray,
lanes: list[Lane],
lanes: List[Lane],
scale: float,
) -> None:
"""
@ -72,7 +72,7 @@ class RoadPart:
"""
self.point_1: np.ndarray = point_1
self.point_2: np.ndarray = point_2
self.lanes: list[Lane] = lanes
self.lanes: List[Lane] = lanes
if lanes:
self.width = sum(map(lambda x: x.get_width(scale), lanes))
else:
@ -284,8 +284,8 @@ class Intersection:
points of the road parts should be the same.
"""
def __init__(self, parts: list[RoadPart]) -> None:
self.parts: list[RoadPart] = sorted(parts, key=lambda x: x.get_angle())
def __init__(self, parts: List[RoadPart]) -> None:
self.parts: List[RoadPart] = sorted(parts, key=lambda x: x.get_angle())
for index in range(len(self.parts)):
next_index: int = 0 if index == len(self.parts) - 1 else index + 1
@ -368,20 +368,20 @@ class Road(Tagged):
def __init__(
self,
tags: dict[str, str],
nodes: list[OSMNode],
tags: Dict[str, str],
nodes: List[OSMNode],
matcher: RoadMatcher,
flinger: Flinger,
) -> None:
super().__init__(tags)
self.nodes: list[OSMNode] = nodes
self.nodes: List[OSMNode] = nodes
self.matcher: RoadMatcher = matcher
self.line: Polyline = Polyline(
[flinger.fling(x.coordinates) for x in self.nodes]
)
self.width: Optional[float] = matcher.default_width
self.lanes: list[Lane] = []
self.lanes: List[Lane] = []
if "lanes" in tags:
try:
@ -391,7 +391,7 @@ class Road(Tagged):
pass
if "width:lanes" in tags:
widths: list[float] = list(
widths: List[float] = list(
map(float, tags["width:lanes"].split("|"))
)
if len(widths) == len(self.lanes):
@ -434,7 +434,7 @@ class Road(Tagged):
scale: float = flinger.get_scale(self.nodes[0].coordinates)
path_commands: str = self.line.get_path()
path: Path = Path(d=path_commands)
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": "none",
"stroke": color.hex,
"stroke-linecap": "butt",
@ -454,7 +454,7 @@ class Road(Tagged):
-self.width / 2 + index * self.width / len(self.lanes)
)
path: Path = Path(d=self.line.get_path(parallel_offset))
style: dict[str, Any] = {
style: Dict[str, Any] = {
"fill": "none",
"stroke": color.hex,
"stroke-linejoin": "round",
@ -467,7 +467,7 @@ class Road(Tagged):
def get_curve_points(
road: Road, scale: float, center: np.ndarray, road_end: np.ndarray
) -> list[np.ndarray]:
) -> List[np.ndarray]:
"""
:param road: road segment
:param scale: current zoom scale
@ -492,11 +492,11 @@ class Connector:
def __init__(
self,
connections: list[tuple[Road, int]],
connections: List[Tuple[Road, int]],
flinger: Flinger,
scale: float,
) -> None:
self.connections: list[tuple[Road, int]] = connections
self.connections: List[Tuple[Road, int]] = connections
self.road_1: Road = connections[0][0]
self.index_1: int = connections[0][1]
@ -520,7 +520,7 @@ class SimpleConnector(Connector):
def __init__(
self,
connections: list[tuple[Road, int]],
connections: List[Tuple[Road, int]],
flinger: Flinger,
scale: float,
) -> None:
@ -558,7 +558,7 @@ class ComplexConnector(Connector):
def __init__(
self,
connections: list[tuple[Road, int]],
connections: List[Tuple[Road, int]],
flinger: Flinger,
scale: float,
) -> None:
@ -574,10 +574,10 @@ class ComplexConnector(Connector):
node: OSMNode = self.road_1.nodes[self.index_1]
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]
)
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]
)
# fmt: off
@ -626,7 +626,7 @@ class SimpleIntersection(Connector):
def __init__(
self,
connections: list[tuple[Road, int]],
connections: List[Tuple[Road, int]],
flinger: Flinger,
scale: float,
) -> None:
@ -661,8 +661,8 @@ class Roads:
"""
def __init__(self) -> None:
self.roads: list[Road] = []
self.connections: dict[int, list[tuple[Road, int]]] = {}
self.roads: List[Road] = []
self.connections: Dict[int, List[Tuple[Road, int]]] = {}
def append(self, road: Road) -> None:
"""Add road and update connections."""
@ -679,8 +679,8 @@ class Roads:
return
scale: float = flinger.get_scale(self.roads[0].nodes[0].coordinates)
layered_roads: dict[float, list[Road]] = {}
layered_connectors: dict[float, list[Connector]] = {}
layered_roads: Dict[float, List[Road]] = {}
layered_connectors: Dict[float, List[Connector]] = {}
for road in self.roads:
if road.layer not in layered_roads:
@ -688,7 +688,7 @@ class Roads:
layered_roads[road.layer].append(road)
for id_ in self.connections:
connected: list[tuple[Road, int]] = self.connections[id_]
connected: List[Tuple[Road, int]] = self.connections[id_]
connector: Connector
if len(self.connections[id_]) == 2:
@ -706,10 +706,10 @@ class Roads:
layered_connectors[connector.layer].append(connector)
for layer in sorted(layered_roads.keys()):
roads: list[Road] = sorted(
roads: List[Road] = sorted(
layered_roads[layer], key=lambda x: x.matcher.priority
)
connectors: list[Connector]
connectors: List[Connector]
if layer in layered_connectors:
connectors = layered_connectors[layer]
else:

View file

@ -5,7 +5,7 @@ import logging
from dataclasses import dataclass
from enum import Enum
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 yaml
@ -27,7 +27,7 @@ from map_machine.text import Label, get_address, get_text
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
IconDescription = list[Union[str, dict[str, str]]]
IconDescription = List[Union[str, Dict[str, str]]]
@dataclass
@ -36,7 +36,7 @@ class LineStyle:
SVG line style and its priority.
"""
style: dict[str, Union[int, float, str]]
style: Dict[str, Union[int, float, str]]
priority: float = 0.0
@ -54,7 +54,7 @@ class MatchingType(Enum):
def is_matched_tag(
matcher_tag_key: str,
matcher_tag_value: Union[str, list],
tags: dict[str, str],
tags: Dict[str, str],
) -> MatchingType:
"""
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}"]'
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."""
if "exclude" in restrictions and country in restrictions["exclude"]:
return False
@ -109,11 +109,11 @@ class Matcher:
"""
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:
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:
self.exception = structure["exception"]
@ -125,7 +125,7 @@ class Matcher:
if "replace_shapes" in structure:
self.replace_shapes = structure["replace_shapes"]
self.location_restrictions: dict[str, str] = {}
self.location_restrictions: Dict[str, str] = {}
if "location_restrictions" in structure:
self.location_restrictions = structure["location_restrictions"]
@ -137,7 +137,7 @@ class Matcher:
def is_matched(
self,
tags: dict[str, str],
tags: Dict[str, str],
configuration: Optional[MapConfiguration] = None,
) -> bool:
"""
@ -186,11 +186,11 @@ class Matcher:
[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."""
return None
def get_style(self) -> dict[str, Any]:
def get_style(self) -> Dict[str, Any]:
"""Return way SVG style."""
return {}
@ -201,7 +201,7 @@ class NodeMatcher(Matcher):
"""
def __init__(
self, structure: dict[str, Any], group: dict[str, Any]
self, structure: Dict[str, Any], group: Dict[str, Any]
) -> None:
# Dictionary with tag keys and values, value lists, or "*"
super().__init__(structure, group)
@ -238,7 +238,7 @@ class NodeMatcher(Matcher):
if "with_icon" in structure:
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."""
if not self.shapes:
return None
@ -250,11 +250,11 @@ class WayMatcher(Matcher):
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)
self.style: dict[str, Any] = {"fill": "none"}
self.style: Dict[str, Any] = {"fill": "none"}
if "style" in structure:
style: dict[str, Any] = structure["style"]
style: Dict[str, Any] = structure["style"]
for key in style:
if str(style[key]).endswith("_color"):
self.style[key] = scheme.get_color(style[key]).hex.upper()
@ -264,7 +264,7 @@ class WayMatcher(Matcher):
if "priority" in structure:
self.priority = structure["priority"]
def get_style(self) -> dict[str, Any]:
def get_style(self) -> Dict[str, Any]:
"""Return way SVG style."""
return self.style
@ -274,7 +274,7 @@ class RoadMatcher(Matcher):
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)
self.border_color: Color = Color(
scheme.get_color(structure["border_color"])
@ -287,7 +287,7 @@ class RoadMatcher(Matcher):
if "priority" in structure:
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
if "layer" in tags:
layer = float(tags.get("layer"))
@ -307,33 +307,33 @@ class Scheme:
specification
"""
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
)
self.node_matchers: list[NodeMatcher] = []
self.node_matchers: List[NodeMatcher] = []
for group in content["node_icons"]:
for element in group["tags"]:
self.node_matchers.append(NodeMatcher(element, group))
self.colors: dict[str, str] = content["colors"]
self.material_colors: dict[str, str] = content["material_colors"]
self.colors: Dict[str, str] = content["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"]
]
self.road_matchers: list[RoadMatcher] = [
self.road_matchers: List[RoadMatcher] = [
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"]
]
self.tags_to_write: list[str] = content["tags_to_write"]
self.prefix_to_write: list[str] = content["prefix_to_write"]
self.tags_to_skip: list[str] = content["tags_to_skip"]
self.prefix_to_skip: list[str] = content["prefix_to_skip"]
self.tags_to_write: List[str] = content["tags_to_write"]
self.prefix_to_write: List[str] = content["prefix_to_write"]
self.tags_to_skip: List[str] = content["tags_to_skip"]
self.prefix_to_skip: List[str] = content["prefix_to_skip"]
# 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:
"""
@ -384,10 +384,10 @@ class Scheme:
def get_icon(
self,
extractor: ShapeExtractor,
tags: dict[str, Any],
processed: set[str],
tags: Dict[str, Any],
processed: Set[str],
configuration: MapConfiguration = MapConfiguration(),
) -> tuple[Optional[IconSet], int]:
) -> Tuple[Optional[IconSet], int]:
"""
Construct icon set.
@ -404,7 +404,7 @@ class Scheme:
return self.cache[tags_hash]
main_icon: Optional[Icon] = None
extra_icons: list[Icon] = []
extra_icons: List[Icon] = []
priority: int = 0
index: int = 0
@ -419,7 +419,7 @@ class Scheme:
and not matcher.check_zoom_level(configuration.zoom_level)
):
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
if not matcher.draw:
processed |= matcher_tags
@ -492,7 +492,7 @@ class Scheme:
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."""
line_styles = []
@ -504,7 +504,7 @@ class Scheme:
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."""
for matcher in self.road_matchers:
if not matcher.is_matched(tags):
@ -513,10 +513,10 @@ class Scheme:
return None
def construct_text(
self, tags: dict[str, str], draw_captions: str, processed: set[str]
) -> list[Label]:
self, tags: Dict[str, str], draw_captions: str, processed: Set[str]
) -> List[Label]:
"""Construct labels for not processed tags."""
texts: list[Label] = []
texts: List[Label] = []
name = None
alt_name = None
@ -542,7 +542,7 @@ class Scheme:
alt_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:
texts.append(Label(name, Color("black")))
@ -587,7 +587,7 @@ class Scheme:
texts.append(Label(tags[tag]))
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."""
for matcher in self.area_matchers:
if matcher.is_matched(tags):
@ -595,7 +595,7 @@ class Scheme:
return False
def process_ignored(
self, tags: dict[str, str], processed: set[str]
self, tags: Dict[str, str], processed: Set[str]
) -> None:
"""
Mark all ignored tag as processed.
@ -607,7 +607,7 @@ class Scheme:
def get_shape_specification(
self,
structure: Union[str, dict[str, Any]],
structure: Union[str, Dict[str, Any]],
extractor: ShapeExtractor,
color: Color = DEFAULT_COLOR,
) -> ShapeSpecification:

View file

@ -5,7 +5,7 @@ import argparse
import logging
from http.server import HTTPServer, SimpleHTTPRequestHandler
from pathlib import Path
from typing import Optional
from typing import List, Optional, Tuple
import cairosvg
@ -28,14 +28,14 @@ class _Handler(SimpleHTTPRequestHandler):
def __init__(
self,
request: bytes,
client_address: tuple[str, int],
client_address: Tuple[str, int],
server: HTTPServer,
) -> None:
super().__init__(request, client_address, server)
def do_GET(self) -> None:
"""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"):
return

View file

@ -7,6 +7,7 @@ import json
import logging
from datetime import datetime
from pathlib import Path
from typing import List
from map_machine import (
__author__,
@ -54,7 +55,7 @@ class TaginfoProjectFile:
):
key: str = list(matcher.tags.keys())[0]
value: str = matcher.tags[key]
ids: list[str] = [
ids: List[str] = [
(x if isinstance(x, str) else x["shape"])
for x in matcher.shapes
]

View file

@ -2,7 +2,7 @@
OSM address tag processing.
"""
from dataclasses import dataclass
from typing import Any
from typing import Any, Dict, List, Set
from colour import Color
@ -25,8 +25,8 @@ class Label:
def get_address(
tags: dict[str, Any], draw_captions_mode: str, processed: set[str]
) -> list[str]:
tags: Dict[str, Any], draw_captions_mode: str, processed: Set[str]
) -> List[str]:
"""
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 processed: set of processed tag keys
"""
address: list[str] = []
address: List[str] = []
if draw_captions_mode == "address":
if "addr:postcode" in tags:
@ -80,10 +80,10 @@ def format_frequency(value: str) -> str:
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."""
texts: list[Label] = []
values: list[str] = []
texts: List[Label] = []
values: List[str] = []
if "voltage:primary" in tags:
values.append(tags["voltage:primary"])

View file

@ -8,7 +8,7 @@ import logging
import sys
from dataclasses import dataclass
from pathlib import Path
from typing import Optional
from typing import List, Optional, Tuple
import cairosvg
import numpy as np
@ -72,7 +72,7 @@ class Tile:
lat_deg: np.ndarray = np.degrees(lat_rad)
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
points.
@ -191,11 +191,11 @@ class Tile:
cairosvg.svg2png(file_obj=input_file, write_to=str(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."""
assert zoom_level >= self.zoom_level
tiles: list["Tile"] = []
tiles: List["Tile"] = []
n: int = 2 ** (zoom_level - self.zoom_level)
for i in range(n):
for j in range(n):
@ -214,7 +214,7 @@ class Tiles:
Collection of tiles.
"""
tiles: list[Tile]
tiles: List[Tile]
tile_1: Tile # Left top tile.
tile_2: Tile # Right bottom tile.
zoom_level: int # OpenStreetMap zoom level.
@ -230,7 +230,7 @@ class Tiles:
:param boundary_box: area to be covered by tiles
:param zoom_level: zoom level in OpenStreetMap terminology
"""
tiles: list[Tile] = []
tiles: List[Tile] = []
tile_1: Tile = Tile.from_coordinates(
boundary_box.get_left_top(), zoom_level
)
@ -327,7 +327,7 @@ class Tiles:
for tile in self.tiles:
x: int = tile.x - self.tile_1.x
y: int = tile.y - self.tile_1.y
area: tuple[int, int, int, int] = (
area: Tuple[int, int, int, int] = (
x * TILE_WIDTH,
y * TILE_HEIGHT,
(x + 1) * TILE_WIDTH,
@ -413,7 +413,7 @@ class Tiles:
def subdivide(self, zoom_level: int) -> "Tiles":
"""Get subtiles from tiles."""
tiles: list[Tile] = []
tiles: List[Tile] = []
for tile in self.tiles:
tiles += tile.subdivide(zoom_level)
return Tiles(
@ -429,9 +429,9 @@ class ScaleConfigurationException(Exception):
"""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."""
parts: list[str]
parts: List[str]
if "," in zoom_level_specification:
parts = zoom_level_specification.split(",")
else:
@ -444,7 +444,7 @@ def parse_zoom_level(zoom_level_specification: str) -> list[int]:
raise ScaleConfigurationException("Scale is too big.")
return parsed_zoom_level
result: list[int] = []
result: List[int] = []
for part in parts:
if "-" in part:
from_, to = part.split("-")
@ -463,7 +463,7 @@ def ui(options: argparse.Namespace) -> None:
"""Simple user interface for tile generation."""
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)
if options.input_file_name:
@ -478,7 +478,7 @@ def ui(options: argparse.Namespace) -> None:
)
tiles.draw(directory, Path(options.cache), configuration, osm_data)
elif options.coordinates:
coordinates: list[float] = list(
coordinates: List[float] = list(
map(float, options.coordinates.strip().split(","))
)
min_tile: Tile = Tile.from_coordinates(

View file

@ -3,6 +3,7 @@ Command-line user interface.
"""
import argparse
import sys
from typing import Dict, List
from map_machine import __version__
from map_machine.map_configuration import BuildingMode, DrawingMode, LabelMode
@ -14,7 +15,7 @@ __email__ = "me@enzet.ru"
BOXES: str = " ▏▎▍▌▋▊▉"
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_with_tooltips": [
"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."""
parser: argparse.ArgumentParser = argparse.ArgumentParser(
description="Map Machine. OpenStreetMap renderer with custom icon set"
@ -117,9 +118,15 @@ def add_map_arguments(parser: argparse.ArgumentParser) -> None:
parser.add_argument(
"--show-tooltips",
help="add tooltips with tags for icons in SVG files",
action=argparse.BooleanOptionalAction,
action="store_true",
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(
"--country",
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."""
parser.add_argument(
"--icons",
action=argparse.BooleanOptionalAction,
action="store_true",
default=True,
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(
"--ways",
action=argparse.BooleanOptionalAction,
action="store_true",
default=False,
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(
"--lifecycle",
action=argparse.BooleanOptionalAction,
action="store_true",
default=True,
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"times",
)
parser.add_argument(
"--no-lifecycle",
dest="lifecycle",
action="store_false",
help="don't add icons for lifecycle tags",
)
def progress_bar(

View file

@ -1,6 +1,8 @@
"""
Vector utility.
"""
from typing import List
import numpy as np
__author__ = "Sergey Vartanov"
@ -45,12 +47,12 @@ class Polyline:
List of connected points.
"""
def __init__(self, points: list[np.ndarray]) -> None:
self.points: list[np.ndarray] = points
def __init__(self, points: List[np.ndarray]) -> None:
self.points: List[np.ndarray] = points
def get_path(self, parallel_offset: float = 0) -> str:
"""Construct SVG path commands."""
points: list[np.ndarray]
points: List[np.ndarray]
try:
points = (
LineString(self.points).parallel_offset(parallel_offset).coords

View file

@ -49,7 +49,7 @@ setup(
"scheme/default.yml",
],
},
python_requires=">=3.9",
python_requires=">=3.8",
install_requires=[
"CairoSVG>=2.5.0",
"colour>=0.1.5",

View file

@ -7,13 +7,15 @@ from subprocess import PIPE, Popen
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
from typing import List
from xml.etree import ElementTree
from xml.etree.ElementTree import Element
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."""
p = Popen(["map-machine"] + arguments, stderr=PIPE)
_, error = p.communicate()
@ -21,7 +23,7 @@ def error_run(arguments: list[str], message: bytes) -> None:
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."""
p = Popen(["map-machine"] + arguments, stderr=PIPE)
_, error = p.communicate()

View file

@ -1,6 +1,8 @@
"""
Test icon generation for nodes.
"""
from typing import Dict, Set
import pytest
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)
def get_icon(tags: dict[str, str]) -> IconSet:
def get_icon(tags: Dict[str, str]) -> IconSet:
"""Construct icon from tags."""
processed: set[str] = set()
processed: Set[str] = set()
icon, _ = SCHEME.get_icon(SHAPE_EXTRACTOR, tags, processed)
return icon

View file

@ -1,6 +1,8 @@
"""
Test label generation for nodes.
"""
from typing import Dict, List, Set
from map_machine.text import Label
from tests import SCHEME
@ -8,9 +10,9 @@ __author__ = "Sergey Vartanov"
__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."""
processed: set[str] = set()
processed: Set[str] = set()
return SCHEME.construct_text(tags, "all", processed)