From c87de4e6d380cbd5ccc3656bf2c0d7be31469cff Mon Sep 17 00:00:00 2001 From: almac2022 Date: Mon, 28 Sep 2026 07:19:27 -0700 Subject: [PATCH 1/5] Install gdalcubes from GitHub now that CRAN archived it (#80) gdalcubes was archived on CRAN on 2026-09-16, which broke the pkgdown workflow's dependency resolve and user installs of the STAC functions. Upstream master (0.7.5) carries the filter_geom fix from our fork, so Remotes points at appelmar/gdalcubes with no suffix. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PRhUJsuKABLfpBGktPoiBN --- DESCRIPTION | 1 + README.md | 8 ++++++++ inst/notes/gdalcubes-pc-gotchas.md | 6 ++++++ 3 files changed, 15 insertions(+) diff --git a/DESCRIPTION b/DESCRIPTION index 47dfad2..ad7c9bf 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -53,6 +53,7 @@ Suggests: tidyterra, tmap Remotes: + appelmar/gdalcubes, bhaskarvk/leaflet.extras, NewGraphEnvironment/flooded, NewGraphEnvironment/gq diff --git a/README.md b/README.md index b0b053d..0e44e6f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/inst/notes/gdalcubes-pc-gotchas.md b/inst/notes/gdalcubes-pc-gotchas.md index 4500666..9e4cdf3 100644 --- a/inst/notes/gdalcubes-pc-gotchas.md +++ b/inst/notes/gdalcubes-pc-gotchas.md @@ -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 From 98df81bf5a0d9a45049f81890c266eea13d4c01c Mon Sep 17 00:00:00 2001 From: almac2022 Date: Mon, 28 Sep 2026 07:32:05 -0700 Subject: [PATCH 2/5] Point the network e2e cache assertions at the #48 scheme dir (#80) Since #48 the cache lives under /v2/, but five opt-in network assertions still listed / and so failed on every run. Found verifying gdalcubes 0.7.5; every data assertion passed. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PRhUJsuKABLfpBGktPoiBN --- tests/testthat/test-dft_stac_cube.R | 6 +++--- tests/testthat/test-dft_stac_fetch.R | 6 +++--- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/tests/testthat/test-dft_stac_cube.R b/tests/testthat/test-dft_stac_cube.R index cb871fc..e535f7b 100644 --- a/tests/testthat/test-dft_stac_cube.R +++ b/tests/testthat/test-dft_stac_cube.R @@ -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_.tif under the source dir) - expect_length(list.files(file.path(cache, "sentinel-2-l2a"), + # the call cached exactly one cube_.tif under the source dir (#48 scheme) + expect_length(list.files(drift:::cache_scheme_dir(cache, "sentinel-2-l2a"), pattern = "^cube_.*\\.tif$"), 1) }) @@ -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_.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 diff --git a/tests/testthat/test-dft_stac_fetch.R b/tests/testthat/test-dft_stac_fetch.R index 4f202a4..85e5d80 100644 --- a/tests/testthat/test-dft_stac_fetch.R +++ b/tests/testthat/test-dft_stac_fetch.R @@ -616,7 +616,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 @@ -629,9 +629,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 From 431577f011e26da3464eb479217cc5747cdc0754 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Mon, 28 Sep 2026 07:50:23 -0700 Subject: [PATCH 3/5] Name the GitHub install when gdalcubes is missing (#80) rlang::check_installed() offered a bare-name install, which now means CRAN and fails. The three gdalcubes guards share one helper whose error gives pak::pak("appelmar/gdalcubes"). Found by code-check round 1. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PRhUJsuKABLfpBGktPoiBN --- NEWS.md | 4 ++++ R/dft_index_expr.R | 2 +- R/dft_stac_cube.R | 2 +- R/dft_stac_fetch.R | 22 +++++++++++++++++++++- tests/testthat/test-dft_stac_fetch.R | 16 ++++++++++++++++ 5 files changed, 43 insertions(+), 3 deletions(-) diff --git a/NEWS.md b/NEWS.md index b3c09d7..87277b2 100644 --- a/NEWS.md +++ b/NEWS.md @@ -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 (1003 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. diff --git a/R/dft_index_expr.R b/R/dft_index_expr.R index f117fca..51ecd16 100644 --- a/R/dft_index_expr.R +++ b/R/dft_index_expr.R @@ -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 diff --git a/R/dft_stac_cube.R b/R/dft_stac_cube.R index 537a306..bed2f55 100644 --- a/R/dft_stac_cube.R +++ b/R/dft_stac_cube.R @@ -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 diff --git a/R/dft_stac_fetch.R b/R/dft_stac_fetch.R index 7039fbf..10584ee 100644 --- a/R/dft_stac_fetch.R +++ b/R/dft_stac_fetch.R @@ -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 @@ -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) { diff --git a/tests/testthat/test-dft_stac_fetch.R b/tests/testthat/test-dft_stac_fetch.R index 85e5d80..0454a6f 100644 --- a/tests/testthat/test-dft_stac_fetch.R +++ b/tests/testthat/test-dft_stac_fetch.R @@ -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") From ed8c7e0886239db58e5950490400fe163f2450e9 Mon Sep 17 00:00:00 2001 From: almac2022 Date: Mon, 28 Sep 2026 07:55:35 -0700 Subject: [PATCH 4/5] Correct the test count in the 0.17.1 NEWS entry (#80) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PRhUJsuKABLfpBGktPoiBN --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 87277b2..27a4a45 100644 --- a/NEWS.md +++ b/NEWS.md @@ -1,6 +1,6 @@ # 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 (1003 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()`. +- **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 From d1ee76639ee16d681d29e79b4f10c3fd1cbb165f Mon Sep 17 00:00:00 2001 From: almac2022 Date: Mon, 28 Sep 2026 07:55:35 -0700 Subject: [PATCH 5/5] Release v0.17.1 (#80) Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01PRhUJsuKABLfpBGktPoiBN --- DESCRIPTION | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/DESCRIPTION b/DESCRIPTION index ad7c9bf..35718c9 100644 --- a/DESCRIPTION +++ b/DESCRIPTION @@ -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"),