Skip to content

Mask a fuel grid with road and water features

You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.

Roads and open water carry no surface fuel, so leaving them at their modeled fuel load lets a QUIC-Fire (or FIRETEC / FDS) run carry fire straight through a highway or a lake. This guide removes that fuel by masking the grid with road and water Feature resources you’ve already attached to the domain — no hand-drawn polygons, no per-request GeoJSON.

You’ll do it in two moves:

  1. Zero all dead surface-fuel bands wherever a road or water feature touches a cell (recipe A).
  2. Thin — rather than remove — fine fuel along a road, keeping a fraction of the load to represent a vegetated shoulder (recipe B).

Both attach the rules to a uniform grid here so the before/after is easy to read, but the same modifications block works on any fuel grid (lookup, fbfm40, pim, an upload). For the concepts behind conditions, actions, buffer_m, and target, see About modifications.

  1. An API key. Set it once here — it propagates to every code block on the page: my-api-key.

  2. A domain in a projected CRS. Plug its id in once: your-domain-id. If you don’t have one, see Create a domain.

  3. A completed road feature and water feature in that domain. See Create road and water features from OpenStreetMap — create both, poll them to completed, and record the two ids: your-road-feature-id and your-water-feature-id.

Here’s the domain we’ll work in — Bigfork, Montana, at the head of Flathead Lake — with its OSM roads and water. These are exactly the geometries the mask will use.

Domain Roads Water
The domain (grey outline) with OSM roads (red) and water (blue). Every cell touching red or blue is what we'll zero out.

Recipe A — remove all fuel inside roads and water

Section titled “Recipe A — remove all fuel inside roads and water”

Attach two modification rules to the grid create call: one per feature. A rule is {conditions, actions}; its conditions are ANDed, so road and water go in separate rules (no cell is ever inside both at once). Each rule’s condition is a feature reference with target: "cell" — match every cell the polygon touches — and its actions zero the three dead-fuel bands.

Create masked fuel grid
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/uniform' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Surface fuel — roads and water masked",
"resolution": 2,
"bands": [
{"key": "fuel_load.1hr", "value": 0.2},
{"key": "fuel_load.10hr", "value": 0.3},
{"key": "fuel_load.100hr", "value": 0.5}
],
"modifications": [
{
"conditions": [
{"source": "feature", "operator": "intersects", "target": "cell", "feature_id": "your-road-feature-id"}
],
"actions": [
{"band": "fuel_load.1hr", "modifier": "replace", "value": 0},
{"band": "fuel_load.10hr", "modifier": "replace", "value": 0},
{"band": "fuel_load.100hr", "modifier": "replace", "value": 0}
]
},
{
"conditions": [
{"source": "feature", "operator": "intersects", "target": "cell", "feature_id": "your-water-feature-id"}
],
"actions": [
{"band": "fuel_load.1hr", "modifier": "replace", "value": 0},
{"band": "fuel_load.10hr", "modifier": "replace", "value": 0},
{"band": "fuel_load.100hr", "modifier": "replace", "value": 0}
]
}
]
}'

The 201 echoes your modifications back verbatim and mints a grid id. Record it: your-grid-id. The grid starts pending with georeference: null — the modification runs server-side during the build, not on this call.

GET grid status
curl -X 'GET' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/your-grid-id' \
-H 'accept: application/json' \
-H 'api-key: my-api-key'

Status walks pending → running → completed. When it lands:

{
"id": "your-grid-id",
"domain_id": "your-domain-id",
"name": "Surface fuel \u2014 roads and water masked",
"description": "",
"status": "completed",
"progress": {
"percent": 100,
"message": "Complete"
},
"created_on": "2026-06-02T19:03:43.070155Z",
"modified_on": "2026-06-02T19:03:46.863919Z",
"source": {
"resolution": 2.0,
"bands": [
{
"key": "fuel_load.1hr",
"value": 0.2
},
{
"key": "fuel_load.10hr",
"value": 0.3
},
{
"key": "fuel_load.100hr",
"value": 0.5
}
],
"name": "uniform"
},
"modifications": [
{
"conditions": [
{
"source": "feature",
"operator": "intersects",
"feature_id": "your-road-feature-id",
"buffer_m": null,
"target": "cell"
}
],
"actions": [
{
"band": "fuel_load.1hr",
"modifier": "replace",
"value": 0
},
{
"band": "fuel_load.10hr",
"modifier": "replace",
"value": 0
},
{
"band": "fuel_load.100hr",
"modifier": "replace",
"value": 0
}
]
},
{
"conditions": [
{
"source": "feature",
"operator": "intersects",
"feature_id": "your-water-feature-id",
"buffer_m": null,
"target": "cell"
}
],
"actions": [
{
"band": "fuel_load.1hr",
"modifier": "replace",
"value": 0
},
{
"band": "fuel_load.10hr",
"modifier": "replace",
"value": 0
},
{
"band": "fuel_load.100hr",
"modifier": "replace",
"value": 0
}
]
}
],
"bands": [
{
"key": "fuel_load.1hr",
"type": "continuous",
"unit": "kg/m**2",
"index": 0,
"nodata": null
},
{
"key": "fuel_load.10hr",
"type": "continuous",
"unit": "kg/m**2",
"index": 1,
"nodata": null
},
{
"key": "fuel_load.100hr",
"type": "continuous",
"unit": "kg/m**2",
"index": 2,
"nodata": null
}
],
"georeference": {
"crs": "EPSG:32611",
"transform": [2.0, 0.0, 717141.6556112946, 0.0, -2.0, 5328248.549818624],
"shape": [862, 777]
},
"error": null,
"chunks": {
"shape": [512, 512],
"count": 4,
"count_by_axis": {
"x": 2,
"y": 2
}
},
"tags": []
}

The georeference and chunks are now populated and the masked bands are written to storage. On this domain the two rules zero fuel_load.1hr in 165,921 of 669,774 cells (≈25%) — every road- and water-touched cell — while the rest keep their uniform 0.2 kg/m**2. Fetch the band data (/grids/{{GRID_ID}}/data/fuel_load.1hr/{chunk}) to confirm: the only two values present are 0.0 and 0.2.

Recipe B — thin fine fuel along a road instead of removing it

Section titled “Recipe B — thin fine fuel along a road instead of removing it”

Sometimes you don’t want a hard zero. A vegetated road shoulder still carries fine fuel — just less of it. Swap the replace action for multiply to scale the band: 0.1 keeps 10% of the load (a 90% reduction).

Thin fine fuel to 10% inside roads
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/uniform' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Fine fuel thinned to 10% inside roads",
"resolution": 2,
"bands": [{"key": "fuel_load.1hr", "value": 0.2}],
"modifications": [
{
"conditions": [
{"source": "feature", "operator": "intersects", "target": "cell", "feature_id": "your-road-feature-id"}
],
"actions": [
{"band": "fuel_load.1hr", "modifier": "multiply", "value": 0.1}
]
}
]
}'

Inside the road footprint, fuel_load.1hr goes from 0.2 to 0.02; everywhere else stays 0.2.

The road footprint is just the carriageway. To also thin fuel in the right-of-way beside it, add buffer_m (meters, in the domain’s projected CRS) to the condition. A 5 m buffer grows the masked strip outward on every side:

Thin fine fuel along a 5 m road shoulder
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/uniform' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Fine fuel thinned to 10% along roads (5 m shoulder)",
"resolution": 2,
"bands": [{"key": "fuel_load.1hr", "value": 0.2}],
"modifications": [
{
"conditions": [
{"source": "feature", "operator": "intersects", "target": "cell", "feature_id": "your-road-feature-id", "buffer_m": 5}
],
"actions": [
{"band": "fuel_load.1hr", "modifier": "multiply", "value": 0.1}
]
}
]
}'

On this domain that nearly doubles the affected cells (57,686 → 114,252). The map shows the difference — the road footprint versus the same roads buffered by 5 m:

Domain 5 m buffer Road footprint
Road footprint (red) and the same roads buffered by 5 m (orange). The buffer is the extra shoulder cells the thinning now reaches.

Grid spatial conditions test each cell against the geometry one of two ways:

  • target: "cell" (used above) — a cell matches if the geometry touches any part of it. This catches every cell a road crosses, which is what you want for masking, so reach for it by default.
  • target: "centroid" (the schema default) — a cell matches only if its center point falls inside the geometry. Thin features can slip between centroids and leave gaps, so prefer cell unless you specifically want center-point semantics.
  • Bad feature_id422 on the create call. Grid creation validates every referenced feature synchronously: it must exist, belong to this domain, and be completed. A bad reference fails fast — the grid is never created, so there’s nothing to clean up:

    {
    "detail": "Modification references feature_id 'does-not-exist', which does not exist in this domain."
    }
  • Referencing a feature that’s still pending. Same 422. Poll the feature to completed (prerequisites, above) before you create the grid.

  • Cross-domain feature. A feature from another domain is rejected with the same “does not exist in this domain” 422. Features and the grids that reference them must share a domain.

  • Forgetting the other fuel bands. Each action names one band. Recipe A repeats the action across fuel_load.1hr/10hr/100hr; if your grid carries live or herbaceous loads too, add those bands to the action list or they keep their original values inside the road.

  • One rule for two features. Putting a road condition and a water condition in the same rule masks only cells inside both (empty). Use one rule per feature, as in recipe A.