About domains
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
A domain is a georeferenced spatial container: the patch of ground a
fuelbed is built over, and the frame of reference everything else in the v2
API is measured in. Concretely it is a GeoJSON FeatureCollection that holds
the polygon you drew plus a computed bounding box, pinned to a single
projected coordinate system. Every other resource — features, point clouds,
inventories, grids, exports — is created inside a domain and inherits that
frame.
This page is about the idea of a domain: what it stores, why it exists, and the mental model that makes the rest of the v2 API hang together. It is not a walk-through of the create request — for that, see the how-to guide Create a domain — and it is not the end-to-end pipeline, which the QUIC-Fire tutorial walks step by step. Here we stay with the concepts that both of those assume.
The anchor of the v2 resource model
Section titled “The anchor of the v2 resource model”Domains exist because every other thing the API builds needs three questions answered before it can exist: where is it, in what units, and on which grid? A domain answers all three, once, so that no downstream resource has to ask again.
- Spatial scoping. The domain fixes the geographic extent. A grid built on it covers exactly that extent; an inventory holds the trees inside it; a feature is clipped to it. “Where” is settled by the domain you attach to.
- CRS unification. The domain fixes one projected coordinate system, and every child resource is expressed in it. Meters mean the same thing across a LANDFIRE grid, an uploaded GeoTIFF, and a voxelized tree inventory because they all live in the domain’s CRS.
- Grid alignment. The domain fixes the rectangle that rasters and voxel grids are cut from, so grids built on the same domain line up cell-for-cell instead of drifting by fractions of a pixel.
Everything else in v2 is downstream of that frame:
Domain ── the spatial frame: extent + CRS + alignment │ ├─ Features (vector: roads, water, uploaded layersets) ├─ Point clouds (uploaded lidar / photogrammetry) ├─ Inventories (trees: from TreeMap, from a CHM, uploaded) │ ▼ Grids ── the rasterized / voxelized fuelbed │ (surface fuel, topography, canopy bulk density, …) ▼ Exports ── packaged inputs for a fire model (QUIC-Fire, …)A domain is the root of that tree. The other resource-level explanation pages
(Features, Inventories, Grids, Point Clouds) each pick up one branch of it;
this page is the trunk they refer back to. In API terms the relationship is
literal: child resources live under domain-scoped URLs
(/v2/domains/{domain_id}/grids/...), and every child document carries the
domain_id it belongs to.
What a domain stores: two features
Section titled “What a domain stores: two features”A domain is a GeoJSON FeatureCollection, and it always contains two named
features. Schematically:
{ "type": "FeatureCollection", "crs": { "type": "name", "properties": { "name": "EPSG:32611" } }, "bbox": [minx, miny, maxx, maxy], // equals the "domain" feature's bounds "features": [ { "properties": { "name": "domain" }, // the working extent "geometry": { "type": "Polygon", "coordinates": [ /* axis-aligned rectangle */ ] } }, { "properties": { "name": "input" }, // what you drew, reprojected "geometry": { "type": "Polygon", "coordinates": [ /* your original polygon */ ] } } ]}The two features play different roles:
inputis the polygon you submitted, reprojected into the domain’s CRS and otherwise left alone. It is preserved for reference and rendering — when a map needs to show “the area the user actually drew,” this is the shape it draws. Nothing downstream rasterizes against it; it is the record of intent.domainis the working extent: the axis-aligned bounding box of the input (optionally snapped outward — seepad_to_resolution). This rectangle is the authoritative footprint that griddle, standgen, and the exporter actually build against. When a grid asks “what area do I cover?”, the answer is thedomainfeature.
The domain rectangle contains the input polygon by construction, so the
bounding box of the whole FeatureCollection is the working extent — no
filtering required. The standard GeoJSON bbox field is populated to the same
rectangle for convenience.
Why a rectangle, not a tight outline
Section titled “Why a rectangle, not a tight outline”It is fair to ask why the working extent is a coarse axis-aligned box rather than the convex hull or the exact polygon you drew. The reason is what comes next in the pipeline: a grid is a north-up raster, and a raster footprint is a rectangle whose sides are parallel to the axes. The fuelbed is voxelized into a regular lattice of cells; that lattice has to be rectangular, and it has to be aligned to north so cells stack predictably. Storing the working extent as an axis-aligned box makes the domain say, in plain GeoJSON, exactly the area every downstream raster will occupy. A tight hull would have to be boxed anyway the moment a grid was built — so the box is computed once, up front, and recorded.
Keeping both shapes makes the domain self-documenting at the data level: a viewer that knows nothing about FastFuels can render both features and show you the rectangle the model will use and the polygon you intended, with no custom fields to interpret.
Coordinate reference systems
Section titled “Coordinate reference systems”A domain is always stored in a projected CRS — one whose units are meters, not degrees of latitude and longitude. This is the single most important thing to understand about a domain’s frame, because it propagates to everything built on it.
Input defaults to EPSG:4326 (WGS84 longitude/latitude), the CRS of almost
every polygon drawn on a web map. What the API does with your input depends on
whether it arrives geographic or already projected:
| Input CRS | What the API does | Resulting domain CRS |
|---|---|---|
Geographic (e.g. EPSG:4326, the default) | Projects it to the UTM zone for the geometry’s location | That UTM zone (e.g. EPSG:32611) |
Already projected (e.g. EPSG:5070, EPSG:32611) | Keeps it unchanged | The CRS you supplied |
The UTM zone is derived deterministically from where your geometry is (its
centroid lands in exactly one zone), not chosen by you. So a polygon near
Missoula always becomes UTM zone 11N (EPSG:32611); the same request always
yields the same projected domain. The CRS the API returns is therefore often
not the one you submitted — submit WGS84, get a UTM zone back — and reading
it off the response is how you learn the frame your data now has to match.
Why meters, not degrees
Section titled “Why meters, not degrees”Projection is not a formality. A degree of longitude is about 111 km at the equator and shrinks toward the poles, so areas and distances measured in degrees are neither uniform nor metric. The domain’s whole job — sizing a fuelbed, enforcing a 16 km² limit, cutting a 2-meter grid, buffering a road by 4 meters — depends on a coordinate where “one unit” is “one meter” everywhere in the extent. UTM gives that: a local, metric, north-up frame. That is why a geographic input is projected before anything else happens, and why a domain can never be stored in a geographic CRS.
Create first, then match your data to the domain
Section titled “Create first, then match your data to the domain”Because the domain fixes the CRS, the workflow runs domain-first: you create (or preview) the domain, read back the projected CRS and the working extent, and then bring every other resource into that frame. An uploaded GeoTIFF must be reprojected and aligned to the domain’s grid before it will upload cleanly; a grid fetched from LANDFIRE or 3DEP is resampled into the domain’s CRS and lattice on the way in. The domain is the fixed point; the data bends to meet it, not the other way around.
The API gives you the alignment target directly. GET /v2/domains/{id}/lattice
returns the domain’s CRS together with the exact affine transform and pixel
shape for a chosen resolution — the lattice a raster must match to nest into
the domain without a second reprojection.
Preview and reproject
Section titled “Preview and reproject”Two stateless helpers exist precisely to support the domain-first workflow:
- Preview runs the full validation-and-projection pipeline and returns the resulting domain — projected CRS, padded working extent, both features — without persisting anything. It is how you inspect the rectangle and the CRS the server will compute before you commit to creating the resource.
- Reproject is a pure utility that converts a
FeatureCollectionfrom one CRS to another. It mints no resource; it is there for preparing or inspecting geometry in whatever frame you need.
Neither creates a domain. They let you see the frame a create would produce so there are no surprises once children start attaching to it.
The bounding box and pad_to_resolution
Section titled “The bounding box and pad_to_resolution”The working extent is [minx, miny, maxx, maxy] in the domain’s projected
CRS. By default it is the exact bounding box of your input. Setting the
optional pad_to_resolution (a value in meters) snaps that box outward —
mins floored, maxs ceiled — to the nearest multiple of the value, so the
working extent’s corners land on a clean grid.
Padding exists for one purpose: making grids at different resolutions share an identical footprint. Anchor a domain’s extent to multiples of, say, 2 meters, and a 2 m grid, a 6 m grid, and a 30 m grid resampled to 2 m all start from the same lower-left corner and tile cleanly into one another — a 30 m cell is exactly 15×15 of the 2 m cells, with no fractional offset. That is what a fire-model export needs: several grids of different native resolutions stacked into one aligned voxel stack. Without padding the extents can differ by a fraction of a cell and the stack is ambiguous.
It is opt-in because most of that benefit only matters when you are composing multiple resolutions, and the right value to pad to is your intended base resolution — something only you know, and only at the moment you decide to build a multi-resolution stack. A single-resolution workflow doesn’t need it: grids anchored to the same unpadded domain at the same resolution already line up. Padding also slightly enlarges the extent, which is one more reason not to impose it by default. Declare it when you know you will compose; leave it off otherwise.
Why domains are bounded: size and location
Section titled “Why domains are bounded: size and location”Two hard limits are checked at creation. Both are deliberate, and both follow from what a domain is for.
| Limit | Value | Checked against | Why |
|---|---|---|---|
| Working-extent area | under 16 km² | the (possibly padded) domain rectangle | predictable compute load |
| Location | entirely within CONUS | the original input polygon | source-data coverage |
The 16 km² cap bounds compute. A domain’s reason for being is to carry a fine-resolution fuelbed — trees voxelized at 1–2 meters, surface fuels rasterized to match. The cost of building and storing that scales with area, so an upper bound on the working extent keeps any single domain’s processing time and memory predictable. The check is applied to the working rectangle (after padding), because that — not the polygon you drew — is what actually gets built. Sixteen square kilometers is a 4 km × 4 km box; landscape-scale work routinely exceeds it.
The CONUS requirement reflects data availability. The datasets a domain’s children draw on — LANDFIRE for surface fuels, 3DEP for topography, TreeMap for modeled tree inventories, NAIP for canopy-height imagery — are products of the conterminous United States. Outside CONUS there is simply nothing to build a fuelbed from, so a domain there could hold geometry but no fuel. The location check runs against the original input polygon (not the padded box) so that padding right up against a coastline or border can’t trip a false rejection.
When your area is too big, the move is to tile it into multiple domains — split the region into sub-areas each under the cap and create a domain per tile. Each domain is independent and processes on its own; you reassemble the outputs downstream. There is no way to raise the cap on a single domain.
Lifecycle and mutability
Section titled “Lifecycle and mutability”A domain has a sharp split between what is frozen and what you can change after creation, and the split is not arbitrary.
Immutable — the geometry (features), the crs, the id, and
created_on. These are frozen because children depend on them. Every grid,
inventory, and feature was built in the domain’s CRS and cut to its extent; if
the domain could be re-shaped or re-projected underneath them, they would all
silently become wrong. So the foundational frame is write-once. To change the
extent or projection, you create a new domain.
Mutable — the descriptive metadata: name, description, tags, and the
optional rendering style. Editing these changes nothing about the spatial
frame, so they are free to update, and modified_on advances when you do.
(style merges field-by-field on update — supplying one color leaves the
others untouched — since it is presentation state, not geometry.)
Deletion and cascade
Section titled “Deletion and cascade”Deleting a domain is guarded because of those same dependents. By default, a
delete is refused if the domain still has child resources, so you don’t
orphan or accidentally destroy work; you opt into a cascade explicitly with
force. A forced delete removes the domain together with its child grids and
inventories (and their stored data).
Exports are the deliberate exception — they survive their domain’s deletion. An export is a finished, self-contained deliverable (the packaged inputs you handed to a fire model), so it is kept as a standalone provenance artifact rather than swept away with the frame it was built in. The principle: resources that only make sense inside the domain go when it goes; a deliverable that has already left the building stays.
How other resources attach to a domain
Section titled “How other resources attach to a domain”Every other v2 resource is defined relative to a domain, and inherits its frame:
- Features — vector geometry scoped to the domain: roads and water pulled from OpenStreetMap, or layersets you upload. They are reprojected into the domain CRS when used, and a feature can only modify resources in its own domain (see About modifications).
- Point clouds — uploaded lidar or photogrammetric points, positioned in the domain’s frame.
- Inventories — the trees over the domain, whether sampled from TreeMap, detected from a canopy height model (see How tree detection from a CHM works), or uploaded. Every tree’s coordinates are in the domain CRS.
- Grids — the rasterized and voxelized fuelbed. A grid’s cells are cut from the domain’s working extent and expressed in its CRS; grids aligned “to the domain” share the lattice the domain defines.
- Exports — bundles of grids packaged for a fire model. They reference the domain they were assembled in, but, as noted above, outlive it.
The thread running through all of these: a domain is the unit of geographic scope in v2. Resources don’t reach across domains — a modification can’t reference a feature in another domain, a grid can’t be aligned to one in another domain — because the domain is precisely the boundary within which “the same place, in the same units, on the same grid” is guaranteed to hold.
Where to go next
Section titled “Where to go next”- Create a domain — the procedural how-to: the POST request, CRS handling in practice, and the common errors.
- Generate QUIC-Fire simulation inputs — the end-to-end pipeline, where the domain is step one and everything above is put to work.
- About modifications — how edits to grids and inventories are scoped, including why they stay within a single domain.
- How tree detection from a CHM works — one branch of the resource tree in depth.
Companion explanation pages for Features, Point Clouds, Inventories, and Grids build on the resource model introduced here.