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
160 changes: 160 additions & 0 deletions docs/guides/point_clouds.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
# How to Create a Tree Inventory from Point Cloud Data

This guide shows you how to create a tree inventory from airborne laser scanning (ALS) point cloud data. Point cloud-derived inventories use LiDAR to segment individual trees, producing high-fidelity tree locations and heights directly from remote sensing data.

For learning the basics, see the [Getting Started tutorial]. For detailed reference, see the [API Reference].

## How to Create an ALS Point Cloud from 3DEP

Before creating a tree inventory from point cloud data, you need an ALS point cloud resource in your domain. The simplest approach uses publicly available [USGS 3DEP](https://www.usgs.gov/3d-elevation-program) data:

```python
from fastfuels_sdk import PointClouds

# Initialize from your domain
point_clouds = PointClouds.from_domain_id("your_domain_id")

# Create ALS point cloud from 3DEP data
als_point_cloud = point_clouds.create_als_point_cloud(sources=["3DEP"])

# Wait for processing to complete (this may take several minutes)
als_point_cloud.wait_until_completed(verbose=True)
```

Processing time depends on your domain size. Use `verbose=True` to monitor progress.

## How to Create a Tree Inventory from Point Cloud Data

Once the ALS point cloud is completed, create a tree inventory from it:

```python
from fastfuels_sdk import Inventories

# Initialize from your domain
inventories = Inventories.from_domain_id("your_domain_id")

# Create tree inventory from the point cloud
tree_inventory = inventories.create_tree_inventory_from_point_cloud()

# Wait for processing to complete
tree_inventory = tree_inventory.wait_until_completed()
```

The point cloud processing pipeline segments individual trees from the ALS data to derive tree locations and heights.

## How to Check Point Cloud Status

To check whether a domain already has an ALS point cloud:

```python
point_clouds = PointClouds.from_domain_id("your_domain_id")

if point_clouds.als:
print(f"ALS point cloud status: {point_clouds.als.status}")
else:
print("No ALS point cloud exists for this domain")
```

To refresh the status of an existing point cloud:

```python
als_point_cloud = point_clouds.als.get()
print(f"Status: {als_point_cloud.status}")
```

## How to Delete an ALS Point Cloud

To remove an ALS point cloud from your domain:

```python
point_clouds = PointClouds.from_domain_id("your_domain_id")

if point_clouds.als:
point_clouds.als.delete()
```

## Complete Workflow Example

This example demonstrates the full workflow from domain creation to tree inventory export:

```python
import geopandas as gpd
from shapely.geometry import Polygon

from fastfuels_sdk import Domain, PointClouds, Inventories

# Step 1: Create a domain
coordinates = [
[-113.43635, 46.89739],
[-113.44842, 46.88948],
[-113.44763, 46.88741],
[-113.44994, 46.88585],
[-113.44924, 46.88430],
[-113.44274, 46.88390],
[-113.43539, 46.88237],
[-113.42006, 46.88523],
[-113.42329, 46.88920],
[-113.42361, 46.89257],
[-113.42635, 46.89528],
[-113.42696, 46.89597],
[-113.42854, 46.89916],
[-113.42711, 46.90211],
[-113.42798, 46.90336],
[-113.42933, 46.90300],
[-113.43242, 46.90099],
[-113.43446, 46.89802],
[-113.43635, 46.89739],
]

polygon = Polygon(coordinates)
roi = gpd.GeoDataFrame(geometry=[polygon], crs="EPSG:4326")

domain = Domain.from_geodataframe(
geodataframe=roi,
name="Lubrecht ALS Example",
description="Lubrecht Experimental Forest",
horizontal_resolution=2.0,
vertical_resolution=1.0,
)

# Step 2: Create ALS point cloud
als_point_cloud = (
PointClouds.from_domain_id(domain.id)
.create_als_point_cloud(sources=["3DEP"])
)
als_point_cloud.wait_until_completed(verbose=True)

# Step 3: Create tree inventory from point cloud
tree_inventory = (
Inventories.from_domain_id(domain.id)
.create_tree_inventory_from_point_cloud()
)
tree_inventory = tree_inventory.wait_until_completed()

# Step 4: Export the inventory
export = tree_inventory.create_export("csv")
export = export.wait_until_completed()
export.to_file("point_cloud_trees.csv")
```

## Using the Convenience Function

You can accomplish the entire point cloud workflow with a single call to `export_roi()`:

```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,
)
```

This handles point cloud creation, tree inventory generation, grid creation, and export in a single function call.
154 changes: 77 additions & 77 deletions docs/tutorials/point_cloud_example.md
Original file line number Diff line number Diff line change
@@ -1,126 +1,126 @@

# Tutorial: Incorporate ALS Point Cloud Data into a FastFuels Domain
This tutorial demonstrates how to incorporate airborne laser scanning (ALS / LiDAR) point cloud data into an existing FastFuels domain. It utilizes a point cloud–derived canopy structure to inform the tree inventory and canopy fuel grids, enabling higher‐fidelity fuels representation.

This tutorial walks you through incorporating airborne laser scanning (ALS / LiDAR) point cloud data into a FastFuels domain. Unlike TreeMap-based inventories that rely on statistical imputation, point cloud-derived inventories use LiDAR to segment individual trees directly from remote sensing data. This produces higher-fidelity tree locations and heights, which in turn enables more accurate canopy fuel grids for fire modeling.

## Prerequisites

Before starting this tutorial, make sure you have:

- FastFuels SDK installed (`pip install fastfuels-sdk`)
- A valid FastFuels API key
- Basic familiarity with Python and GeoPandas

## What You'll Learn

By the end of this tutorial, you'll know how to:
- Create an ALS point cloud within a FastFuels domain
- Generate a tree inventory informed by the ALS data
By the end of this tutorial, you'll understand:

- Why point cloud data produces higher-fidelity tree inventories than TreeMap
- How the ALS pipeline works: point cloud creation, tree segmentation, and inventory generation
- When to choose point cloud-based inventories over other data sources

## When to Use Point Cloud Data

FastFuels supports three tree inventory sources, each suited to different use cases:

- **TreeMap**: Nationwide statistical coverage based on FIA plot data. Best for large-area studies where broad coverage matters more than local precision.
- **File upload**: Your own field measurements. Best when you have site-specific survey data.
- **Point cloud**: ALS/LiDAR-derived tree segmentation. Best when you need spatially explicit tree positions and heights that reflect actual canopy structure.

Point cloud inventories are particularly valuable when:

- Your study area has 3DEP LiDAR coverage (check [USGS 3DEP availability](https://www.usgs.gov/3d-elevation-program))
- Accurate tree positions matter for your fire modeling scenario
- You want tree heights derived from direct measurement rather than statistical imputation

The trade-off is processing time — point cloud inventories take longer to generate than TreeMap inventories because the pipeline must download, process, and segment the raw LiDAR data.

## Step 1: Create a FastFuels Domain
## Step 1: Create a Domain

First, define a region of interest. This example uses an area in the Lubrecht Experimental Forest in Montana:

For the polygon created by the coordinates:
```python
import geopandas as gpd
from shapely.geometry import Polygon
from fastfuels_sdk import Domain

# Define the polygon coordinates for Lubrecht area
coordinates = [
[-113.43635167116199,46.89738742250387],
[-113.44842074255764,46.8894785976307],
[-113.44763362920567,46.88740657162339],
[-113.44993666456837,46.8858524995899],
[-113.44923700825561,46.88429838253265],
[-113.44273603501621,46.88389988372731],
[-113.43538964373204,46.882365635689325],
[-113.42005550954369,46.88523484307359],
[-113.42329141999039,46.88919967571519],
[-113.42361209580038,46.892566564773205],
[-113.4263524163587,46.89527586047467],
[-113.42696461563226,46.895973083547176],
[-113.42853884233625,46.89916027357725],
[-113.42711037736427,46.90210825569156],
[-113.42798494775504,46.90336309078498],
[-113.42932595568779,46.903004569469715],
[-113.43241610440292,46.90099282206219],
[-113.43445676864847,46.8980248588519],
[-113.43635167116199,46.89738742250387]
[-113.43635167116199, 46.89738742250387],
[-113.44842074255764, 46.88947859763070],
[-113.44763362920567, 46.88740657162339],
[-113.44993666456837, 46.88585249958990],
[-113.44923700825561, 46.88429838253265],
[-113.44273603501621, 46.88389988372731],
[-113.43538964373204, 46.88236563568933],
[-113.42005550954369, 46.88523484307359],
[-113.42329141999039, 46.88919967571519],
[-113.42361209580038, 46.89256656477321],
[-113.42635241635870, 46.89527586047467],
[-113.42696461563226, 46.89597308354718],
[-113.42853884233625, 46.89916027357725],
[-113.42711037736427, 46.90210825569156],
[-113.42798494775504, 46.90336309078498],
[-113.42932595568779, 46.90300456946972],
[-113.43241610440292, 46.90099282206219],
[-113.43445676864847, 46.89802485885190],
[-113.43635167116199, 46.89738742250387],
]

# Create a GeoDataFrame
polygon = Polygon(coordinates)
roi = gpd.GeoDataFrame(
geometry=[polygon],
crs="EPSG:4326" # WGS 84 coordinate system
geometry=[Polygon(coordinates)],
crs="EPSG:4326",
)

from fastfuels_sdk.domains import Domain

domain = Domain.from_geodataframe(
geodataframe=roi,
name="Blue Mountain ROI",
description="Test area in Lubrecht Experimental Forest",
horizontal_resolution=2.0, # 2-meter horizontal resolution
vertical_resolution=1.0 # 1-meter vertical resolution
name="Lubrecht ALS Example",
description="Lubrecht Experimental Forest - ALS tutorial",
horizontal_resolution=2.0,
vertical_resolution=1.0,
)

print(f"Created domain with ID: {domain.id}")
```

## Step 2: Create an ALS point cloud within FastFuels
## Step 2: Create an ALS Point Cloud

Next, use 3DEP data to generate a feature point cloud:
With the domain created, the next step is to fetch ALS data. The SDK retrieves publicly available LiDAR data from the USGS 3D Elevation Program (3DEP) and processes it within your domain's spatial extent.

```python
# 1.
from fastfuels_sdk.pointclouds import PointClouds
from fastfuels_sdk import PointClouds

# 2. Correct Method Name & 3. Added Required 'sources' argument
als_pointcloud = (
als_point_cloud = (
PointClouds.from_domain_id(domain.id)
.create_als_point_cloud(sources=["3DEP"])
)

# This works because create_als_point_cloud returns the child object
als_pointcloud.wait_until_completed()

als_point_cloud.wait_until_completed(verbose=True)
```

## Step 3: Create Tree Inventory using the point cloud data
This step downloads the relevant 3DEP tiles, clips them to your domain boundary, and prepares the point cloud for tree segmentation. Processing time depends on domain size and data density — expect anywhere from a few seconds to several minutes.

Create a tree inventory and generate the canopy fuel grid:
## Step 3: Create a Tree Inventory from the Point Cloud

Once the point cloud is ready, the SDK can segment individual trees from it. This process identifies tree tops, delineates crowns, and extracts tree-level attributes (location and height) from the LiDAR returns.

```python
from fastfuels_sdk.inventories import Inventories
from fastfuels_sdk.grids import TreeGridBuilder

# Create tree inventory
# Note: Ensure Step 2 (Point Cloud creation) is fully completed before running this
tree_inventory = Inventories.from_domain_id(
domain.id
).create_tree_inventory_from_point_cloud()
tree_inventory.wait_until_completed()
from fastfuels_sdk import Inventories

tree_inventory = (
Inventories.from_domain_id(domain.id)
.create_tree_inventory_from_point_cloud()
)

tree_inventory = tree_inventory.wait_until_completed()
```

## Using the Convenience Function
The point cloud must be in `"completed"` status before this step. If you call `create_tree_inventory_from_point_cloud()` before the point cloud finishes, the API will return an error.

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

```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
)
```
With a completed tree inventory, you can:

This handles the entire workflow (point cloud creation, tree inventory, grids, and export) in a single function call.
- **Create fuel grids** for fire modeling — see the [Export to QUIC-Fire tutorial](export_to_quicfire.md)
- **Export the inventory** to CSV, Parquet, or GeoJSON for analysis
- **Manage point cloud resources** (check status, delete) — see the [Point Clouds how-to guide](../guides/point_clouds.md)

## Next Steps
For additional guidance and complementary workflows, refer back to the "Create and Export QUIC-Fire Inputs with FastFuels SDK" tutorial.
For quick-reference on the point cloud API, see the [Point Clouds how-to guide](../guides/point_clouds.md).
2 changes: 2 additions & 0 deletions mkdocs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,12 @@ nav:
- Authentication: guides/authentication.md
- Domains: guides/domains.md
- Inventories: guides/inventories.md
- Point Clouds: guides/point_clouds.md
- Features: guides/features.md
- Grids: guides/grids.md
- Tutorials:
- Export to QUIC-Fire: tutorials/export_to_quicfire.md
- ALS Point Cloud: tutorials/point_cloud_example.md

- Reference: reference.md

Expand Down
Loading