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 @@ -11,3 +11,5 @@
^\.lintr$
^doc$
^Meta$
^planning$
^\.claude$
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -8,3 +8,5 @@ docs
/Meta/
*.html
Rplots.pdf
*.Rcheck/
*.tar.gz
14 changes: 10 additions & 4 deletions DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: flooded
Title: Portable Floodplain Delineation from DEM and Stream Network
Version: 0.2.1
Version: 0.3.0
Authors@R: c(
person("Allan", "Irvine", , "al@newgraphenvironment.com", role = c("aut", "cre"),
comment = c(ORCID = "0000-0002-3495-2128")),
Expand Down Expand Up @@ -36,11 +36,17 @@ Imports:
tibble
Suggests:
bookdown,
whitebox,
testthat (>= 3.0.0),
DBI,
fresh,
gdalcubes,
knitr,
rmarkdown,
rstac,
gdalcubes
testthat (>= 3.0.0),
whitebox,
xciter
Remotes:
NewGraphEnvironment/fresh,
NewGraphEnvironment/xciter
Config/testthat/edition: 3
VignetteBuilder: knitr
3 changes: 3 additions & 0 deletions NAMESPACE
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
# Generated by roxygen2: do not edit by hand

export(fl_cost_distance)
export(fl_dem_aoi)
export(fl_flood_assemble)
export(fl_flood_depth)
export(fl_flood_model)
Expand All @@ -16,6 +17,8 @@ export(fl_stream_rasterize)
export(fl_valley_confine)
export(fl_valley_poly)
import(terra)
importFrom(sf,st_buffer)
importFrom(sf,st_crs)
importFrom(sf,st_read)
importFrom(sf,st_transform)
importFrom(sf,st_zm)
6 changes: 6 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# flooded 0.3.0

- New `fl_dem_aoi()` — AOI-driven DEM fetch helper. Defaults to MRDEM-30 via `/vsicurl/` (the rtj-doc-settled choice for watershed-scale BC work) but accepts any local path, `/vsicurl/` URL, or `/vsis3/` S3 URL via `source =`. Buffered crop happens in the source raster's CRS, reprojection after crop. Replaces hand-rolled per-project DEM plumbing (#34).
- New `vignettes/pars-floodplain.Rmd` — watershed-scale showcase running `fl_dem_aoi()` + `fl_valley_confine()` end-to-end on the Parsnip River WSG (5,597 km²). Designed to port to a bookdown report appendix (#34).
- New `data-raw/wsg_vignette_data.R` — generic, parameterised by `wsg <- "PARS"`. Re-runs the full pipeline for any 4-letter BC watershed group, namespaces outputs by WSG code (#34).

# flooded 0.2.1

- Startup quote ritual: `library(flooded)` prints a random fact-checked quote on attach. Italic quote, grey attribution, clickable blue `source` hyperlink (OSC 8). Suppress via `options(flooded.quote_show_source = FALSE)`.
Expand Down
106 changes: 106 additions & 0 deletions R/fl_dem_aoi.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#' Fetch a DEM cropped to an AOI
#'
#' Returns a `SpatRaster` of elevation cropped to a buffered AOI. Source is a
#' local file, an HTTP COG (`/vsicurl/`), or an S3 COG (`/vsis3/`). Defaults to
#' MRDEM-30 — NRCan's Medium-Resolution Digital Elevation Model (30 m, all of
#' Canada, public S3, no auth) — the source-of-truth choice for watershed-scale
#' BC floodplain work as decided in `rtj/docs/dem-sources.md`.
#'
#' @param aoi An `sf` or `sfc` object — polygon, lines, or points. Buffered by
#' `buffer` (via [sf::st_buffer()]) before crop. To crop tightly along a
#' stream corridor (memory-efficient when the watershed-scale AOI has sparse
#' stream coverage), pass the streams `sf` here rather than the full WSG
#' polygon.
#' @param source Character. Path or URL for the source raster. `NULL` (default)
#' uses MRDEM-30 DTM via `/vsicurl/`. Local file paths, `/vsicurl/https://...`
#' URLs, and `/vsis3/...` S3 paths all work — `terra::rast()` handles them
#' identically.
#' @param buffer Numeric. Distance in metres to buffer `aoi` before crop.
#' Default `2000`.
#' @param target_crs CRS spec recognised by [sf::st_crs()] (EPSG code, WKT,
#' PROJ string, or `crs` object). `NULL` (default) returns the raster in the
#' input AOI's CRS. **Reprojection happens after crop**, never before — the
#' 84 GB MRDEM COG must not be reprojected as a whole.
#'
#' @return A `SpatRaster` cropped to the buffered AOI extent, projected to
#' `target_crs` if it differs from the source raster's CRS.
#'
#' @details
#' MRDEM-30 (`s3://canelevation-dem/mrdem-30/mrdem-30-dtm.tif`) is a single
#' 84 GB Cloud-Optimized GeoTIFF in EPSG:3979 covering all of Canada. The
#' `/vsicurl/` access pattern range-reads only the bytes intersecting the AOI,
#' so total bandwidth scales with AOI size, not the COG size.
#'
#' For sub-10 m riparian-scale work where lidar coverage exists, query the
#' `stac-dem-bc` STAC catalog and pass an item's COG URL as `source`. See the
#' example below.
#'
#' @seealso [fl_valley_confine()]
#'
#' @examples
#' aoi <- sf::st_read(
#' system.file("testdata/streams.gpkg", package = "flooded"),
#' quiet = TRUE
#' )
#'
#' # Local file (any path or URL works the same way)
#' dem <- fl_dem_aoi(
#' aoi,
#' source = system.file("testdata/dem.tif", package = "flooded"),
#' buffer = 200
#' )
#' terra::plot(dem)
#'
#' \dontrun{
#' # Default: MRDEM-30 DTM via /vsicurl/ — fetched lazily from NRCan S3
#' dem <- fl_dem_aoi(aoi, buffer = 2000)
#' terra::plot(dem, main = "MRDEM-30 over AOI")
#'
#' # LidarBC via stac-dem-bc — sub-10 m where lidar coverage exists
#' bbox_4326 <- sf::st_bbox(sf::st_transform(aoi, 4326))
#' items <- rstac::stac("https://images.a11s.one/") |>
#' rstac::stac_search(
#' collections = "stac-dem-bc",
#' bbox = unname(bbox_4326)
#' ) |>
#' rstac::post_request() |>
#' rstac::items_fetch()
#' if (length(items$features) > 0L) {
#' cog <- paste0("/vsicurl/", items$features[[1]]$assets$image$href)
#' dem_lidar <- fl_dem_aoi(aoi, source = cog, buffer = 100)
#' }
#' }
#'
#' @export
fl_dem_aoi <- function(aoi, source = NULL, buffer = 2000, target_crs = NULL) {
if (!(inherits(aoi, "sf") || inherits(aoi, "sfc"))) {
stop("`aoi` must be an sf or sfc object.", call. = FALSE)
}
stopifnot(is.numeric(buffer), length(buffer) == 1L, buffer >= 0)

if (is.null(source)) {
source <- paste0(
"/vsicurl/https://canelevation-dem.s3.ca-central-1.amazonaws.com/",
"mrdem-30/mrdem-30-dtm.tif"
)
}
stopifnot(is.character(source), length(source) == 1L)

aoi_crs <- sf::st_crs(aoi)
target_crs <- if (is.null(target_crs)) aoi_crs else sf::st_crs(target_crs)

# GEOS can't buffer XYZM/XYZ — fwapg streams carry route-measure M values
aoi_buf <- sf::st_buffer(sf::st_zm(aoi), dist = buffer)

r <- terra::rast(source)
r_crs <- sf::st_crs(terra::crs(r))

aoi_buf_in_r <- if (r_crs == aoi_crs) aoi_buf else sf::st_transform(aoi_buf, r_crs)
r_clip <- terra::crop(r, terra::vect(aoi_buf_in_r), snap = "out")

if (r_crs != target_crs) {
r_clip <- terra::project(r_clip, target_crs$wkt)
}

r_clip
}
2 changes: 1 addition & 1 deletion R/flooded-package.R
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
#' @keywords internal
#' @import terra
#' @importFrom sf st_crs st_transform st_read
#' @importFrom sf st_crs st_transform st_read st_buffer st_zm
"_PACKAGE"

## usethis namespace: start
Expand Down
50 changes: 50 additions & 0 deletions data-raw/bib_regenerate.R
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#!/usr/bin/env Rscript
#
# data-raw/regenerate_bib.R
#
# Regenerate vignettes/references.bib from the union of [@key] markers in
# vignettes/pars-floodplain.Rmd and the citation_keys columns in
# `flooded::fl_params()` and `flooded::fl_scenarios()`. Pulls source
# records from Zotero via Better BibTeX (rbbt -> BBT).
#
# Run after adding/removing [@key] markers in the vignette or after
# changing flood_params.csv / flood_scenarios.csv:
# Rscript data-raw/regenerate_bib.R
#
# Prerequisites:
# - Zotero desktop running with BBT plugin enabled
# - All keys must resolve to items in the Zotero library
#
# CI does not run this script — vignettes/references.bib is committed
# and pkgdown reads the static file. Re-run + commit whenever cites
# change.

stopifnot(requireNamespace("rbbt", quietly = TRUE))
stopifnot(requireNamespace("flooded", quietly = TRUE))

vignette_path <- here::here("vignettes", "pars-floodplain.Rmd")
out_path <- here::here("vignettes", "references.bib")

# Keys cited inline in the vignette
keys_vignette <- rbbt::bbt_detect_citations(
paste(readLines(vignette_path), collapse = "\n")
)
message(" vignette: ", length(keys_vignette), " keys")

# Keys referenced from the package's parameter / scenario tables
keys_tables <- unique(unlist(strsplit(
c(flooded::fl_params()$citation_keys,
flooded::fl_scenarios()$citation_keys),
";"
)))
keys_tables <- keys_tables[!is.na(keys_tables) & nzchar(keys_tables)]
message(" fl_params + fl_scenarios: ", length(keys_tables), " keys")

all_keys <- sort(unique(c(keys_vignette, keys_tables)))
message("\nUnion: ", length(all_keys), " unique keys")

bib_text <- rbbt::bbt_bib(all_keys, .action = rbbt::bbt_return)
writeLines(bib_text, out_path)

n_entries <- length(grep("^@", readLines(out_path)))
message("Wrote ", n_entries, " entries to ", out_path)
Loading
Loading