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-01T00:00:00Z"
date_end <- "2020-12-31T00: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)
Download links for every asset returned by the query above — one row
per asset, so a paired item shows its dem and
dsm together. NOTE: To view all columns in the table -
please click on one of the sort arrows within column headers before
scrolling to the right.
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:
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.dsm/
directory holding only .laz. Recorded as a declared
coverage gap in data/dsm_pairing_report.md; deriving a
raster from the point cloud is unscoped.Browse open issues for the full backlog.
MIT.