Skip to content

Latest commit

 

History

293 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 preset mix of goals.
  • 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; a preset blends them into a 0–100 impact score (a percentile rank across every cell in the country, so it does not change as you pan or zoom). All five are computed from real data, though env carries weight 0 in every shipped preset.

Does the priority actually work? On a leakage-free backtest of the 2025 BC pilot, the highest-ranked cells turn up 1.1× to 3.0× more new species than the lowest at equal effort (Spearman rho 0.40 to 0.67, permutation p < 0.001 on every taxon), and it holds out-of-sample in Eastern Canada. Mammals and reptiles are the strong cases at 3.0×; insects are the weak one, 1.3× in BC and 1.1× in the East.

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 six 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
Climate Gap overlay A single raster PMTiles, climate_gap.pmtiles, served same-origin not built here: baked from CHELSA outside the repo and uploaded to the release

Why colours are a PNG and not tiles: build_grid_values.py docstring (#116). Why Fungi differs: build_density_pmtiles.py docstring. Climate Gap is the one layer no script in this repo regenerates, so the release asset is its only source; both _site assembly jobs test -s it rather than let a missing file blank the map.

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

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages