Remove trees with road and water features
You are viewing in-progress documentation for v2 (Beta). Switch to the stable version for the current production release.
A tree inventory shouldn’t put trees in a lake or on a road. This guide removes those trees by testing each one against a road or water Feature resource on the domain — the inventory counterpart to masking a fuel grid.
Two recipes:
- Remove every tree within 5 m of water (recipe C).
- Remove only large trees within 5 m of a road — a spatial condition combined with a DBH threshold (recipe D).
Trees are points, so a spatial condition is a point-in-polygon test. There’s
no target field (nothing to choose for a point), and because a point
almost never lands exactly on a feature’s edge, buffer_m is how you give
the feature width. For the concepts, see
About modifications.
The modifications attach to the inventory create call, so the trees are filtered as the inventory is generated — there’s no separate edit step.
Prerequisites
Section titled “Prerequisites”-
An API key: my-api-key.
-
A domain in a projected CRS: your-domain-id. 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 your-road-feature-id and your-water-feature-id. -
A completed PIM source grid. A TreeMap PIM inventory is generated from a
pimgrid, so create one first:Create TreeMap PIM grid curl -X 'POST' \'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/grids/pim/treemap' \-H 'accept: application/json' \-H 'api-key: my-api-key' \-H 'Content-Type: application/json' \-d '{"name": "TreeMap PIM grid"}'Poll it to
completed(it’s small — a TreeMap grid is coarse), then record your-pim-grid-id:{"id": "your-pim-grid-id","domain_id": "your-domain-id","name": "TreeMap PIM grid","description": "","status": "completed","progress": {"percent": 100,"message": "Complete"},"created_on": "2026-06-02T19:02:18.555673Z","modified_on": "2026-06-02T19:02:21.344594Z","source": {"bands": ["tm_id"],"product": "treemap","name": "pim","alignment": {"target": "domain","resolution": null,"method": null},"version": "2022","extent_buffer_cells": 0,"description": "TreeMap plot imputation raster (FIA plot IDs at 30m)"},"modifications": [],"bands": [{"key": "tm_id","type": "categorical","unit": null,"index": 0,"nodata": 4294967295}],"georeference": {"crs": "EPSG:32611","transform": [29.703174954781062, 0.0, 717141.6556112946, 0.0, -29.703174954785382,5328277.037140956],"shape": [59, 53]},"error": null,"chunks": {"shape": [512, 512],"count": 1,"count_by_axis": {"y": 1,"x": 1}},"tags": []}
Recipe C — remove every tree within 5 m of water
Section titled “Recipe C — remove every tree within 5 m of water”Create the inventory with one modification rule: a within test against the
water feature, grown by a 5 m buffer_m, and a remove action. remove
must be the only action in its rule — you can’t both drop a tree and edit it.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/inventories/tree/pim' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Trees with 5 m water buffer removed", "source_pim_grid_id": "your-pim-grid-id", "seed": 42, "modifications": [ { "conditions": [ {"source": "feature", "operator": "within", "feature_id": "your-water-feature-id", "buffer_m": 5} ], "actions": [ {"modifier": "remove"} ] } ]}'import requests
API_KEY = "my-api-key"DOMAIN_ID = "your-domain-id"PIM_GRID_ID = "your-pim-grid-id"WATER_FEATURE_ID = "your-water-feature-id"BASE = "https://api-v2-prod-nyvjyh5ywa-uw.a.run.app"
resp = requests.post( f"{BASE}/domains/{DOMAIN_ID}/inventories/tree/pim", headers={"api-key": API_KEY}, json={ "name": "Trees with 5 m water buffer removed", "source_pim_grid_id": PIM_GRID_ID, "seed": 42, "modifications": [ { # Trees are points: test each tree against the water polygon # grown by 5 m. No `target` field — there's nothing to choose. "conditions": [ { "source": "feature", "operator": "within", "feature_id": WATER_FEATURE_ID, "buffer_m": 5, } ], "actions": [{"modifier": "remove"}], } ], },)resp.raise_for_status()inv = resp.json()print(inv["id"], inv["status"]) # -> <inventory id> pending{ "id": "your-inventory-id", "domain_id": "your-domain-id", "type": "tree", "name": "Trees with 5 m water buffer removed", "description": "", "status": "pending", "progress": null, "created_on": "2026-06-02T19:03:44.532009", "modified_on": "2026-06-02T19:03:44.532009", "source": { "name": "pim", "source_pim_grid_id": "your-pim-grid-id", "point_process": "inhomogeneous_poisson", "seed": 42 }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "within", "feature_id": "your-water-feature-id", "buffer_m": 5.0 } ], "actions": [ { "modifier": "remove" } ] } ], "columns": [ { "key": "x", "type": "continuous", "unit": "m" }, { "key": "y", "type": "continuous", "unit": "m" }, { "key": "fia_species_code", "type": "categorical", "unit": null }, { "key": "fia_status_code", "type": "categorical", "unit": null }, { "key": "dbh", "type": "continuous", "unit": "cm" }, { "key": "height", "type": "continuous", "unit": "m" }, { "key": "crown_ratio", "type": "continuous", "unit": null } ], "georeference": null, "error": null, "tags": []}The 201 echoes your rule and mints an inventory id — record it:
your-inventory-id. Status starts pending; the removal runs
server-side while the inventory is built.
Poll until completed
Section titled “Poll until completed”curl -X 'GET' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/inventories/your-inventory-id' \ -H 'accept: application/json' \ -H 'api-key: my-api-key'{ "id": "your-inventory-id", "domain_id": "your-domain-id", "type": "tree", "name": "Trees with 5 m water buffer removed", "description": "", "status": "completed", "progress": { "percent": 100, "message": "Complete" }, "created_on": "2026-06-02T19:03:44.532009Z", "modified_on": "2026-06-02T19:03:54.274920Z", "source": { "seed": 42, "name": "pim", "source_pim_grid_id": "your-pim-grid-id", "point_process": "inhomogeneous_poisson" }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "within", "feature_id": "your-water-feature-id", "buffer_m": 5.0 } ], "actions": [ { "modifier": "remove" } ] } ], "columns": [ { "key": "x", "type": "continuous", "unit": "m" }, { "key": "y", "type": "continuous", "unit": "m" }, { "key": "fia_species_code", "type": "categorical", "unit": null }, { "key": "fia_status_code", "type": "categorical", "unit": null }, { "key": "dbh", "type": "continuous", "unit": "cm" }, { "key": "height", "type": "continuous", "unit": "m" }, { "key": "crown_ratio", "type": "continuous", "unit": null } ], "georeference": { "crs": "EPSG:32611", "bounds": [ 717141.6556112946, 5326524.549818624, 718694.8955395881, 5328248.228078655 ] }, "error": null, "tags": []}On this domain the rule removes 763 trees (82,961 → 82,198) — the ones inside the water bodies plus the 5 m shoreline tolerance. The shaded ring is that removal zone:
Recipe D — remove only large trees within 5 m of a road
Section titled “Recipe D — remove only large trees within 5 m of a road”Add a second condition to the same rule. Conditions are ANDed, so a tree is
removed only if it’s both inside the buffered road and above a DBH
threshold. Here: trees over 10 inches DBH within 5 m of a road. The
unit: "in" converts the threshold to the native cm before comparing, so
you don’t have to do the arithmetic.
curl -X 'POST' \ 'https://api-v2-prod-nyvjyh5ywa-uw.a.run.app/domains/your-domain-id/inventories/tree/pim' \ -H 'accept: application/json' \ -H 'api-key: my-api-key' \ -H 'Content-Type: application/json' \ -d '{ "name": "Large trees within 5 m of roads removed", "source_pim_grid_id": "your-pim-grid-id", "seed": 42, "modifications": [ { "conditions": [ {"source": "feature", "operator": "within", "feature_id": "your-road-feature-id", "buffer_m": 5}, {"attribute": "dbh", "operator": "gt", "value": 10, "unit": "in"} ], "actions": [ {"modifier": "remove"} ] } ]}'{ "id": "your-inventory-id", "domain_id": "your-domain-id", "type": "tree", "name": "Large trees within 5 m of roads removed", "description": "", "status": "pending", "progress": null, "created_on": "2026-06-02T19:03:44.775154", "modified_on": "2026-06-02T19:03:44.775154", "source": { "name": "pim", "source_pim_grid_id": "your-pim-grid-id", "point_process": "inhomogeneous_poisson", "seed": 42 }, "modifications": [ { "conditions": [ { "source": "feature", "operator": "within", "feature_id": "your-road-feature-id", "buffer_m": 5.0 }, { "attribute": "dbh", "operator": "gt", "value": 10, "unit": "in" } ], "actions": [ { "modifier": "remove" } ] } ], "columns": [ { "key": "x", "type": "continuous", "unit": "m" }, { "key": "y", "type": "continuous", "unit": "m" }, { "key": "fia_species_code", "type": "categorical", "unit": null }, { "key": "fia_status_code", "type": "categorical", "unit": null }, { "key": "dbh", "type": "continuous", "unit": "cm" }, { "key": "height", "type": "continuous", "unit": "m" }, { "key": "crown_ratio", "type": "continuous", "unit": null } ], "georeference": null, "error": null, "tags": []}This removes 196 trees — only the large ones in the road corridor. Smaller trees inside the same buffer survive, because the DBH condition narrows the spatial one. Drop the DBH condition and the rule would remove every tree in the buffer instead.
within vs intersects for trees
Section titled “within vs intersects for trees”Both operators work on point geometries, and for a tree-against-polygon test
they behave the same: a point is “within” a polygon exactly when it
“intersects” it. Use within for readability (“trees within the water”).
outside is the inverse — keep it for “everything except near the feature.”
Common pitfalls
Section titled “Common pitfalls”-
removecombined with another action.removemust be the sole action in its rule — “remove this tree and also set its height” is incoherent, and the API rejects it. To edit surviving trees and remove others, use two separate rules. -
Expecting
buffer_m: 0to catch trees near water. It won’t — see the caution above. Give the feature width with a non-zero buffer. -
Wrong DBH units. DBH’s native unit is centimeters. A bare
"value": 10means 10 cm, not 10 inches. Add"unit": "in"(as in recipe D) when you mean inches — the server converts before comparing. -
Same-domain rule. The road/water feature must live in the same domain as the inventory. A feature from another domain is rejected.