Skip to content

Compose grids

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

POST /domains/{domain_id}/grids/compose creates a grid whose bands are copied or computed from the bands of existing grids. For how grids share a lattice, see Resolution, alignment, and snapping.

  1. An API key: my-api-key.

  2. A domain: your-domain-id. See Create a domain.

  3. Completed 2D input grids on one lattice — same CRS, origin, cell size, and shape. The recipes use three grids from Build surface fuel grids from LANDFIRE:

    • an FBFM40 grid with the fbfm band: your-fbfm40-grid-id;
    • its FBFM40 lookup grid with fuel_load.1hr, fuel_load.10hr, fuel_load.100hr, fuel_load.live_herb, fuel_load.live_woody, and fuel_depth: your-lookup-grid-id;
    • an FCCS lookup grid with fuel_load.litter, fuel_load.1hr, fuel_load.10hr, and fuel_load.live_herb: your-fccs-lookup-grid-id. List all four in the lookup request’s bands.

    To put a grid on another grid’s lattice, resample it.

  4. Optional — a completed road feature in the same domain, for Restrict an operation to a feature: your-road-feature-id. See Create road and water features from OpenStreetMap.

To copy bands, list each input grid in inputs with an alias, then add one select operation per output band. from names the source band as alias.band_key; output names the new band. An alias starts with a letter and contains only letters, digits, and underscores. Each input is a different grid with its own alias.

POST grids/compose — select
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Selected surface bands",
"inputs": [
{
"grid_id": "your-fbfm40-grid-id",
"alias": "codes"
},
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
},
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "select",
"output": "fbfm",
"from": "codes.fbfm"
},
{
"type": "select",
"output": "fuel_depth",
"from": "fbfm40.fuel_depth"
},
{
"type": "select",
"output": "fuel_load.1hr",
"from": "fbfm40.fuel_load.1hr"
},
{
"type": "select",
"output": "fuel_load.litter",
"from": "fccs.fuel_load.litter"
}
]
}'

Record the new grid id: your-grid-id.

  • Output bands follow the order of operations.
  • A selected band keeps the type and unit of its source band.
  • output is a band key: a quantity such as fuel_load, fuel_depth, bulk_density, or fbfm, optionally followed by one or more .label parts, as in fuel_load.1hr or fuel_moisture.live.shrub. The quantity sets the band’s type and unit, so it must match the band you select.
  • Each output appears once per request.
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'

To compute a band, add a compute operation with an operator and a list of operands. Here each cell takes the larger of the FBFM40 and FCCS 1-hour loads.

POST grids/compose — compute
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Larger 1-hr load of FBFM40 and FCCS",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
},
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.1hr",
"operator": "max",
"operands": [
"fbfm40.fuel_load.1hr",
"fccs.fuel_load.1hr"
]
}
]
}'
operatorOperandsOutput unit
add, average, min, maxtwo or morethe first band operand’s unit
subtractexactly twothe first band operand’s unit
multiplytwo or morethe product of the operand units
divideexactly twothe quotient of the operand units
  • Operands are band references, numbers, or typed literals; at least one is a band reference.
  • Band operands must be continuous. For add, subtract, average, min, and max, their units must be compatible; values are converted to the output unit.
  • A compute output is continuous, and its unit must be the unit of the output key’s quantity.
  • Where the operation’s conditions hold, a cell that is nodata in any band operand is nodata in the output.

To multiply or divide by a constant, pass a bare number as an operand. In multiply and divide, a bare number is unitless. In add, subtract, average, min, and max, a bare number is taken in the output unit; a typed literal there needs a unit, or the request returns 422.

POST grids/compose — scale
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Half 1-hr load",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.1hr",
"operator": "multiply",
"operands": [
"fbfm40.fuel_load.1hr",
0.5
]
}
]
}'

To give a constant a unit, pass a typed literal: {"type": "literal", "value": 50, "unit": "g/m**2"}. The value is converted to the output unit, so this adds 0.05 kg/m**2 to every cell. Units are UDUNITS-2 strings with ** exponents, such as kg/m**2, kg/m**3, m, and %.

POST grids/compose — typed literal
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "1-hr load plus 50 g/m**2",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.1hr",
"operator": "add",
"operands": [
"fbfm40.fuel_load.1hr",
{
"type": "literal",
"value": 50,
"unit": "g/m**2"
}
]
}
]
}'

To write a compute result in a different but compatible unit, set unit on the compute operation. Here the live herbaceous load times 67 % has the unit kg*%/m**2; "unit": "kg/m**2" converts it so it fits a fuel_load band.

POST grids/compose — output unit
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Cured herbaceous load",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.cured_herb",
"operator": "multiply",
"operands": [
"fbfm40.fuel_load.live_herb",
{
"type": "literal",
"value": 67,
"unit": "%"
}
],
"unit": "kg/m**2"
}
]
}'

Apply an operation only where conditions hold

Section titled “Apply an operation only where conditions hold”

To restrict an operation to some cells, add conditions and an else fallback. A cell takes the operation’s result where every condition holds and the else value everywhere else. Here the two 1-hour loads are averaged only where both are positive; other cells keep the FBFM40 load.

POST grids/compose — conditions
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Average 1-hr load where both sources have fuel",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
},
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.1hr",
"operator": "average",
"operands": [
"fbfm40.fuel_load.1hr",
"fccs.fuel_load.1hr"
],
"conditions": [
{
"band": "fbfm40.fuel_load.1hr",
"operator": "gt",
"value": 0
},
{
"band": "fccs.fuel_load.1hr",
"operator": "gt",
"value": 0
}
],
"else": "fbfm40.fuel_load.1hr"
}
]
}'
  • An attribute condition has a band (alias.band_key), an operator — eq, ne, gt, lt, ge, le, or in — and a value. in takes a list; gt, lt, ge, and le take a single number.
  • An attribute condition is false on nodata cells.
  • else is required whenever conditions is set. It can be:
    • a band reference with the same type and unit as the output;
    • a number;
    • a typed literal whose unit is the output’s unit;
    • an FBFM40 label such as "NB1", for a categorical output;
    • an inline compute.

To pick between grids by category, test a categorical band with eq, ne, or in. Here grass and grass-shrub cells take the FCCS 1-hour load and all other cells take the FBFM40 load. FBFM40 labels such as "GR1" and codes such as 101 are interchangeable; the stored operation holds the codes.

POST grids/compose — fuel-model condition
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "FCCS 1-hr load in grass fuel models",
"inputs": [
{
"grid_id": "your-fbfm40-grid-id",
"alias": "codes"
},
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
},
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "select",
"output": "fuel_load.1hr",
"from": "fccs.fuel_load.1hr",
"conditions": [
{
"band": "codes.fbfm",
"operator": "in",
"value": [
"GR1",
"GR2",
"GS1",
"GS2"
]
}
],
"else": "fbfm40.fuel_load.1hr"
}
]
}'

To limit an operation to an area you draw, add a geometry condition with "source": "geometry", an operator (within, outside, or intersects), and a GeoJSON geometry. Here the 1-hour load is halved inside a polygon. Replace the coordinates with a polygon inside your domain.

POST grids/compose — geometry condition
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Half 1-hr load inside a treatment polygon",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.1hr",
"operator": "multiply",
"operands": [
"fbfm40.fuel_load.1hr",
0.5
],
"conditions": [
{
"source": "geometry",
"operator": "within",
"geometry": {
"type": "Polygon",
"coordinates": [
[
[
-114.1122,
46.825
],
[
-114.1038,
46.825
],
[
-114.1038,
46.8325
],
[
-114.1122,
46.8325
],
[
-114.1122,
46.825
]
]
]
},
"crs": {
"type": "name",
"properties": {
"name": "EPSG:4326"
}
}
}
],
"else": "fbfm40.fuel_load.1hr"
}
]
}'
  • crs sets the geometry’s CRS as a GeoJSON CRS object. Without it, the coordinates are read in the domain’s CRS.
  • target picks what is tested. With centroid (the default), each cell’s center is tested. With cell, within matches cells entirely inside the geometry, intersects matches every cell the geometry touches, and outside matches cells it doesn’t touch.
  • buffer_m widens the geometry by that many meters before testing.

To limit an operation to a stored feature, add a feature condition with "source": "feature" and the feature’s feature_id. The feature must be completed and in the same domain. Here cells whose center lies within 10 m of a road take 0 kg/m**2.

POST grids/compose — feature condition
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "1-hr load with roads cleared",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
}
],
"operations": [
{
"type": "select",
"output": "fuel_load.1hr",
"from": "fbfm40.fuel_load.1hr",
"conditions": [
{
"source": "feature",
"operator": "outside",
"feature_id": "your-road-feature-id",
"buffer_m": 10
}
],
"else": {
"type": "literal",
"value": 0,
"unit": "kg/m**2"
}
}
]
}'

operator, target, and buffer_m work as for a geometry condition.

To compute the fallback, set else to an inline compute: an object with operator and operands. Here cells where the FCCS 1-hour load is 0 keep the FBFM40 load, and all other cells take the average of the two.

POST grids/compose — inline compute fallback
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "Averaged 1-hr load with FBFM40 infill",
"inputs": [
{
"grid_id": "your-lookup-grid-id",
"alias": "fbfm40"
},
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "select",
"output": "fuel_load.1hr",
"from": "fbfm40.fuel_load.1hr",
"conditions": [
{
"band": "fccs.fuel_load.1hr",
"operator": "eq",
"value": 0
}
],
"else": {
"operator": "average",
"operands": [
"fbfm40.fuel_load.1hr",
"fccs.fuel_load.1hr"
]
}
}
]
}'

The inline compute follows the operand rules of a compute operation, has no conditions of its own, and its unit must be compatible with the output’s.

An operation cannot read another operation’s output: every band reference names an input alias. To get a result that needs more than one step, use one compute with many operands or chain two composes.

To sum, multiply, average, or take the minimum or maximum of several bands, list them all as operands of one compute. Here the FCCS 1-hour, litter, and 10-hour loads are summed.

POST grids/compose — three operands
curl -X 'POST' \
'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
-H 'accept: application/json' \
-H 'api-key: my-api-key' \
-H 'Content-Type: application/json' \
-d '{
"name": "FCCS dead surface load",
"inputs": [
{
"grid_id": "your-fccs-lookup-grid-id",
"alias": "fccs"
}
],
"operations": [
{
"type": "compute",
"output": "fuel_load.dead",
"operator": "add",
"operands": [
"fccs.fuel_load.1hr",
"fccs.fuel_load.litter",
"fccs.fuel_load.10hr"
]
}
]
}'

To mix operators, such as (a + b) ÷ c, compose the first step, then pass the resulting grid as an input to a second compose. A composed grid is on the same lattice as its inputs, so it combines with them.

  1. Compose a + b — here the 1-hour plus live herbaceous load.

    Step 1 — a + b
    curl -X 'POST' \
    'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
    -H 'accept: application/json' \
    -H 'api-key: my-api-key' \
    -H 'Content-Type: application/json' \
    -d '{
    "name": "Fine fuel load",
    "inputs": [
    {
    "grid_id": "your-lookup-grid-id",
    "alias": "fbfm40"
    }
    ],
    "operations": [
    {
    "type": "compute",
    "output": "fuel_load.fine",
    "operator": "add",
    "operands": [
    "fbfm40.fuel_load.1hr",
    "fbfm40.fuel_load.live_herb"
    ]
    }
    ]
    }'

    Record its id, your-composed-grid-id, and poll it to completed.

  2. Compose (a + b) ÷ c, with the step 1 grid as one input — here the fine fuel load divided by the fuel depth. The condition sends cells with zero depth to else.

    Step 2 — (a + b) ÷ c
    curl -X 'POST' \
    'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/compose' \
    -H 'accept: application/json' \
    -H 'api-key: my-api-key' \
    -H 'Content-Type: application/json' \
    -d '{
    "name": "Fine fuel bulk density",
    "inputs": [
    {
    "grid_id": "your-composed-grid-id",
    "alias": "fine"
    },
    {
    "grid_id": "your-lookup-grid-id",
    "alias": "fbfm40"
    }
    ],
    "operations": [
    {
    "type": "compute",
    "output": "bulk_density.fine",
    "operator": "divide",
    "operands": [
    "fine.fuel_load.fine",
    "fbfm40.fuel_depth"
    ],
    "conditions": [
    {
    "band": "fbfm40.fuel_depth",
    "operator": "gt",
    "value": 0
    }
    ],
    "else": {
    "type": "literal",
    "value": 0,
    "unit": "kg/m**3"
    }
    }
    ]
    }'

A request that passes validation can still fail while the grid is built. The grid then lands in status: failed with an error. One cause is a divide whose denominator is zero in some cells. Here, step 2 above without its condition:

{
"id": "your-grid-id",
"domain_id": "your-domain-id",
"name": "Fine fuel bulk density",
"description": "",
"status": "failed",
"progress": {
"percent": 100,
"message": "Failed"
},
"created_on": "2026-10-16T20:58:55.558944Z",
"modified_on": "2026-10-16T20:58:56.821182Z",
"checksum": "9a39f175cb7e44718db3a0f0a129f805",
"source": {
"operations": [
{
"type": "compute",
"operator": "divide",
"output": "bulk_density.fine",
"operands": ["fine.fuel_load.fine", "fbfm40.fuel_depth"]
}
],
"bands": [
{
"unit": "kg/m**3",
"nodata": null,
"type": "continuous",
"description": null,
"name": null,
"summary": null,
"index": 0,
"key": "bulk_density.fine"
}
],
"name": "compose",
"inputs": [
{
"grid_id": "your-composed-grid-id",
"source_grid_checksum": "12450d8466a9403aa8bf2496f2ed8339",
"alias": "fine"
},
{
"grid_id": "your-lookup-grid-id",
"source_grid_checksum": "fc406cea560c4a50bd5a2d7509fa1b59",
"alias": "fbfm40"
}
]
},
"modifications": [],
"bands": [
{
"key": "bulk_density.fine",
"name": null,
"description": null,
"type": "continuous",
"unit": "kg/m**3",
"index": 0,
"nodata": null,
"summary": null
}
],
"georeference": null,
"error": {
"code": "COMPOSE_NON_FINITE_RESULT",
"message": "Compose computation produced non-finite values, such as inf or NaN, in valid cells.",
"suggestion": "Check divide operations for zero denominators, or add a condition (with an else fallback) that routes those cells to the fallback instead of the computed result."
},
"chunks": {
"shape": [512, 512],
"count": null,
"count_by_axis": null
},
"tags": []
}

To fix it, add a condition that sends those cells to else, as in Chain two composes, and submit the request again.

Each of these returns 422 and creates no grid. The Python SDK raises ValueError before sending the request for some of them: an output reference, a missing band, a duplicate output, and conditions without else.

  • Referencing another operation’s output. An operand such as "fuel_load.fine" names a band that another operation in the same request produces, not an input band. Chain two composes instead.

    {
    "detail": "Band reference 'fuel_load.fine' must use an input alias, e.g. 'a.fuel_load.1hr'."
    }
  • Referencing a band the input doesn’t have. Use a band listed in the error, or add an input that carries the band.

    {
    "detail": "Source grid your-fccs-lookup-grid-id is missing required bands: ['fuel_depth']. Available bands: ['fuel_load.10hr', 'fuel_load.1hr', 'fuel_load.litter', 'fuel_load.live_herb']"
    }
  • An output key that doesn’t match the result. Here a load divided by a depth (kg/m**3) is named fuel_load.1hr (kg/m**2). Name the output after what it holds — bulk_density.1hr — or, for a compatible unit, set unit on the compute.

    {
    "detail": "Output band 'fuel_load.1hr' doesn't match what this operation produces: 'fuel_load' bands are continuous with kg/m**2, not continuous with kg/m**3. Name the output after what it holds, or set `unit` on a compute to convert."
    }
  • An output key that isn’t a band key. Start the key with one of the quantities the error lists.

    {
    "detail": [
    {
    "type": "value_error",
    "loc": ["body", "operations", 0, "select", "output"],
    "msg": "Value error, 'fine_fuel' is not a band key. Use <quantity> or <quantity>.<label>, with a quantity from ['annual_disturbance', 'aspect', 'bulk_density', 'cbd', 'cbh', 'cc', 'cfl', 'chm', 'curing', 'duff_depth', 'elevation', 'fbfm', 'fbfm13', 'fccs', 'fuel_depth', 'fuel_load', 'fuel_moisture', 'heat_content', 'irradiance', 'leaf_area_density', 'plt_cn', 'savr', 'slope', 'spcd', 'tm_id', 'tree_id', 'volume_fraction'] and a label of lowercase letters, digits and underscores.",
    "input": "fine_fuel",
    "ctx": {
    "error": {}
    }
    }
    ]
    }
  • A categorical band as a compute operand. Use the categorical band in a condition instead, as in Choose a source grid by fuel model.

    {
    "detail": "Compute operands must reference continuous bands."
    }
  • gt, lt, ge, or le on a categorical band. Use eq, ne, or in with a list of codes or labels.

    {
    "detail": "Categorical compose conditions support only eq, ne, and in."
    }
  • conditions without else. Add an else value.

    {
    "detail": [
    {
    "type": "value_error",
    "loc": ["body", "operations", 0, "select"],
    "msg": "Value error, `else` is required when `conditions` are present.",
    "input": {
    "type": "select",
    "output": "fuel_load.1hr",
    "from": "fbfm40.fuel_load.1hr",
    "conditions": [
    {
    "band": "fbfm40.fuel_depth",
    "operator": "gt",
    "value": 0
    }
    ]
    },
    "ctx": {
    "error": {}
    }
    }
    ]
    }
  • The same output twice. Give each operation its own output key.

    {
    "detail": [
    {
    "type": "value_error",
    "loc": ["body", "operations"],
    "msg": "Value error, Duplicate values are not allowed: fuel_load.1hr",
    "input": [
    {
    "type": "select",
    "output": "fuel_load.1hr",
    "from": "fbfm40.fuel_load.1hr"
    },
    {
    "type": "select",
    "output": "fuel_load.1hr",
    "from": "fccs.fuel_load.1hr"
    }
    ],
    "ctx": {
    "error": {}
    }
    }
    ]
    }
  • Inputs on different lattices. Resample the grid named in the error onto the other grid, as the error says. See Resample or reproject a grid.

    {
    "detail": "Grid grid-to-resample has horizontal shape (15, 22), but grid your-lookup-grid-id has (30, 45). Resample grid grid-to-resample with alignment.target='grid' and alignment.grid_id='your-lookup-grid-id' before using these grids together."
    }

Inputs must also be 2D and completed; a 3D grid or a grid that is still processing is rejected.