GitHub - originalankur/maptoposter: Transform your favorite cities into beautiful, minimalist designs. MapToPoster lets you create and export visually striking map posters with code. · GitHub
City Map Poster Generator
Generate beautiful, minimalist map posters for any city in the world.
Examples
Country | City | Theme | Poster
USA | San Francisco | sunset |
Spain | Barcelona | warm_beige |
Italy | Venice | blueprint |
Japan | Tokyo | japanese_ink |
India | Mumbai | contrast_zones |
Morocco | Marrakech | terracotta |
Singapore | Singapore | neon_cyberpunk |
Australia | Melbourne | forest |
UAE | Dubai | midnight_blue |
USA | Seattle | emerald | 
Installation
With uv (Recommended)
Make sure uv is installed. Running the script by prepending uv run automatically creates and manages a virtual environment.
First run will automatically install dependencies
uv run ./create_map_poster.py --city "Paris" --country "France"
Or sync dependencies explicitly first (using locked versions)
uv sync --locked uv run ./create_map_poster.py --city "Paris" --country "France"
With pip + venv
python -m venv .venv source .venv/bin/activate # On Windows: .venv\Scripts\activate pip install -r requirements.txt
Usage
Generate Poster
If you're using uv:
uv run ./create_map_poster.py --city
Otherwise (pip + venv):
python create_map_poster.py --city
Required Options
Option | Short | Description
--city | -c | City name (used for geocoding)
--country | -C | Country name (used for geocoding)
Optional Flags
Option | Short | Description | Default
OPTIONAL: --latitude | -lat | Override latitude center point (use with --longitude)
OPTIONAL: --longitude | -long | Override longitude center point (use with --latitude)
OPTIONAL: --country-label | Override country text displayed on poster
OPTIONAL: --theme | -t | Theme name | terracotta
OPTIONAL: --distance | -d | Map radius in meters | 18000
OPTIONAL: --list-themes | List all available themes
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)
Multilingual Support - i18n
Display city and country names in your language with custom fonts from google fonts:
Option | Short | Description
--display-city | -dc | Custom display name for city (e.g., "東京")
--display-country | -dC | Custom display name for country (e.g., "日本")
--font-family | Google Fonts family name (e.g., "Noto Sans JP")
Examples:
Japanese
python create_map_poster.py -c "Tokyo" -C "Japan" -dc "東京" -dC "日本" --font-family "Noto Sans JP"
Korean
python create_map_poster.py -c "Seoul" -C "South Korea" -dc "서울" -dC "대한민국" --font-family "Noto Sans KR"
Arabic
python create_map_poster.py -c "Dubai" -C "UAE" -dc "دبي" -dC "الإمارات" --font-family "Cairo"
Note : Fonts are automatically downloaded from Google Fonts and cached locally in fonts/cache/.
Resolution Guide (300 DPI)
Use these values for -W and -H to target specific resolutions:
Target | Resolution (px) | Inches (-W / -H)
Instagram Post | 1080 x 1080 | 3.6 x 3.6 Mobile Wallpaper | 1080 x 1920 | 3.6 x 6.4 HD Wallpaper | 1920 x 1080 | 6.4 x 3.6 4K Wallpaper | 3840 x 2160 | 12.8 x 7.2 A4 Print | 2480 x 3508 | 8.3 x 11.7
Usage Examples
Basic Examples
Simple usage with default theme
python create_map_poster.py -c "Paris" -C "France"
With custom theme and distance
python create_map_poster.py -c "New York" -C "USA" -t noir -d 12000
Multilingual Examples (Non-Latin Scripts)
Display city names in their native scripts:
Japanese
python create_map_poster.py -c "Tokyo" -C "Japan" -dc "東京" -dC "日本" --font-family "Noto Sans JP" -t japanese_ink
Korean
python create_map_poster.py -c "Seoul" -C "South Korea" -dc "서울" -dC "대한민국" --font-family "Noto Sans KR" -t midnight_blue
Thai
python create_map_poster.py -c "Bangkok" -C "Thailand" -dc "กรุงเทพมหานคร" -dC "ประเทศไทย" --font-family "Noto Sans Thai" -t sunset
Arabic
python create_map_poster.py -c "Dubai" -C "UAE" -dc "دبي" -dC "الإمارات" --font-family "Cairo" -t terracotta
Chinese (Simplified)
python create_map_poster.py -c "Beijing" -C "China" -dc "北京" -dC "中国" --font-family "Noto Sans SC"
Khmer
python create_map_poster.py -c "Phnom Penh" -C "Cambodia" -dc "ភ្នំពេញ" -dC "កម្ពុជា" --font-family "Noto Sans Khmer"
Advanced Examples
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
Override center coordinates
python create_map_poster.py --city "New York" --country "USA" -lat 40.776676 -long -73.971321 -t noir
List available themes
python create_map_poster.py --list-themes
Generate posters for every theme
python create_map_poster.py -c "Tokyo" -C "Japan" --all-themes
Distance Guide
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)
Themes
17 themes available in themes/ directory:
Theme | Style
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
emerald | Lush dark green aesthetic
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
Output
Posters are saved to posters/ directory with format:
{city}{theme}.png
Adding Custom Themes
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" }
Project Structure
map_poster/ ├── create_map_poster.py # Main script ├── font_management.py # Font loading and Google Fonts integration ├── themes/ # Theme JSON files ├── fonts/ # Font files │ ├── Roboto-*.ttf # Default Roboto fonts │ └── cache/ # Downloaded Google Fonts (auto-generated) ├── posters/ # Generated posters └── README.md
Hacker's Guide
Quick reference for contributors who want to extend or modify the script.
Contributors Guide
- Bug fixes are welcomed
- Don't submit user interface (web/desktop)
- Don't Dockerize for now
- If you vibe code any fix please test it and see before and after version of poster
- Before embarking on a big feature please ask in Discussions/Issue if it will be merged
Architecture Overview
┌─────────────────┐ ┌──────────────┐ ┌─────────────────┐ │ CLI Parser │────▶│ Geocoding │────▶│ Data Fetching │ │ (argparse) │ │ (Nominatim) │ │ (OSMnx) │ └─────────────────┘ └──────────────┘ └─────────────────┘ │ ┌──────────────┐ ▼ │ Output │◀────┌─────────────────┐ │ (matplotlib)│ │ Rendering │ └──────────────┘ │ (matplotlib) │ └─────────────────┘
Key Functions
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
is_latin_script() | Detects script for typography | Supporting new scripts
load_fonts() | Load custom/default fonts | Changing font loading logic
Rendering Layers (z-order)
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
OSM Highway Types → Road Hierarchy
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), lightest
Typography & Script Detection
The script automatically detects text scripts to apply appropriate typography:
- Latin scripts (English, French, Spanish, etc.): Letter spacing applied for elegant "P A R I S" effect
- Non-Latin scripts (Japanese, Arabic, Thai, Korean, etc.): Natural spacing for "東京" (no gaps between characters)
Script detection uses Unicode ranges (U+0000-U+024F for Latin). If >80% of alphabetic characters are Latin, spacing is applied.
Adding New Features
New 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 = railways.to_crs(g_proj.graph["crs"]) 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
Typography Positioning
All text uses transform=ax.transAxes (0-1 normalized coordinates):
y=0.14 City name (spaced letters for Latin scripts) y=0.125 Decorative line y=0.10 Country name y=0.07 Coordinates y=0.02 Attribution (bottom-right)
Useful OSMnx Patterns
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
Performance Tips
- 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