TerraSelect is a modular geospatial site-suitability scorer. It overlays public EU datasets (land cover, terrain, power infrastructure, water bodies, regional R&D, protected areas, internet exchanges, …) onto a grid of candidate cells, scores each layer independently with a customizable rule, and combines the layers into a single suitability surface that you can explore interactively.
It was originally built to study where AI Gigafactories could plausibly land in Europe — see the accompanying blog post: "AI Gigafactories: Europe's €20B Race and the Site Selection Challenge".
- Streamlit explorer — interactive map; pick layers, adjust weights, click cells for score breakdowns.
- CLI — batch generation of per-layer score grids (
.gpkg); grid combination. - 15+ loaders — one Python file per data source, each with its own scoring rule (distance decay, attribute weighting, normalisation). Add a new one by subclassing
Loader. - Docker — one image bundles app + dependencies; data is mounted at runtime so you don't rebuild for data updates.
git clone https://github.com/nicofirst1/terraselect.git
cd terraselect
poetry installYou now have the code but no data. See the Data section for what to download.
TerraSelect does not bundle the upstream datasets. Each loader either
downloads its dataset on first use or expects you to place the raw file at a
configured path under data_mount/.
The canonical list of sources, licenses, download URLs, and required attributions lives in ATTRIBUTION.md.
Re-downloading every upstream dataset and running build-grid for every
layer × map is several hours of work. Pre-built bundles are published as
GitHub Releases —
the latest is v0.1.0-data.
Each bundle ships 19 of the 23 layers (the ai_gigafactories* proposal
layers and ixp_proximity are omitted for licensing reasons; regenerate
locally if needed).
# Pick the map you want — germany is smallest and quickest to try
curl -LO https://github.com/nicofirst1/terraselect/releases/download/v0.1.0-data/terraselect-grids-germany-v0.1.0.tar.zst
tar -xf terraselect-grids-germany-v0.1.0.tar.zst
rsync -av terraselect-grids-germany-v0.1.0/data_mount/ data_mount/Bundle sizes: germany ~11 MB, eu ~11 MB, eu_highres ~240 MB. The Streamlit
reader prefers .parquet and falls back to .gpkg, so pre-built and
locally-computed layers can coexist in the same data_mount/.
The bundles are a Collective Database — each layer retains its
upstream license, with share-alike (ODbL) attaching only to the
OSM-derived transmissionlines layer. See the bundle's MANIFEST.md
for the full per-layer license matrix.
The ai_gigafactories* loaders read candidate-site coordinates from a CSV
that is not redistributed with TerraSelect — the underlying list was
compiled by a journalist and shared on the condition that it not be
re-published. To enable these layers, supply your own CSV at
data_mount/proposals/gigafactory_proposals.csv with columns
name,lat,lon,exact. See the docstring in
src/terraselect/loaders/giga_factories_proposals.py and the corresponding
row in ATTRIBUTION.md for the source citation. If the CSV
is absent the rest of TerraSelect runs normally without those layers.
poetry run streamlit run src/terraselect/app/main.pyThen open http://localhost:8501. The app expects pre-computed
.gpkg score grids under data_mount/score_grid/ (see next section).
Generate one score grid per layer per map extent. The result is cached as a
GeoPackage under data_mount/score_grid/<layer>_<map>.gpkg and consumed by
the Streamlit app.
poetry run terraselect build-grid --layer universities --map germany
poetry run terraselect build-grid --layer powerplants_all --map euRun poetry run terraselect --help to list every command. Available layer
names are the keys of ALL_AVAILABLE_LAYERS in
src/terraselect/loaders/__init__.py.
poetry run terraselect combine-grids grid1.gpkg grid2.gpkg --grid-alphas 0.7,0.3Pre-built multi-arch images (linux/amd64, linux/arm64) are published to
GitHub Container Registry on every tagged release and on every push to main
(as :edge):
docker run -p 8501:8501 --rm \
-v /absolute/path/to/your/data_mount:/app/data_mount \
ghcr.io/nicofirst1/terraselect:latestOr build the image locally:
docker build -t terraselect-app .
docker run -p 8501:8501 --rm \
-v /absolute/path/to/your/data_mount:/app/data_mount \
terraselect-appOpen http://localhost:8501. The container reads .gpkg files from the
bind-mounted directory, so updating a grid on the host means restarting the
container — no rebuild needed. Override the in-container data path with
-e TERRASELECT_DATA_PATH=/custom/path.
src/terraselect/
├── app/ Streamlit application (main.py is the entry point)
├── cli.py CLI command definitions (entry point: `terraselect`)
├── core/ Grid definitions, loader base class, score breakdown
├── loaders/ One module per data source; subclass Loader to add new ones
├── evaluation/ Offline analysis pipelines (gigafactory comparison, predictors)
├── main.py Library facade: ALL_AVAILABLE_LAYERS, run_build_grid, …
├── utils/ Checkpoint manager, grid combining, normalisation helpers
└── visualization/ Folium / matplotlib helpers (legacy build-viz output)
resources/streamlit/ Per-layer markdown descriptions shown in the sidebar
hpc/ SLURM submission template (Barnard cluster example)
data_mount/ Runtime data — gitignored; not in the public repo
documents/ Design notes and the original gigafactory case study
Issues and PRs welcome. Two areas where help is especially useful:
- New loaders — adding a public EU dataset that improves the suitability signal (e.g., excess grid capacity, fibre backhaul, water rights). Use
loaders/universities.pyas a template. - License-respecting data bundles — re-publishing pre-built score grids under licenses compatible with the upstream sources (see ATTRIBUTION.md).
Code: Apache License 2.0 — see LICENSE. Data: each upstream source carries its own license — see ATTRIBUTION.md.