The goal of stac_uav_bc
is to document and serve out UAV imagery collected in British Columbia
as a SpatioTemporal Asset Catalog
(STAC) collection, organized by region / watershed-group / year.
Queryable by location and time (items carry flight capture datetimes)
via the rstac R
package, QGIS (v3.42+), or any STAC-compliant client. The API
endpoint is https://images.a11s.one; an interactive single-COG
viewer lives at https://viewer.a11s.one.
The repo also covers the two steps either side of the catalogue:
stitching flights with OpenDroneMap, and
planning them. scripts/flight_plan.py
reads a floodplain delineation from the stac-floodplains-bc
collection, splits a reach into blocks that each fit one battery, and
writes DJI WPML .kmz missions — closing the loop, since the
imagery those missions produce comes back into this catalogue.
Sister collections on the same images.a11s.one
endpoint:
stac_dem_bc
— LidarBC digital elevation models (~58k GeoTIFFs)stac_airphoto_bc
— historic airphoto thumbnails (1963–2019)To add new imagery to the catalog follow the recipe in scripts/config/README.md.
Query the imagery-uav-bc-prod collection by bounding
box. Below: every UAV item whose footprint intersects the full BC
extent.
# bc bounding box
bcbbox <- as.numeric(
sf::st_bbox(bcmaps::bc_bound()) |>
sf::st_transform(crs = 4326)
)
# use rstac to query the collection
q <- rstac::stac("https://images.a11s.one/") |>
rstac::stac_search(
collections = "imagery-uav-bc-prod",
bbox = bcbbox
) |>
rstac::post_request()
# get details of the items
r <- q |>
rstac::items_fetch()
saveRDS(r, "data/stac_result.rds")
r <- readRDS("data/stac_result.rds")
# build the table to display the info — split the asset URL into region / watershed_group / year / item
url_bucket <- "https://imagery-uav-bc.s3.amazonaws.com/"
tab <- tibble::tibble(url_download = purrr::map_chr(r$features, ~ purrr::pluck(.x, "assets", "image", "href"))) |>
dplyr::mutate(stub = stringr::str_replace_all(url_download, url_bucket, "")) |>
tidyr::separate(
col = stub,
into = c("region", "watershed_group", "year", "item", "rest"),
sep = "/",
extra = "drop"
) |>
dplyr::mutate(
link_view = dplyr::case_when(
!tools::file_path_sans_ext(basename(url_download)) %in% c("dsm", "dtm") ~
ngr::ngr_str_link_url(
url_base = "https://viewer.a11s.one/?cog=",
url_resource = url_download,
url_resource_path = FALSE,
anchor_text = tools::file_path_sans_ext(basename(url_download))
),
TRUE ~ "-"
),
link_download = ngr::ngr_str_link_url(url_base = url_download, anchor_text = url_download)
) |>
dplyr::select(region, watershed_group, year, item, link_view, link_download)
Drone imagery download and viewer links. NOTE: To view all columns in the table - please click on one of the sort arrows within column headers before scrolling to the right.
Beside datetime, title and the
proj: fields, each item carries its site’s row from data/sites.csv. Each field’s
prefix says what it describes (#38).
A blank registry cell is left off the item rather than written
empty.
| field | holds | from |
|---|---|---|
newgraph:region |
New Graph’s region (skeena, fraser,
mackenzie, kootenay), also the first part of
the item id |
registry |
uav:stream_name |
the stream the flight covers | registry, picked from PSCIS, bcfishpass or the Freshwater Atlas
(name_source says which) |
uav:stream_name_02,
uav:stream_name_03 |
further streams in the same flight | registry |
fwa:watershed_group_code |
the Freshwater Atlas watershed group code (MORR) |
registry |
fwa:watershed_group_name |
the atlas’s name for that group (Morice River) |
atlas, by code (scripts/sites_fill-wsg_name.py) |
bcfishpass:aggregated_crossings_id |
the crossing the flight covers | bcfishpass |
newgraph:alias |
New Graph’s name for the site, which separates repeat flights in the
title (moose pre-replacement) |
registry |
newgraph:project |
New Graph’s project | registry (none set yet) |
They replace the nge: fields
(nge:stream_name, nge:wsg_code and the rest)
that items carried until the release that ships #38, whose
NEWS.md entry lists each old and new name. Filter on any of
them with CQL2, for example every item in the Morice watershed
group:
rstac::stac("https://images.a11s.one/") |>
rstac::stac_search(collections = "imagery-uav-bc-prod", limit = 1000) |>
rstac::ext_filter(`fwa:watershed_group_code` == "MORR") |>
rstac::post_request()
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
Collection, item, and catalogue creation are documented in three Quarto notebooks:
stac_create_catalouge.qmd
— root catalogue assemblystac_create_collection.qmd
— collection-level metadatastac_create_item.qmd
— per-item assemblyThe scripts/ directory holds the orchestration
helpers:
| Script | Role |
|---|---|
cog_convert.R |
Convert ODM-output GeoTIFFs to Cloud-Optimized GeoTIFFs |
odm_process.R |
OpenDroneMap processing (orthomosaic, DSM, DTM generation) |
s3_sync.R |
Sync COGs to the imagery-uav-bc S3 bucket |
s3_index.R, s3_map.R |
Index + map S3 contents for ingestion |
item_create.py |
Create STAC items + update collection.json: additive for new COGs,
--rebuild for every item, --selftest as its
gate |
web.R |
Web/viewer utilities |
stacs.sh |
The pinned stacs
CLI — registers and verifies the catalogue against
stacs.toml |
config/ |
Server docs + item_unregister.sh (the delete path;
stacs is upsert-only) — see the add-imagery recipe |
viewer.html |
Standalone viewer-page template for single-COG previews |
Recent infrastructure work — see commit history — added client-side
pagination handling (so very large queries return correctly via
successive items_next() fetches) and split S3 uploads into
1000-item chunks for memory safety.
stac/ directory cleanup (#11)
— detect + remove STAC item directories that no longer correspond to a
source mission.Browse open issues for the full backlog.
MIT.