About grids
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
A grid is FastFuels’ continuous picture of fuel and terrain: a georeferenced lattice of cells, each holding one or more values, cut to a domain’s extent and expressed in its CRS. Where an inventory names fuel elements one by one and a layerset describes them statistically, a grid fills space — every cell in the lattice carries a value, defined everywhere the grid covers. It is what the v2 pipeline ultimately produces: the rasterized, voxelized form that physics-based fire models actually read, and the common shape that surface fuels, topography, canopy structure, remote-sensing rasters, and voxelized tree crowns are all expressed in.
This page is about the idea of a grid: what a cell carries, why FastFuels reduces every source to one lattice, how a 2D surface raster differs from a 3D canopy voxel grid, why alignment to the domain is what makes grids composable, and how a tree inventory becomes 3D fuel. It is not a walk-through of any request — the grid how-to guides cover the procedures — and it does not re-teach the rule syntax for masking a grid, which has its own page. Here we stay with the concepts those assume.
Why grids exist
Section titled “Why grids exist”Physics-based fire models — QUIC-Fire, FIRETEC, FDS — consume fuel as a regular lattice of cells: a value at every cell of a fixed 2D or 3D grid, each cell carrying a physical fuel property (a bulk density, a moisture, a surface-area-to-volume ratio) in units the model’s equations can use. Vector polygons, scattered point clouds, and bare fuel-model codes must be resolved onto that lattice first. The data FastFuels starts from is more varied. Some of it is already gridded, but in clashing projections, resolutions, and origins — a 30 m LANDFIRE raster, a sub-meter canopy height model. Some of it is not gridded at all — lidar point clouds, scattered field plots, a vector road network, a list of trees. A grid is the common form all of it is brought onto, reprojected and aligned to one shared lattice. It does four things at once:
- It gives every source a common spatial frame — the domain’s CRS, origin, and cell size — so a surface-fuel grid and a canopy grid describe the same ground in the same units.
- It puts fuel in a model-ready format — a regular lattice of named, typed, unit-bearing values a model can consume without interpretation.
- It handles every source uniformly — once something is a grid, it is read, aligned, masked, resampled, and exported the same way, whether it came from LANDFIRE or your own GeoTIFF.
- It carries reproducible lineage — each grid stores the recipe that built it and a checksum of its contents, so a rebuild is deterministic and a downstream consumer can tell when its source has drifted.
That last property is why a grid is modeled as a persistent resource rather than a bare array of cells: it records the source and parameters that produced its values, so any grid can be traced to its origin and rebuilt from it.
Bands: what a grid carries
Section titled “Bands: what a grid carries”A grid can carry more than one value per cell. It does so through bands —
named layers, each a physical quantity defined over the same lattice. A
topography grid has elevation, slope, and aspect. A LANDFIRE canopy grid
has chm, cbd, cbh, and cc. A band is the unit you read, mask, and
export. A grid is a stack of bands that share one footprint.
Four fields on each band control how its values are interpreted:
key— a dot-notation name that says what the band is:elevation,fuel_load.1hr,bulk_density.foliage.live. The namespacing (<quantity>.<size-or-component>[.<state>]) groups related bands — the five surface timelag loads runfuel_load.1hrthroughfuel_load.live_woody— and the key is what you name when fetching data or writing a modification.type—continuousorcategorical. The type determines how the value behaves when the grid is resampled. A continuous band (elevation, a bulk density) is a measured quantity that can be averaged and interpolated — so when it is resampled, neighboring cells are blended. A categorical band (thefbfmfuel-model codes, atree_id) is an identifier where the average of code 102 and code 165 is meaningless — so it is resampled by nearest-neighbor, never blended. The type protects the data from being silently corrupted by the wrong arithmetic.unit— the physical unit, in UDUNITS-2 form (kg/m**3,1/m,%,m). Categorical identifier bands have none. The unit travels with the data so a consumer never has to guess whether a load is in kg/m² or tons/acre.nodata— the value that marks “no data here” (a sentinel like-9999, ornullwhen missing cells are float NaN). Because a lattice has a cell at every position, a grid needs an explicit way to mark a particular cell as empty, since it cannot simply omit one.
Beyond those four, a band also carries a human-readable name and description
used only for display. They label the band but do not change how its data is
read. A grid advertises its full band list in its metadata, so a consumer can
discover what a grid offers — which quantities, in which units, of which type —
without reading a single cell of data:
{ "key": "fuel_load.1hr", // what the band is (dot-notation) "name": "1-hour Fuel Load", // human-readable label, for display "description": "…", // longer prose description, for display "type": "continuous", // continuous → interpolate; categorical → nearest only "unit": "kg/m**2", // UDUNITS-2; null for categorical / identifier bands "nodata": null // explicit empty-cell marker (null = float NaN)}2D and 3D grids
Section titled “2D and 3D grids”This is the distinction the rest of the design turns on. A grid is either a 2D surface raster or a 3D voxel grid. Which one a quantity is carried in depends on what the model needs from it and what the source can provide — the same property can appear both ways. Canopy bulk density, for instance, is a single value per pixel in a 2D LANDFIRE raster but a full vertical profile of voxels in a voxelized inventory.
| 2D surface grid | 3D voxel grid | |
|---|---|---|
| Georeference | Georeference | Georeference3D |
| Cell shape | (y, x) — a pixel | (z, y, x) — a voxel |
| Adds | — | z_resolution, z_origin (vertical cell size + base) |
| Holds | surface fuel models, fuel loads, topography | canopy bulk density, moisture, SAVR, tree_id, volume_fraction |
| Drives | surface fire spread | crown fire and in-canopy wind |
A 2D grid is a flat raster: one value per (y, x) column of ground, north-up,
exactly like a GeoTIFF. Elevation, a Scott & Burgan fuel-model code, a surface
fuel load — these are properties of a place, fully described by where you are
on the map. A 3D grid adds a vertical axis: the same horizontal lattice, stacked
into z layers of voxels, each z_resolution meters tall, starting at
z_origin. Now a value can say how much foliage sits between 4 and 5 meters
above the ground at this spot — a property of a place and a height.
Why surface fuels are 2D and canopy structure is 3D
Section titled “Why surface fuels are 2D and canopy structure is 3D”The reason the canopy needs a third dimension — and the surface usually does not — is what physics-based models do with the fuel. The quasi-empirical surface-spread models behind most fire software treat fuel as a flat, homogeneous bed and predict a rate of spread across it, and a 2D surface grid suits that representation. The canopy, by contrast, shapes a fire through its three-dimensional structure — the spatial arrangement of its foliage, which a flat measure of total mass cannot capture.
A stand of trees is a porous obstacle in the wind. It absorbs momentum from the air moving through it — fast above the canopy, slowed and stirred within — and the shear at the canopy top spins up intermittent gusts: high-momentum air sweeping down into the stand, slower air venting back up. Those gusts carry heat into unburned fuel and make fire spread through a forest bursty rather than steady. How strong these effects are depends on where the foliage sits — its vertical density and the gaps between crowns. The canopy is both fuel and the structure that governs the wind and microclimate a fire burns in. An averaged fuel-model class — a single label standing in for a whole stand — discards this vertical arrangement, which controls the in-stand wind and the microclimate beneath it. The gaps between crowns matter as much as the crowns themselves, because they determine where wind penetrates the stand and where a fire slows or stops, while the crowns determine where it carries.
A voxel grid preserves that structure. A vertical column of
bulk_density.foliage.live voxels records the foliage-density profile that the
wind and fire respond to, with empty voxels marking the gaps between crowns. Two
of the structural controls on crown fire live in this vertical profile: how low
the canopy base sits governs whether a surface fire can reach the crowns
(crown-fire initiation), and how dense the canopy is governs whether a crown
fire can carry itself tree to tree (crown-fire spread). Neither can be read
off a 2D map of total fuel, which is why a physics-based model requires the
canopy as a 3D voxel grid rather than a surface raster.
The cost of the third dimension
Section titled “The cost of the third dimension”The third dimension is not free. A 2D grid has one cell per column. A 3D grid has as many cells per column as the canopy is voxels tall — a 40 m canopy at 1 m vertical resolution is forty layers, and every band multiplies that. The vertical axis is also where most cells are empty (a canopy is mostly air), which is why 3D grids are read in a sparse encoding — only the occupied voxels are sent — while dense 2D rasters are read whole. The trade-off is the one the domain’s resolution choice makes concrete: finer voxels resolve more structure at a multiplying cost in compute and storage. For that reason the canopy is voxelized, where its vertical structure matters to the model, while the surface and terrain remain 2D rasters, where it does not.
Every source produces a grid
Section titled “Every source produces a grid”Every way of producing a grid yields the same kind of resource. A grid fetched from LANDFIRE, a DEM from 3DEP, a canopy height model from Meta, your own uploaded GeoTIFF, a tree inventory voxelized into canopy fuel, a categorical grid run through a lookup table — all of them are grids, with bands, a georeference, and a lattice, read and aligned and exported by the same machinery. Only the source differs. The resulting resource always has the same structure and is handled in the same way.
What a source records is its recipe: a source object naming where the grid
came from and the parameters that built it, so the grid can be rebuilt
deterministically and its provenance stays legible. Some sources also fold
metadata back onto that record after processing — a CHM grid from Meta stores
the tile dates and the dataset’s required attribution, so the licensing and
vintage are stored alongside the data.
The sources fall into a few families:
| Family | Sources | Produces |
|---|---|---|
| Fetch a national product | LANDFIRE (FBFM40, FCCS, canopy, topography), 3DEP (topography), Meta / NAIP (CHM), TreeMap (PIM) | bands pulled from national raster datasets |
| Upload | GeoTIFF, netCDF | a grid from your own raster (2D or 3D) |
| Derive from a grid | lookup (categorical → continuous via a table), resample (re-grid / reproject), rasterize (a layerset feature → fuel bands) | a new grid computed from an existing one |
| Derive from an inventory | voxelize (a tree inventory → 3D canopy fuel) | a 3D voxel grid |
| Constant | uniform (user-supplied values, at a chosen resolution) | a grid filled with constants |
Two distinctions cut across that table. The first is async versus immediate.
Anything that fetches from an external service or processes a raster is
asynchronous — the create call returns a pending grid and a background job
fills it in, which is why those grids have no georeference until they complete.
A few are immediate in concept — a uniform grid is just constants, a lookup
is a table join — even though they run through the same job machinery. The
second is the two-step shape of uploads: creating an upload grid hands back a
signed URL you PUT your file to, and the server processes it once it arrives,
rather than taking the bytes inline.
One naming detail is important here: the categorical fuel-model band is fbfm
(the Scott & Burgan code), while fbfm40 is the product that fetches it.
Fetching produces a categorical fbfm grid. Turning those codes into the
continuous fuel_load.*, savr.*, and fuel_depth bands a model needs is a
separate lookup against the SB40 table, demonstrated in the
surface-fuel how-to.
The codes cannot be skipped because an abstraction like FBFM40 is not a measured
loading: it is a category tuned to reproduce observed fire behavior, and the
lookup is where it becomes a set of physical values.
A grid is a container, not a fuel model
Section titled “A grid is a container, not a fuel model”The grid resource imposes very little of its own. It is a typed lattice of values, and the source decides the resolution, the dimensionality, and what physical quantity each band holds. Three grids show the range:
- a 0.6 m canopy height model — a 2D raster of vegetation-top height, at roughly the resolution of the imagery it was derived from;
- a 30 m canopy bulk density raster — a 2D LANDFIRE layer carrying one stand-scale value per pixel;
- a 1 m voxel canopy — a 3D grid that resolves bulk density in 1 m cells through the height of the canopy.
All three are grids, and all three describe the canopy, yet they differ in resolution, in dimension, and in purpose. The canopy height model is a measurement of structure — an input you derive fuel from, not fuel itself. The 30 m raster is a coarse, stand-scale summary of canopy fuel. The voxel grid is a model-ready 3D field. A grid records none of this distinction: it does not know whether its numbers are a measurement or a model input, whether 30 m is adequate for your question or far too coarse, or whether its source predates the disturbance you are trying to capture.
This flexibility has a consequence for how grids are used. The API will fetch, resample, mask, combine, and export any of these grids, but it does not evaluate whether the result is a physically meaningful fuel model. The resource enforces structural validity — a well-formed lattice, consistent bands, a known CRS — not fitness for purpose. Whether a grid’s resolution matches the phenomenon being modeled, whether a source’s accuracy and vintage suit the landscape, and whether a set of layers forms a complete fuelbed are modeling decisions made when the grids are chosen and assembled, not properties the resource validates.
Resolution, alignment, and snapping
Section titled “Resolution, alignment, and snapping”A grid is born aligned to its domain: its cells reprojected into the domain’s CRS and, by default, kept at the source’s native cell size — 30 m for LANDFIRE, near a meter for a Meta CHM. You can request a coarser target resolution as the grid is built, or resample an existing grid onto a new lattice afterward, but resolution is a deliberate choice — it trades detail against compute and storage — and is not changed implicitly by a fetch.
Resolution, though, is only half of what makes two grids line up. Two grids compose — overlay cell-for-cell so you can stack a surface load, a canopy density, and a terrain into one fuelbed — only if they share the same CRS, the same origin, and the same cell size. A shared resolution is not enough: if two equal-resolution grids start from corners offset by half a cell, every cell of one straddles four of the other, and there is no clean overlay.
That is what alignment solves, and why the default target is the domain.
Every fetched grid anchors its cells to the domain’s origin, so grids built on
the same domain tile into one another by construction — no drift, no fractional
offset. (You can instead align to an existing grid’s exact lattice, or keep the
source’s native pixel anchor, when a workflow calls for it.) The domain is the
shared peg every grid hangs on, and setting
pad_to_resolution
on the domain locks that peg to a clean multiple of your base resolution
early, so a 2 m grid, a 6 m grid, and a 30 m grid resampled to 2 m all share one
footprint. Alignment is what allows several grids of the same place to be
combined into one stackable fuelbed, which is what an export to a fire model
requires.
Grids and the structure of a fuelbed
Section titled “Grids and the structure of a fuelbed”A complete fuel model is rarely a single grid. Fuel science describes a fuelbed as a stack of vertical strata, and the strata burn differently:
- Ground fuels — duff and buried organics, which smolder rather than spread.
- Surface fuels — litter, downed wood, grass, and low shrub, the layer that carries a spreading fire.
- Mid-level, or ladder, fuels — tall shrubs and low branches that bridge the surface to the canopy and determine whether a surface fire can climb.
- Canopy fuels — tree crowns, which govern crown fire.
Traditional fire modeling collapses most of this into a single classified type
per cell. A Scott & Burgan fuel model (the fbfm band) names a 2D surface
fuelbed whose properties were tuned to reproduce observed fire spread, which is
why, as noted earlier, its codes cannot be reverse-engineered into measured
loadings. That abstraction is efficient and is all a quasi-empirical
surface-spread model requires, but it represents the surface as one flat layer
and says nothing about vertical structure.
Physics-based modeling takes the opposite approach. A general framework for
representing fuels in three dimensions decomposes the fuelbed by fuel type and
stratum, represents each combination with the method best suited to it, and
combines the results in one aligned 3D space
(Tutland et al. 2025). FastFuels
implements this approach
(Marcozzi et al. 2025), and in
v2 it takes the concrete form of a stack of grids: a 2D surface fuel grid
(from an fbfm lookup, an upload, or uniform values), a 2D topography grid, and
a 3D voxel canopy (from a
voxelized inventory), alongside the
layersets that describe the statistical
mid-level and surface vegetation an inventory does not enumerate. Each grid is
the representation that fits its stratum, and alignment to the shared domain is
what lets them occupy one voxel space, where overlapping fuels are combined into
the single field a model reads.
The API does not check that a stack of grids forms a complete fuelbed. A canopy voxel grid can be exported on its own, with no surface fuel grid beneath it, even though a physics-based model needs surface fuel for a fire to spread along the ground. The mid-level, or ladder, stratum is the most consequential to omit: it governs whether a surface fire reaches the canopy, and it is also the hardest stratum to observe, because the overstory occludes it from remote sensing. Assembling a coherent fuelbed therefore requires accounting for each stratum explicitly — selecting the ones a simulation needs and sourcing a grid or layerset for each — because the resource combines whatever grids it is given without evaluating what they omit. As noted for inventories, a single grid is one stratum of the fuelbed, not the whole of it.
Voxelizing an inventory into a 3D grid
Section titled “Voxelizing an inventory into a 3D grid”One source warrants closer attention, because it produces the 3D voxel grid. Voxelization turns a tree inventory — a list of stems, each with a position, height, diameter, and species — into a 3D canopy fuel grid. Conceptually, for each tree it:
- Shapes the crown from the tree’s diameter, crown ratio, species, and height — the solid the foliage occupies.
- Cuts that solid into the voxel lattice, recording each voxel’s
volume_fraction: the share of its volume the crown fills. - Distributes the tree’s foliage biomass (estimated allometrically) across the occupied voxels, giving each a bulk density.
- Accumulates overlapping crowns, summing their densities into a continuous 3D canopy over the whole domain.
The result is a voxel grid of bulk_density.foliage.live (and optionally
moisture, SAVR, tree_id) — the location-specific 3D canopy fuel no 2D product
can supply. The per-tree mechanics live on the inventory side and are
covered in
About inventories.
The point here is the round trip. A raster such as a PIM or CHM grid can be
used to build an inventory, and the inventory voxelizes back into a grid. A
raster and a discrete inventory are two representations of the same fuel, and
voxelization turns the per-tree detail into a model-ready 3D field.
Editing a grid: masking, not cutting
Section titled “Editing a grid: masking, not cutting”You often want to change a 2D grid after it is built — zero out the surface fuel under a road, thin the load along a firebreak. A grid expresses these as modifications: rules that select cells (by a band’s value, or by a spatial shape — an inline polygon or a referenced feature) and then transform a band on the matching cells (replace it with zero, multiply it down).
The conceptual thing to understand is why a grid is masked rather than cut. A
grid’s lattice is fixed: every cell exists at its place, always. You cannot
delete the cells inside a lake the way you can drop the trees inside it from an
inventory — there is no “remove a cell,” because the lattice would be left with a
hole. So a grid edit always replaces or scales a value, never removes a
position: masking the surface fuel inside a water feature means setting those
cells’ load to zero, not deleting them. This asymmetry — grids replace,
inventories remove — follows from the difference between a fixed lattice of
cells and a set of discrete objects. The full rule vocabulary (condition kinds,
the action verbs, buffer_m) is the
subject of About modifications.
This page only explains why a grid’s edits replace and scale rather than remove.
Lifecycle, storage, and determinism
Section titled “Lifecycle, storage, and determinism”Building a grid is asynchronous. A create call returns immediately with
status: "pending", and the grid walks pending → running → completed (or
failed). Its georeference and chunks — the CRS, transform, shape, and how
the data is tiled — stay empty until it reaches completed, at which point the
data becomes readable.
The data itself is stored as a chunked array (Zarr, in cloud storage): the grid is cut into fixed-size tiles so a reader can fetch one region without downloading the whole grid. Reading a grid is therefore a two-step process: request its metadata for the chunk layout, then fetch the chunks you need. This allows a grid larger than memory to be read in parts. 2D chunks are returned dense, with every cell included. 3D chunks are returned sparse, with only the occupied voxels, matching how each is stored. The fetch-data how-to walks the reassembly.
Determinism is built into the resource, the same way it is for inventories.
A grid stores a checksum — a content-version marker that changes whenever
the data is rebuilt and is untouched by metadata-only edits (renaming a grid does
not change it). A resource derived from a grid records the checksum it was built
from, so it can later tell whether its source has drifted. Together with the
stored source recipe and the replayable list of modifications, the checksum
makes a grid a fully reproducible record: the same source, parameters, and edits
always rebuild the same cells.
Exports
Section titled “Exports”An export is the terminus of the pipeline — the step that turns a domain’s grids into a file a fire model or GIS tool can open.
The simplest export packages one grid’s bands into a standard raster or array file, and the format follows the grid’s dimensionality: a GeoTIFF holds 2D rasters only, while NetCDF and Zarr carry 2D or 3D. The voxel canopy can therefore be exported only as NetCDF or Zarr, so the 2D-versus-3D distinction that shapes the data also constrains the export format.
The export that matters most assembles a fire model’s entire input at once. A
QUIC-Fire export takes a role assignment — this grid’s
bulk_density.foliage.live band is the canopy bulk density, that
grid’s fuel_load.1hr is the surface fuel load, a topography grid’s elevation
is the terrain — and resolves all of them onto one fire grid: the single
lattice the model runs on. This step relies on the alignment described earlier.
The exporter crops a grid that overhangs the others but does not resample to
force a fit, so the layers can be combined into one lattice only because they
were already aligned to the same domain. Where two of them fall in the same voxel,
their values merge by a defined rule — canopy bulk densities sum, just as
overlapping crowns did at voxelization. The
QUIC-Fire tutorial walks
this assembly end to end.
Two properties make an export a deliverable in its own right. It is built asynchronously, like a grid — the create call returns a pending export and a job assembles the file. And it outlives its domain: deleting a domain can cascade to its grids and inventories, but a finished export is kept as a standalone artifact, because it is the deliverable you already handed to a fire model and may later need to reproduce that run.
How grids relate to everything else
Section titled “How grids relate to everything else”A grid belongs to exactly one domain and inherits its CRS, extent, and alignment lattice. It can reference only resources in that same domain. Its connections to the rest of v2:
- The domain is the frame. A grid’s cells are cut from the domain’s working
extent and expressed in its CRS, and grids aligned “to the domain” share the
lattice it defines — the reason same-domain grids compose.
pad_to_resolutionis set on the domain, not the grid, precisely so a whole stack of grids agrees. - Inventories and point clouds feed in — and back out. A tree inventory is voxelized into a 3D canopy grid. Conversely, a PIM or CHM grid is where some inventories come from. Grids are both an input to and an output of the discrete representation.
- Features mask and rasterize. A feature — a road, a water body — can be referenced by a modification’s spatial condition to mask the cells that fall inside it, and a layerset feature can be rasterized directly into a fuel grid.
- Modifications are replayed. A grid’s edits are stored and re-applied on rebuild, so a masked grid is the deterministic product of its source plus its rules.
- Exports consume grids. A finished export assembles a domain’s aligned grids into a fire model’s input and outlives the domain it was built in. It is the final output of the pipeline.
The thread, as everywhere in v2: the domain is the unit of geographic scope, and a grid is the continuous, model-ready field of fuel and terrain measured within it — the form a fire model finally consumes.
Where to go next
Section titled “Where to go next”The procedures that put all of this to work are the grid how-to guides:
- Build a surface fuel grid from LANDFIRE
— fetch the categorical
fbfmgrid, then look up continuous fuel loads. - Build a topography grid from 3DEP — elevation, slope, and aspect from USGS 3DEP.
- Upload a GeoTIFF to create a grid — bring your own 2D raster, single- or multi-band.
- Upload a netCDF to create a grid — bring a CF-conformant 2D or 3D file.
- Resample or reproject a grid — re-grid onto another lattice or to a target resolution so layers share one footprint.
- Fetch and stream grid data — discover the chunk layout and read the cells back out.
To see grids built, aligned, and exported as one fuelbed end to end, the QUIC-Fire tutorial runs the whole pipeline.
The explanation pages around this one go deeper on what it only summarizes:
- About modifications — the rule
shape, condition kinds, action vocabulary, and
buffer_msemantics behind masking a grid. - About domains — the frame a grid is cut
from, and where
pad_to_resolutionsets the shared lattice that makes grids compose. - About inventories — the discrete, per-tree counterpart a grid is voxelized from, and which a grid can also feed.
Companion explanation pages for Features and Point Clouds build on the same resource model — the vector geometry that masks a grid, and a future detection source for the inventories a grid voxelizes.