#!/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()