fifteen/README.md
2026-03-01 21:59:44 +01:00

105 lines
3.5 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Transportationer — 15-Minute City Analyzer
A web application for analyzing urban accessibility through the lens of the 15-minute city concept. Shows a heatmap indicating distance to locations of interest across 5 categories: **Service & Trade**, **Transport**, **Work & School**, **Culture & Community**, and **Recreation**.
## Architecture
```
Browser (Next.js / React)
├── MapLibre GL JS (map + canvas heatmap)
└── API calls → Next.js API routes
Next.js App Server
├── Public API: /api/cities /api/pois /api/grid /api/stats /api/isochrones
├── Admin API: /api/admin/** (auth-protected)
├── PostgreSQL + PostGIS (POI data, grid scores, isochrone cache)
└── Valkey (API response cache, sessions, BullMQ queue)
BullMQ Worker (separate process)
├── download-pbf → streams OSM data from Geofabrik
├── extract-pois → osmium-tool + osm2pgsql → PostGIS
├── generate-grid → PostGIS SQL (200m grid)
├── compute-scores → KNN lateral join + sigmoid scoring
└── build-valhalla → Valhalla routing tile build
Valhalla → local routing (isochrones)
Protomaps → self-hosted map tiles (PMTiles)
```
## Quick Start
### 1. Configure environment
```bash
cp .env.example .env
# Edit .env with strong passwords
# Generate admin password hash
node -e "require('bcryptjs').hash('yourpassword', 12).then(console.log)"
# Paste result as ADMIN_PASSWORD_HASH in .env
```
### 2. Start services
```bash
docker compose up -d
```
### 3. Add a city
Open [http://localhost:3000/admin](http://localhost:3000/admin), log in, click **Add City**, browse Geofabrik regions (e.g. `europe/germany/berlin`), and start ingestion. Progress is shown live.
Processing time:
- Small city (< 100k pop): ~515 minutes
- Large city (1M+ pop): ~3090 minutes
### 4. Explore
Open [http://localhost:3000](http://localhost:3000) and select your city.
## Map Tiles
By default the app uses CartoDB Positron (CDN). For fully offline operation, download a PMTiles file for your region:
```bash
# Example: download Berlin region tiles
wget https://maps.protomaps.com/builds/berlin.pmtiles -O apps/web/public/tiles/region.pmtiles
# Then switch to the PMTiles style:
cp apps/web/public/tiles/style.pmtiles.json apps/web/public/tiles/style.json
```
## Development
```bash
npm install
npm run dev # Next.js dev server on :3000
npm run worker:dev # BullMQ worker with hot reload
```
Required local services: PostgreSQL+PostGIS, Valkey. Easiest via:
```bash
docker compose up postgres valkey -d
```
## Category Definitions
| Category | OSM Sources | Default Threshold |
|----------|-------------|-------------------|
| Service & Trade | shops, restaurants, pharmacies, banks | 10 min |
| Transport | bus stops, metro, train, bike share | 8 min |
| Work & School | offices, schools, universities | 20 min |
| Culture & Community | libraries, hospitals, museums, community centers | 15 min |
| Recreation | parks, sports, gyms, green spaces | 10 min |
## Scoring
For each grid point (200m spacing), the nearest POI in each category is found using a PostGIS KNN lateral join. The Euclidean distance is converted to travel time using mode speed assumptions (walking 5 km/h, cycling 15 km/h, driving 40 km/h). A sigmoid function converts travel time to a score in [0,1]:
```
score = 1 / (1 + exp(k * (travel_time - threshold)))
```
Where `k = 4/threshold`, giving score=0.5 exactly at the threshold.
The composite score is a weighted average of all 5 category scores, with user-adjustable weights.