Skip to content

Commit fd03ef3

Browse files
Ankush GuptaAnkush Gupta
authored andcommitted
feat: Setup web dashboard, api backend, developer scripts and guides
1 parent 6d03069 commit fd03ef3

65 files changed

Lines changed: 9480 additions & 1 deletion

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.gitignore

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,13 @@ env/
66
.venv/
77
venv/
88

9+
# Environment files and secrets
10+
.env
11+
*.env
12+
.env.local
13+
.env.*
14+
15+
916
# Build artifacts
1017
*.egg-info/
1118
dist/

DEVELOPMENT.md

Lines changed: 122 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,122 @@
1+
# Developer Guide: MapToPoster Setup & Architecture
2+
3+
Welcome to the **MapToPoster** developer guide! This document explains how to set up the development environment, configure the codebase, and understand the application's core architecture.
4+
5+
MapToPoster consists of a Python command-line map renderer, a FastAPI REST API (with an asynchronous sequential job queue), and a Next.js single-page web app.
6+
7+
---
8+
9+
## 🛠️ Prerequisites
10+
11+
Ensure you have the following tools installed on your local machine:
12+
1. **Node.js** (v18.0.0 or higher) and `npm`
13+
2. **Python** (v3.11 or higher)
14+
3. **uv** (v0.1.0 or higher) — Astrals' ultra-fast Python package installer
15+
* Install on macOS/Linux via Homebrew: `brew install uv`
16+
* Install via curl: `curl -LsSf https://astral.sh/uv/install.sh | sh`
17+
18+
---
19+
20+
## 🚀 Getting Started (Quick Setup)
21+
22+
We have provided a custom setup script that configures virtual environments, installs Python and Node dependencies, and creates template configuration `.env` files automatically.
23+
24+
1. **Clone & Navigate** to the repository.
25+
2. **Run the setup script**:
26+
```bash
27+
./setup_dev.sh
28+
```
29+
3. **Start the developer services**:
30+
```bash
31+
./start_dev.sh
32+
```
33+
34+
`start_dev.sh` spins up both:
35+
* **FastAPI Backend**: [http://localhost:8000](http://localhost:8000) (Interactive Swagger Docs: `/api/swagger`)
36+
* **Next.js Frontend**: [http://localhost:3000](http://localhost:3000)
37+
38+
Press `Ctrl+C` in the terminal to gracefully shut down both services.
39+
40+
---
41+
42+
## ⚙️ Environment Variables
43+
44+
### FastAPI Backend (`web/backend/.env`)
45+
46+
| Variable | Description | Default |
47+
|----------|-------------|---------|
48+
| `ALLOWED_ORIGINS` | CORS allowed origins (comma-separated) | `http://localhost:3000` |
49+
| `CACHE_DIR` | Directory for geocoding & map features caching | `../../cache` |
50+
51+
### Next.js Frontend (`web/frontend/.env.local`)
52+
53+
| Variable | Description | Default |
54+
|----------|-------------|---------|
55+
| `BACKEND_URL` | Endpoint of the FastAPI backend server | `http://localhost:8000` |
56+
| `NEXT_PUBLIC_SITE_URL` | Canonical URL of the frontend website | `http://localhost:3000` |
57+
58+
---
59+
60+
## 📁 Repository Structure
61+
62+
```text
63+
MapToPoster/
64+
├── create_map_poster.py # Core map rendering engine (matplotlib + OSMnx)
65+
├── font_management.py # Google Fonts loader and downloader
66+
├── setup_dev.sh # One-click environment setup script
67+
├── start_dev.sh # Concurrent developer services runner
68+
├── DEVELOPMENT.md # This developer guide
69+
├── EXAMPLES.md # API integration code samples
70+
├── pyproject.toml # uv/python project metadata & requirements
71+
├── themes/ # Curated theme JSON files
72+
├── fonts/ # Custom fonts directory (Roboto + caches)
73+
├── posters/ # Outputs directory for generated map posters
74+
└── web/
75+
├── backend/
76+
│ ├── main.py # FastAPI REST application & Sequential Worker Queue
77+
│ └── requirements.txt
78+
└── frontend/
79+
├── package.json
80+
├── next.config.ts
81+
└── src/
82+
├── app/ # Next.js Pages & Proxying (/api/[...proxy]/route.ts)
83+
└── components/ # UI Elements (Header, Footer, ScrollReveal)
84+
```
85+
86+
---
87+
88+
## 🏗️ Architecture & Core Components
89+
90+
```text
91+
┌────────────────────────┐
92+
│ Next.js Web UI │ (localhost:3000)
93+
└────────────────────────┘
94+
95+
▼ (Next.js API route proxying)
96+
┌────────────────────────┐
97+
│ FastAPI Server │ (localhost:8000)
98+
└────────────────────────┘
99+
100+
▼ (Job Submission)
101+
┌────────────────────────┐
102+
│ Sequential Worker Queue│ (Thread-safe background worker loop)
103+
└────────────────────────┘
104+
105+
▼ (Calls)
106+
┌────────────────────────┐
107+
│ create_map_poster.py │ (Generates maps with Nominatim + OSMnx)
108+
└────────────────────────┘
109+
```
110+
111+
### 1. The Rendering Engine (`create_map_poster.py`)
112+
Fetches spatial features (streets, water bodies, park polygons) using the [OSMnx](https://github.com/gboeing/osmnx) library from OpenStreetMap APIs, scales road widths and highlights coordinates using [geopandas](https://geopandas.org/), and draws high-resolution posters with custom typography through [matplotlib](https://matplotlib.org/).
113+
114+
### 2. FastAPI Async Queue Backend (`web/backend/main.py`)
115+
Matplotlib and geocoding resources contain global variables that are not thread-safe. To handle concurrent API requests safely, the backend:
116+
1. Receives requests via `/api/generate` and assigns a UUID (`job_id`).
117+
2. Appends the job request into an in-memory sequential queue (`_job_queue`).
118+
3. A background worker loop processes jobs one-by-one to avoid state corruption.
119+
4. Clients poll `/api/jobs/{job_id}` until status is `done`, returning the poster image encoded in base64.
120+
121+
### 3. Next.js App Proxy (`web/frontend/src/app/api/[...proxy]/route.ts`)
122+
To prevent CORS preflight requests in production, Next.js uses an catch-all proxy route. Any requests to `/api/*` on port `3000` are automatically forwarded to `BACKEND_URL/api/*` on the server-side.

EXAMPLES.md

Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
1+
# MapToPoster API Examples
2+
3+
This document provides complete, copy-pasteable examples for programmatically generating city map posters using the MapToPoster REST API.
4+
5+
* **Local Base URL**: `http://localhost:8000`
6+
* **Production Base URL**: `https://your-service.onrender.com` (Replace in examples if deploying to cloud)
7+
8+
---
9+
10+
## 1. Using cURL (Terminal)
11+
12+
Because the API uses an asynchronous job polling pattern, generating a poster involves two steps: submitting the request and checking status until finished.
13+
14+
### Step 1: Submit a generation request
15+
```bash
16+
curl -X POST http://localhost:8000/api/generate \
17+
-H "Content-Type: application/json" \
18+
-d '{
19+
"city": "Rome",
20+
"country": "Italy",
21+
"theme": "warm_beige",
22+
"distance": 8000,
23+
"width": 12,
24+
"height": 16
25+
}'
26+
```
27+
28+
**Expected Response**:
29+
```json
30+
{
31+
"job_id": "4b68e9e1-64d8-4f8b-a7a2-f8c6db24e392",
32+
"status": "pending"
33+
}
34+
```
35+
36+
### Step 2: Poll status using the `job_id`
37+
```bash
38+
curl http://localhost:8000/api/jobs/4b68e9e1-64d8-4f8b-a7a2-f8c6db24e392
39+
```
40+
41+
**Expected Response (when processing)**:
42+
```json
43+
{
44+
"job_id": "4b68e9e1-64d8-4f8b-a7a2-f8c6db24e392",
45+
"status": "processing"
46+
}
47+
```
48+
49+
**Expected Response (when complete)**:
50+
```json
51+
{
52+
"job_id": "4b68e9e1-64d8-4f8b-a7a2-f8c6db24e392",
53+
"status": "done",
54+
"city": "Rome",
55+
"country": "Italy",
56+
"theme": "warm_beige",
57+
"image_base64": "iVBORw0KGgoAAAANSUhEUg...",
58+
"filename": "rome_warm_beige_22060525_120000.png",
59+
"error": null,
60+
"created_at": "2026-05-25T12:00:00.000",
61+
"completed_at": "2026-05-25T12:01:15.000"
62+
}
63+
```
64+
65+
---
66+
67+
## 2. Using Python
68+
69+
This complete Python script submits a request, polls the backend API until completion, decodes the resulting base64 image, and saves it to a local file.
70+
71+
```python
72+
import base64
73+
import time
74+
import requests
75+
76+
API_BASE = "http://localhost:8000/api"
77+
78+
def generate_poster(city, country, theme="terracotta", distance=15000):
79+
payload = {
80+
"city": city,
81+
"country": country,
82+
"theme": theme,
83+
"distance": distance,
84+
"width": 12.0,
85+
"height": 16.0
86+
}
87+
88+
# 1. Submit the generation job
89+
print(f"Submitting job for {city}, {country} ({theme} theme)...")
90+
res = requests.post(f"{API_BASE}/generate", json=payload)
91+
res.raise_for_status()
92+
job_data = res.json()
93+
job_id = job_data["job_id"]
94+
print(f"Job enqueued with ID: {job_id}")
95+
96+
# 2. Poll for completion
97+
while True:
98+
status_res = requests.get(f"{API_BASE}/jobs/{job_id}")
99+
status_res.raise_for_status()
100+
status_data = status_res.json()
101+
status = status_data["status"]
102+
103+
print(f"Current Job Status: {status}")
104+
105+
if status == "done":
106+
img_b64 = status_data["image_base64"]
107+
filename = status_data.get("filename", f"{city.lower()}_poster.png")
108+
109+
# Decode and write to disk
110+
with open(filename, "wb") as f:
111+
f.write(base64.b64decode(img_b64))
112+
print(f"🎉 Success! Poster downloaded and saved to: {filename}")
113+
break
114+
elif status == "error":
115+
print(f"❌ Generation failed. Error: {status_data.get('error')}")
116+
break
117+
118+
time.sleep(5) # wait 5 seconds before polling again
119+
120+
if __name__ == "__main__":
121+
generate_poster("Monaco", "Monaco", theme="neon_cyberpunk", distance=3000)
122+
```
123+
124+
---
125+
126+
## 3. Using JavaScript / Node.js
127+
128+
This example uses Node's native `fetch` API to query, poll, and write the generated base64 image data to a file.
129+
130+
```javascript
131+
const fs = require('fs');
132+
const path = require('path');
133+
134+
const API_BASE = "http://localhost:8000/api";
135+
136+
async function generatePoster(city, country, theme = "blueprint", distance = 10000) {
137+
const payload = { city, country, theme, distance };
138+
139+
try {
140+
// 1. Submit Job
141+
console.log(`Submitting job for ${city}, ${country}...`);
142+
const submitRes = await fetch(`${API_BASE}/generate`, {
143+
method: "POST",
144+
headers: { "Content-Type": "application/json" },
145+
body: JSON.stringify(payload)
146+
});
147+
148+
if (!submitRes.ok) throw new Error(`Submission failed: ${submitRes.statusText}`);
149+
const { job_id } = await submitRes.json();
150+
console.log(`Job enqueued. ID: ${job_id}`);
151+
152+
// 2. Poll Status
153+
const pollInterval = setInterval(async () => {
154+
const pollRes = await fetch(`${API_BASE}/jobs/${job_id}`);
155+
if (!pollRes.ok) {
156+
console.error(`Polling failed, retrying...`);
157+
return;
158+
}
159+
160+
const job = await pollRes.json();
161+
console.log(`Status: ${job.status}`);
162+
163+
if (job.status === "done") {
164+
clearInterval(pollInterval);
165+
const buffer = Buffer.from(job.image_base64, 'base64');
166+
const outputPath = path.join(__dirname, job.filename || `${city}_poster.png`);
167+
168+
fs.writeFileSync(outputPath, buffer);
169+
console.log(`🎉 Success! Poster saved to ${outputPath}`);
170+
} else if (job.status === "error") {
171+
clearInterval(pollInterval);
172+
console.error(`❌ Generation error: ${job.error}`);
173+
}
174+
}, 4000);
175+
176+
} catch (error) {
177+
console.error("API error:", error);
178+
}
179+
}
180+
181+
// Execute
182+
generatePoster("Venice", "Italy", "blueprint", 4000);
183+
```

README.md

Lines changed: 22 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
# City Map Poster Generator
22

3-
Generate beautiful, minimalist map posters for any city in the world.
3+
Generate beautiful, minimalist map posters for any city in the world via a CLI tool or the new local Web App!
4+
5+
> [!NOTE]
6+
> This repository is a clone and web-extension of the original command-line interface map poster generator.
7+
> All credit for the core map rendering engine, custom matplotlib styling, and themes goes to the original creator: [originalankur/maptoposter](https://github.com/originalankur/maptoposter).
48
59
<img src="posters/singapore_neon_cyberpunk_20260118_153328.png" width="250">
610
<img src="posters/dubai_midnight_blue_20260118_140807.png" width="250">
@@ -20,6 +24,23 @@ Generate beautiful, minimalist map posters for any city in the world.
2024
| UAE | Dubai | midnight_blue | <img src="posters/dubai_midnight_blue_20260118_140807.png" width="250"> |
2125
| USA | Seattle | emerald | <img src="posters/seattle_emerald_20260124_162244.png" width="250"> |
2226

27+
## Web App & API Setup
28+
29+
This project includes a web interface consisting of a Next.js frontend and a FastAPI backend. To run the web dashboard locally:
30+
31+
1. **One-click developer environment setup**:
32+
```bash
33+
./setup_dev.sh
34+
```
35+
2. **Launch development services concurrently**:
36+
```bash
37+
./start_dev.sh
38+
```
39+
* Open the dashboard at [http://localhost:3000](http://localhost:3000)
40+
* Open interactive API documentation at [http://localhost:8000/api/swagger](http://localhost:8000/api/swagger)
41+
42+
See [DEVELOPMENT.md](file:///Users/ankush/Documents/MapToPoster/maptoposter/DEVELOPMENT.md) for more details, and [EXAMPLES.md](file:///Users/ankush/Documents/MapToPoster/maptoposter/EXAMPLES.md) for API usage code snippets.
43+
2344
## Installation
2445

2546
### With uv (Recommended)
5.04 MB
Loading
1.3 MB
Loading

render.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
services:
2+
- type: web
3+
name: maptoposter-api
4+
env: python
5+
region: oregon
6+
plan: free
7+
buildCommand: pip install -r web/backend/requirements.txt
8+
startCommand: uvicorn web.backend.main:app --host 0.0.0.0 --port $PORT
9+
envVars:
10+
- key: PYTHON_VERSION
11+
value: 3.11.0
12+
- key: ALLOWED_ORIGINS
13+
value: https://maptoposter.vercel.app,http://localhost:3000
14+
- key: CACHE_DIR
15+
value: /tmp/maptoposter_cache

0 commit comments

Comments
 (0)