Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions .Rbuildignore
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,5 @@
^Meta$
^CLAUDE\.md$
^\.claude$
^Rplots\.pdf$
^planning$
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@ inst/doc
docs
/doc/
/Meta/
Rplots.pdf
5 changes: 3 additions & 2 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
Package: fly
Title: Airphoto Footprint Estimation and Coverage Selection
Version: 0.4.0
Date: 2026-08-28
Version: 0.5.0
Date: 2026-08-29
Authors@R: c(
person("Allan", "Irvine", , "[email protected]", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-3495-2128")),
Expand Down Expand Up @@ -29,6 +29,7 @@ Suggests:
bookdown,
furrr,
future,
terra,
testthat (>= 3.0.0),
knitr,
rmarkdown
Expand Down
10 changes: 10 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,15 @@
# fly (development version)

## 0.5.0 (2026-08-29)

- `fly_footprint()` gains a `dem` argument, sizing each frame from its height above ground instead of the reported scale ([#9](https://github.com/NewGraphEnvironment/fly/issues/9)). On the bundled Upper Bulkley AOI the reported scale understates footprint **area by a median 14%, ranging to 26%** — and always in the same direction, because the scale is referenced to an elevation above the valley floor the photos cover. This is a datum offset, not the slope effect the issue described
- `FLYING_HEIGHT` is metres above sea level, so subtracting terrain elevation is what turns it into the height ground coverage scales with. Elevation is the mean under the whole footprint, not a reading at the centroid — the two differ by up to 140 m on a 7.2 km frame. It is measured in two passes, because the footprint being averaged over is itself what the correction changes
- New `footprint_terrain`, `height_agl` and `dem_coverage` columns record which terrain treatment each frame received, the height it was sized from, and how much of its footprint the DEM actually covered — measured against the cells the footprint should have covered, so that a footprint running past the edge of a cropped DEM is reported rather than counted as complete. `footprint_basis` is unchanged: it is already matched by value downstream, so encoding terrain into it would break caller filters
- `dem` is accepted by `fly_coverage()`, `fly_overlap()`, `fly_filter()`, `fly_select()` and `fly_georef()`, so the correction is reachable from every function that builds a footprint
- Frames the DEM cannot correct — outside its coverage, or with unusable `flying_height` / `focal_length` — fall back to nominal scale with a warning rather than being dropped. A frame the DEM covers only partly is still corrected, from the mean of the covered part, and warns below 95% coverage
- New `inst/testdata/dem.tif`, a clip of NRCan's MRDEM-30. `terra` added to Suggests; it is only needed when a `dem` is supplied
- Footprints remain axis-aligned rectangles under a nadir assumption. Per-corner ray-casting measures ~2% against the 14% the DEM addresses, and is deferred

## 0.4.0 (2026-08-28)

- `fly_footprint()` sizes each frame from its `media` value instead of applying a fixed 9-inch negative to everything. A digital frame has no negative, and the catalogue mixes film and digital in one layer ([#30](https://github.com/NewGraphEnvironment/fly/issues/30))
Expand Down
7 changes: 5 additions & 2 deletions R/fly_coverage.R
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
#' @param photos_sf An sf point object with a `scale` column.
#' @param aoi_sf An sf polygon to check coverage against.
#' @param by Column name to group by (default `"photo_year"`).
#' @param dem Optional elevation raster passed to [fly_footprint()], sizing each
#' frame from its height above ground instead of the reported scale. See the
#' **Terrain** section of [fly_footprint()].
#' @return A tibble with the grouping column, `n_photos`, `covered_km2`,
#' and `coverage_pct`.
#'
Expand All @@ -15,7 +18,7 @@
#' fly_coverage(centroids, aoi, by = "scale")
#'
#' @export
fly_coverage <- function(photos_sf, aoi_sf, by = "photo_year") {
fly_coverage <- function(photos_sf, aoi_sf, by = "photo_year", dem = NULL) {
sf::sf_use_s2(FALSE)
on.exit(sf::sf_use_s2(TRUE))

Expand All @@ -24,7 +27,7 @@ fly_coverage <- function(photos_sf, aoi_sf, by = "photo_year") {
sf::st_make_valid()
aoi_area <- as.numeric(sf::st_area(aoi_albers))

footprints <- fly_footprint(photos_sf) |> sf::st_transform(3005)
footprints <- fly_footprint(photos_sf, dem = dem) |> sf::st_transform(3005)
fly_warn_unsized(footprints, "coverage")

photos_with_fp <- photos_sf
Expand Down
8 changes: 6 additions & 2 deletions R/fly_filter.R
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@
#' @param method One of `"footprint"` (default) or `"centroid"`.
#' @param buffer Buffer distance in metres added to the AOI before testing
#' intersection (default 0). Applied in BC Albers (EPSG:3005).
#' @param dem Optional elevation raster passed to [fly_footprint()], sizing each
#' frame from its height above ground instead of the reported scale. See the
#' **Terrain** section of [fly_footprint()].
#' @return A subset of `photos_sf` that intersects the AOI.
#'
#' @examples
Expand All @@ -20,7 +23,8 @@
#' nrow(fp_result) >= nrow(ct_result)
#'
#' @export
fly_filter <- function(photos_sf, aoi_sf, method = c("footprint", "centroid"), buffer = 0) {
fly_filter <- function(photos_sf, aoi_sf, method = c("footprint", "centroid"),
buffer = 0, dem = NULL) {
method <- match.arg(method)

aoi_3005 <- sf::st_transform(aoi_sf, 3005) |>
Expand All @@ -36,7 +40,7 @@ fly_filter <- function(photos_sf, aoi_sf, method = c("footprint", "centroid"), b
if (method == "centroid") {
hits <- sf::st_intersects(photos_sf, aoi_test, sparse = FALSE)[, 1]
} else {
footprints <- fly_footprint(photos_sf)
footprints <- fly_footprint(photos_sf, dem = dem)
fly_warn_unsized(footprints, "this filter")
hits <- sf::st_intersects(footprints, aoi_test, sparse = FALSE)[, 1]
}
Expand Down
Loading
Loading