Skip to content

Latest commit

 

History

259 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Where to Blitz the Gap

A trip planner for Blitz the Gap, the Canada-wide iNaturalist bioblitz: pick what counts as impact, see where a new observation adds most to what we know about Canadian biodiversity, and plan a trip to the best spot you can reach and get back from.

Live: https://pollocklab.github.io/where-to-blitz/

A companion planning tool for the Pollock Lab Blitz the Gap campaign at McGill — a planning aid, not ground truth.

What it does

  • An equal-area grid of Canada: 23,214 cells of 25 km, and 536,164 of 5 km when you zoom in. Each cell is scored by a mix of goals you weight with sliders.
  • Explore the priority map, plan a trip (real driving/cycling/walking routes via OSRM, travel time, field time, CO₂), or compare goals side by side.
  • Tap a cell to see what species to record there and which common ones are still missing ("fill the gap"), pulled live from the iNaturalist API.
  • English / Canadian-French toggle; geolocation; challenge presets linked to the real Blitz the Gap iNaturalist sub-projects.

How cells are scored

Five goals per cell, each scored 0–1; you blend them with sliders into a 0–100 impact score (a percentile rank across the cells shown). All five are built from real data.

Does the priority actually work? On a leakage-free backtest of the 2025 BC pilot, the highest-ranked cells turn up roughly 2–3× more new species than the lowest at equal effort, and it holds out-of-sample in Eastern Canada.

METHODOLOGY.md has the glossary, the per-axis formulas and sources, the per-taxon validation numbers, and the scope those numbers hold within.

Build

The app is a single self-contained index.html generated by build_webapp.py:

python build_webapp.py    # regenerates index.html from cluster_results/ca/

Edit the template webapp/index.html (or build_webapp.py), never the generated index.html — it is overwritten on every build and on every push to main. The build is deterministic (no timestamps/randomness) — same inputs produce a byte-identical index.html.

  • cluster_results/ca/index.json — group → data-file map, lattice definition, methods metadata.
  • cluster_results/ca/webapp_data_<group>.json — per-group cell data, fetched at runtime.
  • cluster_results/ca/ca_density_*.tif — density rasters (gitignored, regenerable via the build_*_ca.py scripts).

How it fits together

No server, no database: Python writes files, the browser reads them. The five map layers are not the same kind of thing, which is the one non-obvious part:

Layer Served as Built by
Base map XYZ raster tiles (CARTO / ArcGIS / OpenTopoMap)
Cell geometry GeoJSON polygons in the LAEA lattice grid_lattice.py
Cell colours values/*.png, one pixel per cell, painted onto those polygons build_grid_values.py
Density overlay Raster PMTiles under density/, served same-origin build_density_pmtiles.py
Density, Fungi only Live TiTiler over a 1 km COG on Arbutus

Why colours are a PNG and not tiles: build_grid_values.py docstring (#116). Why Fungi differs: build_density_pmtiles.py docstring.

Run it locally

A fresh clone looks empty on the map: cluster_results/ca/*.json is in git, but tiles/ and cluster_results/ca/values/ are gitignored. Both are release assets, and their names already match what the app fetches, so nothing needs renaming:

gh release download grid-outputs-v1 --repo PollockLab/where-to-blitz \
  --dir tiles --pattern "density_All.pmtiles" --clobber
gh release download grid-outputs-v1 --repo PollockLab/where-to-blitz \
  --pattern grid_values.tar.gz --clobber
mkdir -p cluster_results/ca/values && tar -xzf grid_values.tar.gz -C cluster_results/ca/values
python build_webapp.py
npx http-server -p 8765 -c-1      # then open http://localhost:8765/

That is 48 MB, about a minute. Use --pattern "*.pmtiles" for every layer (180 MB).

Serve it with something that honours Range. PMTiles reads byte ranges out of one large file. python -m http.server ignores the Range header and answers 200 with the whole body (Python 3.11 and 3.14 both do), so pmtiles.js stops with "Server returned no content-length header or content-length exceeding request. Check that your storage backend supports HTTP Byte Serving." The vector grid still draws, which is what makes it confusing: only the rasters go missing. Check your server in one line — it must print 206:

curl -s -o /dev/null -w '%{http_code}\n' -r 0-99 http://localhost:8765/tiles/density_All.pmtiles

Opening index.html as a file:// URL does not work either; use the local server.

Responsible use

A planning aid, not a census. Sensitive and at-risk species are gated out of suggestions (taxon_geoprivacy=open, threatened=false), and the at-risk layer is only ever exposed as a per-cell score, never as point locations. Obscure sensitive locations and respect Indigenous data sovereignty before any public use. The reasoning, including the dual-use guard it implements, is in METHODOLOGY.md.

About

Where should I go to record biodiversity? An interactive sampling-priority planner for Blitz the Gap

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages