status scope api

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:

To add new imagery to the catalog follow the recipe in scripts/config/README.md.


Query the collection

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.

Item properties

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 Data Source Manager (v3.42+)

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

Connecting to https://images.a11s.one

Using the field of view in QGIS to filter results

Using the field of view in QGIS to filter results

Build pipeline

Collection, item, and catalogue creation are documented in three Quarto notebooks:

The 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.

Roadmap

Browse open issues for the full backlog.

License

MIT.