From e2a4ae6b742134971c81cfd7c041f7aadb5441a6 Mon Sep 17 00:00:00 2001 From: SasVel Date: Thu, 12 Feb 2026 15:03:09 +0200 Subject: [PATCH 1/4] Add an option for an output directory --- README.md | 1 + create_map_poster.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 59d32aa8b..9e971d7b8 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ python create_map_poster.py --city --country [options] | **OPTIONAL:** `--all-themes` | | Generate posters for all available themes | | | **OPTIONAL:** `--width` | `-W` | Image width in inches | 12 (max: 20) | | **OPTIONAL:** `--height` | `-H` | Image height in inches | 16 (max: 20) | +| **OPTIONAL:** `--output` | `-o` | Path to output directory | (default: posters) | ### Multilingual Support - i18n diff --git a/create_map_poster.py b/create_map_poster.py index 3eab412b2..483d37d17 100755 --- a/create_map_poster.py +++ b/create_map_poster.py @@ -955,6 +955,13 @@ def list_themes(): choices=["png", "svg", "pdf"], help="Output format for the poster (default: png)", ) + parser.add_argument( + "--output", + "-o", + type=str, + default="posters", + help="Path to output directory for the poster (default: posters)" + ) args = parser.parse_args() @@ -986,6 +993,10 @@ def list_themes(): ) args.height = 20.0 + # Change output directory + if args.output: + POSTERS_DIR = args.output + available_themes = get_available_themes() if not available_themes: print("No themes found in 'themes/' directory.") From be2147b3ade222a7efcdcf488d945570f6d0bf42 Mon Sep 17 00:00:00 2001 From: SasVel Date: Thu, 12 Feb 2026 15:03:09 +0200 Subject: [PATCH 2/4] Add an option for an output directory --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 9e971d7b8..b949d8816 100644 --- a/README.md +++ b/README.md @@ -79,7 +79,7 @@ python create_map_poster.py --city --country [options] | **OPTIONAL:** `--all-themes` | | Generate posters for all available themes | | | **OPTIONAL:** `--width` | `-W` | Image width in inches | 12 (max: 20) | | **OPTIONAL:** `--height` | `-H` | Image height in inches | 16 (max: 20) | -| **OPTIONAL:** `--output` | `-o` | Path to output directory | (default: posters) | +| **OPTIONAL:** `--output` | `-o` | Path to output directory | default: posters | ### Multilingual Support - i18n From 6e201ed140b5f24ef8f2de93c66bb68799ae37bf Mon Sep 17 00:00:00 2001 From: SasVel Date: Sat, 21 Feb 2026 20:51:50 +0200 Subject: [PATCH 3/4] Added points of interest visualisation based on OSM feature tags --- README.md | 2 ++ create_map_poster.py | 67 ++++++++++++++++++++++++++++++++++++++---- themes/terracotta.json | 3 +- 3 files changed, 65 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index b949d8816..0ba5ae755 100644 --- a/README.md +++ b/README.md @@ -80,6 +80,8 @@ python create_map_poster.py --city --country [options] | **OPTIONAL:** `--width` | `-W` | Image width in inches | 12 (max: 20) | | **OPTIONAL:** `--height` | `-H` | Image height in inches | 16 (max: 20) | | **OPTIONAL:** `--output` | `-o` | Path to output directory | default: posters | +| **OPTIONAL:** `--points-of-interest-key` | `-poiK` | Top level tag from OpenStreetMap ( use with --points-of-interest-val) | | +| **OPTIONAL:** `--points-of-interest-val` | `-poiV` | List of points of interest under the top level tag ( use with --points-of-interest-key) | | ### Multilingual Support - i18n diff --git a/create_map_poster.py b/create_map_poster.py index 483d37d17..7fa1cda2d 100755 --- a/create_map_poster.py +++ b/create_map_poster.py @@ -20,6 +20,7 @@ import matplotlib.colors as mcolors import matplotlib.pyplot as plt +from matplotlib.patches import Circle import numpy as np import osmnx as ox from geopandas import GeoDataFrame @@ -492,6 +493,7 @@ def create_poster( name_label=None, display_city=None, display_country=None, + poi_dict={}, fonts=None, ): """ @@ -524,7 +526,7 @@ def create_poster( # Progress bar for data fetching with tqdm( - total=3, + total=4, desc="Fetching map data", unit="step", bar_format="{l_bar}{bar}| {n_fmt}/{total_fmt}", @@ -557,6 +559,17 @@ def create_poster( ) pbar.update(1) + # 4. Fetch POIs + if len(poi_dict) > 0 and poi_dict[next(iter(poi_dict))] != []: + pbar.set_description("Downloading points of interest") + points_of_interest = fetch_features( + point, + compensated_dist, + tags=poi_dict, + name="_".join(poi_dict[next(iter(poi_dict))]) + ) + pbar.update(1) + print("✓ All data retrieved successfully!") # 2. Setup Plot @@ -591,13 +604,20 @@ def create_poster( except Exception: parks_polys = parks_polys.to_crs(g_proj.graph['crs']) parks_polys.plot(ax=ax, facecolor=THEME['parks'], edgecolor='none', zorder=0.8) + # Layer 2: Roads with hierarchy coloring print("Applying road hierarchy colors...") edge_colors = get_edge_colors_by_type(g_proj) edge_widths = get_edge_widths_by_type(g_proj) + # Determine cropping limits to maintain the poster aspect ratio crop_xlim, crop_ylim = get_crop_limits(g_proj, point, fig, compensated_dist) + + # Calculate scale factor based on smaller dimension (reference 12 inches) + # This ensures text scales properly for both portrait and landscape orientations + scale_factor = min(height, width) / 12.0 + # Plot the projected graph and then apply the cropped limits ox.plot_graph( g_proj, ax=ax, bgcolor=THEME['bg'], @@ -611,14 +631,32 @@ def create_poster( ax.set_xlim(crop_xlim) ax.set_ylim(crop_ylim) - # Layer 3: Gradients (Top and Bottom) + # Layer 3: Points of interest + if (len(poi_dict) > 0 and poi_dict[next(iter(poi_dict))] != []) \ + and points_of_interest is not None and not points_of_interest.empty: + points_of_interest = ox.projection.project_gdf(points_of_interest) + for poi in points_of_interest.geometry: + center = (0,0) + if poi.geom_type == "Point": + center = poi.x, poi.y + else: + center = poi.centroid.x, poi.centroid.y + + c = Circle( + center, + radius=12 * scale_factor, + facecolor=THEME.get("poi", "text"), + edgecolor=THEME.get("poi", "text"), + linewidth=1, + alpha=1, + zorder=9, + ) + ax.add_patch(c) + + # Layer 4: Gradients (Top and Bottom) create_gradient_fade(ax, THEME['gradient_color'], location='bottom', zorder=10) create_gradient_fade(ax, THEME['gradient_color'], location='top', zorder=10) - # Calculate scale factor based on smaller dimension (reference 12 inches) - # This ensures text scales properly for both portrait and landscape orientations - scale_factor = min(height, width) / 12.0 - # Base font sizes (at 12 inches width) base_main = 60 base_sub = 22 @@ -680,6 +718,7 @@ def create_poster( family="monospace", weight="bold", size=adjusted_font_size ) + # --- BOTTOM TEXT --- ax.text( 0.5, @@ -962,6 +1001,21 @@ def list_themes(): default="posters", help="Path to output directory for the poster (default: posters)" ) + parser.add_argument( + "--points-of-interest-key", + "-poiK", + type=str, + default="", + help="Top level tag from OpenStreetMap (ex: amenities)" + ) + parser.add_argument( + "--points-of-interest-val", + "-poiV", + type=str, + nargs='+', + default=[], + help="List of points of interest under the top level tag (ex: pub)" + ) args = parser.parse_args() @@ -1047,6 +1101,7 @@ def list_themes(): country_label=args.country_label, display_city=args.display_city, display_country=args.display_country, + poi_dict={ args.points_of_interest_key: args.points_of_interest_val }, fonts=custom_fonts, ) diff --git a/themes/terracotta.json b/themes/terracotta.json index 086740fb9..05d3b898c 100644 --- a/themes/terracotta.json +++ b/themes/terracotta.json @@ -11,5 +11,6 @@ "road_secondary": "#C9846A", "road_tertiary": "#D9A08A", "road_residential": "#E5C4B0", - "road_default": "#D9A08A" + "road_default": "#D9A08A", + "poi": "#F75E00" } From da25c414bed4a09b47a78b2340060a7f0cb224e1 Mon Sep 17 00:00:00 2001 From: SasVel Date: Sat, 21 Feb 2026 21:25:03 +0200 Subject: [PATCH 4/4] Add an option for an output directory --- README.md | 1 + create_map_poster.py | 11 +++++++++++ 2 files changed, 12 insertions(+) diff --git a/README.md b/README.md index 59d32aa8b..ecafe93ac 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,7 @@ python create_map_poster.py --city --country [options] | **OPTIONAL:** `--all-themes` | | Generate posters for all available themes | | | **OPTIONAL:** `--width` | `-W` | Image width in inches | 12 (max: 20) | | **OPTIONAL:** `--height` | `-H` | Image height in inches | 16 (max: 20) | +| **OPTIONAL:** `--output` | `-o` | Path to output directory | posters | ### Multilingual Support - i18n diff --git a/create_map_poster.py b/create_map_poster.py index 3eab412b2..483d37d17 100755 --- a/create_map_poster.py +++ b/create_map_poster.py @@ -955,6 +955,13 @@ def list_themes(): choices=["png", "svg", "pdf"], help="Output format for the poster (default: png)", ) + parser.add_argument( + "--output", + "-o", + type=str, + default="posters", + help="Path to output directory for the poster (default: posters)" + ) args = parser.parse_args() @@ -986,6 +993,10 @@ def list_themes(): ) args.height = 20.0 + # Change output directory + if args.output: + POSTERS_DIR = args.output + available_themes = get_available_themes() if not available_themes: print("No themes found in 'themes/' directory.")