You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Derive canopy fuel grid bands directly from high-resolution canopy data — a NAIP or Meta CHM grid, or a point cloud — summarized to a landscape-scale cell.
This is the second of two non-LANDFIRE paths to canopy fuels. The other is #452 (summarize a voxelized inventory profile). They are not interchangeable, and the difference is the whole design question here.
A CHM is a surface, not a volume. It records the top of the canopy and nothing beneath it, so it cannot yield canopy base height or bulk density — both are properties of the vertical profile (Keane §4.3.1, p. 62). Any claim otherwise would be fabricating structure.
This bounds the issue honestly:
CHM -> CH + CC is a real, useful shortcut: no tree detection, no allometry, no voxelization. Fast, and defensible for the two variables it covers.
Point cloud -> CBD/CBH is genuinely different: the returns carry real vertical structure, so a profile could be binned directly without inventing trees. But point clouds are stored as plain LAZ in the domain CRS and the API cannot read them (it's GDAL-free by design), so this needs a worker and is materially more work.
Proposed scope
Recommend splitting, and doing only the first now:
Phase 1 — CHM -> CC + CH (partial canopy grid)
POST /v2/domains/{domain_id}/grids/canopy/chm
{
"source_grid_id": "<NAIP or Meta CHM grid>",
"band": "chm",
"cover_height_threshold": 2.0,
"height_statistic": "p95",
"alignment": {"target": "domain", "resolution": 30.0}
}
CC = fraction of source pixels within the output cell whose height exceeds cover_height_threshold (2 m is the common convention). This is the standard remote-sensing canopy cover and it's exactly what a fine CHM is good for.
CH = a per-cell statistic of the source heights. LANDFIRE CH is nominally "average height of dominant and codominant trees", which has no clean CHM analogue — hence height_statistic as an explicit parameter (p95 / mean / max / mean_top_decile) rather than a silent choice. Needs a decision on the default; p95 is a defensible starting point (max is noise-sensitive, mean is biased low by gaps).
Phase 2 — point cloud -> full profile (separate issue when wanted)
Bin returns into a vertical profile per column, then reuse #452's summarization. Needs a LAZ-reading worker; see the point-cloud storage notes (plain cloud.laz, domain CRS, 1 GiB cap).
On the 10 m / 30 m / 270 m question
Multi-resolution landscapes do not need new machinery. POST /grids/{grid_id}/resample already aggregates onto a target lattice with per-band method_overrides. What's missing is guidance, not code:
cc (%) — average
cbd — average (a density; mass-conserving-ish)
chm — average vs max is a real modeling choice
cbh — average vs min; min is conservative for initiation
Worth deciding whether the resample endpoint's band-type-aware defaults should special-case canopy bands, or whether this is purely a docs matter. Coarsening to 270 m is then just a resample, and 10 m is only meaningful when the source is finer than 30 m — which is precisely what this issue and #452 unlock.
Work (phase 1)
fastfuels-core — cover fraction + height statistic over a window.
API — grids/canopy/chm/ constructor, reusing the existing canopy cc / chm band definitions so the output is indistinguishable from a LANDFIRE canopy grid's equivalent bands.
griddle — handler; a windowed aggregation over the source zarr.
Validation — source grid 2D + completed; band exists with unit m; output resolution must be coarser than the source (a fractional-cover statistic over <1 source pixel per output cell is meaningless) -> 422.
Tests — core unit tests on synthetic CHMs with known cover fractions; griddle integration; API schema/router.
Open questions
Default height_statistic (recommend p95).
Is a two-band canopy grid (cc + chm) acceptable as a first-class resource, or should the endpoint require composition into a four-band grid? (Recommend allowing it — bands are already per-role in the export.)
Summary
Derive canopy fuel grid bands directly from high-resolution canopy data — a NAIP or Meta CHM grid, or a point cloud — summarized to a landscape-scale cell.
This is the second of two non-LANDFIRE paths to canopy fuels. The other is #452 (summarize a voxelized inventory profile). They are not interchangeable, and the difference is the whole design question here.
What each source can actually produce
A CHM is a surface, not a volume. It records the top of the canopy and nothing beneath it, so it cannot yield canopy base height or bulk density — both are properties of the vertical profile (Keane §4.3.1, p. 62). Any claim otherwise would be fabricating structure.
This bounds the issue honestly:
/inventories/tree/chm, exists) -> allometry (exists) -> voxelize (exists) -> Derive canopy fuel grids (cbd/cbh/chm/cc) directly from a tree inventory — reproduce the FuelCalc/LANDFIRE method #452. Every piece but the last already ships.Proposed scope
Recommend splitting, and doing only the first now:
Phase 1 — CHM -> CC + CH (partial canopy grid)
{ "source_grid_id": "<NAIP or Meta CHM grid>", "band": "chm", "cover_height_threshold": 2.0, "height_statistic": "p95", "alignment": {"target": "domain", "resolution": 30.0} }cover_height_threshold(2 m is the common convention). This is the standard remote-sensing canopy cover and it's exactly what a fine CHM is good for.height_statisticas an explicit parameter (p95/mean/max/mean_top_decile) rather than a silent choice. Needs a decision on the default; p95 is a defensible starting point (max is noise-sensitive, mean is biased low by gaps).ccandchmbands — a legitimate partial canopy grid. The landscape export requires all four canopy roles, so users compose: CHM-derivedcc/chm+ Derive canopy fuel grids (cbd/cbh/chm/cc) directly from a tree inventory — reproduce the FuelCalc/LANDFIRE method #452-derivedcbh/cbd, or fall back to LANDFIRE for the crown pair. Roles are per-{grid_id, band}, so mixing sources across bands already works with no export change.Phase 2 — point cloud -> full profile (separate issue when wanted)
Bin returns into a vertical profile per column, then reuse #452's summarization. Needs a LAZ-reading worker; see the point-cloud storage notes (plain
cloud.laz, domain CRS, 1 GiB cap).On the 10 m / 30 m / 270 m question
Multi-resolution landscapes do not need new machinery.
POST /grids/{grid_id}/resamplealready aggregates onto a target lattice with per-bandmethod_overrides. What's missing is guidance, not code:cc(%) —averagecbd—average(a density; mass-conserving-ish)chm—averagevsmaxis a real modeling choicecbh—averagevsmin;minis conservative for initiationWorth deciding whether the resample endpoint's band-type-aware defaults should special-case canopy bands, or whether this is purely a docs matter. Coarsening to 270 m is then just a resample, and 10 m is only meaningful when the source is finer than 30 m — which is precisely what this issue and #452 unlock.
Work (phase 1)
fastfuels-core— cover fraction + height statistic over a window.grids/canopy/chm/constructor, reusing the existing canopycc/chmband definitions so the output is indistinguishable from a LANDFIRE canopy grid's equivalent bands.bandexists with unitm; output resolution must be coarser than the source (a fractional-cover statistic over <1 source pixel per output cell is meaningless) -> 422.Open questions
height_statistic(recommendp95).cc+chm) acceptable as a first-class resource, or should the endpoint require composition into a four-band grid? (Recommend allowing it — bands are already per-role in the export.)/grids/canopy/...family with a discriminated source, or separate constructors? (Recommend separate, mirroring/meta,/naip,/landfire.)Related