Generate beautiful, minimalist map posters for any city in the world.
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"python -m venv .venv
source .venv/bin/activate # On Windows: .venv\Scripts\activate
pip install -r requirements.txtIf you're using uv:
uv run ./create_map_poster.py --city <city> --country <country> [options]Otherwise (pip + venv):
python create_map_poster.py --city <city> --country <country> [options]| Option | Short | Description |
|---|---|---|
--city |
-c |
City name (used for geocoding) |
--country |
-C |
Country name (used for geocoding) |
| 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) |
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/.
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 |
# 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 12000Display 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"# 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 | 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 |
|---|---|
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 |
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
├── 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
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 |
is_latin_script() |
Detects script for typography | Supporting new scripts |
load_fonts() |
Load custom/default fonts | Changing font loading logic |
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), lightestThe 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.
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.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 for Latin scripts)
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









