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:
- Zero all dead surface-fuel bands wherever a road or water feature touches a cell (recipe A).
- 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.
Prerequisites
Section titled “Prerequisites”-
An API key. Set it once here — it propagates to every code block on the page: my-api-key.
-
A domain in a projected CRS. Plug its id in once: your-domain-id. If you don’t have one, see Create a domain.
-
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.
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.
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} ] } ]}'import requests
API_KEY = "my-api-key"DOMAIN_ID = "your-domain-id"ROAD_FEATURE_ID = "your-road-feature-id"WATER_FEATURE_ID = "your-water-feature-id"BASE = "https://api-v2-prod-nyvjyh5ywa-uw.a.run.app"
# Zero every dead-fuel band where a feature's cells are hit. One rule per# feature — conditions within a rule are ANDed, so road and water must be# separate rules (a cell is never inside both at once).def wipe(feature_id): return { "conditions": [ { "source": "feature", "operator": "intersects", "target": "cell", "feature_id": 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}, ], }
resp = requests.post( f"{BASE}/domains/{DOMAIN_ID}/grids/uniform", headers={"api-key": API_KEY}, json={ "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": [wipe(ROAD_FEATURE_ID), wipe(WATER_FEATURE_ID)], },)resp.raise_for_status()grid = resp.json()print(grid["id"], grid["status"]) # -> <grid id> pending{ "id": "your-grid-id", "domain_id": "your-domain-id", "name": "Surface fuel \u2014 roads and water masked", "description": "", "status": "pending", "progress": null, "created_on": "2026-06-02T19:03:43.070155", "modified_on": "2026-06-02T19:03:43.070155", "source": { "name": "uniform", "bands": [ { "key": "fuel_load.1hr", "value": 0.2 }, { "key": "fuel_load.10hr", "value": 0.3 }, { "key": "fuel_load.100hr", "value": 0.5 } ], "resolution": 2.0 }, "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": null, "error": null, "chunks": { "shape": [512, 512], "count": null, "count_by_axis": null }, "tags": []}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.
Poll until completed
Section titled “Poll until completed”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).
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} ] } ]}'{ "id": "your-grid-id", "domain_id": "your-domain-id", "name": "Fine fuel thinned to 10% inside roads", "description": "", "status": "pending", "progress": null, "created_on": "2026-06-02T19:03:43.477283", "modified_on": "2026-06-02T19:03:43.477283", "source": { "name": "uniform", "bands": [ { "key": "fuel_load.1hr", "value": 0.2 } ], "resolution": 2.0 }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "intersects", "feature_id": "your-road-feature-id", "buffer_m": null, "target": "cell" } ], "actions": [ { "band": "fuel_load.1hr", "modifier": "multiply", "value": 0.1 } ] } ], "bands": [ { "key": "fuel_load.1hr", "type": "continuous", "unit": "kg/m**2", "index": 0, "nodata": null } ], "georeference": null, "error": null, "chunks": { "shape": [512, 512], "count": null, "count_by_axis": null }, "tags": []}Inside the road footprint, fuel_load.1hr goes from 0.2 to 0.02;
everywhere else stays 0.2.
Widen to the shoulder with buffer_m
Section titled “Widen to the shoulder with buffer_m”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:
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:
target: cell vs centroid
Section titled “target: cell vs centroid”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 prefercellunless you specifically want center-point semantics.
Common pitfalls
Section titled “Common pitfalls”-
Bad
feature_id→422on the create call. Grid creation validates every referenced feature synchronously: it must exist, belong to this domain, and becompleted. 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. Same422. Poll the feature tocompleted(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 acrossfuel_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.