Refactor argument parsing.

This commit is contained in:
Sergey Vartanov 2021-07-12 02:47:40 +03:00
parent 070ed05c8c
commit b4682e32d7
6 changed files with 127 additions and 88 deletions

View file

@ -8,6 +8,7 @@ import sys
from pathlib import Path
from typing import List
import logging
import numpy as np
import svgwrite
@ -17,8 +18,13 @@ from roentgen.flinger import Flinger
from roentgen.grid import draw_icons, write_mapcss
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
AUTHOR_MODE,
CREATION_TIME_MODE,
ICONS_FILE_NAME,
Painter,
TAGS_FILE_NAME,
check_level_number,
check_level_overground,
)
from roentgen.osm_getter import get_osm
from roentgen.osm_reader import Map, OSMReader, OverpassReader
@ -28,16 +34,14 @@ from roentgen.ui import error, parse_options
from roentgen.util import MinMax
def main(argv) -> None:
def main(options) -> None:
"""
Röntgen entry point.
:param argv: command-line arguments
"""
options: argparse.Namespace = parse_options(argv)
if not options:
sys.exit(1)
if options.boundary_box:
options.boundary_box = options.boundary_box.replace(" ", "")
cache_path: Path = Path(options.cache)
cache_path.mkdir(parents=True, exist_ok=True)
@ -65,7 +69,7 @@ def main(argv) -> None:
map_ = reader.map_
view_box = MinMax(
np.array((map_.boundary_box[0].min_, map_.boundary_box[1].min_)),
np.array((map_.boundary_box[0].max_, map_.boundary_box[1].max_))
np.array((map_.boundary_box[0].max_, map_.boundary_box[1].max_)),
)
else:
is_full: bool = options.mode in [AUTHOR_MODE, CREATION_TIME_MODE]
@ -82,11 +86,11 @@ def main(argv) -> None:
if options.boundary_box:
boundary_box: List[float] = list(
map(float, options.boundary_box.split(','))
map(float, options.boundary_box.split(","))
)
view_box = MinMax(
np.array((boundary_box[1], boundary_box[0])),
np.array((boundary_box[3], boundary_box[2]))
np.array((boundary_box[3], boundary_box[2])),
)
else:
view_box = map_.view_box
@ -94,6 +98,8 @@ def main(argv) -> None:
flinger: Flinger = Flinger(view_box, options.scale)
size: np.array = flinger.size
Path("out").mkdir(parents=True, exist_ok=True)
svg: svgwrite.Drawing = svgwrite.Drawing(
options.output_file_name, size=size
)
@ -102,35 +108,49 @@ def main(argv) -> None:
)
def check_level(x) -> bool:
""" Draw objects on all levels. """
"""Draw objects on all levels."""
return True
if options.level:
if options.level == "overground":
check_level = check_level_overground
elif options.level == "underground":
def check_level(x) -> bool:
""" Draw underground objects. """
"""Draw underground objects."""
return not check_level_overground(x)
else:
def check_level(x) -> bool:
""" Draw objects on the specified level. """
"""Draw objects on the specified level."""
return not check_level_number(x, float(options.level))
constructor: Constructor = Constructor(
map_, flinger, scheme, icon_extractor, check_level, options.mode,
options.seed)
map_,
flinger,
scheme,
icon_extractor,
check_level,
options.mode,
options.seed,
)
constructor.construct()
painter: Painter = Painter(
show_missing_tags=options.show_missing_tags, overlap=options.overlap,
mode=options.mode, label_mode=options.label_mode,
map_=map_, flinger=flinger, svg=svg, icon_extractor=icon_extractor,
scheme=scheme)
overlap=options.overlap,
mode=options.mode,
label_mode=options.label_mode,
map_=map_,
flinger=flinger,
svg=svg,
icon_extractor=icon_extractor,
scheme=scheme,
)
painter.draw(constructor)
print("Writing output SVG...")
print(f"Writing output SVG to {options.output_file_name}...")
with open(options.output_file_name, "w") as output_file:
svg.write(output_file)
@ -152,8 +172,13 @@ def draw_element(target: str, tags_description: str):
is_for_node: bool = target == "node"
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
icon,
labels,
tags,
np.array((32, 32)),
None,
is_for_node=is_for_node,
draw_outline=is_for_node,
)
border: np.array = np.array((16, 16))
size: np.array = point.get_size() + border
@ -171,15 +196,20 @@ def draw_element(target: str, tags_description: str):
if __name__ == "__main__":
if len(sys.argv) == 3 and sys.argv[1] in ["node", "way", "area"]:
draw_element(sys.argv[1], sys.argv[2])
elif len(sys.argv) == 2 and sys.argv[1] == "icons":
logging.basicConfig(format='%(levelname)s %(message)s', level=logging.INFO)
options: argparse.Namespace = parse_options(sys.argv)
if options.command == "render":
main(options)
elif options.command == "tile":
tile.ui(options)
elif options.command == "icons":
draw_icons()
elif len(sys.argv) == 2 and sys.argv[1] == "mapcss":
elif options.command == "mapcss":
write_mapcss()
elif len(sys.argv) >= 2 and sys.argv[1] == "tile":
tile.ui(sys.argv[2:])
elif len(sys.argv) >= 2 and sys.argv[1] == "server":
elif options.command == "element":
draw_element(options)
elif options.command == "server":
server.ui(sys.argv[2:])
else:
main(sys.argv)

View file

@ -335,8 +335,6 @@ class Constructor:
key=lambda x: -self.map_.nodes[x].coordinates[0],
)
missing_tags = Counter()
for node_id in sorted_node_ids: # type: int
processed: Set[str] = set()
@ -383,10 +381,4 @@ class Constructor:
) # fmt: skip
self.points.append(point)
missing_tags.update(
f"{key}: {tags[key]}"
for key in tags
if key not in icon_set.processed
)
ui.progress_bar(-1, len(self.map_.nodes), text="Constructing nodes")

View file

@ -5,6 +5,7 @@ from dataclasses import dataclass
from pathlib import Path
from typing import Dict, List, Optional, Set
import logging
import numpy as np
from colour import Color
from svgwrite import Drawing
@ -231,9 +232,14 @@ def draw_icons() -> None:
Path("icons/icons.svg"), Path("icons/config.json")
)
collection: IconCollection = IconCollection.from_scheme(scheme, extractor)
collection.draw_grid(out_path / "icon_grid.svg")
icon_grid_path: Path = out_path / "icon_grid.svg"
collection.draw_grid(icon_grid_path)
logging.info(f"Icon grid is written to {icon_grid_path}.")
collection.draw_icons(icons_by_id_path)
collection.draw_icons(icons_by_name_path, by_name=True)
logging.info(
f"Icons are written to {icons_by_name_path} and {icons_by_id_path}."
)
def write_mapcss() -> None:
@ -241,7 +247,11 @@ def write_mapcss() -> None:
Write MapCSS 0.2 scheme.
"""
out_path: Path = Path("out")
icons_with_outline_path: Path = out_path / "roentgen_icons" / "icons"
directory: Path = (out_path / "roentgen_icons_mapcss")
directory.mkdir(exist_ok=True)
icons_with_outline_path: Path = directory / "icons"
icons_with_outline_path.mkdir(parents=True, exist_ok=True)
scheme: Scheme = Scheme(Path("scheme/default.yml"))
extractor: ShapeExtractor = ShapeExtractor(
@ -251,13 +261,12 @@ def write_mapcss() -> None:
collection.draw_icons(
icons_with_outline_path, color=Color("black"), outline=True
)
(out_path / "roentgen_icons").mkdir(exist_ok=True)
with Path("data/roentgen_icons_part.mapcss").open() as input_file:
with (out_path / "roentgen_icons" / "roentgen_icons.mapcss").open(
"w+"
) as output_file:
with (directory / "roentgen_icons.mapcss").open("w+") as output_file:
for line in input_file.readlines():
if line == "%CONTENT%\n":
output_file.write(collection.get_mapcss_selectors())
else:
output_file.write(line)
logging.info(f"MapCSS 0.2 scheme is written to {directory}.")

View file

@ -26,7 +26,6 @@ __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"
AUTHOR_MODE = "user-coloring"
CREATION_TIME_MODE = "time"
@ -44,12 +43,10 @@ class Painter:
svg: svgwrite.Drawing,
icon_extractor: ShapeExtractor,
scheme: Scheme,
show_missing_tags: bool = False,
overlap: int = 12,
mode: str = "normal",
label_mode: str = "main",
):
self.show_missing_tags: bool = show_missing_tags
self.overlap: int = overlap
self.mode: str = mode
self.label_mode: str = label_mode

View file

@ -86,7 +86,7 @@ class Tile:
)
return np.array(extended_1), np.array(extended_2)
def load_map(self) -> Optional[Map]:
def load_map(self, cache_path: Path) -> Optional[Map]:
"""
Construct map data from extended boundary box.
"""
@ -103,7 +103,7 @@ class Tile:
error("cannot download OSM data")
return None
return OSMReader().parse_osm_file("map" / Path(boundary_box + ".osm"))
return OSMReader().parse_osm_file(cache_path / (boundary_box + ".osm"))
def get_map_name(self, directory_name: Path) -> Path:
"""
@ -119,13 +119,13 @@ class Tile:
f"https://tile.openstreetmap.org/{self.scale}/{self.x}/{self.y}.png"
)
def draw(self, directory_name: Path):
def draw(self, directory_name: Path, cache_path: Path):
"""
Draw tile to SVG file.
:param directory_name: output directory to storing tiles
"""
map_ = self.load_map()
map_ = self.load_map(cache_path)
lat1, lon1 = self.get_coordinates()
lat2, lon2 = Tile(self.x + 1, self.y + 1, self.scale).get_coordinates()
@ -164,18 +164,12 @@ class Tile:
svg.write(output_file)
def ui(args) -> None:
def ui(options) -> None:
"""
Simple user interface for tile generation.
"""
parser: argparse.ArgumentParser = argparse.ArgumentParser()
parser.add_argument("-c")
parser.add_argument("-s")
parser.add_argument("-t")
options = parser.parse_args(args)
directory: Path = Path("tiles")
directory.mkdir(exist_ok=True)
directory: Path = Path("out/tiles")
directory.mkdir(parents=True, exist_ok=True)
tile: Tile
if options.c and options.s:
@ -187,5 +181,5 @@ def ui(args) -> None:
else:
sys.exit(1)
tile.draw(directory)
tile.draw(directory, Path(options.cache))
print(tile.get_carto_address())

View file

@ -20,66 +20,83 @@ def parse_options(args) -> argparse.Namespace:
"""
Parse Röntgen command-line options.
"""
parser = argparse.ArgumentParser()
parser = argparse.ArgumentParser(
description="Röntgen. OpenStreetMap renderer with custom icon set"
)
subparser = parser.add_subparsers(dest="command")
parser.add_argument(
render = subparser.add_parser("render")
icons = subparser.add_parser("icons")
mapcss = subparser.add_parser("mapcss")
tile = subparser.add_parser("tile")
render.add_argument(
"-i",
"--input",
dest="input_file_name",
metavar="<path>",
nargs="*",
help="input XML file name (if not specified, file will be downloaded "
"using OpenStreetMap API)",
help="input XML file name or names (if not specified, file will be "
"downloaded using OpenStreetMap API)",
)
parser.add_argument(
render.add_argument(
"-o",
"--output",
dest="output_file_name",
metavar="<path>",
default="map.svg",
help="output SVG file name (map.svg by default)",
default="out/map.svg",
help="output SVG file name (out/map.svg by default)",
)
parser.add_argument(
render.add_argument(
"-b",
"--boundary-box",
dest="boundary_box",
metavar="<lon1>,<lat1>,<lon2>,<lat2>",
help='geo boundary box, use space before "-" for negative values',
help='geo boundary box, use space before "-" if the first value is '
"negative",
)
parser.add_argument(
render.add_argument(
"-s",
"--scale",
metavar="<float>",
help="OSM zoom level (may not be integer, default is 18)",
default=18,
dest="scale",
type=float,
)
parser.add_argument(
"--cache", help="path for temporary OSM files", default="cache"
render.add_argument(
"--cache",
help="path for temporary OSM files",
default="cache",
metavar="<path>",
)
parser.add_argument(
render.add_argument(
"--labels",
help="label drawing mode: `no`, `main`, or `all`",
dest="label_mode",
default="main",
)
parser.add_argument(
"--show-missing-tags", dest="show_missing_tags", action="store_true"
render.add_argument(
"--overlap",
dest="overlap",
default=12,
type=int,
help="how many pixels should be left around icons and text",
)
parser.add_argument(
"--no-show-missing-tags", dest="show_missing_tags", action="store_false"
render.add_argument("--mode", default="normal")
render.add_argument("--seed", default="")
render.add_argument("--level", default=None)
tile.add_argument("-c")
tile.add_argument("-s")
tile.add_argument("-t")
tile.add_argument(
"--cache",
help="path for temporary OSM files",
default="cache",
metavar="<path>",
)
parser.add_argument("--overlap", dest="overlap", default=12, type=int)
parser.add_argument("--mode", default="normal")
parser.add_argument("--seed", default="")
parser.add_argument("--level", default=None)
arguments: argparse.Namespace = parser.parse_args(args[1:])
if arguments.boundary_box:
arguments.boundary_box = arguments.boundary_box.replace(" ", "")
return arguments