Turn the cities you love into stunning designs
English | 简体中文
A browser-based upgrade to maptoposter (Python CLI) — no installation needed, just open and go
| China - Beijing | Japan - Tokyo | South Korea - Seoul | China - Hong Kong |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Singapore | Malaysia - Kuala Lumpur | Thailand - Bangkok | India - New Delhi |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Switzerland - Zurich | Norway - Oslo | Sweden - Stockholm | Denmark - Copenhagen |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Austria - Vienna | Germany - Berlin | United Kingdom - London | France - Paris |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Italy - Rome | Russia - Moscow | Turkey - Istanbul | Netherlands - Amsterdam |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| USA - New York | Canada - Ottawa | Brazil - São Paulo | Mexico - Mexico City |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
| Argentina - Buenos Aires | Australia - Melbourne | South Africa - Cape Town | Chile - Santiago |
|---|---|---|---|
![]() |
![]() |
![]() |
![]() |
- 🚀 Zero installation — Runs entirely in the browser. Open the site, pick a city, and download your poster
- ⚡ Rust/WASM rendering engine — High-performance map rendering compiled from Rust to WebAssembly (powered by tiny-skia)
- 👁️ Live preview — See changes instantly and confirm results before exporting
- 🎨 20 built-in themes — From frozen Nordic minimalism to cyberpunk neon, vintage nautical to glitch purple
- ✏️ Custom color controls — Fine-tune every color: background, roads, water, green spaces, POIs, and text
- 📍 Custom POI pushpins — Search places with Amap, save your own POIs, and render them on the exported poster
- 📐 Multiple export formats — A4 (portrait/landscape), square, phone wallpaper, desktop 16:9, at 300 DPI for high-quality print
- 🌐 Multi-language interface — Supports English, Japanese, Korean, Simplified Chinese, German, Spanish, and French
- 💾 IndexedDB caching — Previously fetched map data is cached locally for faster regeneration
- 🔤 Dynamic font loading — Use built-in serif fonts or upload your own TTF/OTF files
- 🐍 Snake game — Beat boredom while waiting for your poster to generate (inspired by Chrome Dinosaur Game)
This project was inspired by maptoposter (Python CLI) — they each have their own strengths for different use cases:
| maptoposter-online | maptoposter (Python CLI) | |
|---|---|---|
| Usage | Open in browser, no install needed | Command-line interface, requires local setup |
| Best for | Quick start, on-the-go usage | Command-line enthusiasts, advanced local customization |
| Rendering engine | Rust/WASM (tiny-skia) | Python/matplotlib |
| Platform | Cross-browser, any device | Desktop only (requires Python) |
Different tech stacks, same goal — turning your favorite city into unique art.
- Build — Vite 7 + Bun
- Frontend — React 19 + TypeScript
- Styling — Tailwind CSS v4
- UI components — Radix UI + lucide-react
- Map data — OpenStreetMap (Overpass API) + Protomaps
- Rendering — Rust (wasm-pack) + tiny-skia
- i18n — @inlang/paraglide-js
- Caching — IndexedDB (idb)
# 1. Install dependencies
bun install
# 2. Build the Rust/WASM rendering engine
# Compile Rust to WebAssembly using wasm-pack
cd wasm && wasm-pack build --target web --out-dir ../src/pkg
# Or use the npm script:
bun run build:wasm
# 3. Start the dev server
bun run dev
# 4. App available at http://localhost:5173| Command | Description |
|---|---|
bun run dev |
Start dev server |
bun run build |
Build for production |
bun run build:wasm |
Rebuild WASM engine |
bun run preview |
Preview production build |
bun run lint |
Run linter |
bun run fix |
Format + lint with auto-fix |
The custom POI dialog calls the Amap place-search API directly from the browser.
- Apply for your own Amap Web Service API key at the Amap Open Platform.
- Open the
Pushpinsection in the app, paste the key, test it, then start searching.
Notes:
- Users must provide their own Amap key. Free-tier limits are managed by Amap.
- Font anti-aliasing — 2× supersampling + Box Filter downsampling
- Road hierarchy lacking depth — Road casing rendered in two passes (stroke first, then fill) + Z-order controls draw sequence by road class
- Rendering too slow — Douglas-Peucker in screen coordinate space removes subpixel redundancy; single-scan dispatch by feature type
- Python OSMnx workflow ported — Professional geospatial data processing logic adapted from osmnx
- Overpass query failures — Auto-splits oversized areas into smaller chunks (2500km² default limit) to prevent Overpass failures
- Single node timeout causing long waits — Concurrent requests to 4 mirror servers, fastest response wins
- Generation blocking the page — Data fetching, projection transforms, and WASM rendering all run in a Web Worker; road precision auto-reduces at large radii
- Repeated generation taking too long — IndexedDB Gzip-compressed cache, ~100KB per city; direct read on regeneration
MIT License — see LICENSE
If this project has saved you time or made your life easier, consider buying me a coffee. Your support is the driving force behind the continued maintenance of this project!
| Platform | Payment Method | Link |
|---|---|---|
| Afdian | WeChat / Alipay | 👉 Click to Sponsor |
| Buy Me a Coffee | Credit Card / Apple Pay / Google Pay | 👉 Click to Sponsor |
Inspired by @originalankur's maptoposter
Map data provided by OpenStreetMap and Protomaps
Font LXGW Neo ZhiSong (霞鹜新致宋) by lxgw, licensed under IPA Font License 1.0
scripts/record-cold-start.ps1 launches a fresh headless Chrome profile for each run and records cold-start navigation, long-task, and main-thread timing through the Chrome DevTools Protocol.
Requirements: Windows PowerShell 5.1+ and Google Chrome installed at C:\Program Files\Google\Chrome\Application\chrome.exe (or pass -ChromePath). Build and start the production preview before running the script:
bun run build
bun run preview -- --host localhost --port 4173In a second terminal, collect a three-run summary:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\record-cold-start.ps1 -Runs 3 -SummaryUse thresholds to turn it into a regression test. The command exits with code 1 when any run exceeds a supplied limit:
powershell.exe -NoProfile -ExecutionPolicy Bypass -File scripts\record-cold-start.ps1 `
-Runs 3 -Summary -MaxLoadMs 800 -MaxLongTaskMs 300 -MaxTaskDurationMs 3000-MaxLoadMs checks the page load event, -MaxLongTaskMs checks the largest browser long task, and -MaxTaskDurationMs checks Chrome's cumulative main-thread task duration. Set a threshold to 0 (the default) to disable that check. Omit -Summary to retain the detailed per-resource JSON record.
This is a browser-level regression test, not a hermetic unit test: map tiles, IP geolocation, and other external requests can vary by network. Use it locally or in scheduled monitoring first; a blocking CI gate should use mocked external endpoints and a calibrated baseline.



























