Based on the original work by Ankur.
Original Repository: maptoposter
Vibe coding with Google Antigravity and Gemini.⛱️
Create a docker-compose.yml file:
version: '3.8' services: maptoposter: image: ghcr.io/cthonney/maptoposter-docker:latest container_name: maptoposter ports: - "5025:5025" volumes: # Local directory to retrieve generated PNGs - ./posters:/app/posters restart: unless-stoppedTo start the web application:
docker-compose up -dAccess the application at http://localhost:5025.
Build the image:
docker build -t maptoposter .Run the container:
docker run -p 5025:5025 -v $(pwd)/posters:/app/posters maptoposterOnce the container is running (via Docker Compose or Docker CLI), access the web interface at:
Generate beautiful, minimalist map posters for any city in the world.
pip install -r requirements.txtpython create_map_poster.py --city <city> --country <country> [options]| Option | Short | Description | Default |
|---|---|---|---|
--city | -c | City name | required |
--country | -C | Country name | required |
--theme | -t | Theme name | feature_based |
--distance | -d | Map radius in meters | 29000 |
--list-themes | List all available themes |
# Iconic grid patterns python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000 # Manhattan grid python create_map_poster.py -c "Barcelona" -C "Spain" -t warm_beige -d 8000 # Eixample district # Waterfront & canals python create_map_poster.py -c "Venice" -C "Italy" -t blueprint -d 4000 # Canal network python create_map_poster.py -c "Amsterdam" -C "Netherlands" -t ocean -d 6000 # Concentric canals python create_map_poster.py -c "Dubai" -C "UAE" -t midnight_blue -d 15000 # Palm & coastline # Radial patterns python create_map_poster.py -c "Paris" -C "France" -t pastel_dream -d 10000 # Haussmann boulevards python create_map_poster.py -c "Moscow" -C "Russia" -t noir -d 12000 # Ring roads # Organic old cities python create_map_poster.py -c "Tokyo" -C "Japan" -t japanese_ink -d 15000 # Dense organic streets python create_map_poster.py -c "Marrakech" -C "Morocco" -t terracotta -d 5000 # Medina maze python create_map_poster.py -c "Rome" -C "Italy" -t warm_beige -d 8000 # Ancient layout # Coastal cities python create_map_poster.py -c "San Francisco" -C "USA" -t sunset -d 10000 # Peninsula grid python create_map_poster.py -c "Sydney" -C "Australia" -t ocean -d 12000 # Harbor city python create_map_poster.py -c "Mumbai" -C "India" -t contrast_zones -d 18000 # Coastal peninsula # River cities python create_map_poster.py -c "London" -C "UK" -t noir -d 15000 # Thames curves python create_map_poster.py -c "Budapest" -C "Hungary" -t copper_patina -d 8000 # Danube split # List available themes python create_map_poster.py --list-themes| Distance | Best for |
|---|---|
| 4000-6000m | Small/dense cities (Venice, Amsterdam center) |
| 8000-12000m | Medium cities, focused downtown (Paris, Barcelona) |
| 15000-20000m | Large metros, full city view (Tokyo, Mumbai) |
17 themes available in themes/ directory:
| Theme | Style |
|---|---|
feature_based | Classic black & white with road hierarchy |
gradient_roads | Smooth gradient shading |
contrast_zones | High contrast urban density |
noir | Pure black background, white roads |
midnight_blue | Navy background with gold roads |
blueprint | Architectural blueprint aesthetic |
neon_cyberpunk | Dark with electric pink/cyan |
warm_beige | Vintage sepia tones |
pastel_dream | Soft muted pastels |
japanese_ink | Minimalist ink wash style |
forest | Deep greens and sage |
ocean | Blues and teals for coastal cities |
terracotta | Mediterranean warmth |
sunset | Warm oranges and pinks |
autumn | Seasonal burnt oranges and reds |
copper_patina | Oxidized copper aesthetic |
monochrome_blue | Single blue color family |
Posters are saved to posters/ directory with format:
{city}_{theme}_{YYYYMMDD_HHMMSS}.png Create a JSON file in themes/ directory:
{ "name": "My Theme", "description": "Description of the theme", "bg": "#FFFFFF", "text": "#000000", "gradient_color": "#FFFFFF", "water": "#C0C0C0", "parks": "#F0F0F0", "road_motorway": "#0A0A0A", "road_primary": "#1A1A1A", "road_secondary": "#2A2A2A", "road_tertiary": "#3A3A3A", "road_residential": "#4A4A4A", "road_default": "#3A3A3A" }map_poster/ ├── create_map_poster.py # Main script ├── themes/ # Theme JSON files ├── fonts/ # Roboto font files ├── posters/ # Generated posters └── README.md Quick reference for contributors who want to extend or modify the script.
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ CLI Parser │────▶│ Geocoding │────▶│ Data Fetching │ │ (argparse) │ │ (Nominatim) │ │ (OSMnx) │ └─────────────────┘ └──────────────┘ └─────────────────┘ │ ┌──────────────┐ ▼ │ Output │◀────┌─────────────────┐ │ (matplotlib)│ │ Rendering │ └──────────────┘ │ (matplotlib) │ └─────────────────┘ | Function | Purpose | Modify when... |
|---|---|---|
get_coordinates() | City → lat/lon via Nominatim | Switching geocoding provider |
create_poster() | Main rendering pipeline | Adding new map layers |
get_edge_colors_by_type() | Road color by OSM highway tag | Changing road styling |
get_edge_widths_by_type() | Road width by importance | Adjusting line weights |
create_gradient_fade() | Top/bottom fade effect | Modifying gradient overlay |
load_theme() | JSON theme → dict | Adding new theme properties |
z=11 Text labels (city, country, coords) z=10 Gradient fades (top & bottom) z=3 Roads (via ox.plot_graph) z=2 Parks (green polygons) z=1 Water (blue polygons) z=0 Background color # In get_edge_colors_by_type() and get_edge_widths_by_type() motorway, motorway_link → Thickest (1.2), darkest trunk, primary → Thick (1.0) secondary → Medium (0.8) tertiary → Thin (0.6) residential, living_street → Thinnest (0.4), lightestNew map layer (e.g., railways):
# In create_poster(), after parks fetch: try: railways = ox.features_from_point(point, tags={'railway': 'rail'}, dist=dist) except: railways = None # Then plot before roads: if railways is not None and not railways.empty: railways.plot(ax=ax, color=THEME['railway'], linewidth=0.5, zorder=2.5)New theme property:
- Add to theme JSON:
"railway": "#FF0000" - Use in code:
THEME['railway'] - Add fallback in
load_theme()default dict
All text uses transform=ax.transAxes (0-1 normalized coordinates):
y=0.14 City name (spaced letters) y=0.125 Decorative line y=0.10 Country name y=0.07 Coordinates y=0.02 Attribution (bottom-right) # Get all buildings buildings = ox.features_from_point(point, tags={'building': True}, dist=dist) # Get specific amenities cafes = ox.features_from_point(point, tags={'amenity': 'cafe'}, dist=dist) # Different network types G = ox.graph_from_point(point, dist=dist, network_type='drive') # roads only G = ox.graph_from_point(point, dist=dist, network_type='bike') # bike paths G = ox.graph_from_point(point, dist=dist, network_type='walk') # pedestrian- Large
distvalues (>20km) = slow downloads + memory heavy - Cache coordinates locally to avoid Nominatim rate limits
- Use
network_type='drive'instead of'all'for faster renders - Reduce
dpifrom 300 to 150 for quick previews








