An open-source Web-GIS platform. PostGIS vector tiles, satellite imagery, full symbology, 3D models on the terrain, and a real-time asset layer. No API keys anywhere.
The map is one JSON document. The core diffs two versions of it and emits a list of operations; an adapter applies those operations to MapLibre. Editing the document changes the map — and swapping the basemap does not destroy your layers.
That one decision is why undo is sixty whole documents deep, why a drawing survives a basemap swap, and why the renderer could be replaced without touching the model.
packages/core the arithmetic · knows nothing about any renderer
packages/maplibre the only folder that knows MapLibre exists
packages/three the only folder that knows three.js exists
apps/studio React client
services/api FastAPI: ingest, vector tiles, imagery, WMS, live feed
cp .env.example .env
# --env-file matters: compose looks for .env next to the compose file, not here.
docker compose --env-file .env -f deploy/docker-compose.yml up -d --build
# Install from the repository root. The studio depends on two workspace packages,
# so installing inside apps/studio cannot see them.
pnpm install
pnpm dev- Studio — http://localhost:5173
- API health — http://localhost:8000/api/health
- Live feed — ws://localhost:8000/api/live/assets, switched on from the Live assets row
- Postgres — host port 5433, because 5432 is usually already taken
The database ships empty. There is no seeded demo layer: one cannot be deleted from the studio, it comes back on every fresh volume, and it makes an install that has nothing in it look like it already has data. Get data in through Add data, or load straight into PostGIS:
./data/seed.sh wards.gpkgTwo things live on volumes rather than in the database, because they are files:
uploaded .glb models and converted imagery. A single satellite scene is a couple of
hundred megabytes, so give the imagery volume room before loading a folder of them.
For a server rather than a laptop, deploy/docker-compose.prod.yml runs images
built in CI instead of building anything locally, and publishes nothing but
Caddy. See deployment.
Nothing here needs one, and that is a constraint rather than a boast: a demo that dies when someone's free tier changes is worse than a demo with fewer basemaps. The canvases and the buildings are OpenFreeMap, the imagery and terrain styles are Esri, and the elevation is Mapzen terrarium.
pnpm test # 585 tests in 37 files, Node only: no browser, no WebGL
pnpm typecheck
pnpm build
cd services/api && pytest # 70 more; 55 of them want no database at allCI runs the API twice on purpose: once with nothing but Python, for the parsing, the
naming rules, the live feed and every imagery decision that is arithmetic on a
gdalinfo document; and once against a real PostGIS with data/init/ loaded, for the
tiles and the registry.
Core tests assert on the operation array the reconciler emits for a given pair of project states, so slot ordering, classification, filter compilation and the imagery rules are all tested without rendering anything. Adapter tests use a fake renderer that records calls and refuses the same things a real one refuses. Nothing in the suite touches a GPU.
packages/core/tests/regressions.test.ts holds one test per defect that has been
fixed, named after the symptom rather than the cause.
- Design notes — why each subsystem is built the way it is, at length: the document model, geodesic drawing, the sun, the live feed's contract.
- Imagery — the GeoTIFF catalogue, footprints, dates, mosaic rules and band maths, and what the endpoints answer.
- Imagery prior art — TiTiler, STAC, mosaic datasets and EO Browser: what the field already does, what was taken from it, and what is left.
- Deployment — production on a small VPS: images built in CI, Caddy for TLS, tile caching, and the parts that only fail once you are live.
- Contributing
Apache-2.0.