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
3 changes: 2 additions & 1 deletion DESCRIPTION
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
Package: drift
Title: Detecting Riparian and Inland Floodplain Transitions
Version: 0.17.0
Version: 0.17.1
Date: 2026-09-06
Authors@R: c(
person("Allan", "Irvine", , "al@newgraphenvironment.com", role = c("aut", "cre"),
Expand Down Expand Up @@ -53,6 +53,7 @@ Suggests:
tidyterra,
tmap
Remotes:
appelmar/gdalcubes,
bhaskarvk/leaflet.extras,
NewGraphEnvironment/flooded,
NewGraphEnvironment/gq
Expand Down
4 changes: 4 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
# drift 0.17.1

- **gdalcubes installs from GitHub (#80).** gdalcubes was archived on CRAN on 2026-09-16. That broke the pkgdown workflow's dependency resolve, which had failed on every push since 2026-09-26, and it left new users unable to install what `dft_stac_fetch()` and `dft_stac_cube()` need. `appelmar/gdalcubes` is now in `Remotes:` with no suffix, and the README says to install it with `pak::pak("appelmar/gdalcubes")`. So does the prompt `dft_stac_fetch()`, `dft_stac_cube()` and `dft_index_expr()` raise when it is missing. rlang's own install offer means CRAN and would fail. Upstream master (0.7.5) has merged the `filter_geom` segfault fix from `NewGraphEnvironment/gdalcubes@newgraph`, so the fork is no longer needed. On 0.7.5 the offline suite passes (1007 tests), and so does the opt-in network end-to-end for the cube and fetch paths (109 and 165). That run also surfaced five network assertions that had listed the pre-#48 cache path since that change, so they failed on every opt-in run. They now use `cache_scheme_dir()`.

# drift 0.17.0

- **The article gains patch shape and location, and the corridor question comes back negative (#73).** [What a Land-Cover Change Figure Is Made Of](https://newgraphenvironment.github.io/drift/articles/temporal-composition.html) reported *how much* of a two-epoch comparison is temporally unstable and never raised the spatial axis. It now reports both grains. Body prose 731 -> 1469 words against a cap raised 1000 -> 1500; the cap is a variable now, because the literal appeared twice on one line and changing one would have left the message lying.
Expand Down
2 changes: 1 addition & 1 deletion R/dft_index_expr.R
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ dft_index_expr <- function(cube,
roles = NULL,
scale = NULL,
offset = NULL) {
rlang::check_installed("gdalcubes", reason = "to apply an index to a cube")
check_gdalcubes("to apply an index to a cube")
cfg <- dft_stac_config(source)
roles <- roles %||% cfg$roles
scale <- scale %||% cfg$scale %||% 1
Expand Down
2 changes: 1 addition & 1 deletion R/dft_stac_cube.R
Original file line number Diff line number Diff line change
Expand Up @@ -162,7 +162,7 @@ dft_stac_cube <- function(aoi,
cache_dir = NULL,
force = FALSE,
sign_fn = rstac::sign_planetary_computer()) {
rlang::check_installed("gdalcubes", reason = "to fetch STAC cubes")
check_gdalcubes("to fetch STAC cubes")

# gdalcubes worker processes for the read. drift never set this before v0.9.0,
# so every fetch ran single-threaded — not a considered choice, just the
Expand Down
22 changes: 21 additions & 1 deletion R/dft_stac_fetch.R
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,7 @@ dft_stac_fetch <- function(aoi,
cache_dir = NULL,
force = FALSE,
sign_fn = rstac::sign_planetary_computer()) {
rlang::check_installed("gdalcubes", reason = "to fetch STAC rasters")
check_gdalcubes("to fetch STAC rasters")

# Normalize tile_size ONCE so the path gate (is.null) and the cache key derive
# from the same snapped scalar. When tiling, tune GDAL for the many extra
Expand Down Expand Up @@ -863,6 +863,26 @@ cache_hit_ok <- function(cache_file, label) {
}


#' Require gdalcubes, naming where to get it
#'
#' gdalcubes was archived on CRAN on 2026-09-16 (#80). `rlang::check_installed()`
#' offers to install a missing package by bare name, which now means CRAN and
#' fails, and it never reads drift's `Remotes:`. So this aborts with the GitHub
#' install instead, for anyone who meets it before the README.
#' @noRd
check_gdalcubes <- function(reason) {
if (gdalcubes_available()) return(invisible(TRUE))
cli::cli_abort(c(
"The {.pkg gdalcubes} package is required {reason}.",
"i" = "It was archived on CRAN on 2026-09-16. Install it from GitHub:",
" " = "{.code pak::pak(\"appelmar/gdalcubes\")}"
))
}

#' Whether gdalcubes can be loaded (a seam, so tests can simulate its absence)
#' @noRd
gdalcubes_available <- function() requireNamespace("gdalcubes", quietly = TRUE)

#' Auto-detect UTM EPSG code from sf geometry
#' @noRd
auto_utm_epsg <- function(x) {
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,14 @@ Monitoring riparian vegetation loss matters for fish habitat, water quality, and
pak::pak("NewGraphEnvironment/drift")
```

The STAC fetch and cube functions (`dft_stac_fetch()`, `dft_stac_cube()`) also need
[gdalcubes](https://github.com/appelmar/gdalcubes), which was archived on CRAN on
2026-09-16. Install it from GitHub until it is back:

```r
pak::pak("appelmar/gdalcubes")
```

## Quick start

```r
Expand Down
6 changes: 6 additions & 0 deletions inst/notes/gdalcubes-pc-gotchas.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,12 @@ Provenance, because it is now mixed: the #30-era bullets were verified on
(`8bad203`), which is 0.7.4 plus the `filter_geom` segfault fix — with terra
1.9.34. Each bullet says which.

**Where gdalcubes comes from now (#80, 2026-09-28).** gdalcubes was archived on
CRAN on 2026-09-16. drift lists `appelmar/gdalcubes` in `Remotes:` with no suffix,
and upstream master (0.7.5, `ed68331`) has merged the `filter_geom` fix (their
PR #111), so the `NewGraphEnvironment/gdalcubes@newgraph` fork is no longer needed.
Do not install the fork over 0.7.5.

- **`gdalcubes::filter_geom()` is not worth using — now for measured reasons, not
because it crashes (#47).** The original defect was a segfault in the compute
worker (`gc_exec_worker`, `address 0x120`) or, intermittently, a silent all-NA
Expand Down
6 changes: 3 additions & 3 deletions tests/testthat/test-dft_stac_cube.R
Original file line number Diff line number Diff line change
Expand Up @@ -401,8 +401,8 @@ test_that("dft_stac_cube fetches an index stack end-to-end", {
# and coverage is not one surviving pixel. Loose on purpose: cloud masking
# legitimately removes a lot, so this guards the degenerate case, not quality.
expect_lt(mean(is.na(vals[inpoly, , drop = FALSE])), 0.9)
# second call hits the cache (one cube_<key>.tif under the source dir)
expect_length(list.files(file.path(cache, "sentinel-2-l2a"),
# the call cached exactly one cube_<key>.tif under the source dir (#48 scheme)
expect_length(list.files(drift:::cache_scheme_dir(cache, "sentinel-2-l2a"),
pattern = "^cube_.*\\.tif$"), 1)
})

Expand Down Expand Up @@ -441,7 +441,7 @@ test_that("dft_stac_cube tiled read reproduces the untiled cube over the AOI", {
expect_equal(terra::nlyr(tiled), terra::nlyr(untiled)) # same monthly axis
expect_false(anyNA(terra::time(tiled))) # time set per layer
# tiled and untiled each cache one cube_<key>.tif, keyed apart (2 files total)
expect_length(list.files(file.path(cache, "sentinel-2-l2a"),
expect_length(list.files(drift:::cache_scheme_dir(cache, "sentinel-2-l2a"),
pattern = "^cube_.*\\.tif$"), 2)

# the efficiency claim: for this diagonal reach the tiled read streams fewer
Expand Down
22 changes: 19 additions & 3 deletions tests/testthat/test-dft_stac_fetch.R
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,22 @@ test_that("auto_utm_epsg handles prime meridian", {
expect_equal(epsg, "EPSG:32631")
})

test_that("a missing gdalcubes names the GitHub install, not only CRAN", {
# gdalcubes is archived on CRAN (#80), so a bare install offer fails; the
# message has to say where it lives now. The tests below skip whenever
# gdalcubes is installed, so this simulates its absence through the seam.
testthat::local_mocked_bindings(gdalcubes_available = function() FALSE)
expect_error(drift:::check_gdalcubes("to fetch STAC rasters"),
"appelmar/gdalcubes", fixed = TRUE)
expect_error(drift:::check_gdalcubes("to fetch STAC rasters"),
"to fetch STAC rasters", fixed = TRUE)
aoi <- sf::st_read(system.file("extdata", "example_aoi.gpkg", package = "drift"),
quiet = TRUE)
expect_error(dft_stac_fetch(aoi, source = "io-lulc", years = 2020),
"appelmar/gdalcubes", fixed = TRUE)
expect_error(dft_stac_cube(aoi), "appelmar/gdalcubes", fixed = TRUE)
})

test_that("dft_stac_fetch requires gdalcubes", {
skip_if(requireNamespace("gdalcubes", quietly = TRUE),
"gdalcubes is installed, can't test missing-package path")
Expand Down Expand Up @@ -616,7 +632,7 @@ test_that("dft_stac_fetch tiled result matches untiled over the AOI", {
cfg$stac_url, cfg$collection, cfg$asset, tile_size = NULL
)
)
expect_length(list.files(file.path(cache, "io-lulc"),
expect_length(list.files(drift:::cache_scheme_dir(cache, "io-lulc"),
pattern = paste0("^2020_", attr(untiled_list, "cache_key"),
"\\.nc$")), 1)
# small tile_size relative to the AOI bbox → several tiles, most bbox-only
Expand All @@ -629,9 +645,9 @@ test_that("dft_stac_fetch tiled result matches untiled over the AOI", {
expect_s4_class(tiled, "SpatRaster")
expect_equal(terra::nlyr(tiled), 1L)
# extension routing: untiled caches a gdalcubes .nc, tiled a terra .tif
expect_length(list.files(file.path(cache, "io-lulc"),
expect_length(list.files(drift:::cache_scheme_dir(cache, "io-lulc"),
pattern = "^2020_.*\\.nc$"), 1)
expect_length(list.files(file.path(cache, "io-lulc"),
expect_length(list.files(drift:::cache_scheme_dir(cache, "io-lulc"),
pattern = "^2020_.*\\.tif$"), 1)
# tiled == untiled over their common in-AOI cells: tiling changes only which
# bbox pixels are streamed, not the classification. Put the tiled mosaic onto
Expand Down
Loading