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,7 +54,8 @@ def draw_all_icons(
if constructed_icon not in icons:
icons.append(constructed_icon)
for element in scheme.icons: # type: Dict[str, Any]
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]

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,8 +200,10 @@ class Scheme:
processed: Set[str] = set()
priority: int = 0
for index, matcher in enumerate(self.icons):
index: int
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()
@ -238,6 +242,8 @@ class Scheme:
if "set_main_color" in matcher:
main_icon.recolor(self.get_color(matcher["set_main_color"]))
index += 1
color: Optional[Color] = None
for tag_key in tags: # type: str

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:

View file

@ -69,8 +69,8 @@ colors:
node_icons:
# No draw
- group: "No draw"
tags:
- tags: {type: multipolygon}
draw: false
- tags: {place: "*"}
@ -78,8 +78,8 @@ node_icons:
- tags: {building: "yes"}
draw: false
# Transport hubs
- group: "Transport hubs"
tags:
- tags: {amenity: ferry_terminal}
icon: [anchor]
- tags: {amenity: ferry_terminal, cargo: vehicle}
@ -133,30 +133,27 @@ node_icons:
- tags: {highway: stop}
icon: [stop]
# Big territory
- group: "Big territory"
tags:
- tags: {leisure: fishing}
icon: [fishing_angle]
- tags: {power: substation}
icon: [electricity]
# plant=*
- tags: {plant: christmas_trees}
icon: [{shape: christmas_tree, color: orchard_border_color}]
# produce=*
- tags: {produce: apple}
icon: [{shape: apple, color: orchard_border_color}]
- tags: {produce: christmas_trees}
icon: [{shape: christmas_tree, color: orchard_border_color}]
- tags: {produce: pear}
icon: [{shape: pear, color: orchard_border_color}]
# trees=*
- tags: {trees: apple_trees}
icon: [{shape: apple, color: orchard_border_color}]
- tags: {trees: pear_trees}
icon: [{shape: pear, color: orchard_border_color}]
# Bigger objects
- group: "Bigger objects"
tags:
- tags: {waterway: waterfall}
icon: [{shape: waterfall, color: water_border_color}]
- tags: {natural: cliff}
@ -168,8 +165,8 @@ node_icons:
- tags: {shop: mall, building: "yes"}
icon: [bag]
# Important big objects
- group: "Important big objects"
tags:
- tags: {amenity: pharmacy}
icon: [medicine_bottle]
- tags: {amenity: embassy}
@ -226,8 +223,8 @@ node_icons:
- tags: {historic: tomb, tomb: mausoleum}
icon: [mausoleum]
# Normal big objects
- group: "Normal big objects"
tags:
- tags: {shop: supermarket}
icon: [supermarket_cart]
- tags: {amenity: arts_centre}
@ -324,8 +321,8 @@ node_icons:
- tags: {shop: electronics}
icon: [tv]
# Big objects not for all
- group: "Big objects not for all"
tags:
- tags: {building: apartments}
icon: [apartments]
- tags: {building: kindergarten}
@ -342,8 +339,8 @@ node_icons:
- tags: {office: telecommunication}
icon: [telephone]
# Not important big objects
- group: "Not important big objects"
tags:
- tags: {man_made: tower}
icon: [tower]
- tags: {building: garages}
@ -351,8 +348,8 @@ node_icons:
- tags: {building: garage}
icon: [garages]
# Emergency
- group: "Emergency"
tags:
- tags: {emergency: defibrillator}
icon: [{shape: defibrillator, color: emergency_color}]
- tags: {emergency: fire_extinguisher}
@ -364,8 +361,8 @@ node_icons:
- tags: {emergency: phone}
icon: [{shape: sos_phone, color: emergency_color}]
# Transport-important middle objects
- group: "Transport-important middle objects"
tags:
- tags: {ford: "yes"}
icon: [ford]
- tags: {amenity: charging_station}
@ -399,15 +396,15 @@ node_icons:
- tags: {crossing_ref: toucan}
icon: [toucan_crossing]
# Important middle objects
- group: "Important middle objects"
tags:
- tags: {tourism: attraction, attraction: amusement_ride}
icon: [amusement_ride]
- tags: {amenity: toilets}
icon: [woman_and_man]
# Normal middle objects
- group: "Normal middle objects"
tags:
- tags: {shop: kiosk}
icon: [kiosk]
- tags: {building: "yes", shop: kiosk}
@ -419,8 +416,8 @@ node_icons:
- tags: {natural: cave_entrance}
icon: [cave]
# Not important middle objects
- group: "Not important middle objects"
tags:
- tags: {building: ventilation_shaft}
icon: [ventilation]
- tags: {power: generator}
@ -476,8 +473,8 @@ node_icons:
- tags: {power: tower, design: portal_three-level}
icon: [power_tower_portal_3_level]
# Important small objects
- group: "Important small objects"
tags:
- tags: {historic: memorial}
icon: [memorial]
- tags: {historic: memorial, memorial: plaque}
@ -552,8 +549,8 @@ node_icons:
- tags: {xmas:feature: tree}
icon: {christmas_tree}
# Normal small objects
- group: "Normal small objects"
tags:
- tags: {amenity: binoculars}
icon: [binoculars_on_pole]
- tags: {amenity: post_box}
@ -587,8 +584,8 @@ node_icons:
- tags: {leisure: picnic_table}
icon: [table]
# Entrances
- group: "Entrances"
tags:
- tags: {barrier: gate}
icon: [gate]
- tags: {entrance: main}
@ -606,8 +603,8 @@ node_icons:
- tags: {door: "no"}
icon: [no_door]
# Not important small objects
- group: "Not important small objects"
tags:
- tags: {amenity: bench}
icon: [bench]
- tags: {amenity: bench, backrest: "yes"}
@ -652,7 +649,7 @@ node_icons:
icon: [lowered_kerb]
- tags: {kerb: lowered}
icon: [lowered_kerb]
# Trees
- tags: {natural: tree}
icon: [{shape: tree, color: tree_color}]
- tags: {leaf_type: broadleaved}
@ -679,7 +676,6 @@ node_icons:
set_main_color: evergreen_color
- tags: {natural: bush}
icon: [{shape: bush, color: tree_color}]
# Tree genus
- tags: {natural: tree, genus: Betula}
icon: [{shape: betula, color: tree_color}]
- tags: {natural: tree, "genus:en": Birch}
@ -734,13 +730,13 @@ node_icons:
- tags: {barrier: bollard}
icon: [bollard]
# Indoor
- group: "Indoor"
tags:
- tags: {door: "yes"}
icon: [entrance]
# Add and over
- group: "Add and over"
tags:
- tags: {support: pole}
over_icon: [support_pole]
under_icon: [clock, information_board]

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