From a8d511f0464360c4696015f1373bf0d49415df00 Mon Sep 17 00:00:00 2001 From: m9k Date: Sun, 27 Sep 2026 13:42:13 +0000 Subject: [PATCH] =?UTF-8?q?=D0=94=D0=BE=D0=B1=D0=B0=D0=B2=D0=B8=D1=82?= =?UTF-8?q?=D1=8C=20PrettyMaps/generate.py?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- PrettyMaps/generate.py | 219 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 219 insertions(+) create mode 100644 PrettyMaps/generate.py diff --git a/PrettyMaps/generate.py b/PrettyMaps/generate.py new file mode 100644 index 0000000..bcea8d0 --- /dev/null +++ b/PrettyMaps/generate.py @@ -0,0 +1,219 @@ +#!/usr/bin/env python3 +""" +Generate a prettymaps poster. + +Parameters (priority: CLI args > env vars > defaults): + + QUERY / --query location string (geocoded by osmnx) + LAT / --lat latitude (use with LON instead of QUERY) + LON / --lon longitude + RADIUS_M / --radius radius in metres + CIRCLE / --circle circular clip (true/false, 1/0, yes/no) + TITLE / --title map title + PRESET / --preset prettymaps preset name + OUTPUT_NAME / --output-name basename for png/pdf (no extension) + DPI / --dpi output dpi +""" + +import argparse +import os +from pathlib import Path + +import osmnx as ox + +# osmnx 2.x: project_gdf left the top-level namespace +if not hasattr(ox, "project_gdf"): + ox.project_gdf = ox.projection.project_gdf +if not hasattr(ox, "project_graph"): + if hasattr(ox, "projection") and hasattr(ox.projection, "project_graph"): + ox.project_graph = ox.projection.project_graph + +import prettymaps +import prettymaps.draw as _pm_draw +import matplotlib + +matplotlib.use("Agg") + +# --- patch: KeyError 'highway' when streets gdf has no highway column --- +_orig_graph_to_shapely = _pm_draw.graph_to_shapely + + +def _safe_graph_to_shapely(gdf, width=1.0): + if gdf is None or getattr(gdf, "empty", True): + from shapely.geometry import GeometryCollection + + return GeometryCollection() + if isinstance(width, dict) and "highway" not in gdf.columns: + width = width.get("default", next(iter(width.values()), 1.0)) + return _orig_graph_to_shapely(gdf, width) + + +_pm_draw.graph_to_shapely = _safe_graph_to_shapely +# ----------------------------------------------------------------------- + +# --- defaults (single place) --- +DEFAULT_QUERY = "Akademgorodok, Novosibirsk, Russia" +DEFAULT_LAT = None +DEFAULT_LON = None +DEFAULT_RADIUS_M = 2200 +DEFAULT_CIRCLE = True +DEFAULT_TITLE = "Академгородок · Новосибирск" +DEFAULT_PRESET = "default" +DEFAULT_OUTPUT_NAME = "akademgorodok_poster" +DEFAULT_DPI = 300 +DEFAULT_FIGSIZE = (14, 14) +# -------------------------------- + + +def _env(name, default=None): + val = os.environ.get(name) + if val is None or val == "": + return default + return val + + +def _env_bool(name, default): + val = os.environ.get(name) + if val is None or val == "": + return default + return val.strip().lower() in ("1", "true", "yes", "y", "on") + + +def _env_float(name, default): + val = os.environ.get(name) + if val is None or val == "": + return default + return float(val) + + +def _env_int(name, default): + val = os.environ.get(name) + if val is None or val == "": + return default + return int(val) + + +def parse_args(): + p = argparse.ArgumentParser(description="Generate a prettymaps poster") + p.add_argument( + "--query", + default=_env("QUERY", DEFAULT_QUERY), + help="Location string for geocoding", + ) + p.add_argument( + "--lat", + type=float, + default=_env_float("LAT", DEFAULT_LAT), + help="Latitude (use with --lon instead of --query)", + ) + p.add_argument( + "--lon", + type=float, + default=_env_float("LON", DEFAULT_LON), + help="Longitude (use with --lat instead of --query)", + ) + p.add_argument( + "--radius", + type=int, + default=_env_int("RADIUS_M", DEFAULT_RADIUS_M), + help="Radius in metres", + ) + p.add_argument( + "--circle", + default=None, + help="Circular clip: true/false (env CIRCLE)", + ) + p.add_argument( + "--title", + default=_env("TITLE", DEFAULT_TITLE), + help="Map title", + ) + p.add_argument( + "--preset", + default=_env("PRESET", DEFAULT_PRESET), + help="prettymaps preset name", + ) + p.add_argument( + "--output-name", + default=_env("OUTPUT_NAME", DEFAULT_OUTPUT_NAME), + help="Output basename without extension", + ) + p.add_argument( + "--dpi", + type=int, + default=_env_int("DPI", DEFAULT_DPI), + help="Output DPI", + ) + args = p.parse_args() + + if args.circle is None: + args.circle = _env_bool("CIRCLE", DEFAULT_CIRCLE) + else: + args.circle = str(args.circle).strip().lower() in ( + "1", + "true", + "yes", + "y", + "on", + ) + + return args + + +def main(): + args = parse_args() + + output_dir = Path("/work") if Path("/work").is_dir() else Path(".") + output_dir.mkdir(parents=True, exist_ok=True) + out_png = output_dir / f"{args.output_name}.png" + out_pdf = output_dir / f"{args.output_name}.pdf" + + if args.lat is not None and args.lon is not None: + location = (args.lat, args.lon) + location_desc = f"({args.lat}, {args.lon})" + else: + location = args.query + location_desc = args.query + + print(f"Location: {location_desc}") + print(f"Radius: {args.radius} m, circle={args.circle}, preset={args.preset}") + print(f"Title: {args.title}") + print("Downloading OSM data and rendering (this can take several minutes)...") + + plot = prettymaps.plot( + location, + radius=args.radius, + circle=args.circle, + figsize=DEFAULT_FIGSIZE, + preset=args.preset, + ) + + if args.title: + plot.ax.set_title( + args.title, + fontsize=28, + pad=20, + fontfamily="DejaVu Sans", + ) + + plot.fig.savefig( + out_png, + dpi=args.dpi, + bbox_inches="tight", + facecolor=plot.fig.get_facecolor(), + edgecolor="none", + ) + print(f"Saved: {out_png}") + + plot.fig.savefig( + out_pdf, + bbox_inches="tight", + facecolor=plot.fig.get_facecolor(), + edgecolor="none", + ) + print(f"Saved: {out_pdf}") + print("Done.") + + +if __name__ == "__main__": + main() \ No newline at end of file