Transform your favorite cities into beautiful, minimalist designs. MapToPoster lets you create and export visually striking map posters with code.
Python
14,204
51 commits
updated Aug 6, 2026
Generate beautiful, minimalist map posters for any city in the world.
| 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 | ![]() |
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.txt
If 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 12000
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"
# 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), lightest
The script automatically detects text scripts to apply appropriate typography:
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 = railways.to_crs(g_proj.graph["crs"])
railways.plot(ax=ax, color=THEME['railway'], linewidth=0.5, zorder=2.5)
New theme property:
"railway": "#FF0000"THEME['railway']load_theme() default dictAll 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
dist values (>20km) = slow downloads + memory heavynetwork_type='drive' instead of 'all' for faster rendersdpi from 300 to 150 for quick previews(top 24 of 131)
3,475 followers · starred Feb 2026
1,314 followers · starred Jan 2026
1,670 followers · starred Jan 2026
6,480 followers · starred Jan 2026
Transform your favorite cities into beautiful, minimalist designs. MapToPoster lets you create and export visually striking map posters with code.
Python
14,204
51 commits
updated Aug 6, 2026
Generate beautiful, minimalist map posters for any city in the world.
| 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 | ![]() |
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.txt
If 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 12000
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"
# 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), lightest
The script automatically detects text scripts to apply appropriate typography:
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 = railways.to_crs(g_proj.graph["crs"])
railways.plot(ax=ax, color=THEME['railway'], linewidth=0.5, zorder=2.5)
New theme property:
"railway": "#FF0000"THEME['railway']load_theme() default dictAll 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
dist values (>20km) = slow downloads + memory heavynetwork_type='drive' instead of 'all' for faster rendersdpi from 300 to 150 for quick previews(top 24 of 131)
3,475 followers · starred Feb 2026
1,314 followers · starred Jan 2026
1,670 followers · starred Jan 2026
6,480 followers · starred Jan 2026