Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 61 additions & 0 deletions docs/tutorials/export_to_quicfire.md
Original file line number Diff line number Diff line change
Expand Up @@ -395,8 +395,16 @@ features_config = {

##### Tree Inventory Configuration

The tree inventory configuration supports three data sources:

1. **TreeMap** (default): Nationwide tree data from USFS TreeMap
2. **Point Cloud**: ALS-derived trees from 3DEP point cloud data
3. **File Upload**: Your own tree inventory from a CSV file

```python
# Default: TreeMap source
tree_inventory_config = {
"source": "TreeMap", # "TreeMap", "pointcloud", or "file"
"version": "2022", # TreeMap version: "2014", "2016", "2020", "2022"
"seed": 42, # Random seed for reproducibility (optional)
"featureMasks": ["road", "water"], # Features to mask from inventory
Expand All @@ -423,6 +431,18 @@ tree_inventory_config = {
}
]
}

# ALS Point Cloud source
tree_inventory_config = {
"source": "pointcloud",
"pointCloudSources": ["3DEP"] # ALS data sources (default: ["3DEP"])
}

# Custom file upload
tree_inventory_config = {
"source": "file",
"filePath": "/path/to/my_trees.csv" # Required when source is "file"
}
```

**Available modification attributes:**
Expand Down Expand Up @@ -507,6 +527,47 @@ tree_inventory_config = {
}
```

**Use ALS point cloud for high-resolution tree data:**

```python
# Use ALS-derived trees from 3DEP point cloud data
tree_inventory_config = {
"source": "pointcloud",
"pointCloudSources": ["3DEP"]
}

export = export_roi(
roi, "als_trees_export.zip",
tree_inventory_config=tree_inventory_config,
verbose=True # Recommended - point cloud processing takes longer
)
```

**Use your own tree inventory data:**

```python
# Use custom tree data from a CSV file
tree_inventory_config = {
"source": "file",
"filePath": "/path/to/field_measurements.csv"
}

export = export_roi(
roi, "custom_trees_export.zip",
tree_inventory_config=tree_inventory_config
)
```

Your CSV file must include these columns:
- `TREE_ID` (Integer): Unique identifier for each tree
- `SPCD` (Integer): FIA species code
- `STATUSCD` (Integer): Tree status (1: Live, 2: Dead, etc.)
- `DIA` (Float): Diameter in cm
- `HT` (Float): Height in meters
- `CR` (Float): Crown ratio (0-1)
- `X` (Float): X coordinate in projected CRS
- `Y` (Float): Y coordinate in projected CRS

#### Configuration Tips

1. **Partial Overrides**: Only specify the parameters you want to change. The function will merge your configuration with sensible defaults.
Expand Down
24 changes: 23 additions & 1 deletion docs/tutorials/point_cloud_example.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,5 +100,27 @@ tree_inventory = Inventories.from_domain_id(
tree_inventory.wait_until_completed()
```

## Using the Convenience Function

You can also use ALS point cloud data through the `export_roi()` convenience function by specifying `"pointcloud"` as the tree inventory source:

```python
from fastfuels_sdk import export_roi

tree_inventory_config = {
"source": "pointcloud",
"pointCloudSources": ["3DEP"]
}

export = export_roi(
roi=roi,
export_path="als_export.zip",
tree_inventory_config=tree_inventory_config,
verbose=True # Recommended - point cloud processing takes longer
)
```

This handles the entire workflow (point cloud creation, tree inventory, grids, and export) in a single function call.

## Next Steps
For additional guidance and complementary workflows, refer back to the Create and Export QUIC-Fire Inputs with FastFuels SDK tutorial.
For additional guidance and complementary workflows, refer back to the "Create and Export QUIC-Fire Inputs with FastFuels SDK" tutorial.
111 changes: 96 additions & 15 deletions fastfuels_sdk/convenience.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
from fastfuels_sdk.grids import Grids
from fastfuels_sdk.features import Features
from fastfuels_sdk.inventories import Inventories
from fastfuels_sdk.pointclouds import PointClouds
from fastfuels_sdk.grids import (
TopographyGridBuilder,
SurfaceGridBuilder,
Expand Down Expand Up @@ -103,6 +104,7 @@ def _deep_merge(base: Dict[str, Any], override: Dict[str, Any]) -> None:
}

DEFAULT_TREE_INVENTORY_CONFIG = {
"source": "TreeMap",
"version": "2022",
"featureMasks": ["road", "water"],
"canopyHeightMapSource": "Meta2024",
Expand Down Expand Up @@ -252,6 +254,63 @@ def _configure_tree_builder(domain_id: str, config: Dict[str, Any]):
return builder.build()


def _create_tree_inventory(
domain_id: str, config: Dict[str, Any], verbose: bool = False
):
"""Create tree inventory based on configuration source.

Args:
domain_id: The domain ID to create the inventory for
config: Tree inventory configuration dictionary
verbose: If True, prints progress messages

Returns:
The created tree inventory object
"""
source = config.get("source", "TreeMap")
inventories = Inventories.from_domain_id(domain_id)

if source == "TreeMap":
return inventories.create_tree_inventory_from_treemap(
version=config.get("version", "2022"),
seed=config.get("seed"),
canopy_height_map_source=config.get("canopyHeightMapSource"),
modifications=config.get("modifications"),
treatments=config.get("treatments"),
feature_masks=config.get("featureMasks", []),
)

elif source == "pointcloud":
# First create the ALS point cloud
if verbose:
print("Creating ALS point cloud for the domain")
pc = PointClouds.from_domain_id(domain_id)
point_cloud_sources = config.get("pointCloudSources", ["3DEP"])
als = pc.create_als_point_cloud(sources=point_cloud_sources)
als.wait_until_completed(verbose=verbose)

# Then create tree inventory from point cloud
if verbose:
print("Creating tree inventory from point cloud")
return inventories.create_tree_inventory_from_point_cloud()

elif source == "file":
file_path = config.get("filePath")
if not file_path:
raise ValueError("filePath is required when source is 'file'")
if verbose:
print(f"Creating tree inventory from file: {file_path}")
return inventories.create_tree_inventory_from_file_upload(
file_path=Path(file_path)
)

else:
raise ValueError(
f"Unknown tree inventory source: {source}. "
f"Supported sources are: 'TreeMap', 'pointcloud', 'file'"
)


def export_roi(
roi: GeoDataFrame,
export_path: Path | str,
Expand Down Expand Up @@ -367,9 +426,12 @@ def export_roi(
Structure::

{
"source": "TreeMap" | "pointcloud" | "file", # Data source (default: "TreeMap")

# TreeMap options (when source="TreeMap"):
"version": "2022", # TreeMap version
"featureMasks": ["road", "water"], # Features to mask out
"canopyHeightMapSource": "Meta2024", # High-resolution canopy height map
"version": "2022", # TreeMap version
"seed": 42, # Random seed for reproducibility
"modifications": [ # Tree attribute modifications
{
Expand All @@ -383,7 +445,13 @@ def export_roi(
"targetMetric": "basalArea",
"targetValue": 25.0
}
]
],

# Point cloud options (when source="pointcloud"):
"pointCloudSources": ["3DEP"], # ALS data sources (default: ["3DEP"])

# File upload options (when source="file"):
"filePath": "/path/to/trees.csv", # Path to CSV file (required)
}

Returns
Expand Down Expand Up @@ -469,6 +537,28 @@ def export_roi(
... tree_inventory_config=tree_inventory_config
... )

Use ALS point cloud data for tree inventory:

>>> tree_inventory_config = {
... "source": "pointcloud",
... "pointCloudSources": ["3DEP"]
... }
>>> export = export_roi(
... roi, "als_trees.zip",
... tree_inventory_config=tree_inventory_config
... )

Use custom tree inventory from CSV file:

>>> tree_inventory_config = {
... "source": "file",
... "filePath": "/path/to/my_trees.csv"
... }
>>> export = export_roi(
... roi, "custom_trees.zip",
... tree_inventory_config=tree_inventory_config
... )

Notes
-----
This function performs the complete export workflow:
Expand Down Expand Up @@ -569,19 +659,10 @@ def export_roi(
feature_grid = None

# Create tree inventory using configuration
if verbose:
print("Creating tree inventory for the domain")
tree_inventory = Inventories.from_domain_id(
domain.id
).create_tree_inventory_from_treemap(
version=merged_tree_inventory_config.get("version", "2022"),
seed=merged_tree_inventory_config.get("seed"),
canopy_height_map_source=merged_tree_inventory_config.get(
"canopyHeightMapSource"
),
modifications=merged_tree_inventory_config.get("modifications"),
treatments=merged_tree_inventory_config.get("treatments"),
feature_masks=merged_tree_inventory_config.get("featureMasks", []),
tree_inventory = _create_tree_inventory(
domain_id=domain.id,
config=merged_tree_inventory_config,
verbose=verbose,
)

# Create surface grid using configuration
Expand Down
Loading