Change scheme format; support pathlib.

Change name documentation as well.
This commit is contained in:
Sergey Vartanov 2021-05-22 01:37:58 +03:00
parent c5bb1529f5
commit bb70935c19
27 changed files with 885 additions and 850 deletions

6
.gitignore vendored
View file

@ -22,8 +22,10 @@ missed_tags.yml
# Cache
map/ # OSM XML files
cache/
map/
# Generated files
icon_set/ # Generated SVG icon files
icon_set/
out/

Binary file not shown.

Before

Width:  |  Height:  |  Size: 72 KiB

After

Width:  |  Height:  |  Size: 72 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 116 KiB

After

Width:  |  Height:  |  Size: 116 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 14 KiB

After

Width:  |  Height:  |  Size: 652 B

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 145 KiB

After

Width:  |  Height:  |  Size: 144 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 167 KiB

After

Width:  |  Height:  |  Size: 169 KiB

Before After
Before After

Binary file not shown.

Before

Width:  |  Height:  |  Size: 68 KiB

After

Width:  |  Height:  |  Size: 68 KiB

Before After
Before After

View file

@ -7,6 +7,7 @@ import argparse
import os
import sys
from pathlib import Path
from typing import List
import numpy as np
import svgwrite
@ -15,7 +16,7 @@ from roentgen import ui
from roentgen.constructor import Constructor
from roentgen.flinger import Flinger
from roentgen.grid import draw_all_icons
from roentgen.icon import ShapeExtractor, ShapeConfiguration
from roentgen.icon import ShapeExtractor
from roentgen.mapper import (
AUTHOR_MODE, CREATION_TIME_MODE, ICONS_FILE_NAME, Painter, TAGS_FILE_NAME,
check_level_number, check_level_overground
@ -38,22 +39,26 @@ def main(argv) -> None:
if not options:
sys.exit(1)
input_file_names: List[Path]
if options.input_file_name:
input_file_name = options.input_file_name
input_file_names = list(map(Path, options.input_file_name))
else:
content = get_osm(options.boundary_box)
if not content:
ui.error("cannot download OSM data")
input_file_name = [os.path.join("map", options.boundary_box + ".osm")]
input_file_names = ["map" / Path(options.boundary_box + ".osm")]
scheme: Scheme = Scheme(TAGS_FILE_NAME)
scheme: Scheme = Scheme(Path(TAGS_FILE_NAME))
min_: np.array
max_: np.array
if input_file_name[0].endswith(".json"):
if input_file_names[0].name.endswith(".json"):
reader: OverpassReader = OverpassReader()
reader.parse_json_file(input_file_name[0])
reader.parse_json_file(input_file_names[0])
map_ = reader.map_
min1 = np.array((map_.boundary_box[0].min_, map_.boundary_box[1].min_))
max1 = np.array((map_.boundary_box[0].max_, map_.boundary_box[1].max_))
min_ = np.array((map_.boundary_box[0].min_, map_.boundary_box[1].min_))
max_ = np.array((map_.boundary_box[0].max_, map_.boundary_box[1].max_))
else:
boundary_box = list(map(float, options.boundary_box.split(',')))
@ -65,19 +70,19 @@ def main(argv) -> None:
osm_reader = OSMReader()
for file_name in input_file_name:
if not os.path.isfile(file_name):
print("Fatal: no such file: " + file_name + ".")
for file_name in input_file_names:
if not file_name.is_file():
print(f"Fatal: no such file: {file_name}.")
sys.exit(1)
osm_reader.parse_osm_file(file_name, full=full)
map_: Map = osm_reader.map_
min1: np.array = np.array((boundary_box[1], boundary_box[0]))
max1: np.array = np.array((boundary_box[3], boundary_box[2]))
min_ = np.array((boundary_box[1], boundary_box[0]))
max_ = np.array((boundary_box[3], boundary_box[2]))
flinger: Flinger = Flinger(MinMax(min1, max1), options.scale)
flinger: Flinger = Flinger(MinMax(min_, max_), options.scale)
size: np.array = flinger.size
svg: svgwrite.Drawing = (
@ -130,11 +135,13 @@ def draw_element(target: str, tags_description: str):
comma, key from value is separated by equals sign.
"""
tags = dict([x.split("=") for x in tags_description.split(",")])
scheme = Scheme("scheme/default.yml")
extractor = ShapeExtractor("icons/icons.svg", Path("icons/config.json"))
scheme = Scheme(Path("scheme/default.yml"))
extractor = ShapeExtractor(
Path("icons/icons.svg"), Path("icons/config.json")
)
icon, priority = scheme.get_icon(extractor, tags)
is_for_node: bool = target == "node"
labels = scheme.construct_text(tags, True)
labels = scheme.construct_text(tags, "all")
point = Point(
icon, labels, tags, np.array((32, 32)), None, is_for_node=is_for_node,
draw_outline=is_for_node
@ -154,7 +161,7 @@ def draw_element(target: str, tags_description: str):
svg.write(open("test_icon.svg", "w+"))
def draw_grid():
def draw_grid() -> None:
"""
Draw all possible icon shapes combinations as grid.
"""

View file

@ -1,7 +1,5 @@
"""
Color utility.
Author: Sergey Vartanov (me@enzet.ru)
"""
from typing import Any, List
@ -9,6 +7,9 @@ from colour import Color
from roentgen.util import MinMax
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
def is_bright(color: Color) -> bool:
"""

View file

@ -1,7 +1,5 @@
"""
Construct Röntgen nodes and ways.
Author: Sergey Vartanov (me@enzet.ru).
"""
from collections import Counter
from datetime import datetime
@ -23,6 +21,9 @@ from roentgen.point import Point
from roentgen.scheme import DEFAULT_COLOR, LineStyle, Scheme
from roentgen.util import MinMax
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEBUG: bool = False
TIME_COLOR_SCALE: List[Color] = [
Color("#581845"), Color("#900C3F"), Color("#C70039"), Color("#FF5733"),

View file

@ -1,13 +1,14 @@
"""
Direction tag support.
Author: Sergey Vartanov (me@enzet.ru).
"""
from typing import Iterator, List, Optional, Union
import numpy as np
from portolan import middle
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
SVGPath = Union[float, str, np.array]
SHIFT: float = -np.pi / 2

View file

@ -1,6 +1,4 @@
"""
Author: Sergey Vartanov (me@enzet.ru)
Geo projection.
"""
from typing import Optional
@ -9,6 +7,9 @@ import numpy as np
from roentgen.util import MinMax
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
EQUATOR_LENGTH: float = 40_075_017 # (in meters)

View file

@ -1,7 +1,5 @@
"""
Icon grid drawing.
Author: Sergey Vartanov (me@enzet.ru).
"""
from os.path import join
from pathlib import Path
@ -14,6 +12,9 @@ from svgwrite import Drawing
from roentgen.icon import Icon, ShapeExtractor, ShapeSpecification
from roentgen.scheme import Scheme
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
def draw_all_icons(
output_file_name: str, output_directory: str, columns: int = 16,
@ -31,14 +32,13 @@ def draw_all_icons(
:param background_color: background color
:param color: icon color
"""
tags_file_name: str = "scheme/default.yml"
scheme: Scheme = Scheme(tags_file_name)
scheme: Scheme = Scheme(Path("scheme/default.yml"))
icons: List[Icon] = []
icons_file_name: str = "icons/icons.svg"
extractor: ShapeExtractor = ShapeExtractor(
icons_file_name, Path("icons/config.json")
Path(icons_file_name), Path("icons/config.json")
)
def add() -> None:
@ -54,32 +54,33 @@ def draw_all_icons(
if constructed_icon not in icons:
icons.append(constructed_icon)
for element in scheme.icons: # type: Dict[str, Any]
for key in ["icon", "add_icon"]:
if key in element:
current_set = element[key]
add()
if "over_icon" not in element:
continue
if "under_icon" in element:
for group in scheme.icons:
for element in group["tags"]: # type: Dict[str, Any]
for key in ["icon", "add_icon"]:
if key in element:
current_set = element[key]
add()
if "over_icon" not in element:
continue
if "under_icon" in element:
for icon_id in element["under_icon"]: # type: str
current_set = set([icon_id] + element["over_icon"])
add()
if not ("under_icon" in element and "with_icon" in element):
continue
for icon_id in element["under_icon"]: # type: str
current_set = set([icon_id] + element["over_icon"])
add()
if not ("under_icon" in element and "with_icon" in element):
continue
for icon_id in element["under_icon"]: # type: str
for icon_2_id in element["with_icon"]: # type: str
current_set: Set[str] = set(
[icon_id] + [icon_2_id] + element["over_icon"])
add()
for icon_2_id in element["with_icon"]: # type: str
for icon_3_id in element["with_icon"]: # type: str
current_set = set(
[icon_id] + [icon_2_id] + [icon_3_id] +
element["over_icon"])
if (icon_2_id != icon_3_id and icon_2_id != icon_id and
icon_3_id != icon_id):
add()
for icon_2_id in element["with_icon"]: # type: str
current_set: Set[str] = set(
[icon_id] + [icon_2_id] + element["over_icon"])
add()
for icon_2_id in element["with_icon"]: # type: str
for icon_3_id in element["with_icon"]: # type: str
current_set = set(
[icon_id] + [icon_2_id] + [icon_3_id] +
element["over_icon"])
if (icon_2_id != icon_3_id and icon_2_id != icon_id and
icon_3_id != icon_id):
add()
specified_ids: Set[str] = set()

View file

@ -1,7 +1,5 @@
"""
Extract icons from SVG file.
Author: Sergey Vartanov (me@enzet.ru).
"""
import json
import re
@ -19,6 +17,9 @@ from svgwrite.path import Path as SvgPath
from roentgen.color import is_bright
from roentgen.ui import error
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEFAULT_COLOR: Color = Color("#444444")
DEFAULT_SHAPE_ID: str = "default"
DEFAULT_SMALL_SHAPE_ID: str = "default_small"
@ -94,7 +95,7 @@ class ShapeExtractor:
Shape is a single path with "id" attribute that aligned to 16×16 grid.
"""
def __init__(self, svg_file_name: str, configuration_file_name: Path):
def __init__(self, svg_file_name: Path, configuration_file_name: Path):
"""
:param svg_file_name: input SVG file name with icons. File may contain
any other irrelevant graphics.
@ -102,7 +103,7 @@ class ShapeExtractor:
self.configuration = ShapeConfiguration(configuration_file_name)
self.shapes: Dict[str, Shape] = {}
with open(svg_file_name) as input_file:
with svg_file_name.open() as input_file:
content: Document = parse(input_file)
for element in content.childNodes: # type: Element
if element.nodeName != "svg":

View file

@ -1,7 +1,5 @@
"""
Simple OpenStreetMap renderer.
Author: Sergey Vartanov (me@enzet.ru).
"""
from typing import Any, Dict
@ -21,6 +19,9 @@ from roentgen.osm_reader import Map
from roentgen.point import Occupied, Point
from roentgen.scheme import Scheme
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
ICONS_FILE_NAME: str = "icons/icons.svg"
TAGS_FILE_NAME: str = "scheme/default.yml"
MISSING_TAGS_FILE_NAME: str = "missing_tags.yml"

View file

@ -1,18 +1,19 @@
"""
Getting OpenStreetMap data from the web.
Author: Sergey Vartanov (me@enzet.ru).
"""
import os
import re
import time
import urllib
from pathlib import Path
from typing import Dict, Optional
import urllib3
from roentgen.ui import error
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
"""
@ -21,10 +22,10 @@ def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
:param boundary_box: borders of the map part to download
:param to_update: update cache files
"""
result_file_name = os.path.join("map", boundary_box + ".osm")
result_file_name: Path = "map" / Path(boundary_box + ".osm")
if not to_update and os.path.isfile(result_file_name):
return open(result_file_name).read()
if not to_update and result_file_name.is_file():
return result_file_name.open().read()
matcher = re.match(
"(?P<left>[0-9.-]*),(?P<bottom>[0-9.-]*)," +
@ -58,7 +59,7 @@ def get_osm(boundary_box: str, to_update: bool = False) -> Optional[str]:
"api.openstreetmap.org/api/0.6/map",
{"bbox": boundary_box}, is_secure=True)
open(result_file_name, "w+").write(content.decode("utf-8"))
result_file_name.open("w+").write(content.decode("utf-8"))
return content.decode("utf-8")

View file

@ -1,10 +1,9 @@
"""
Reading OpenStreetMap data from XML file.
Author: Sergey Vartanov (me@enzet.ru).
"""
import json
from datetime import datetime
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Union
import numpy as np
@ -12,6 +11,9 @@ import numpy as np
from roentgen.ui import progress_bar
from roentgen.util import MinMax
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
OSM_TIME_PATTERN: str = "%Y-%m-%dT%H:%M:%SZ"
@ -67,7 +69,8 @@ class OSMNode(Tagged):
self.visible = get_value("visible", text)
self.changeset = get_value("changeset", text)
self.timestamp = datetime.strptime(
get_value("timestamp", text), OSM_TIME_PATTERN)
get_value("timestamp", text), OSM_TIME_PATTERN
)
self.user = get_value("user", text)
self.uid = get_value("uid", text)
@ -296,11 +299,11 @@ class OverpassReader:
def __init__(self):
self.map_ = Map()
def parse_json_file(self, file_name: str) -> Map:
def parse_json_file(self, file_name: Path) -> Map:
"""
Parse JSON structure from the file and construct map.
"""
with open(file_name) as input_file:
with file_name.open() as input_file:
structure = json.load(input_file)
node_map = {}
@ -332,7 +335,7 @@ class OSMReader:
self.map_ = Map()
def parse_osm_file(
self, file_name: str, parse_nodes: bool = True,
self, file_name: Path, parse_nodes: bool = True,
parse_ways: bool = True, parse_relations: bool = True,
full: bool = False
) -> Map:
@ -341,7 +344,7 @@ class OSMReader:
:param file_name: input OSM XML file name
"""
with open(file_name) as input_file:
with file_name.open() as input_file:
lines_number: int = sum(1 for _ in input_file)
print(f"Parsing OSM file {file_name}...")
@ -349,7 +352,7 @@ class OSMReader:
element: Optional[Union[OSMNode, OSMWay, OSMRelation]] = None
with open(file_name) as input_file:
with file_name.open() as input_file:
for line in input_file.readlines(): # type: str
line = line.strip()

View file

@ -11,6 +11,9 @@ from roentgen.icon import Icon, IconSet
from roentgen.osm_reader import Tagged
from roentgen.text import Label
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEFAULT_FONT: str = "Roboto"

View file

@ -1,10 +1,9 @@
"""
Röntgen drawing scheme.
Author: Sergey Vartanov (me@enzet.ru).
"""
from dataclasses import dataclass
from enum import Enum
from pathlib import Path
from typing import Any, Dict, List, Optional, Set, Tuple, Union
import yaml
@ -17,6 +16,9 @@ from roentgen.icon import (
)
from roentgen.text import Label, get_address, get_text
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
@dataclass
class LineStyle:
@ -103,12 +105,12 @@ class Scheme:
Specifies map colors and rules to draw icons for OpenStreetMap tags.
"""
def __init__(self, file_name: str):
def __init__(self, file_name: Path):
"""
:param file_name: scheme file name with tags, colors, and tag key
:param file_name: name of the scheme file with tags, colors, and tag key
specification
"""
with open(file_name) as input_file:
with file_name.open() as input_file:
content: Dict[str, Any] = yaml.load(
input_file.read(), Loader=yaml.FullLoader)
@ -198,45 +200,49 @@ class Scheme:
processed: Set[str] = set()
priority: int = 0
for index, matcher in enumerate(self.icons):
index: int
matcher: Dict[str, Any]
matched: bool = is_matched(matcher, tags)
matcher_tags: Set[str] = matcher["tags"].keys()
if not matched:
continue
priority = len(self.icons) - index
if "draw" in matcher and not matcher["draw"]:
processed |= set(matcher_tags)
if "icon" in matcher:
main_icon = Icon([
ShapeSpecification.from_structure(x, icon_extractor, self)
for x in matcher["icon"]
])
processed |= set(matcher_tags)
if "over_icon" in matcher:
if main_icon:
main_icon.add_specifications([
ShapeSpecification.from_structure(
x, icon_extractor, self
)
for x in matcher["over_icon"]
index: int = 0
for group in self.icons:
for matcher in group["tags"]:
matcher: Dict[str, Any]
matched: bool = is_matched(matcher, tags)
matcher_tags: Set[str] = matcher["tags"].keys()
if not matched:
continue
priority = len(self.icons) - index
if "draw" in matcher and not matcher["draw"]:
processed |= set(matcher_tags)
if "icon" in matcher:
main_icon = Icon([
ShapeSpecification.from_structure(x, icon_extractor, self)
for x in matcher["icon"]
])
processed |= set(matcher_tags)
if "over_icon" in matcher:
if main_icon:
main_icon.add_specifications([
ShapeSpecification.from_structure(
x, icon_extractor, self
)
for x in matcher["over_icon"]
])
for key in matcher_tags:
processed.add(key)
if "add_icon" in matcher:
extra_icons += [Icon([
ShapeSpecification.from_structure(
x, icon_extractor, self, Color("#888888")
)
for x in matcher["add_icon"]
])]
for key in matcher_tags:
processed.add(key)
if "add_icon" in matcher:
extra_icons += [Icon([
ShapeSpecification.from_structure(
x, icon_extractor, self, Color("#888888")
)
for x in matcher["add_icon"]
])]
for key in matcher_tags:
processed.add(key)
if "color" in matcher:
assert False
if "set_main_color" in matcher:
main_icon.recolor(self.get_color(matcher["set_main_color"]))
if "color" in matcher:
assert False
if "set_main_color" in matcher:
main_icon.recolor(self.get_color(matcher["set_main_color"]))
index += 1
color: Optional[Color] = None

View file

@ -1,13 +1,14 @@
"""
OSM address tag processing.
Author: Sergey Vartanov (me@enzet.ru).
"""
from dataclasses import dataclass
from typing import Any, Dict, List
from colour import Color
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
DEFAULT_COLOR: Color = Color("#444444")

View file

@ -1,11 +1,14 @@
"""
Author: Sergey Vartanov (me@enzet.ru).
Command-line user interface.
"""
import argparse
import sys
from typing import List, Optional
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
BOXES: List[str] = [" ", "", "", "", "", "", "", ""]
BOXES_LENGTH: int = len(BOXES)

View file

@ -1,11 +1,12 @@
"""
Röntgen utility file.
Author: Sergey Vartanov (me@enzet.ru).
"""
from dataclasses import dataclass
from typing import Any
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
@dataclass
class MinMax:

File diff suppressed because it is too large Load diff

View file

@ -1,12 +1,13 @@
"""
Test direction processing.
Author: Sergey Vartanov (me@enzet.ru).
"""
import numpy as np
from roentgen.direction import parse_vector
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
def test_compass_points_1() -> None:
""" Test north direction. """

View file

@ -1,7 +1,5 @@
"""
Test icon generation for nodes.
Author: Sergey Vartanov (me@enzet.ru).
"""
from os import makedirs
from pathlib import Path
@ -11,9 +9,12 @@ from roentgen.grid import draw_all_icons
from roentgen.icon import ShapeExtractor
from roentgen.scheme import Scheme
SCHEME: Scheme = Scheme("scheme/default.yml")
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
SCHEME: Scheme = Scheme(Path("scheme/default.yml"))
ICON_EXTRACTOR: ShapeExtractor = ShapeExtractor(
"icons/icons.svg", Path("icons/config.json")
Path("icons/icons.svg"), Path("icons/config.json")
)

View file

@ -1,14 +1,16 @@
"""
Test label generation for nodes.
Author: Sergey Vartanov (me@enzet.ru).
"""
from pathlib import Path
from typing import List
from roentgen.scheme import Scheme
from roentgen.text import Label
SCHEME: Scheme = Scheme("scheme/default.yml")
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
SCHEME: Scheme = Scheme(Path("scheme/default.yml"))
def construct_labels(tags) -> List[Label]:

View file

@ -1,10 +1,11 @@
"""
Test text generation.
Author: Sergey Vartanov (me@enzet.ru).
"""
from roentgen.text import format_voltage
__author__ = "Sergey Vartanov"
__email__ = "me@enzet.ru"
def test_voltage() -> None:
"""