Skip to content

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.

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.

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:

  • input is 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.
  • domain is the working extent: the axis-aligned bounding box of the input (optionally snapped outward — see pad_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 the domain feature.

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.

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.

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 CRSWhat the API doesResulting domain CRS
Geographic (e.g. EPSG:4326, the default)Projects it to the UTM zone for the geometry’s locationThat UTM zone (e.g. EPSG:32611)
Already projected (e.g. EPSG:5070, EPSG:32611)Keeps it unchangedThe 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.

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.

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 FeatureCollection from 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 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.

LimitValueChecked againstWhy
Working-extent areaunder 16 km²the (possibly padded) domain rectanglepredictable compute load
Locationentirely within CONUSthe original input polygonsource-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.

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.)

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.

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.

Companion explanation pages for Features, Point Clouds, Inventories, and Grids build on the resource model introduced here.