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.
- An equal-area grid of Canada: 23,354 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 three recording-priority maps, Getting Even's group recommendations, and Climate Gap's coverage overlay. Trip planning and side-by-side goal comparison are optional development views.
- Tap a cell for live iNaturalist recording suggestions and group comparisons. A recording gap means no matching record under the query's filters, not biological absence.
- Save a chosen cell as a GPX file for the field: a waypoint at its centre, its boundary as a track, and the loaded species suggestions in the description. It opens in GPS apps such as Gaia, OsmAnd and Garmin, in Google Earth and in QGIS, with no signal needed afterwards.
- English / Canadian-French toggle; geolocation; recording presets with a join link to the current general Blitz the Gap iNaturalist project.
The Goal menu has five choices, with Spatial Gap selected by default: Spatial Gap, Species discovery, Conservation, Getting Even, and Climate Gap. The first three blend five underlying 0–1 axes into a national priority percentile across Canada-eligible 25 km cells for the selected species group. Equal raw scores share their average rank. Panning and zooming preserve that percentile; at 5 km it still describes the enclosing 25 km cell. Getting Even recommends a taxonomic group, and Climate Gap shows climate sampling coverage, each with its own legend.
Historical discovery evidence is a separate Map display option, not a sixth visible goal. It shows the supplied 2025 discovery points divided by matched 2025 research-grade observation counts, multiplied by 10,000, on both grid tiers. This historical rate is separate from the Species discovery recording preset and its priority percentile. Neutral cells have observations but no supplied points; blank areas have no usable rate. At 5 km, counted teal diamonds let you inspect supplied scientific names and years at their reported positions. The original point-selection rule is unknown, source-feature identities are not verified iNaturalist observation IDs, and the rate does not predict future discoveries. Orange Conservation Challenge sightings use a separate source and recent window. Historical method, provenance and reproduction describe the exact inputs and checks.
Map colours rescale to the cells in view, with a national colour scale when too few cells or too little colour spread are visible. Live popup records describe the selected 25 km or 5 km cell using its geographic bounding box, independently of the frozen priority score. The axes use observation density, climate, recorded conservation status, sampling history and cumulative forest loss. Where sampling-history data are missing, staleness uses an inverse-density fallback. env has no direct preset weight but orders Spatial Gap's zero-density cells.
What has been checked? In retrospective BC and Eastern Canada project samples, a training-period scarcity proxy is associated with expected new taxon IDs at K = 5 later observations (rho 0.48 to 0.71; 1.2x to 3.2x expected novelty at upper versus lower score-tercile cutoffs). Archived grid scores were compared on the viewer's declared 25 km lattice using their original grid and record-input snapshots. Those coefficients have not been rerun for the latest rebuilt grid. These comparisons use sampled records and observation dates, with historical availability unknown; they do not establish future trip yield.
METHODOLOGY.md has the glossary, the per-axis formulas and sources, the per-taxon validation numbers, and the scope those numbers hold within.
Replay all eight frozen retrospective sampled-record benchmarks without replacing the committed results:
.venv/bin/python verify_appscore_results.py --downloadThis checks the archived public inputs, analysis sources and committed 25 km inventory before comparing complete result payloads. Downloads use a temporary directory. Use --inputs-dir PATH for already downloaded release assets. Runtime versions are reported separately. This replay does not validate later rebuilt live Conservation data, the 5 km inventory or prospective trip yield; reproduction details describe the checks.
Suijker, W. & Pollock Lab (2026). Where to Blitz the Gap. McGill University. https://pollocklab.github.io/where-to-blitz/ (code: https://github.com/PollockLab/where-to-blitz). Using the data? Also cite the source datasets listed in METHODOLOGY.md. Machine-readable: CITATION.cff.
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 thebuild_*_ca.pyscripts).
No server, no database: Python writes files, the browser reads them. The map layers use different data and display formats:
| 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 built into density/ (gitignored), published to the grid-outputs-v1 release, served same-origin |
build_density_pmtiles.py |
| 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.
The published file is cut to z0-8 with repack_pmtiles.py so its whole directory arrives in the first 16 KB read (z9 added no visible detail). Rerun it on any new upload: python repack_pmtiles.py climate_gap_z0-9.pmtiles climate_gap.pmtiles --maxzoom 8.
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.pmtilesOpening index.html as a file:// URL does not work either; use the local server.
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.