City Map Poster Generator

March 1, 2026 · View on GitHub

Generate beautiful, minimalist map posters for any city in the world.

Examples

CountryCityThemePoster
USASan Franciscosunset
SpainBarcelonawarm_beige
ItalyVeniceblueprint
JapanTokyojapanese_ink
IndiaMumbaicontrast_zones
MoroccoMarrakechterracotta
SingaporeSingaporeneon_cyberpunk
AustraliaMelbourneforest
UAEDubaimidnight_blue
USASeattleemerald

Installation

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 <city> --country <country> [options]

Otherwise (pip + venv):

python create_map_poster.py --city <city> --country <country> [options]

Required Options

OptionShortDescription
--city-cCity name (used for geocoding)
--country-CCountry name (used for geocoding)

Optional Flags

OptionShortDescriptionDefault
OPTIONAL: --latitude-latOverride latitude center point (use with --longitude)
OPTIONAL: --longitude-longOverride longitude center point (use with --latitude)
OPTIONAL: --country-labelOverride country text displayed on poster
OPTIONAL: --theme-tTheme nameterracotta
OPTIONAL: --distance-dMap radius in meters18000
OPTIONAL: --list-themesList all available themes
OPTIONAL: --all-themesGenerate posters for all available themes
OPTIONAL: --width-WImage width in inches12 (max: 20)
OPTIONAL: --height-HImage height in inches16 (max: 20)

Multilingual Support - i18n

Display city and country names in your language with custom fonts from google fonts:

OptionShortDescription
--display-city-dcCustom display name for city (e.g., "東京")
--display-country-dCCustom display name for country (e.g., "日本")
--font-familyGoogle 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:

TargetResolution (px)Inches (-W / -H)
Instagram Post1080 x 10803.6 x 3.6
Mobile Wallpaper1080 x 19203.6 x 6.4
HD Wallpaper1920 x 10806.4 x 3.6
4K Wallpaper3840 x 216012.8 x 7.2
A4 Print2480 x 35088.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

DistanceBest for
4000-6000mSmall/dense cities (Venice, Amsterdam center)
8000-12000mMedium cities, focused downtown (Paris, Barcelona)
15000-20000mLarge metros, full city view (Tokyo, Mumbai)

Themes

17 themes available in themes/ directory:

ThemeStyle
gradient_roadsSmooth gradient shading
contrast_zonesHigh contrast urban density
noirPure black background, white roads
midnight_blueNavy background with gold roads
blueprintArchitectural blueprint aesthetic
neon_cyberpunkDark with electric pink/cyan
warm_beigeVintage sepia tones
pastel_dreamSoft muted pastels
japanese_inkMinimalist ink wash style
emeraldLush dark green aesthetic
forestDeep greens and sage
oceanBlues and teals for coastal cities
terracottaMediterranean warmth
sunsetWarm oranges and pinks
autumnSeasonal burnt oranges and reds
copper_patinaOxidized copper aesthetic
monochrome_blueSingle blue color family

Output

Posters are saved to posters/ directory with format:

{city}_{theme}_{YYYYMMDD_HHMMSS}.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

FunctionPurposeModify when...
get_coordinates()City → lat/lon via NominatimSwitching geocoding provider
create_poster()Main rendering pipelineAdding new map layers
get_edge_colors_by_type()Road color by OSM highway tagChanging road styling
get_edge_widths_by_type()Road width by importanceAdjusting line weights
create_gradient_fade()Top/bottom fade effectModifying gradient overlay
load_theme()JSON theme → dictAdding new theme properties
is_latin_script()Detects script for typographySupporting new scripts
load_fonts()Load custom/default fontsChanging 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:

  1. Add to theme JSON: "railway": "#FF0000"
  2. Use in code: THEME['railway']
  3. 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 dist values (>20km) = slow downloads + memory heavy
  • Cache coordinates locally to avoid Nominatim rate limits
  • Use network_type='drive' instead of 'all' for faster renders
  • Reduce dpi from 300 to 150 for quick previews