Skip to content

samesense/maptoposter

 
 

Repository files navigation

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

Otherwise (pip + venv):

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

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)
OPTIONAL: --db Path to a SQLite database (e.g. checkins.db) with venue locations to plot as dots

Venue Overlay

Plot venue locations as colored dots on your map poster using a SQLite database (e.g. one created by swarm-to-sqlite). The database must have a venues table with latitude and longitude columns. Only venues within the map bounds are plotted -- out-of-bounds venues are automatically filtered out.

# Plot Philly venues on a Philadelphia map (ignores venues in other cities)
python create_map_poster.py -c "Philadelphia" -C "USA" -t midnight_blue --db data/checkins.db

# Full Manhattan with venues
python create_map_poster.py -c "New York" -C "USA" -t noir -d 14000 -lat 40.75 -long -73.98 --db data/checkins.db

Each theme includes a venue color that contrasts with the theme's palette. You can customize it by adding a "venue" key to any theme JSON file.

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

Animation

Two animation scripts turn your checkin history into videos. Both read from a SQLite database (e.g. one created by swarm-to-sqlite) and output 9:16 (Reels/Shorts) MP4 video via ffmpeg.

Philadelphia twist animation

Checkin Animation

Renders a static base map, then overlays venue dots that grow as checkin counts accumulate month by month. A HUD shows the current date, total checkins, and number of places.

uv run ./animate_checkins.py -c "New York" -C "USA" -t noir \
  --db data/checkins.db

# With custom center, distance, and output path
uv run ./animate_checkins.py -c "Philadelphia" -C "USA" -t midnight_blue \
  -lat 39.95 -long -75.16 -d 8000 \
  --db data/checkins.db \
  -o posters/philly_checkins.mp4
Option Short Description Default
--city -c City name (required)
--country -C Country name (required)
--db Path to SQLite database (required)
--theme -t Theme name noir
--distance -d Map radius in meters 12000
--latitude -lat Override latitude
--longitude -long Override longitude
--output -o Output file path (.mp4 or .gif) posters/checkins_animation.mp4
--fps Frames per second 10
--hold-last Seconds to hold final frame 3
--frame-duration Duration per frame in ms (GIF only) 100
--font-family Google Fonts family name

Twist Animation

A cinematic variant: starts zoomed in and rotated 90° on your first cluster of checkins, then smoothly untwists and zooms out to the full city view as dots accumulate. The zoom lands with a satisfying bounce overshoot. During the hold phase, all dots brighten to full intensity — dots then disappear in reverse chronological order at full brightness, ending with a fade to the background color.

Visual effects:

  • Frontier tint: New venues glow warm; older venues cool down unless frequently revisited
  • New-venue pulse: A glow ring highlights venues the first month they appear
  • Cluster burst: Radial glow when 3+ new venues appear near each other in the same month
  • Smooth tweening: Dot sizes interpolate between months instead of snapping
  • Hold brighten: At full zoom-out, all dots ramp to full brightness before the reverse phase
uv run ./animate_twist.py -c "Philadelphia" -C "USA" -t neon_cyberpunk \
  --db data/checkins.db

# With trails, season tints, and audio
uv run ./animate_twist.py -c "Philadelphia" -C "USA" -t neon_cyberpunk \
  --db data/checkins.db \
  --trails --seasons --audio music/track.mp3

# Customize timing and zoom
uv run ./animate_twist.py -c "Tokyo" -C "Japan" -t japanese_ink \
  --db data/checkins.db \
  --start-zoom 4.0 --forward-duration 10 --reverse-duration 4
Option Short Description Default
--city -c City name (required)
--country -C Country name (required)
--db Path to SQLite database (required)
--theme -t Theme name noir
--distance -d Map radius in meters 12000
--latitude -lat Override latitude
--longitude -long Override longitude
--output -o Output MP4 path posters/checkins_twist.mp4
--fps Frames per second 30
--hold Seconds to hold full view 2
--start-zoom Initial zoom factor 3.0
--end-zoom Final zoom factor (below 1.0 zooms out further) 1.0
--forward-duration Forward phase duration (seconds) 8
--reverse-duration Reverse phase duration (seconds) 3
--fade-out Fade to background duration (seconds) 1
--font-family Google Fonts family name
--audio Path to audio file to mux into output
--trails Draw lines connecting venues in visit order
--seasons Tint map by season (warm summers, cool winters)

Database Format

The SQLite database must have checkins and venues tables, as created by swarm-to-sqlite. The key columns used are:

  • venues: id, latitude, longitude, name
  • checkins: venue (foreign key to venues.id), created (ISO 8601 datetime)

Adding Audio

Use add_audio.sh to add a music track to any generated video, or use the --audio flag on animate_twist.py to mux audio automatically. The audio is trimmed to the video length with a 2-second fade-out at the end.

# Standalone script (works with any video)
./add_audio.sh posters/checkins_twist.mp4 music/track.mp3

# Custom output path
./add_audio.sh posters/checkins_twist.mp4 music/track.mp3 posters/final_with_audio.mp4

Requires ffmpeg.

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",
  "venue": "#FF6B6B"
}

Project Structure

map_poster/
├── create_map_poster.py    # Main poster script
├── animate_checkins.py     # Checkin animation (dots accumulate over time)
├── animate_twist.py        # Twist animation (zoom + rotate cinematic effect)
├── add_audio.sh            # Add music track to generated videos
├── generate_maps.sh        # Example: generate static posters
├── generate_twist.sh       # Example: generate twist animation
├── 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 and videos
└── 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_linkThickest (1.2), darkest
trunk, primaryThick (1.0)
secondaryMedium (0.8)
tertiaryThin (0.6)
residential, living_streetThinnest (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" (see "venue" for a working example)
  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

About

Transform your favorite cities into beautiful, minimalist designs. MapToPoster lets you create and export visually striking map posters with code.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

 
 
 

Contributors

Languages

  • Python 94.4%
  • Shell 5.6%