stac-elevation-bc serves British Columbia’s
LidarBC elevation data as a SpatioTemporal
Asset Catalog (STAC) — 102,460 tiles on the
provincial objectstore, searchable by location and time from the
rstac R package, QGIS
(v3.42+), or any STAC-compliant client. The endpoint is
https://images.a11s.one.
Each item carries up to two assets from the same flight over the same footprint:
| asset | what it is |
|---|---|
dem |
bare-earth digital elevation model — every item has one |
dsm |
digital surface model, where the delivery published one — 95,888 of 102,460 |
The catalog refreshes monthly: a scheduled GitHub Actions
workflow detects new tiles on the
objectstore and appends them incrementally, with each month’s changes
landing as a commit to data/ — a git audit trail of what was
added, removed, and validated. How the automation works, its failure
modes, and where the evidence lives are documented in
scripts/, alongside a plain-language guide to the
concepts behind the pipeline (COG, STAC, pgstac, validation caching).
Use bcdata to define an area of
interest, then query the stac-elevation-bc collection for elevation
tiles intersecting it. Below: all DEMs covering the Bulkley River
watershed group between 2018 and 2020.
aoi <- bcdata::bcdc_query_geodata("freshwater-atlas-watershed-groups") |> bcdata::filter(WATERSHED_GROUP_NAME == "Bulkley River") |> bcdata::collect() |> sf::st_transform(crs = 4326) date_start <- "2018年01月01日T00:00:00Z" date_end <- "2020年12月31日T00:00:00Z" # use rstac to query the collection q <- rstac::stac("https://images.a11s.one/") |> rstac::stac_search( collections = "stac-elevation-bc", intersects = jsonlite::fromJSON( geojsonsf::sf_geojson( aoi, atomise = TRUE, simplify = FALSE ), simplifyVector = FALSE ) |> (\(x) x$geometry)(), datetime = paste0(date_start, "/", date_end) ) |> rstac::post_request() # get details of the items r <- q |> rstac::items_fetch() # burn the results locally so we can serve it instantly on index.html builds saveRDS(r, "data/stac_result.rds")
r <- readRDS("data/stac_result.rds") # One row per ASSET, not per item. Every item carries a bare-earth `dem`, and # most also carry a `dsm` from the same flight -- a dem-only column hid half of # what the collection serves. tab <- purrr::map_dfr(r$features, function(f) { purrr::imap_dfr(f$assets, function(a, key) { tibble::tibble( tile = f$id, date = substr(f$properties$datetime, 1, 10), type = key, download = glue::glue('<a href="{a$href}" target="_blank">{basename(a$href)}</a>') ) }) }) |> dplyr::arrange(tile, type)
Please see http://www.newgraphenvironment.com/stac_dem_bc for the published table of collection links.
Every row above is a dem, because the Bulkley was flown between 2000
and 2020 and those deliveries published no surface model. Province-wide
95,888 of 102,460 items carry a dsm — the same flight over the
same footprint at the same time — but the coverage is concentrated in
the 2024 deliveries, so whether you get one depends on when your area
was last flown rather than on where it is.
QGIS 3.42 added native STAC support — connect directly to the catalog and filter by the current map view. See Lutra Consulting’s STAC-in-QGIS blog post for a walk-through.
Connecting to https://images.a11s.one
Using the field of view in QGIS to filter results
The same images.a11s.one STAC API serves several complementary BC
collections:
stac_floodplains_bc— floodplain land-cover change (stac-floodplains-bc)stac_airphoto_bc— historic airphoto thumbnails, 1963–2019 (stac-airphoto-bc)stac_uav_bc— UAV imagery, organized by watershed (imagery-uav-bc-prod)
The big one landed in 2026: the catalog is now self-updating (#23) — the goal open since the first build — and the July catch-up grew the collection from 58k to ~98k fully-validated items. Items now also carry the digital surface model alongside the bare-earth DEM (#31), paired on tile id and acquisition date. Still ahead:
- Registration from CI — registration is now a client-side upsert in
this repo (
scripts/catalogue_register.sh), but it still runs from a laptop: no GitHub Actions runner can reach the STAC host today. Closing that needs a tailnet or deploy-key decision in the infrastructure repo, and it unblocks every catalogue repo at once. - Upstream-deletion handling (#28) — propagate objectstore removals to the catalog. The September run settled the open question — the deleted tiles did not reappear under new names, so they are deletions rather than a rename in flight.
- Surface models published as point cloud only — 1,211 DEM tiles
across 11 mapsheet-years have a
dsm/directory holding only.laz. Recorded as a declared coverage gap indata/dsm_pairing_report.md; deriving a raster from the point cloud is unscoped. - CHM (#29) — 264 canopy-height tiles are published province-wide, ~1.1% coverage of the mapsheet-years that carry them. Worth indexing for completeness; not a substitute for deriving.
- True footprint geometry (#2) — recalculate per-item footprints to exclude no-data pixels rather than using bounding boxes; gives accurate spatial-overlap queries.
- Structured logging + performance benchmarking (#6) — instrument the pipeline so build performance is quantifiable across runs.
- uv-based Python dependency management (#16) — the CI workflow already installs with uv; migrate the local conda environment to match.
Browse open issues for the full backlog.
MIT.