diff --git a/docs/guides/point_clouds.md b/docs/guides/point_clouds.md new file mode 100644 index 0000000..5bf3163 --- /dev/null +++ b/docs/guides/point_clouds.md @@ -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. diff --git a/docs/tutorials/point_cloud_example.md b/docs/tutorials/point_cloud_example.md index ba9f9ee..734ba01 100644 --- a/docs/tutorials/point_cloud_example.md +++ b/docs/tutorials/point_cloud_example.md @@ -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). diff --git a/mkdocs.yml b/mkdocs.yml index e9dbe26..aa72ac8 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -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