""" Command-line user interface. """ import argparse import sys from map_machine import __version__ from map_machine.map_configuration import BuildingMode, DrawingMode, LabelMode from map_machine.osm_reader import STAGES_OF_DECAY __author__ = "Sergey Vartanov" __email__ = "me@enzet.ru" BOXES: str = " ▏▎▍▌▋▊▉" BOXES_LENGTH: int = len(BOXES) COMMANDS: dict[str, list[str]] = { "render": ["render", "-b", "10.000,20.000,10.001,20.001"], "render_with_tooltips": [ "render", "-b", "10.000,20.000,10.001,20.001", "--show-tooltips", ], "icons": ["icons"], "mapcss": ["mapcss"], "element": ["element", "--node", "amenity=bench,material=wood"], "tile": ["tile", "--coordinates", "50.000,40.000"], } def parse_options(args: list[str]) -> argparse.Namespace: """Parse Map Machine command-line options.""" parser: argparse.ArgumentParser = argparse.ArgumentParser( description="Map Machine. OpenStreetMap renderer with custom icon set" ) parser.add_argument( "-v", "--version", action="version", version="Map Machine " + __version__, ) subparser = parser.add_subparsers(dest="command") render_parser = subparser.add_parser("render", help="draw SVG map") add_render_arguments(render_parser) add_map_arguments(render_parser) tile_parser = subparser.add_parser( "tile", help="generate PNG tiles for slippy maps" ) add_tile_arguments(tile_parser) add_map_arguments(tile_parser) add_server_arguments(subparser.add_parser("server", help="run tile server")) add_element_arguments( subparser.add_parser( "element", help="draw OSM element: node, way, relation" ) ) add_mapcss_arguments( subparser.add_parser("mapcss", help="write MapCSS file") ) subparser.add_parser("icons", help="draw icons") subparser.add_parser("taginfo", help="write Taginfo JSON file") arguments: argparse.Namespace = parser.parse_args(args[1:]) return arguments def add_map_arguments(parser: argparse.ArgumentParser) -> None: """Add map-specific arguments.""" parser.add_argument( "--buildings", metavar="", default="flat", choices=(x.value for x in BuildingMode), help="building drawing mode: " + ", ".join(x.value for x in BuildingMode), ) parser.add_argument( "--mode", default="normal", metavar="", choices=(x.value for x in DrawingMode), help="map drawing mode: " + ", ".join(x.value for x in DrawingMode), ) parser.add_argument( "--overlap", dest="overlap", default=12, type=int, help="how many pixels should be left around icons and text", metavar="", ) parser.add_argument( "--labels", dest="label_mode", default="main", metavar="", choices=(x.value for x in LabelMode), help="label drawing mode: " + ", ".join(x.value for x in LabelMode), ) parser.add_argument( "--level", default="overground", help="display only this floor level", ) parser.add_argument( "--seed", default="", help="seed for random", metavar="", ) parser.add_argument( "--show-tooltips", help="add tooltips with tags for icons in SVG files", action=argparse.BooleanOptionalAction, default=False, ) def add_tile_arguments(parser: argparse.ArgumentParser) -> None: """Add arguments for tile command.""" parser.add_argument( "-c", "--coordinates", metavar=",", help="coordinates of any location inside the tile", ) parser.add_argument( "-t", "--tile", metavar="//", help="tile specification", ) parser.add_argument( "--cache", help="path for temporary OSM files", default="cache", metavar="", ) parser.add_argument( "-b", "--boundary-box", help="construct the minimum amount of tiles that cover requested " "boundary box", metavar=",,,", ) parser.add_argument( "-z", "--zoom", type=str, metavar="", help="OSM zoom levels; can be list of numbers or ranges, e.g. `16-18`, " "`16,17,18`, or `16,18-20`", default="18", ) parser.add_argument( "-i", "--input", dest="input_file_name", metavar="", help="input OSM XML file name (if not specified, file will be " "downloaded using OpenStreetMap API)", ) def add_server_arguments(parser: argparse.ArgumentParser) -> None: """Add arguments for server command.""" parser.add_argument( "--cache", help="path for temporary OSM files", default="cache", metavar="", ) parser.add_argument( "--port", help="port number", default=8080, type=int, metavar="", ) def add_element_arguments(parser: argparse.ArgumentParser) -> None: """Add arguments for element command.""" parser.add_argument("-n", "--node") parser.add_argument("-w", "--way") parser.add_argument("-r", "--relation") def add_render_arguments(parser: argparse.ArgumentParser) -> None: """Add arguments for render command.""" parser.add_argument( "-i", "--input", dest="input_file_name", metavar="", nargs="*", help="input XML file name or names (if not specified, file will be " "downloaded using OpenStreetMap API)", ) parser.add_argument( "-o", "--output", dest="output_file_name", metavar="", default="out/map.svg", help="output SVG file name", ) parser.add_argument( "-b", "--boundary-box", metavar=",,,", help="geo boundary box; if first value is negative, enclose the value " "with quotes and use space before `-`", ) parser.add_argument( "--cache", help="path for temporary OSM files", default="cache", metavar="", ) parser.add_argument( "-z", "--zoom", type=int, metavar="", help="OSM zoom level", default=18, ) def add_mapcss_arguments(parser: argparse.ArgumentParser) -> None: """Add arguments for mapcss command.""" parser.add_argument( "--icons", action=argparse.BooleanOptionalAction, default=True, help="add icons for nodes and areas", ) parser.add_argument( "--ways", action=argparse.BooleanOptionalAction, default=False, help="add style for ways and relations", ) parser.add_argument( "--lifecycle", action=argparse.BooleanOptionalAction, 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", ) def progress_bar( number: int, total: int, length: int = 20, step: int = 1000, text: str = "" ) -> None: """ Draw progress bar using Unicode symbols. :param number: current value :param total: maximum value :param length: progress bar length. :param step: frequency of progress bar updating (assuming that numbers go subsequently) :param text: short description """ if number == -1: sys.stdout.write(f"100 % {length * '█'}▏{text}\n") elif number % step == 0: ratio: float = number / total parts: int = int(ratio * length * BOXES_LENGTH) fill_length: int = int(parts / BOXES_LENGTH) box: str = BOXES[int(parts - fill_length * BOXES_LENGTH)] sys.stdout.write( f"{str(int(int(ratio * 1000) / 10)):>3} % {fill_length * '█'}{box}" f"{int(length - fill_length - 1) * ' '}▏{text}\n\033[F" )