Skip to content

Tile dft_stac_fetch to bound download over sparse-floodplain bounding boxes (#36)#39

Open
NewGraphEnvironment wants to merge 7 commits into
mainfrom
36-tile-dft-stac-fetch-to-bound-download-over-spars
Open

Tile dft_stac_fetch to bound download over sparse-floodplain bounding boxes (#36)#39
NewGraphEnvironment wants to merge 7 commits into
mainfrom
36-tile-dft-stac-fetch-to-bound-download-over-spars

Conversation

@NewGraphEnvironment

Copy link
Copy Markdown
Owner

Summary

  • Adds an opt-in tile_size to dft_stac_fetch() that bounds the STAC download to the AOI footprint. By default one gdalcubes cube is streamed over the whole AOI bounding box, so a thin, diagonal floodplain corridor downloads ~10× more pixels than the polygon needs (measured 10.1% of bbox cells inside a real io-lulc floodplain). When tile_size (CRS units) is set, the bbox is split into a res-aligned grid, only tiles intersecting the AOI polygon are streamed, and the results are mosaicked with terra::merge().
  • This is the filter_geom-independent path — the polygon clip that would do this in the cube pipeline segfaults on the pinned gdalcubes build (dft_stac_cube: restore AOI-polygon clip (filter_geom segfault workaround) #32). Tiled fetches cache a terra GeoTIFF (.tif) rather than a gdalcubes NetCDF (.nc) and key distinctly, so existing untiled caches are untouched; tile_size = NULL (default) is byte-for-byte the previous behavior.
  • Delivered tests-first across five atomic phases: tile_grid() + tile_size_check() (offline), a conditional cache-key append with a frozen golden-hash regression, extraction of the shared fetch_extent_to() primitive, the tiled branch (extension split + GDAL /vsicurl config scoped to the tiled path), and release docs.

Related Issues

Test plan

  • devtools::test() — 352 pass / 5 skip / 0 fail (network + bfast tests opt-in)
  • devtools::check() — 0 errors / 0 warnings / 0 notes; vignettes rebuild
  • lintr clean; roxygen regenerated
  • Offline merge oracle (byte-for-byte tile reassembly, single-tile, .tif round-trip); cache-key golden regression preserves the legacy untiled key
  • Opt-in network e2e (DRIFT_TEST_NETWORK=true): tiled == untiled over the in-AOI overlap; .nc/.tif extension routing — runs only with network + Planetary Computer access

Notes

  • terra's NetCDF write is fragile on the pinned stack, so the tiled mosaic is a GeoTIFF (mirrors dft_stac_cube()); the cache key keys the two formats apart.
  • Tiles are snapped to a multiple of res and anchored at the bbox lower-left so per-tile grids are co-lattice — otherwise the merge seams. Boundary tiles are left un-trimmed (overhang masked away).
  • tile_size is normalized once so the path gate and the cache key derive from the same snapped scalar (no gate/key desync).

🤖 Generated with Claude Code

NewGraphEnvironment and others added 7 commits July 9, 2026 15:26
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Phase 1 of #36: the offline, network-free core of bounding dft_stac_fetch()'s
download to the AOI footprint. tile_size_check() validates a tile_size and snaps
it to a multiple of res (so tile pixel grids align for a seam-free merge);
tile_grid() splits the AOI bbox into a res-aligned grid anchored at the lower-
left and keeps only tiles intersecting the AOI polygon — a thin corridor fetches
near its footprint, not its full bbox. Boundary tiles are left un-trimmed (the
overhang is masked away; trimming would break res-alignment). Tests written
first (validation, snapping, intersecting-subset, alignment, single-tile,
degenerate-AOI); 40 pass / 1 skip; lint clean; code-check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Phase 2 of #36: stac_cache_key() gains an optional tile_size, appended to the
hash only when non-NULL. An untiled fetch (tile_size = NULL) reproduces the
exact pre-tiling 9-element hash, so existing io-lulc caches stay valid on
upgrade; a tiled fetch keys distinctly, so its terra .tif mosaic is never served
as an untiled gdalcubes .nc (or vice versa). A frozen golden-hash regression
guards the legacy-preservation invariant. tile_size arrives already snapped by
the caller (single normalization site — no gate/key desync). 45 pass / 1 skip;
lint + code-check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
#36)

Phase 3 of #36: pull the cube_view + raster_cube + write_ncdf block out of the
per-year loop into fetch_extent_to(). The untiled path now routes through it,
writing straight to the existing <yr>_<key>.nc cache — identical filename,
format, and I/O to before (faithful code-motion; cube_view built identically).
Sharing this primitive is what will guarantee each tile fetches identically to
the corresponding slice of the untiled cube when the tiled branch lands in
Phase 4. 45 pass / 1 skip; lint clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Phase 4 of #36: tiled fetch end-to-end. When tile_size is set, dft_stac_fetch()
splits the AOI bbox into a res-aligned grid, streams only tiles that intersect
the AOI polygon (via the shared fetch_extent_to()), mosaics them with
terra::merge(terra::sprc(...)) (new mosaic_tiles() helper), and caches a terra
GeoTIFF — so a thin corridor fetches near its footprint instead of its full
bounding box. Extension routes on is.null(tile_size) (.nc untiled / .tif tiled),
matching the distinct cache key. tile_size is normalized once (snap to res
multiple) so the path gate and cache key agree; GDAL /vsicurl config (mirroring
dft_stac_cube) is set with an on.exit restore, scoped to the tiled path, since
tiling multiplies per-item COG opens. Default tile_size = NULL is unchanged.

Offline merge oracle (byte-for-byte reassembly, single-tile, .tif round-trip)
and an opt-in DRIFT_TEST_NETWORK e2e (tiled == untiled over the in-AOI overlap,
extension routing). 352 pass / 5 skip; document + lint + code-check clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Phase 5 of #36. Document the download-tiling workaround in the gdalcubes gotchas
note (the filter_geom-independent way to bound the read, cross-referencing the
cube-path residual #38); add the NEWS 0.6.0 entry for tile_size; bump DESCRIPTION
0.5.0 -> 0.6.0. devtools::check() clean: 0 errors / 0 warnings / 0 notes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01DKqnW6Fni3tc44Lo5D5fJz
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Tile dft_stac_fetch to bound download over sparse-floodplain bounding boxes

1 participant