Warps images to their estimated ground footprint using GCPs (ground control
points) derived from fly_footprint(). Produces georeferenced GeoTIFFs in
BC Albers (EPSG:3005). Works with thumbnails and full-resolution scans.
Usage
fly_georef(
fetch_result,
photos_sf,
dest_dir = "georef",
overwrite = FALSE,
srcnodata = "0",
rotation = "auto"
)Arguments
- fetch_result
A tibble returned by
fly_fetch(), with columnsairp_id,dest, andsuccess.- photos_sf
The same sf object passed to
fly_fetch(), with ascalecolumn for footprint estimation. If arotationcolumn is present, per-photo rotation values are used (see Rotation below).- dest_dir
Directory for output GeoTIFFs. Created if it does not exist.
- overwrite
If
FALSE(default), skip files that already exist.- srcnodata
Source nodata value passed to GDAL warp. Black pixels matching this value are treated as transparent (alpha=0 for RGB, nodata for grayscale). Default
"0"masks camera frame borders and film holder edges at the cost of losing real black pixels — acceptable for thumbnails but may need adjustment for full-resolution scans. Set toNULLto disable source nodata detection entirely.- rotation
Image rotation in degrees clockwise. One of
"auto",0,90,180, or270."auto"(default) computes flight line bearing from consecutive centroids and derives rotation per-photo — requiresfilm_rollandframe_numbercolumns. Fixed values apply the same rotation to all photos. Overridden per-photo ifphotos_sfcontains arotationcolumn.
Details
Each image's four corners are mapped to the corresponding footprint
polygon corners computed by fly_footprint() in BC Albers. GDAL
translates the image with GCPs then warps to the target CRS using
bilinear resampling.
Rotation: Aerial photos may appear rotated in their footprints
because the camera orientation relative to north varies by flight
direction, camera mounting, and scanner orientation. The rotation
parameter rotates the GCP corner mapping:
0— top of image maps to north edge of footprint (original behavior)90— top of image maps to east edge (90° clockwise)180— top of image maps to south edge (default, correct for most BC photos)270— top of image maps to west edge
When rotation = "auto", the bearing-to-rotation formula is:
floor((bearing + 91) / 90) * 90 %% 360. This was calibrated on
BC aerial photos spanning 1968–2019 across multiple camera systems
and scanners. Photos on diagonal flight lines (~45° off cardinal)
may be imperfect — check visually and override with a rotation
column if needed.
Within a film roll, consecutive flight legs alternate direction
(back-and-forth pattern), so different frames on the same roll may
need different rotations. This is why "auto" computes per-photo,
not per-roll. To override, add a rotation column to photos_sf:
photos$rotation <- dplyr::case_when(
photos$film_roll == "bc5282" ~ 270,
.default = NA # fall through to auto
)Nodata handling: Two sources of unwanted black pixels are masked:
Warp fill — GDAL creates black pixels outside the rotated source frame. RGB images get an alpha band (
-dstalpha); grayscale usedstnodata=0.Camera frame borders — film holder edges, fiducial marks, and scanning artifacts produce black (value 0) pixels within the source image. The
srcnodataparameter (default"0") tells GDAL to treat these as transparent before warping.
Tradeoff: srcnodata = "0" also masks real black pixels (deep
shadows). At thumbnail resolution (~1250x1250) this is acceptable —
shadow detail is minimal. For full-resolution scans where shadow
detail matters, set srcnodata = NULL and handle frame masking
downstream (e.g., circle detection).
Accuracy: footprints assume flat terrain and nadir camera angle.
The georeferenced images are approximate — useful for visual context,
not survey-grade positioning. See fly_footprint() for details on
limitations.
Examples
centroids <- sf::st_read(system.file("testdata/photo_centroids.gpkg", package = "fly"))
#> Reading layer `photo_centroids' from data source
#> `/home/runner/work/_temp/Library/fly/testdata/photo_centroids.gpkg'
#> using driver `GPKG'
#> Simple feature collection with 20 features and 16 fields
#> Geometry type: POINT
#> Dimension: XY
#> Bounding box: xmin: -126.7631 ymin: 54.34512 xmax: -126.449 ymax: 54.47635
#> Geodetic CRS: WGS 84
# Fetch and georeference with auto rotation (uses bearing from centroids)
fetched <- fly_fetch(centroids[1:2, ], type = "thumbnail",
dest_dir = tempdir())
#> Downloaded 2 of 2 files
georef <- fly_georef(fetched, centroids[1:2, ],
dest_dir = tempdir())
#> Georeferenced 2 of 2 images
georef
#> # A tibble: 2 × 4
#> airp_id source dest success
#> <int> <chr> <chr> <lgl>
#> 1 699370 /tmp/RtmpW9Vd76/bc5282_176_thumb.jpg /tmp/RtmpW9Vd76/bc5282_1… TRUE
#> 2 699415 /tmp/RtmpW9Vd76/bc5282_221_thumb.jpg /tmp/RtmpW9Vd76/bc5282_2… TRUE
