- Generate city map posters sized for Instagram (1080 × 1350 px) and other presets
- 17 built-in color themes
- Provide coordinates (
lat/lon) or choose from a supported cities list (city/country); poster labels (display_city,display_country) are always required - List supported cities and map layer toggles (roads, water, parks, railways) via API
- Swagger UI for interactive API documentation
- Production-grade architecture with >70% test coverage
# Install dependencies
make deps
# Build (generates Swagger docs + compiles binary)
make build
# Start the server
make run
# Or with live reload (rebuilds on .go changes)
make run-air # requires: go install github.com/air-verse/air@latestServer starts on http://localhost:8080. Set PORT env var to change.
| Method | Endpoint | Description |
|---|---|---|
POST |
/api/v1/poster |
Generate a poster (returns PNG) |
POST |
/api/v1/poster/batch |
Generate posters for multiple themes (returns ZIP) |
GET |
/api/v1/themes |
List available themes |
GET |
/api/v1/themes/:name |
Get theme details |
GET |
/api/v1/frames |
List available frame/size presets |
GET |
/api/v1/cities |
List supported cities with coordinates |
GET |
/api/v1/features |
List map layer toggles (include_roads, include_water, etc.) |
GET |
/health |
Health check |
GET |
/swagger/index.html |
Swagger UI |
curl -X POST http://localhost:8080/api/v1/poster \
-H "Content-Type: application/json" \
-d '{
"display_city": "Paris",
"display_country": "France",
"city": "Paris",
"country": "France",
"theme": "terracotta",
"distance": 8000,
"frame": "a4_portrait"
}' --output poster.png| Field | Type | Required | Default | Description |
|---|---|---|---|---|
display_city |
string | ✅ | — | City name shown on poster |
display_country |
string | ✅ | — | Country name shown on poster |
lat |
float | — | — | Latitude (use with lon, or omit and use city+country) |
lon |
float | — | — | Longitude |
city |
string | — | — | City for coordinate lookup from supported cities list (if lat/lon not set) |
country |
string | — | — | Country for lookup (name or ISO code) |
theme |
string | — | terracotta |
Theme name |
distance |
int | — | 18000 |
Map radius in metres |
frame |
string | — | instagram_post |
Frame preset name |
width |
int | — | from frame | Custom width in px (max 6000) |
height |
int | — | from frame | Custom height in px (max 6000) |
include_roads |
bool | — | true |
Draw roads/highways |
include_water |
bool | — | true |
Draw water bodies |
include_parks |
bool | — | true |
Draw parks and green areas |
include_railways |
bool | — | true |
Draw railway lines |
font |
string | — | roboto |
Font id for poster text (see GET /api/v1/fonts). If the font does not support your script, no fallback is applied. |
Use GET /api/v1/fonts to list available fonts (only fonts whose files are present are listed). To add more languages, download the TTF and add it under internal/poster/fonts/ with the expected filename (e.g. NotoSansJP-Regular.ttf, NotoSansArabic-Regular.ttf); the registry in code must reference it.
Provide either lat and lon or city and country (from the supported cities list). If using city/country, coordinates are resolved from the embedded list. Use GET /api/v1/cities to list all supported cities with coordinates.
Optional layer toggles (default all true): include_roads, include_water, include_parks, include_railways. Use GET /api/v1/features to list them and their defaults.
Frame presets:
| Preset | Dimensions | Use Case |
|---|---|---|
instagram_post |
1080 × 1350 | Default — Instagram 4:5 |
instagram_story |
1080 × 1920 | Stories / Reels 9:16 |
square |
1080 × 1080 | Social media 1:1 |
a4_portrait |
2480 × 3508 | A4 print at 300 DPI |
a3_portrait |
3508 × 4961 | A3 print at 300 DPI |
desktop_wallpaper |
2560 × 1440 | QHD desktop 16:9 |
Distance guide: 4–6 km for dense cities (Venice), 8–12 km for mid-size (Paris), 15–20 km for large metros (Tokyo).
autumn · blueprint · contrast_zones · copper_patina · emerald · forest · gradient_roads · japanese_ink · midnight_blue · monochrome_blue · neon_cyberpunk · noir · ocean · pastel_dream · sunset · terracotta · warm_beige
├── cmd/ # Server entry point (main.go)
├── docs/ # Auto-generated Swagger docs
├── internal/
│ ├── api/ # Gin handlers and router
│ ├── geocoder/ # Embedded cities list (coordinate lookup)
│ ├── osm/ # Overpass API client
│ ├── poster/ # Image renderer (fogleman/gg)
│ └── theme/ # Theme JSON loader
├── themes/ # 17 color theme JSONs
└── Makefile
make test # Run all tests with race detector
make run-air # Start server with Air (live reload on .go changes)
make swagger # Regenerate Swagger docs
make fmt # Format source files
make lint # Lint (requires golangci-lint)
make clean # Remove build artifactsAt runtime, poster generation calls:
| Service | Default URL | Purpose |
|---|---|---|
| Overpass API | https://overpass-api.de/api/interpreter |
Fetches roads, water, parks, and railways (OpenStreetMap data) for the map |
Coordinates come from the request: either you supply lat/lon or the app looks up city/country from an embedded list of supported cities (no external geocoding service). The app uses rate limits and timeouts for Overpass. Poster generation can take 5–30+ seconds per image. See .env.example for Overpass URL and rate limit; for your own instance set OVERPASS_RATE_LIMIT_MS=0.
- Go 1.22+
- Internet connection (for Overpass API when generating posters)
Contributions are welcome! Please:
- Fork the repository
- Create a feature branch (
git checkout -b feature/my-feature) - Write tests for your changes
- Ensure
make testandmake lintpass - Submit a pull request
For bugs or feature requests, open an issue.
This project is a Go rewrite of MapToPoster by @originalankur. The original Python project provides the theme definitions, rendering concept, and design inspiration used here.
Map data © OpenStreetMap contributors.
This project is licensed under the MIT License.