status photos api

The goal of stac_airphoto_bc is to serve georeferenced historical aerial photograph thumbnails for British Columbia as a STAC collection. Currently covering the Neexdzii Kwa (Upper Bulkley River) watershed and three small southeast BC areas, 10,100 photos spanning 1967–2019. Queryable by location and time via rstac and QGIS (v3.42+) at https://images.a11s.one.


This work is built on the fly R package which handles airphoto footprint estimation, spatial filtering, thumbnail downloading, and georeferencing. Sister collections on the same endpoint:



Pipeline

Source centroids from the BC Data Catalogue, process through five stages:

Step Script What
Fetch 01_fetch.R Query centroids, size DEM-corrected footprints, assign per-roll rotation and placement, select, download thumbnails
Georef 02_georef.R Warp thumbnails to their footprints (BC Albers, EPSG:3005)
COG 03_cog.py Apply the measured placement shift, embed metadata tags, write each Cloud-Optimized GeoTIFF once
STAC 05_stac_register.py Generate items with checksums and provenance, merge into the collection, validate
S3 04_s3_upload.R Re-check every item against its COG, back up the published item JSONs, sync

Run end-to-end: bash scripts/run_pipeline.sh

Each COG carries embedded metadata (visible in QGIS layer properties): AIRP_ID, PHOTO_DATE, SCALE, FILM_ROLL, FRAME_NUMBER, FOCAL_LENGTH, FLYING_HEIGHT, FILENAME, and how it was placed: ROTATION, ROTATION_SOURCE, PLACEMENT_SOURCE, SHIFT_X_M_3005, SHIFT_Y_M_3005, HEIGHT_SOURCE, FOOTPRINT_BASIS, FLY_VERSION, FLY_SHA, PIPELINE_SHA.

How each frame is placed

Footprints come from fly, sized with a DEM (MRDEM-30): checked against the province’s published photogrammetric solutions it agrees on width to 0–2%, and without it film is about 9% too small. Two measured corrections from a separate validation against provincial orthophotos are then applied, and every item says where its values came from, so frames can be filtered and checked:

  • airphoto:rotation_source — how the scanned image sits on its footprint, a per-roll constant: measured (fitted against orthophotos), reviewed (a person looked), disputed (a person disagrees with the measured value, kept and flagged), or assumed_by_series (no measurement; the default for the roll’s series). Absent on digital frames, whose orientation fly derives itself.
  • airphoto:placement_source — a per-frame shift in EPSG:3005 metres (airphoto:shift_x_m_3005, airphoto:shift_y_m_3005): correlator (the frame’s own fit), roll_model (the roll’s measured bias, only where held-out frames confirm it), manual, or none (where the catalogue puts it).
  • airphoto:height_source — whether fly used the catalogue’s flying height, repaired a known unit slip, or refused an implausible one.

Each thumbnail asset carries file:checksum (a sha256 multihash) and file:size, and each item the fly and pipeline commits that built it (nge:fly_version, nge:fly_sha, nge:pipeline_sha, nge:produced_datetime).

Every item also carries the BC Data Catalogue’s own metadata as queryable airphoto: properties — media, bcgs_tile, nts_tile, ground_sample_distance (centimetres), and georef_metadata, a boolean saying whether a published photogrammetric solution exists for that frame. 1,775 of 9,976 frames have one. Where the catalogue links a retrievable file — the PAT-B solution, the camera calibration report, the scanned flight log page — it appears as an asset with a metadata role, so a client can fetch it without a second trip to the catalogue.

# frames with a photogrammetric solution
rstac::stac_search(
  q, collections = "stac-airphoto-bc", limit = 100
) |> rstac::ext_filter(`airphoto:georef_metadata` == TRUE)


Query with rstac

library(rstac)
library(sf)

# AOIs come from the registry in scripts/aoi.R, not from constants here.
# Swap the id for any other registered area — se_a, se_b, se_c.
source("scripts/aoi.R")
aoi <- aoi_resolve("neexdzii_kwa") |> sf::st_transform(4326)

# Search for photos between 1965 and 1975
q <- rstac::stac("https://images.a11s.one/") |>
  rstac::stac_search(
    collections = "stac-airphoto-bc",
    intersects = jsonlite::fromJSON(
      geojsonsf::sf_geojson(aoi, atomise = TRUE, simplify = FALSE),
      simplifyVector = FALSE
    ) |> (\(x) x$geometry)(),
    datetime = "1965-01-01T00:00:00Z/1975-12-31T00:00:00Z"
  ) |>
  rstac::post_request()

r <- q |> rstac::items_fetch()

saveRDS(r, "data/stac_result.rds")
r <- readRDS("data/stac_result.rds")

tab <- tibble::tibble(
  title = purrr::map_chr(r$features, ~ purrr::pluck(.x, "properties", "title", .default = .x$id)),
  url = purrr::map_chr(r$features, ~ purrr::pluck(.x, "assets", "thumbnail", "href"))
) |>
  dplyr::mutate(
    link_view = ngr::ngr_str_link_url(
      url_base = "https://viewer.a11s.one/?cog=",
      url_resource = url,
      url_resource_path = FALSE,
      anchor_text = title
    ),
    link_download = ngr::ngr_str_link_url(
      url_base = url,
      anchor_text = basename(url)
    )
  ) |>
  dplyr::select(link_view, link_download)


QGIS Integration

As of QGIS 3.42, STAC items can be accessed directly via the Data Source Manager. Connect to https://images.a11s.one and browse the stac-airphoto-bc collection. See this blog for details.

Items display with descriptive titles (airp_id -- roll_frame -- date) for easy identification:

Browsing the airphoto collection in QGIS STAC Data Source Manager

Browsing the airphoto collection in QGIS STAC Data Source Manager


Roadmap

Browse open issues for the current backlog.