UrbanViable is composed of three independent layers that never need to run at the same time: an ETL pipeline that executes on a developer workstation to produce a staticDocumentation Index
Fetch the complete documentation index at: https://mintlify.com/danizd/urbanviable/llms.txt
Use this file to discover all available pages before exploring further.
.mbtiles file; a two-container Docker stack that serves that file as vector tiles alongside the React application; and a MapLibre GL JS frontend that receives raw normalised data inside the tiles and converts it into a live heatmap entirely on the client GPU. There is no application server, no scoring API, and no database.
System overview
urbanviable-web (Nginx) exposes ports to the outside world. urbanviable-tiles is reachable only from within the Docker bridge network, via the internal hostname urbanviable-tiles:8080. The React app is served as pre-built static files from /usr/share/nginx/html; Nginx proxies any request matching /tiles/* to TileServer GL.
Docker containers
| Container | Image | Exposed ports | Internal port | Purpose |
|---|---|---|---|---|
urbanviable-tiles | maptiler/tileserver-gl:latest | 8080 → 8080 | 8080 | Serves .pbf vector tiles from galicia_scouting.mbtiles |
urbanviable-web | nginx:1.25-alpine | 3002 → 80 | 80 | Reverse proxy + React static file server |
urbanviable-net bridge network. urbanviable-web declares depends_on: urbanviable-tiles to ensure the tile server is healthy before Nginx starts accepting traffic. TileServer GL exposes a health-check endpoint at GET /health used by Docker to gate the readiness probe.
Docker network
Docker volumes
| Volume (host path) | Container mount | Access | Purpose |
|---|---|---|---|
./tiles_data | /data (tileserver) | read-write | Stores galicia_scouting.mbtiles and config.json |
./tiles_data | /data (nginx) | read-only | Allows Nginx to serve the raw .mbtiles file if needed |
./nginx/conf.d | /etc/nginx/conf.d | read-only | Nginx virtual-host and proxy configuration |
./frontend/build | /usr/share/nginx/html | read-only | React production build (output of npm run build) |
./certs | /etc/letsencrypt | read-only | Let’s Encrypt SSL certificates |
TileServer GL configuration
TileServer GL is started with--config /data/config.json. The configuration file registers the MBTiles tileset under the key galicia-scouting:
http://urbanviable-tiles:8080/data/galicia-scouting/{z}/{x}/{y}.pbf
Tileset metadata (useful for debugging): http://urbanviable-tiles:8080/data/galicia-scouting.json
ETL pipeline
The ETL is a separate local process, not a Docker service. It runs on a developer workstation once per data refresh cycle (typically annual) and produces the single artifact that feeds the entire system.etl/data/processed/last_update.json:
GET /api/v1/layers/status call in a traditional REST architecture).
User interaction flow
There is no server-side scoring. The MapLibre GL JS expression model
computes
score = Σ(var_norm × peso) / Σ(pesos_activos) entirely on the
client GPU. Every _norm column in the vector tile features is a raw
Min-Max normalised float [0, 1] written by the ETL. The weights come
exclusively from the sidebar sliders; nothing is stored or pre-computed on
the server.- Steps 1–3 involve one HTTP request per viewport tile (cached by the browser after first load, governed by
Cache-Controlheaders set by TileServer GL). - Steps 4–6 produce zero network requests. React serialises the current weight map into a MapLibre expression literal and calls
setPaintProperty. The GPU re-shades all visible polygons in a single render pass. - Step 7 reads feature properties already in GPU memory — no second fetch.
Network flow in production
./certs, then routes tile requests internally to TileServer GL and serves the React bundle directly from disk for everything else.
Performance reference
| Metric | Expected value |
|---|---|
.mbtiles file size | 15–40 MB |
| Initial map load | < 2 s (only viewport tiles are fetched) |
| Heatmap colour update after slider move | < 16 ms (60 fps, GPU) |
| Server requests per slider move | 0 |
| Tile caching | Automatic via HTTP Cache-Control headers |
Infrastructure reference
The production server runs on Oracle Cloud Infrastructure Always Free tier:| Aspect | Detail |
|---|---|
| Provider | Oracle Cloud Infrastructure (OCI) — Always Free |
| Architecture | ARM (Ampere A1) |
| RAM | 24 GB |
| Storage | 200 GB block storage |
| OS | Ubuntu 22.04 LTS (ARM64) |
ETL Overview
Detailed walkthrough of the Python transformation steps, Min-Max
normalisation logic, and Tippecanoe tile configuration.
Docker Deployment
Step-by-step guide to deploying the two-container stack on a fresh Oracle
Cloud ARM instance with SSL via Let’s Encrypt.