drift 0.8.0
-
dft_transition_attribute()tags change patches fromdft_transition_vectors()with columns from any overlay polygon layer — fire perimeters, cutblocks, roads, tenures — so a driver can separate mapped transitions by cause without hand-rolling spatial joins (#42). Deliberately generic: drift carries no BC/domain knowledge; the caller supplies the overlay, the columns to carry (cols), and optionally a numeric temporal filter (time_col+time_interval, bounds inclusive) that keeps only overlay features whose time falls within the transition interval — a 2022 fire attributes a 2017→2023 loss, a 2012 fire does not. Two assignment modes for a patch that straddles multiple overlay features:match_mode = "all"(left join, one row per match) or"largest"(one row per patch by greatest overlap area). Largest-overlap assignment is intersection-based, so combining it with a custompredicateis an error rather than a silent mis-attribution; the overlay is reprojected to the patch CRS and run throughst_make_valid()automatically, since real-world disturbance perimeters routinely fail validity checks.
drift 0.7.0
-
dft_stac_cube()gainstile_size(defaultNULL), the continuous-path twin ofdft_stac_fetch()’stile_size(#36): an opt-in that bounds the STAC read to the AOI footprint (#38). By default one gdalcubes cube is streamed over the whole AOI bounding box, so the COG streaming — the dominant cost (~10-30 min for a multi-year monthly Sentinel-2 fetch) — scales with the bbox, not the AOI; for a thin, diagonal floodplain corridor the bbox is largely empty. Whentile_size(CRS units — metres for the default UTM CRS) is set, the bbox is split into ares-aligned grid and only tiles that intersect the AOI polygon are streamed — each carrying the full SCL mask, spectral index, and 2022 baseline-offset split — then mosaicked withterra::merge(), so a corridor reads close to its footprint. This is thefilter_geom-independent path (the polygon clip that would do this in-cube segfaults on the pinned gdalcubes build). The cube always caches a.tifand a tiled read keys distinctly, so untiled caches are untouched andtile_size = NULLis byte-for-byte the previous behavior. Because the cube resamples with bilinear, a tiled cube faithfully reproduces the untiled cube (bilinear-aligned correlation ~0.997, per-layer means within ~1e-3, no tile seams) but lands on a bbox-anchored grid that is sub-pixel-offset from — not pixel-identical to — the untiled cube; the offset is immaterial to the per-pixeldft_rast_break()/dft_rast_trend()reducers. Withtile_sizeset,clip = FALSEreturns the AOI-intersecting tile union (withNAwhere empty tiles were skipped), not a gap-free bounding box.
drift 0.6.0
-
dft_stac_fetch()gainstile_size(defaultNULL), an opt-in that bounds the STAC download to the AOI footprint (#36). By default a single cube is streamed over the whole AOI bounding box, so for a thin, diagonal floodplain corridor (measured ~10% of the bbox inside the polygon) roughly 10× more pixels are downloaded than the AOI needs. Whentile_size(CRS units — metres for the default UTM CRS) is set, the bbox is split into ares-aligned grid and only tiles that intersect the AOI polygon are streamed, then mosaicked withterra::merge()— so a corridor fetches close to its footprint. Smaller tiles waste less bbox but cost more per-tile round trips (no auto-tuning). This is thefilter_geom-independent path (the polygon-clip that would do this in the cube pipeline segfaults on the pinned gdalcubes build). Tiled fetches cache a terra GeoTIFF (.tif) rather than a gdalcubes NetCDF (.nc) and key distinctly, so existing untiled caches are untouched;tile_size = NULLis byte-for-byte the previous behavior. The same read residual on the continuousdft_stac_cube()path is tracked as #38.
drift 0.5.0
-
dft_stac_cube()gainsclip(defaultTRUE), restoring AOI-polygon-tight output (#32). The assembled index stack is masked to the AOI polygon withterra::mask()— client-side, becausegdalcubes::filter_geom()segfaults / returns an all-NA cube on the pinned build — so cells outside the polygon areNAon every layer. The reduced raster fromdft_rast_break()/dft_rast_trend()is now polygon-tight with no caller-side mask, and those reducers skip out-of-AOI pixels via their valid-observation gate.clip = FALSEkeeps the full bounding box. This is an output change for callers that relied on the bounding-box extent, and the clip is folded into the cube cache key, so existing cached cubes rebuild once. Note the clip affects the output only — the full bbox of COGs is still streamed either way (the AOI cannot be pushed into the read on the pinned gdalcubes build).
drift 0.4.0
- Categorical land-cover change detection no longer exhausts memory on large-floodplain AOIs (#34, #28).
dft_rast_transition()was rewritten to stream entirely throughterra— transitions are encoded and filtered with raster arithmetic,terra::subst(),patches(), and a singleterra::freq(), with noterra::values()pull and no full-grid R vectors — so peak memory scales with the number of distinct transitions and patches, not the grid size (producer-only peak at 16M cells dropped from 2.66 GB to 1.63 GB). Output is byte-identical to the previous version, verified by a golden snapshot across the full parameter matrix. -
dft_transition_vectors()gainschanges_only(defaultFALSE): whenTRUE, stable (from == to) transitions are dropped at the raster level before polygonizing, soterra::as.polygons()only builds geometry for actual change patches. On a fragmented floodplain — where the stable mosaic is most of the grid and polygonization dominates memory — this roughly halves peak use (a 9M-cell, 415k-patch benchmark went from 3.83 GB to 1.71 GB). The result equals the default output filtered to change patches. Whenpatch_area_minis set, small patches are also dropped before polygonizing, with identical output. -
patch_idindft_transition_vectors()is numbered over the surviving patches when filtering drops any, and an empty result now carries the zone column so per-zone results bind cleanly.
drift 0.3.0
- Continuous index-trajectory change detection for floodplain reaches (#30). A new fetch-and-reduce pipeline complements the categorical
dft_stac_fetch()path.dft_stac_cube()builds a cloud-masked monthly spectral-index stack from Sentinel-2 (via a new"sentinel-2-l2a"source);dft_rast_break()reduces it per pixel withbfast::bfastmonitor()into a two-band raster of abrupt break date and magnitude; anddft_rast_trend()reduces it to a per-pixel gradual trend — a robust Theil-Sen slope (index change per year) with Mann-Kendall significance — for degradation/recovery monitoring the annual labels cannot show. Together they let a continuous trajectory validate categorical land-cover transitions (confirming which mapped losses carry a real spectral decline) and detect gradual change. See the “Trajectories as a Check on Land-Cover Change” vignette. -
dft_index_expr()anddft_index_table()add a table-driven spectral-index registry (NDVI, kNDVI, NDMI) whose formulas are written over band roles, so one index resolves against any reflectance source; the reflectance scale/offset is folded into each expression. - Sentinel-2 handling is correctness-focused:
dft_stac_cube()masks cloud/shadow/cirrus/snow, restricts to caller-chosen calendarmonths(e.g. the growing season) to sharpen the signal and cut scenes streamed, and — because the +1000 DN reflectance offset only applies from processing baseline 04.00 (2022-01-25) — splits items at that boundary and corrects each side, so a multi-year series carries no artificial index step at 2022. -
dft_stac_config()gains a role-based schema for reflectance cube sources (band roles, mask classes, scale/offset, offset boundary), leaving the categoricalio-lulc/esa-worldcoversources unchanged.bfastadded to Suggests. - Known limitation tracked as a follow-up: the cube spans the AOI bounding box rather than the polygon (a gdalcubes
filter_geomlimitation, #32); labelling breaks with from/to land-cover classes is #31.
drift 0.2.4
-
dft_transition_vectors()no longer exhausts memory on large-extent rasters (#27). The per-class loop allocated full-grid vectors per class and per patch — ncell × n_patches churn that OOM-killed a 102.6M-cell, 56-class floodplain. Replaced by a singleterra::patches(values = TRUE)pass plus a sparse patch-to-label map. Output is identical (verified patch-by-patch against the old implementation); onlypatch_idnumbering / row order changes, to raster scan order. Benchmark at 24M cells: 1.9 s for a 4,799-patch raster; the old code took 122 s on a milder 1,232-patch raster of the same size. - terra dependency floored at
>= 1.8-10: earlier versions had an edge-wraparound bug inpatches(values = TRUE)that silently merged patches touching opposite raster edges.
drift 0.2.3
- Fix silent cross-AOI cache collision in
dft_stac_fetch()(#25). Cache files were keyed by source + year only, so fetching a second AOI with the same source/year silently returned the first AOI’s raster masked to the second AOI’s extent. Cache filenames now include a hash of the AOI geometry and all fetch-affecting parameters (res,crs,dt,aggregation,resampling,stac_url,collection,asset). Existing caches re-fetch on first use after upgrading;dft_cache_clear()reclaims the orphaned old-format files. -
force = TRUEnow overwrites the cached file instead of erroring with “File already exists” (#25).
drift 0.2.2
- Startup quote pool expanded to 113. Adds 52 domain-expert quotes from 11 voices across floodplain/river process (David Montgomery, Ellen Wohl), Indigenous stewardship (Robin Wall Kimmerer, Kyle Whyte, Nancy Turner, Jeannette Armstrong), ecosystem valuation (Kai Chan), Canadian public voices (David Suzuki, Wade Davis), and legacy conservation (Aldo Leopold, Wendell Berry).
- Tim Beechie was on the target list but yielded zero — no public interview / podcast / documentary footprint. Process-paper voice only.
- Same rigor as v0.2.1: parallel research agents, independent fact-check pass (3 dropped for misattribution or text drift, 2 fixed from fact-check flags).
drift 0.2.1
- Startup quote ritual:
library(drift)prints a random fact-checked quote from 15 hip-hop artists on attach. Italic quote, grey attribution, clickable bluesourcehyperlink to the primary-source interview. Suppress viaoptions(drift.quote_show_source = FALSE). - Curated via the soul
/quotes-enableskill using multi-agent research + independent primary-source fact-check. 61 entries. Seedata-raw/quotes_build.Rfor full provenance. -
cliadded to Imports for OSC 8 hyperlinks and styling inR/zzz.R.
drift 0.2.0
-
dft_rast_transition()— addpatch_area_minparameter to filter small connected patches of changed pixels; return$removedraster for visual QA of filtered patches; addfrom_class/to_classfilters -
dft_transition_vectors()— vectorize transition raster into sf polygons with per-patch area, transition labels, and optional zone attribution -
dft_rast_consensus()— per-pixel mode across classified rasters for temporal noise filtering; optional confidence layer -
dft_map_interactive()— newtransitionparameter overlays transition layers as checkboxes; Google Satellite and Esri Satellite basemaps; custom tile URL support -
dft_check_crs()— internal helper that errors on geographic CRS input; wired intodft_rast_transition()anddft_rast_summarize() - Vignette: transition detection, tree loss filtering, patch area filtering with comparison table, interactive map with transition overlays
drift 0.1.0
Initial public release.
-
dft_stac_fetch()— fetch classified rasters from STAC catalogs via gdalcubes -
dft_rast_classify()— apply class labels, colors, and optional remap to SpatRasters -
dft_rast_summarize()— compute area by class and year with unit conversion -
dft_map_interactive()— interactive leaflet map with layer toggle, legend, fullscreen, and titiler COG support -
dft_class_table()— shipped class tables for IO LULC and ESA WorldCover -
dft_stac_config()— STAC endpoint registry - Cache management:
dft_cache_path(),dft_cache_info(),dft_cache_clear() - Vignette: Neexdzii Kwa floodplain land cover change 2017-2023
