From 90d6cec2d3b990b33cde1c0e79205763ac1136df Mon Sep 17 00:00:00 2001 From: amarcozzi Date: Wed, 26 Aug 2026 10:19:45 -0600 Subject: [PATCH 1/2] Regenerate v2 client library for API changes since 2026-08-04 Regenerate fastfuels_sdk/v2/client_library/ via generate_client.sh (openapi-python-client 0.29.0, clean run with no generator warnings). Adds 6 endpoint wrappers (inventory canopy, Fosberg fuel moisture, Leaflux irradiance, point-cloud data metadata/json/binary) and 44 models; model count 263 -> 306. Also brings in seasonal FBFM40 versions, configurable CHM aggregation + spike filter, and the fia_crown_class_code inventory column. The 3DEP coverage response dropped point_budget/exceeds_point_budget; update the live test and the v2 API design note accordingly. Closes #195 --- .../create_fosberg_fuel_moisture_grid.py | 314 ++++++++ .../api/grids/create_inventory_canopy_grid.py | 476 +++++++++++ .../api/grids/create_landfire_fbfm40.py | 40 + .../grids/create_leaflux_irradiance_grid.py | 216 +++++ .../api/grids/create_point_cloud_chm.py | 116 ++- .../api/grids/get_grid_data_json.py | 72 +- .../check_3dep_point_cloud_coverage.py | 28 +- .../point_clouds/create_3dep_point_cloud.py | 20 +- .../api/point_clouds/delete_point_cloud.py | 84 +- .../api/point_clouds/get_point_cloud.py | 84 +- .../get_point_cloud_data_binary.py | 603 ++++++++++++++ .../point_clouds/get_point_cloud_data_json.py | 649 +++++++++++++++ .../get_point_cloud_data_metadata.py | 419 ++++++++++ .../api/point_clouds/list_point_clouds.py | 132 +-- .../list_point_clouds_cross_domain.py | 108 +-- .../api/point_clouds/update_point_cloud.py | 148 ++-- .../v2/client_library/models/__init__.py | 90 +++ .../models/allometry_canopy_biomass_source.py | 78 ++ ...anopy_allometry_max_crown_radius_source.py | 82 ++ .../models/canopy_available_fuel.py | 77 ++ .../models/canopy_biomass_equations.py | 10 + .../models/canopy_branchwood.py | 102 +++ .../canopy_branchwood_size_partition.py | 10 + .../client_library/models/canopy_cbd_depth.py | 11 + .../models/canopy_cbd_load_over_depth.py | 85 ++ .../models/canopy_cbd_running_mean.py | 115 +++ .../client_library/models/canopy_cbh_mean.py | 67 ++ .../models/canopy_cbh_minimum.py | 55 ++ .../models/canopy_cbh_percentile.py | 68 ++ .../models/canopy_cc_cover_fraction.py | 66 ++ .../models/canopy_cc_crown_overlap.py | 55 ++ .../models/canopy_cc_crown_union.py | 55 ++ .../models/canopy_chm_height_percentile.py | 62 ++ .../models/canopy_crown_width_equations.py | 9 + .../canopy_fuelcalc_crown_class_adjustment.py | 79 ++ .../models/canopy_horizontal_distribution.py | 9 + .../canopy_no_crown_class_adjustment.py | 51 ++ .../models/canopy_profile_threshold.py | 154 ++++ .../models/canopy_running_mean_edge.py | 10 + .../models/canopy_species_inclusion.py | 9 + .../models/canopy_vertical_distribution.py | 9 + .../models/chm_max_aggregation.py | 54 ++ .../models/chm_mean_aggregation.py | 54 ++ .../models/chm_median_aggregation.py | 54 ++ .../models/chm_percentile_aggregation.py | 63 ++ .../client_library/models/chm_spike_filter.py | 87 ++ .../create_fosberg_fuel_moisture_request.py | 144 ++++ .../models/create_inventory_canopy_request.py | 752 ++++++++++++++++++ .../models/create_landfire_fbfm_13_request.py | 2 +- .../models/create_landfire_fbfm_40_request.py | 35 +- .../models/create_landfire_fccs_request.py | 2 +- .../create_leaflux_irradiance_request.py | 141 ++++ .../models/create_point_cloud_chm_request.py | 113 +++ .../client_library/models/dense_grid_data.py | 13 +- .../models/fuel_moisture_month.py | 19 + .../models/inventory_canopy_band.py | 12 + .../inventory_column_canopy_biomass_source.py | 84 ++ .../models/inventory_column_mapping.py | 22 + .../models/landfire_fbfm_40_version.py | 1 + .../client_library/models/landfire_season.py | 11 + .../v2/client_library/models/leaflux_band.py | 9 + .../models/point_cloud_data_metadata.py | 180 +++++ .../point_cloud_data_metadata_columns.py | 50 ++ ...point_cloud_three_dep_coverage_response.py | 18 - .../models/point_cloud_tile_data_response.py | 204 +++++ .../point_cloud_tile_data_response_columns.py | 47 ++ .../point_cloud_tile_data_response_data.py | 59 ++ .../models/point_cloud_tile_metadata.py | 104 +++ .../models/relative_elevation.py | 10 + .../client_library/models/sparse_grid_data.py | 16 +- fastfuels_sdk/v2/v2_api_design.md | 2 +- tests/v2/test_point_clouds.py | 2 - 72 files changed, 6988 insertions(+), 303 deletions(-) create mode 100644 fastfuels_sdk/v2/client_library/api/grids/create_fosberg_fuel_moisture_grid.py create mode 100644 fastfuels_sdk/v2/client_library/api/grids/create_inventory_canopy_grid.py create mode 100644 fastfuels_sdk/v2/client_library/api/grids/create_leaflux_irradiance_grid.py create mode 100644 fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_binary.py create mode 100644 fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_json.py create mode 100644 fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_metadata.py create mode 100644 fastfuels_sdk/v2/client_library/models/allometry_canopy_biomass_source.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_allometry_max_crown_radius_source.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_available_fuel.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_biomass_equations.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_branchwood.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_branchwood_size_partition.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbd_depth.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbd_load_over_depth.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbd_running_mean.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbh_mean.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbh_minimum.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cbh_percentile.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cc_cover_fraction.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cc_crown_overlap.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_cc_crown_union.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_chm_height_percentile.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_crown_width_equations.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_fuelcalc_crown_class_adjustment.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_horizontal_distribution.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_no_crown_class_adjustment.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_profile_threshold.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_running_mean_edge.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_species_inclusion.py create mode 100644 fastfuels_sdk/v2/client_library/models/canopy_vertical_distribution.py create mode 100644 fastfuels_sdk/v2/client_library/models/chm_max_aggregation.py create mode 100644 fastfuels_sdk/v2/client_library/models/chm_mean_aggregation.py create mode 100644 fastfuels_sdk/v2/client_library/models/chm_median_aggregation.py create mode 100644 fastfuels_sdk/v2/client_library/models/chm_percentile_aggregation.py create mode 100644 fastfuels_sdk/v2/client_library/models/chm_spike_filter.py create mode 100644 fastfuels_sdk/v2/client_library/models/create_fosberg_fuel_moisture_request.py create mode 100644 fastfuels_sdk/v2/client_library/models/create_inventory_canopy_request.py create mode 100644 fastfuels_sdk/v2/client_library/models/create_leaflux_irradiance_request.py create mode 100644 fastfuels_sdk/v2/client_library/models/fuel_moisture_month.py create mode 100644 fastfuels_sdk/v2/client_library/models/inventory_canopy_band.py create mode 100644 fastfuels_sdk/v2/client_library/models/inventory_column_canopy_biomass_source.py create mode 100644 fastfuels_sdk/v2/client_library/models/landfire_season.py create mode 100644 fastfuels_sdk/v2/client_library/models/leaflux_band.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata_columns.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_columns.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_data.py create mode 100644 fastfuels_sdk/v2/client_library/models/point_cloud_tile_metadata.py create mode 100644 fastfuels_sdk/v2/client_library/models/relative_elevation.py diff --git a/fastfuels_sdk/v2/client_library/api/grids/create_fosberg_fuel_moisture_grid.py b/fastfuels_sdk/v2/client_library/api/grids/create_fosberg_fuel_moisture_grid.py new file mode 100644 index 0000000..f6bb241 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/grids/create_fosberg_fuel_moisture_grid.py @@ -0,0 +1,314 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.create_fosberg_fuel_moisture_request import ( + CreateFosbergFuelMoistureRequest, +) +from ...models.grid import Grid +from ...models.http_validation_error import HTTPValidationError +from ...models.quota_exceeded_detail import QuotaExceededDetail +from ...types import Response + + +def _get_kwargs( + domain_id: str, + *, + body: CreateFosbergFuelMoistureRequest, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/domains/{domain_id}/grids/fuel-moisture/dead/fosberg".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + if response.status_code == 201: + response_201 = Grid.from_dict(response.json()) + + return response_201 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = QuotaExceededDetail.from_dict(response.json()) + + return response_429 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateFosbergFuelMoistureRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + r"""Create a Fosberg 1-hour dead fuel moisture grid + + # Create Fosberg 1-hour Dead Fuel Moisture Grid + + Creates a grid with a single continuous band, `fuel_moisture.dead.1hr` + (percent), computed with the Fosberg & Deeming (1971) 1-hour dead fuel + moisture model. + + The grid is derived from two completed source grids in the same domain: + + - **Topography** — supplies the `slope` and `aspect` bands (both degrees). + - **Leaflux irradiance** — supplies `irradiance.surface.relative`, from + which per-cell shading is derived as `1 - irradiance.surface.relative`. + + The remaining inputs are scalar weather/scenario parameters: + `dry_bulb_temp` (°F), `relative_humidity` (%), `time` (local HHMM, + 0800-1959), `month`, and `elevation` (site position relative to the + reference weather station). + + The output inherits the topography grid's domain, CRS, transform, and + georeference. Keeping `time`/`month` consistent with the sun position that + produced the irradiance grid is the caller's responsibility. + + ## Response + + Returns the created Grid resource with status \"pending\". The backend + computes the moisture surface and updates status to \"completed\". + + Args: + domain_id (str): + body (CreateFosbergFuelMoistureRequest): Request body for a Fosberg 1-hour dead fuel + moisture content grid. + + Does not extend CreateSourceGridRequestBase: this is a grid -> grid + derivation with no external raster and no alignment input. The output + inherits the topography grid's domain, CRS, transform, and georeference. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateFosbergFuelMoistureRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + r"""Create a Fosberg 1-hour dead fuel moisture grid + + # Create Fosberg 1-hour Dead Fuel Moisture Grid + + Creates a grid with a single continuous band, `fuel_moisture.dead.1hr` + (percent), computed with the Fosberg & Deeming (1971) 1-hour dead fuel + moisture model. + + The grid is derived from two completed source grids in the same domain: + + - **Topography** — supplies the `slope` and `aspect` bands (both degrees). + - **Leaflux irradiance** — supplies `irradiance.surface.relative`, from + which per-cell shading is derived as `1 - irradiance.surface.relative`. + + The remaining inputs are scalar weather/scenario parameters: + `dry_bulb_temp` (°F), `relative_humidity` (%), `time` (local HHMM, + 0800-1959), `month`, and `elevation` (site position relative to the + reference weather station). + + The output inherits the topography grid's domain, CRS, transform, and + georeference. Keeping `time`/`month` consistent with the sun position that + produced the irradiance grid is the caller's responsibility. + + ## Response + + Returns the created Grid resource with status \"pending\". The backend + computes the moisture surface and updates status to \"completed\". + + Args: + domain_id (str): + body (CreateFosbergFuelMoistureRequest): Request body for a Fosberg 1-hour dead fuel + moisture content grid. + + Does not extend CreateSourceGridRequestBase: this is a grid -> grid + derivation with no external raster and no alignment input. The output + inherits the topography grid's domain, CRS, transform, and georeference. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateFosbergFuelMoistureRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + r"""Create a Fosberg 1-hour dead fuel moisture grid + + # Create Fosberg 1-hour Dead Fuel Moisture Grid + + Creates a grid with a single continuous band, `fuel_moisture.dead.1hr` + (percent), computed with the Fosberg & Deeming (1971) 1-hour dead fuel + moisture model. + + The grid is derived from two completed source grids in the same domain: + + - **Topography** — supplies the `slope` and `aspect` bands (both degrees). + - **Leaflux irradiance** — supplies `irradiance.surface.relative`, from + which per-cell shading is derived as `1 - irradiance.surface.relative`. + + The remaining inputs are scalar weather/scenario parameters: + `dry_bulb_temp` (°F), `relative_humidity` (%), `time` (local HHMM, + 0800-1959), `month`, and `elevation` (site position relative to the + reference weather station). + + The output inherits the topography grid's domain, CRS, transform, and + georeference. Keeping `time`/`month` consistent with the sun position that + produced the irradiance grid is the caller's responsibility. + + ## Response + + Returns the created Grid resource with status \"pending\". The backend + computes the moisture surface and updates status to \"completed\". + + Args: + domain_id (str): + body (CreateFosbergFuelMoistureRequest): Request body for a Fosberg 1-hour dead fuel + moisture content grid. + + Does not extend CreateSourceGridRequestBase: this is a grid -> grid + derivation with no external raster and no alignment input. The output + inherits the topography grid's domain, CRS, transform, and georeference. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateFosbergFuelMoistureRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + r"""Create a Fosberg 1-hour dead fuel moisture grid + + # Create Fosberg 1-hour Dead Fuel Moisture Grid + + Creates a grid with a single continuous band, `fuel_moisture.dead.1hr` + (percent), computed with the Fosberg & Deeming (1971) 1-hour dead fuel + moisture model. + + The grid is derived from two completed source grids in the same domain: + + - **Topography** — supplies the `slope` and `aspect` bands (both degrees). + - **Leaflux irradiance** — supplies `irradiance.surface.relative`, from + which per-cell shading is derived as `1 - irradiance.surface.relative`. + + The remaining inputs are scalar weather/scenario parameters: + `dry_bulb_temp` (°F), `relative_humidity` (%), `time` (local HHMM, + 0800-1959), `month`, and `elevation` (site position relative to the + reference weather station). + + The output inherits the topography grid's domain, CRS, transform, and + georeference. Keeping `time`/`month` consistent with the sun position that + produced the irradiance grid is the caller's responsibility. + + ## Response + + Returns the created Grid resource with status \"pending\". The backend + computes the moisture surface and updates status to \"completed\". + + Args: + domain_id (str): + body (CreateFosbergFuelMoistureRequest): Request body for a Fosberg 1-hour dead fuel + moisture content grid. + + Does not extend CreateSourceGridRequestBase: this is a grid -> grid + derivation with no external raster and no alignment input. The output + inherits the topography grid's domain, CRS, transform, and georeference. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + body=body, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/grids/create_inventory_canopy_grid.py b/fastfuels_sdk/v2/client_library/api/grids/create_inventory_canopy_grid.py new file mode 100644 index 0000000..287ac5e --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/grids/create_inventory_canopy_grid.py @@ -0,0 +1,476 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.create_inventory_canopy_request import CreateInventoryCanopyRequest +from ...models.grid import Grid +from ...models.http_validation_error import HTTPValidationError +from ...models.quota_exceeded_detail import QuotaExceededDetail +from ...types import Response + + +def _get_kwargs( + domain_id: str, + *, + body: CreateInventoryCanopyRequest, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/domains/{domain_id}/grids/canopy/inventory".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + if response.status_code == 201: + response_201 = Grid.from_dict(response.json()) + + return response_201 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = QuotaExceededDetail.from_dict(response.json()) + + return response_429 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateInventoryCanopyRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + r"""Create a canopy fuel grid from a tree inventory + + # Create Inventory Canopy Grid + + Derives the canopy fuel metrics operational fire models consume — canopy + bulk density (`cbd`), canopy base height (`cbh`), canopy height (`chm`), + canopy cover (`cc`), and optionally canopy fuel load (`cfl`) — directly + from a tree inventory, with no voxelization. + + For each tree, available canopy fuel is estimated from crown biomass, + distributed vertically over the crown, and attributed to output cells; + each cell's vertical profile is then reduced to the requested bands. This + is the FuelCalc-style profile method computed per cell from real stem + positions instead of per plot from expanded tree records. Bands share + keys and units with the LANDFIRE canopy source, so the result drops into + anything that accepts one — including the landscape export. + + Only live trees contribute canopy fuel, matching FuelCalc's exclusion of + dead trees. + + ## Request Body + + - **source_inventory_id**: (required) ID of a completed tree inventory in + this domain. Required columns depend on the selected methods; the + defaults need `x`, `y`, `height`, `crown_ratio`, `dbh`, and + `fia_species_code`. + - **alignment**: (optional) Output lattice. Against the domain (the + default) `resolution` defaults to 30 m — an inventory has no native + cell size to inherit. Against another grid, omitting `resolution` + matches that grid's lattice exactly. `target: \"native\"` is not + supported. + - **bands**: (optional) Defaults to `[\"cbd\", \"cbh\", \"chm\", \"cc\"]` — the + four landscape-file canopy roles. Add `cfl` for canopy fuel load. + - **biomass_source**: (optional) `allometry` with `nsvb` (default), + `jenkins`, or `brown_1978` equations, or `inventory_column` carrying + precomputed per-tree available canopy fuel. + - **available_fuel**: (optional) Foliage fraction plus the fine-branchwood + size partition and fraction. Resolved to `null` with an + `inventory_column` biomass source. + - **species_inclusion**, **crown_class_adjustment**, **min_tree_height**: + (optional) Which trees contribute, and how crown weight is adjusted for + canopy position. + - **vertical_distribution**, **layer_depth**: (optional) How each tree's + fuel stacks over its crown, and the profile layer depth (default + 0.3048 m, FuelCalc's 1 ft). + - **horizontal_distribution**: (optional) `crown_projected` (default) + splits each tree's fuel over the cells its crown covers; + `stem` assigns it to the stem cell. + - **max_crown_radius_source**: (optional) Allometric crown radii + (default) or a per-tree inventory column (e.g. from LiDAR). + - **cbd**, **cbh**, **chm**, **cc**: (optional) Per-band reduction + methods. Each may only be supplied when its band is requested; + requested bands default to the FuelCalc-style methods. + - **name**, **description**, **tags**: (optional) Standard metadata. + + The stored grid `source` records every resolved choice, including + defaults, so the grid is exactly reproducible from the resource alone. + + ## Response + + Returns the created Grid with status `\"pending\"` and + `georeference: null`. Griddle computes the canopy metrics asynchronously + and updates the grid to `\"completed\"` with a 2D `Georeference` when done. + + Args: + domain_id (str): + body (CreateInventoryCanopyRequest): Request body for creating a canopy fuel grid from a + tree inventory. + + Only live trees contribute canopy fuel: the worker reads live + inventory records only, matching FuelCalc, which excludes dead trees + from all calculations. + + Does not extend CreateGridRequestBase: like DUET and the 3D voxel + grids, inventory-derived grids do not support modifications — apply + treatments and modifications to the inventory before deriving canopy + metrics. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateInventoryCanopyRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + r"""Create a canopy fuel grid from a tree inventory + + # Create Inventory Canopy Grid + + Derives the canopy fuel metrics operational fire models consume — canopy + bulk density (`cbd`), canopy base height (`cbh`), canopy height (`chm`), + canopy cover (`cc`), and optionally canopy fuel load (`cfl`) — directly + from a tree inventory, with no voxelization. + + For each tree, available canopy fuel is estimated from crown biomass, + distributed vertically over the crown, and attributed to output cells; + each cell's vertical profile is then reduced to the requested bands. This + is the FuelCalc-style profile method computed per cell from real stem + positions instead of per plot from expanded tree records. Bands share + keys and units with the LANDFIRE canopy source, so the result drops into + anything that accepts one — including the landscape export. + + Only live trees contribute canopy fuel, matching FuelCalc's exclusion of + dead trees. + + ## Request Body + + - **source_inventory_id**: (required) ID of a completed tree inventory in + this domain. Required columns depend on the selected methods; the + defaults need `x`, `y`, `height`, `crown_ratio`, `dbh`, and + `fia_species_code`. + - **alignment**: (optional) Output lattice. Against the domain (the + default) `resolution` defaults to 30 m — an inventory has no native + cell size to inherit. Against another grid, omitting `resolution` + matches that grid's lattice exactly. `target: \"native\"` is not + supported. + - **bands**: (optional) Defaults to `[\"cbd\", \"cbh\", \"chm\", \"cc\"]` — the + four landscape-file canopy roles. Add `cfl` for canopy fuel load. + - **biomass_source**: (optional) `allometry` with `nsvb` (default), + `jenkins`, or `brown_1978` equations, or `inventory_column` carrying + precomputed per-tree available canopy fuel. + - **available_fuel**: (optional) Foliage fraction plus the fine-branchwood + size partition and fraction. Resolved to `null` with an + `inventory_column` biomass source. + - **species_inclusion**, **crown_class_adjustment**, **min_tree_height**: + (optional) Which trees contribute, and how crown weight is adjusted for + canopy position. + - **vertical_distribution**, **layer_depth**: (optional) How each tree's + fuel stacks over its crown, and the profile layer depth (default + 0.3048 m, FuelCalc's 1 ft). + - **horizontal_distribution**: (optional) `crown_projected` (default) + splits each tree's fuel over the cells its crown covers; + `stem` assigns it to the stem cell. + - **max_crown_radius_source**: (optional) Allometric crown radii + (default) or a per-tree inventory column (e.g. from LiDAR). + - **cbd**, **cbh**, **chm**, **cc**: (optional) Per-band reduction + methods. Each may only be supplied when its band is requested; + requested bands default to the FuelCalc-style methods. + - **name**, **description**, **tags**: (optional) Standard metadata. + + The stored grid `source` records every resolved choice, including + defaults, so the grid is exactly reproducible from the resource alone. + + ## Response + + Returns the created Grid with status `\"pending\"` and + `georeference: null`. Griddle computes the canopy metrics asynchronously + and updates the grid to `\"completed\"` with a 2D `Georeference` when done. + + Args: + domain_id (str): + body (CreateInventoryCanopyRequest): Request body for creating a canopy fuel grid from a + tree inventory. + + Only live trees contribute canopy fuel: the worker reads live + inventory records only, matching FuelCalc, which excludes dead trees + from all calculations. + + Does not extend CreateGridRequestBase: like DUET and the 3D voxel + grids, inventory-derived grids do not support modifications — apply + treatments and modifications to the inventory before deriving canopy + metrics. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateInventoryCanopyRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + r"""Create a canopy fuel grid from a tree inventory + + # Create Inventory Canopy Grid + + Derives the canopy fuel metrics operational fire models consume — canopy + bulk density (`cbd`), canopy base height (`cbh`), canopy height (`chm`), + canopy cover (`cc`), and optionally canopy fuel load (`cfl`) — directly + from a tree inventory, with no voxelization. + + For each tree, available canopy fuel is estimated from crown biomass, + distributed vertically over the crown, and attributed to output cells; + each cell's vertical profile is then reduced to the requested bands. This + is the FuelCalc-style profile method computed per cell from real stem + positions instead of per plot from expanded tree records. Bands share + keys and units with the LANDFIRE canopy source, so the result drops into + anything that accepts one — including the landscape export. + + Only live trees contribute canopy fuel, matching FuelCalc's exclusion of + dead trees. + + ## Request Body + + - **source_inventory_id**: (required) ID of a completed tree inventory in + this domain. Required columns depend on the selected methods; the + defaults need `x`, `y`, `height`, `crown_ratio`, `dbh`, and + `fia_species_code`. + - **alignment**: (optional) Output lattice. Against the domain (the + default) `resolution` defaults to 30 m — an inventory has no native + cell size to inherit. Against another grid, omitting `resolution` + matches that grid's lattice exactly. `target: \"native\"` is not + supported. + - **bands**: (optional) Defaults to `[\"cbd\", \"cbh\", \"chm\", \"cc\"]` — the + four landscape-file canopy roles. Add `cfl` for canopy fuel load. + - **biomass_source**: (optional) `allometry` with `nsvb` (default), + `jenkins`, or `brown_1978` equations, or `inventory_column` carrying + precomputed per-tree available canopy fuel. + - **available_fuel**: (optional) Foliage fraction plus the fine-branchwood + size partition and fraction. Resolved to `null` with an + `inventory_column` biomass source. + - **species_inclusion**, **crown_class_adjustment**, **min_tree_height**: + (optional) Which trees contribute, and how crown weight is adjusted for + canopy position. + - **vertical_distribution**, **layer_depth**: (optional) How each tree's + fuel stacks over its crown, and the profile layer depth (default + 0.3048 m, FuelCalc's 1 ft). + - **horizontal_distribution**: (optional) `crown_projected` (default) + splits each tree's fuel over the cells its crown covers; + `stem` assigns it to the stem cell. + - **max_crown_radius_source**: (optional) Allometric crown radii + (default) or a per-tree inventory column (e.g. from LiDAR). + - **cbd**, **cbh**, **chm**, **cc**: (optional) Per-band reduction + methods. Each may only be supplied when its band is requested; + requested bands default to the FuelCalc-style methods. + - **name**, **description**, **tags**: (optional) Standard metadata. + + The stored grid `source` records every resolved choice, including + defaults, so the grid is exactly reproducible from the resource alone. + + ## Response + + Returns the created Grid with status `\"pending\"` and + `georeference: null`. Griddle computes the canopy metrics asynchronously + and updates the grid to `\"completed\"` with a 2D `Georeference` when done. + + Args: + domain_id (str): + body (CreateInventoryCanopyRequest): Request body for creating a canopy fuel grid from a + tree inventory. + + Only live trees contribute canopy fuel: the worker reads live + inventory records only, matching FuelCalc, which excludes dead trees + from all calculations. + + Does not extend CreateGridRequestBase: like DUET and the 3D voxel + grids, inventory-derived grids do not support modifications — apply + treatments and modifications to the inventory before deriving canopy + metrics. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateInventoryCanopyRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + r"""Create a canopy fuel grid from a tree inventory + + # Create Inventory Canopy Grid + + Derives the canopy fuel metrics operational fire models consume — canopy + bulk density (`cbd`), canopy base height (`cbh`), canopy height (`chm`), + canopy cover (`cc`), and optionally canopy fuel load (`cfl`) — directly + from a tree inventory, with no voxelization. + + For each tree, available canopy fuel is estimated from crown biomass, + distributed vertically over the crown, and attributed to output cells; + each cell's vertical profile is then reduced to the requested bands. This + is the FuelCalc-style profile method computed per cell from real stem + positions instead of per plot from expanded tree records. Bands share + keys and units with the LANDFIRE canopy source, so the result drops into + anything that accepts one — including the landscape export. + + Only live trees contribute canopy fuel, matching FuelCalc's exclusion of + dead trees. + + ## Request Body + + - **source_inventory_id**: (required) ID of a completed tree inventory in + this domain. Required columns depend on the selected methods; the + defaults need `x`, `y`, `height`, `crown_ratio`, `dbh`, and + `fia_species_code`. + - **alignment**: (optional) Output lattice. Against the domain (the + default) `resolution` defaults to 30 m — an inventory has no native + cell size to inherit. Against another grid, omitting `resolution` + matches that grid's lattice exactly. `target: \"native\"` is not + supported. + - **bands**: (optional) Defaults to `[\"cbd\", \"cbh\", \"chm\", \"cc\"]` — the + four landscape-file canopy roles. Add `cfl` for canopy fuel load. + - **biomass_source**: (optional) `allometry` with `nsvb` (default), + `jenkins`, or `brown_1978` equations, or `inventory_column` carrying + precomputed per-tree available canopy fuel. + - **available_fuel**: (optional) Foliage fraction plus the fine-branchwood + size partition and fraction. Resolved to `null` with an + `inventory_column` biomass source. + - **species_inclusion**, **crown_class_adjustment**, **min_tree_height**: + (optional) Which trees contribute, and how crown weight is adjusted for + canopy position. + - **vertical_distribution**, **layer_depth**: (optional) How each tree's + fuel stacks over its crown, and the profile layer depth (default + 0.3048 m, FuelCalc's 1 ft). + - **horizontal_distribution**: (optional) `crown_projected` (default) + splits each tree's fuel over the cells its crown covers; + `stem` assigns it to the stem cell. + - **max_crown_radius_source**: (optional) Allometric crown radii + (default) or a per-tree inventory column (e.g. from LiDAR). + - **cbd**, **cbh**, **chm**, **cc**: (optional) Per-band reduction + methods. Each may only be supplied when its band is requested; + requested bands default to the FuelCalc-style methods. + - **name**, **description**, **tags**: (optional) Standard metadata. + + The stored grid `source` records every resolved choice, including + defaults, so the grid is exactly reproducible from the resource alone. + + ## Response + + Returns the created Grid with status `\"pending\"` and + `georeference: null`. Griddle computes the canopy metrics asynchronously + and updates the grid to `\"completed\"` with a 2D `Georeference` when done. + + Args: + domain_id (str): + body (CreateInventoryCanopyRequest): Request body for creating a canopy fuel grid from a + tree inventory. + + Only live trees contribute canopy fuel: the worker reads live + inventory records only, matching FuelCalc, which excludes dead trees + from all calculations. + + Does not extend CreateGridRequestBase: like DUET and the 3D voxel + grids, inventory-derived grids do not support modifications — apply + treatments and modifications to the inventory before deriving canopy + metrics. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + body=body, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/grids/create_landfire_fbfm40.py b/fastfuels_sdk/v2/client_library/api/grids/create_landfire_fbfm40.py index c873764..5989c4c 100644 --- a/fastfuels_sdk/v2/client_library/api/grids/create_landfire_fbfm40.py +++ b/fastfuels_sdk/v2/client_library/api/grids/create_landfire_fbfm40.py @@ -94,12 +94,22 @@ def sync_detailed( - **description**: (optional) Description. - **tags**: (optional) Tags for organizing grids. - **version**: (optional) LANDFIRE version. Default: \"2024\". + Fetches data from a saved copy of the annual release, unless `season` is set. + - **season**: (optional) LANDFIRE Seasonal Fuels release: \"ES\" (early + spring), \"SP\" (spring), \"SU\" (summer), or \"FA\" (fall). Setting + `season` fetches data from the LANDFIRE Product Service on demand + rather than a saved annual copy. ## Response Returns the created Grid resource with status \"pending\". The backend will fetch the data and update status to \"completed\" when ready. + The response `source` reports `year`: the calendar year the fuel data + represents. For an annual grid this is the landscape vintage (same as + `version`); for a seasonal grid it is the projected season year (e.g. + `version` 2025 + `season` \"SP\" is spring 2026). + Args: domain_id (str): body (CreateLandfireFbfm40Request): Request to create a grid from LANDFIRE FBFM40. @@ -151,12 +161,22 @@ def sync( - **description**: (optional) Description. - **tags**: (optional) Tags for organizing grids. - **version**: (optional) LANDFIRE version. Default: \"2024\". + Fetches data from a saved copy of the annual release, unless `season` is set. + - **season**: (optional) LANDFIRE Seasonal Fuels release: \"ES\" (early + spring), \"SP\" (spring), \"SU\" (summer), or \"FA\" (fall). Setting + `season` fetches data from the LANDFIRE Product Service on demand + rather than a saved annual copy. ## Response Returns the created Grid resource with status \"pending\". The backend will fetch the data and update status to \"completed\" when ready. + The response `source` reports `year`: the calendar year the fuel data + represents. For an annual grid this is the landscape vintage (same as + `version`); for a seasonal grid it is the projected season year (e.g. + `version` 2025 + `season` \"SP\" is spring 2026). + Args: domain_id (str): body (CreateLandfireFbfm40Request): Request to create a grid from LANDFIRE FBFM40. @@ -203,12 +223,22 @@ async def asyncio_detailed( - **description**: (optional) Description. - **tags**: (optional) Tags for organizing grids. - **version**: (optional) LANDFIRE version. Default: \"2024\". + Fetches data from a saved copy of the annual release, unless `season` is set. + - **season**: (optional) LANDFIRE Seasonal Fuels release: \"ES\" (early + spring), \"SP\" (spring), \"SU\" (summer), or \"FA\" (fall). Setting + `season` fetches data from the LANDFIRE Product Service on demand + rather than a saved annual copy. ## Response Returns the created Grid resource with status \"pending\". The backend will fetch the data and update status to \"completed\" when ready. + The response `source` reports `year`: the calendar year the fuel data + represents. For an annual grid this is the landscape vintage (same as + `version`); for a seasonal grid it is the projected season year (e.g. + `version` 2025 + `season` \"SP\" is spring 2026). + Args: domain_id (str): body (CreateLandfireFbfm40Request): Request to create a grid from LANDFIRE FBFM40. @@ -258,12 +288,22 @@ async def asyncio( - **description**: (optional) Description. - **tags**: (optional) Tags for organizing grids. - **version**: (optional) LANDFIRE version. Default: \"2024\". + Fetches data from a saved copy of the annual release, unless `season` is set. + - **season**: (optional) LANDFIRE Seasonal Fuels release: \"ES\" (early + spring), \"SP\" (spring), \"SU\" (summer), or \"FA\" (fall). Setting + `season` fetches data from the LANDFIRE Product Service on demand + rather than a saved annual copy. ## Response Returns the created Grid resource with status \"pending\". The backend will fetch the data and update status to \"completed\" when ready. + The response `source` reports `year`: the calendar year the fuel data + represents. For an annual grid this is the landscape vintage (same as + `version`); for a seasonal grid it is the projected season year (e.g. + `version` 2025 + `season` \"SP\" is spring 2026). + Args: domain_id (str): body (CreateLandfireFbfm40Request): Request to create a grid from LANDFIRE FBFM40. diff --git a/fastfuels_sdk/v2/client_library/api/grids/create_leaflux_irradiance_grid.py b/fastfuels_sdk/v2/client_library/api/grids/create_leaflux_irradiance_grid.py new file mode 100644 index 0000000..264e4ca --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/grids/create_leaflux_irradiance_grid.py @@ -0,0 +1,216 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.create_leaflux_irradiance_request import CreateLeafluxIrradianceRequest +from ...models.grid import Grid +from ...models.http_validation_error import HTTPValidationError +from ...models.quota_exceeded_detail import QuotaExceededDetail +from ...types import Response + + +def _get_kwargs( + domain_id: str, + *, + body: CreateLeafluxIrradianceRequest, +) -> dict[str, Any]: + headers: dict[str, Any] = {} + + _kwargs: dict[str, Any] = { + "method": "post", + "url": "/domains/{domain_id}/grids/solar/irradiance/leaflux".format( + domain_id=quote(str(domain_id), safe=""), + ), + } + + _kwargs["json"] = body.to_dict() + + headers["Content-Type"] = "application/json" + + _kwargs["headers"] = headers + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + if response.status_code == 201: + response_201 = Grid.from_dict(response.json()) + + return response_201 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if response.status_code == 429: + response_429 = QuotaExceededDetail.from_dict(response.json()) + + return response_429 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateLeafluxIrradianceRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + """Create a 3D LeafLux irradiance grid from a source fuel grid + + Create a pending irradiance Grid and dispatch it to Treevox for compute. + + Args: + domain_id (str): + body (CreateLeafluxIrradianceRequest): Request body for creating a LeafLux irradiance grid + from a 3D fuel grid. + + Does not extend CreateGridRequestBase because 3D grids do not support + modifications. This is a grid -> grid derivation aligned to the source + grid's geometry, so there is no resolution input. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateLeafluxIrradianceRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + """Create a 3D LeafLux irradiance grid from a source fuel grid + + Create a pending irradiance Grid and dispatch it to Treevox for compute. + + Args: + domain_id (str): + body (CreateLeafluxIrradianceRequest): Request body for creating a LeafLux irradiance grid + from a 3D fuel grid. + + Does not extend CreateGridRequestBase because 3D grids do not support + modifications. This is a grid -> grid derivation aligned to the source + grid's geometry, so there is no resolution input. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return sync_detailed( + domain_id=domain_id, + client=client, + body=body, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateLeafluxIrradianceRequest, +) -> Response[Grid | HTTPValidationError | QuotaExceededDetail]: + """Create a 3D LeafLux irradiance grid from a source fuel grid + + Create a pending irradiance Grid and dispatch it to Treevox for compute. + + Args: + domain_id (str): + body (CreateLeafluxIrradianceRequest): Request body for creating a LeafLux irradiance grid + from a 3D fuel grid. + + Does not extend CreateGridRequestBase because 3D grids do not support + modifications. This is a grid -> grid derivation aligned to the source + grid's geometry, so there is no resolution input. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[Grid | HTTPValidationError | QuotaExceededDetail] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + body=body, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + *, + client: AuthenticatedClient, + body: CreateLeafluxIrradianceRequest, +) -> Grid | HTTPValidationError | QuotaExceededDetail | None: + """Create a 3D LeafLux irradiance grid from a source fuel grid + + Create a pending irradiance Grid and dispatch it to Treevox for compute. + + Args: + domain_id (str): + body (CreateLeafluxIrradianceRequest): Request body for creating a LeafLux irradiance grid + from a 3D fuel grid. + + Does not extend CreateGridRequestBase because 3D grids do not support + modifications. This is a grid -> grid derivation aligned to the source + grid's geometry, so there is no resolution input. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Grid | HTTPValidationError | QuotaExceededDetail + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + client=client, + body=body, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/grids/create_point_cloud_chm.py b/fastfuels_sdk/v2/client_library/api/grids/create_point_cloud_chm.py index 9b36f19..6c698cd 100644 --- a/fastfuels_sdk/v2/client_library/api/grids/create_point_cloud_chm.py +++ b/fastfuels_sdk/v2/client_library/api/grids/create_point_cloud_chm.py @@ -81,7 +81,21 @@ def sync_detailed( # Create a CHM Grid from a Point Cloud Creates a grid with canopy height data rasterized from a point cloud. Each - cell holds the greatest height above ground of any return that falls in it. + cell reduces the heights above ground of the returns that fall in it to one + value — by default the greatest of them. + + ## Aggregation + + Which statistic that is matters more as cells grow, because a cell 1 m + across describes a crown while a cell 30 m across describes a stand, and a + maximum reports the tallest tree in either. Measured on one cloud + rasterized twice, the same returns gave a mean height of 4.83 m at 1 m + cells and 15.48 m at 10 m. + + `max` is the tallest return. `mean` averages every return, so a cell that + is half canopy and half gap reads between the two. `median` and + `percentile` take a rank, which is what keeps one surviving noise return + from setting a cell on its own. The resulting grid carries the same `chm` band as the Meta, NAIP, and LANDFIRE canopy sources, so it can be used anywhere they can — including as @@ -116,6 +130,19 @@ def sync_detailed( the new cell size. The target grid must be in this domain's CRS. `target: \"native\"` is not supported — a point cloud has no pixel anchor to preserve. + - **spike_filter**: (optional) Removal of lone spurious returns. Under the + default `max` aggregation a cell takes the tallest return in it, so one + bad return — a bird, haze — sets the cell unless the cloud classified it + as noise, and many clouds do not. Such a return leaves a shape real + canopy cannot: a single cell towering over everything around it. Both + fields are in meters, so they mean the same thing at any resolution. Send + `null` to keep every return. A `mean`, `median`, or `percentile` + aggregation already resists a lone return, so the filter matters most + with `max`. + - **aggregation**: (optional) The statistic above, as + `{\"method\": \"max\" | \"mean\" | \"median\" | \"percentile\"}`. `percentile` + carries the rank it takes, as in + `{\"method\": \"percentile\", \"percentile\": 98}`. Defaults to `max`. - **name**, **description**, **tags**: (optional) Metadata. ## Response @@ -166,7 +193,21 @@ def sync( # Create a CHM Grid from a Point Cloud Creates a grid with canopy height data rasterized from a point cloud. Each - cell holds the greatest height above ground of any return that falls in it. + cell reduces the heights above ground of the returns that fall in it to one + value — by default the greatest of them. + + ## Aggregation + + Which statistic that is matters more as cells grow, because a cell 1 m + across describes a crown while a cell 30 m across describes a stand, and a + maximum reports the tallest tree in either. Measured on one cloud + rasterized twice, the same returns gave a mean height of 4.83 m at 1 m + cells and 15.48 m at 10 m. + + `max` is the tallest return. `mean` averages every return, so a cell that + is half canopy and half gap reads between the two. `median` and + `percentile` take a rank, which is what keeps one surviving noise return + from setting a cell on its own. The resulting grid carries the same `chm` band as the Meta, NAIP, and LANDFIRE canopy sources, so it can be used anywhere they can — including as @@ -201,6 +242,19 @@ def sync( the new cell size. The target grid must be in this domain's CRS. `target: \"native\"` is not supported — a point cloud has no pixel anchor to preserve. + - **spike_filter**: (optional) Removal of lone spurious returns. Under the + default `max` aggregation a cell takes the tallest return in it, so one + bad return — a bird, haze — sets the cell unless the cloud classified it + as noise, and many clouds do not. Such a return leaves a shape real + canopy cannot: a single cell towering over everything around it. Both + fields are in meters, so they mean the same thing at any resolution. Send + `null` to keep every return. A `mean`, `median`, or `percentile` + aggregation already resists a lone return, so the filter matters most + with `max`. + - **aggregation**: (optional) The statistic above, as + `{\"method\": \"max\" | \"mean\" | \"median\" | \"percentile\"}`. `percentile` + carries the rank it takes, as in + `{\"method\": \"percentile\", \"percentile\": 98}`. Defaults to `max`. - **name**, **description**, **tags**: (optional) Metadata. ## Response @@ -246,7 +300,21 @@ async def asyncio_detailed( # Create a CHM Grid from a Point Cloud Creates a grid with canopy height data rasterized from a point cloud. Each - cell holds the greatest height above ground of any return that falls in it. + cell reduces the heights above ground of the returns that fall in it to one + value — by default the greatest of them. + + ## Aggregation + + Which statistic that is matters more as cells grow, because a cell 1 m + across describes a crown while a cell 30 m across describes a stand, and a + maximum reports the tallest tree in either. Measured on one cloud + rasterized twice, the same returns gave a mean height of 4.83 m at 1 m + cells and 15.48 m at 10 m. + + `max` is the tallest return. `mean` averages every return, so a cell that + is half canopy and half gap reads between the two. `median` and + `percentile` take a rank, which is what keeps one surviving noise return + from setting a cell on its own. The resulting grid carries the same `chm` band as the Meta, NAIP, and LANDFIRE canopy sources, so it can be used anywhere they can — including as @@ -281,6 +349,19 @@ async def asyncio_detailed( the new cell size. The target grid must be in this domain's CRS. `target: \"native\"` is not supported — a point cloud has no pixel anchor to preserve. + - **spike_filter**: (optional) Removal of lone spurious returns. Under the + default `max` aggregation a cell takes the tallest return in it, so one + bad return — a bird, haze — sets the cell unless the cloud classified it + as noise, and many clouds do not. Such a return leaves a shape real + canopy cannot: a single cell towering over everything around it. Both + fields are in meters, so they mean the same thing at any resolution. Send + `null` to keep every return. A `mean`, `median`, or `percentile` + aggregation already resists a lone return, so the filter matters most + with `max`. + - **aggregation**: (optional) The statistic above, as + `{\"method\": \"max\" | \"mean\" | \"median\" | \"percentile\"}`. `percentile` + carries the rank it takes, as in + `{\"method\": \"percentile\", \"percentile\": 98}`. Defaults to `max`. - **name**, **description**, **tags**: (optional) Metadata. ## Response @@ -329,7 +410,21 @@ async def asyncio( # Create a CHM Grid from a Point Cloud Creates a grid with canopy height data rasterized from a point cloud. Each - cell holds the greatest height above ground of any return that falls in it. + cell reduces the heights above ground of the returns that fall in it to one + value — by default the greatest of them. + + ## Aggregation + + Which statistic that is matters more as cells grow, because a cell 1 m + across describes a crown while a cell 30 m across describes a stand, and a + maximum reports the tallest tree in either. Measured on one cloud + rasterized twice, the same returns gave a mean height of 4.83 m at 1 m + cells and 15.48 m at 10 m. + + `max` is the tallest return. `mean` averages every return, so a cell that + is half canopy and half gap reads between the two. `median` and + `percentile` take a rank, which is what keeps one surviving noise return + from setting a cell on its own. The resulting grid carries the same `chm` band as the Meta, NAIP, and LANDFIRE canopy sources, so it can be used anywhere they can — including as @@ -364,6 +459,19 @@ async def asyncio( the new cell size. The target grid must be in this domain's CRS. `target: \"native\"` is not supported — a point cloud has no pixel anchor to preserve. + - **spike_filter**: (optional) Removal of lone spurious returns. Under the + default `max` aggregation a cell takes the tallest return in it, so one + bad return — a bird, haze — sets the cell unless the cloud classified it + as noise, and many clouds do not. Such a return leaves a shape real + canopy cannot: a single cell towering over everything around it. Both + fields are in meters, so they mean the same thing at any resolution. Send + `null` to keep every return. A `mean`, `median`, or `percentile` + aggregation already resists a lone return, so the filter matters most + with `max`. + - **aggregation**: (optional) The statistic above, as + `{\"method\": \"max\" | \"mean\" | \"median\" | \"percentile\"}`. `percentile` + carries the rank it takes, as in + `{\"method\": \"percentile\", \"percentile\": 98}`. Defaults to `max`. - **name**, **description**, **tags**: (optional) Metadata. ## Response diff --git a/fastfuels_sdk/v2/client_library/api/grids/get_grid_data_json.py b/fastfuels_sdk/v2/client_library/api/grids/get_grid_data_json.py index e8b50c1..e98d773 100644 --- a/fastfuels_sdk/v2/client_library/api/grids/get_grid_data_json.py +++ b/fastfuels_sdk/v2/client_library/api/grids/get_grid_data_json.py @@ -137,8 +137,22 @@ def sync_detailed( **Sparse** (`array_format=sparse`): `data.format = \"sparse\"`, `data.indices` are flat positions of non-fill cells, `data.values` are their values. `data.fill_value` is the band's fill value, or `null` if - the band does not define one — in which case every cell is listed and no - compression has been applied. + the band's missing cells are nodata (see below) or it defines no fill at + all — in the latter case every cell is listed and no compression has been + applied. + + ## Missing Cells + + A cell with no data reads back as `null`. Float bands store missing cells + as NaN, which JSON cannot represent — a canopy height model, for instance, + is NaN wherever no lidar return landed. Bands that mark missing cells with + a sentinel value instead (the band's `nodata`) return that value as-is. + + A sparse read of such a band compresses the missing cells out: they are + absent from `indices`, and `fill_value` is `null`. Reconstruct the chunk + by filling every position not named in `indices` with `null`. + + The `/binary` variant is unaffected — it returns the raw NaN bit patterns. ## Errors @@ -233,8 +247,22 @@ def sync( **Sparse** (`array_format=sparse`): `data.format = \"sparse\"`, `data.indices` are flat positions of non-fill cells, `data.values` are their values. `data.fill_value` is the band's fill value, or `null` if - the band does not define one — in which case every cell is listed and no - compression has been applied. + the band's missing cells are nodata (see below) or it defines no fill at + all — in the latter case every cell is listed and no compression has been + applied. + + ## Missing Cells + + A cell with no data reads back as `null`. Float bands store missing cells + as NaN, which JSON cannot represent — a canopy height model, for instance, + is NaN wherever no lidar return landed. Bands that mark missing cells with + a sentinel value instead (the band's `nodata`) return that value as-is. + + A sparse read of such a band compresses the missing cells out: they are + absent from `indices`, and `fill_value` is `null`. Reconstruct the chunk + by filling every position not named in `indices` with `null`. + + The `/binary` variant is unaffected — it returns the raw NaN bit patterns. ## Errors @@ -324,8 +352,22 @@ async def asyncio_detailed( **Sparse** (`array_format=sparse`): `data.format = \"sparse\"`, `data.indices` are flat positions of non-fill cells, `data.values` are their values. `data.fill_value` is the band's fill value, or `null` if - the band does not define one — in which case every cell is listed and no - compression has been applied. + the band's missing cells are nodata (see below) or it defines no fill at + all — in the latter case every cell is listed and no compression has been + applied. + + ## Missing Cells + + A cell with no data reads back as `null`. Float bands store missing cells + as NaN, which JSON cannot represent — a canopy height model, for instance, + is NaN wherever no lidar return landed. Bands that mark missing cells with + a sentinel value instead (the band's `nodata`) return that value as-is. + + A sparse read of such a band compresses the missing cells out: they are + absent from `indices`, and `fill_value` is `null`. Reconstruct the chunk + by filling every position not named in `indices` with `null`. + + The `/binary` variant is unaffected — it returns the raw NaN bit patterns. ## Errors @@ -418,8 +460,22 @@ async def asyncio( **Sparse** (`array_format=sparse`): `data.format = \"sparse\"`, `data.indices` are flat positions of non-fill cells, `data.values` are their values. `data.fill_value` is the band's fill value, or `null` if - the band does not define one — in which case every cell is listed and no - compression has been applied. + the band's missing cells are nodata (see below) or it defines no fill at + all — in the latter case every cell is listed and no compression has been + applied. + + ## Missing Cells + + A cell with no data reads back as `null`. Float bands store missing cells + as NaN, which JSON cannot represent — a canopy height model, for instance, + is NaN wherever no lidar return landed. Bands that mark missing cells with + a sentinel value instead (the band's `nodata`) return that value as-is. + + A sparse read of such a band compresses the missing cells out: they are + absent from `indices`, and `fill_value` is `null`. Reconstruct the chunk + by filling every position not named in `indices` with `null`. + + The `/binary` variant is unaffected — it returns the raw NaN bit patterns. ## Errors diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/check_3dep_point_cloud_coverage.py b/fastfuels_sdk/v2/client_library/api/point_clouds/check_3dep_point_cloud_coverage.py index 3150a88..71c4e6f 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/check_3dep_point_cloud_coverage.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/check_3dep_point_cloud_coverage.py @@ -79,10 +79,9 @@ def sync_detailed( ## Response Reports whether any lidar is available, the fraction of the domain covered, - the surveys that would be read with what each contributes, and the - estimated point count against the per-fetch budget. `datasets[].name` - values can be passed as `datasets` when creating the point cloud to pin the - fetch. + the surveys that would be read with what each contributes, and roughly how + many points a fetch would return. `datasets[].name` values can be passed as + `datasets` when creating the point cloud to pin the fetch. ## Error Responses @@ -132,10 +131,9 @@ def sync( ## Response Reports whether any lidar is available, the fraction of the domain covered, - the surveys that would be read with what each contributes, and the - estimated point count against the per-fetch budget. `datasets[].name` - values can be passed as `datasets` when creating the point cloud to pin the - fetch. + the surveys that would be read with what each contributes, and roughly how + many points a fetch would return. `datasets[].name` values can be passed as + `datasets` when creating the point cloud to pin the fetch. ## Error Responses @@ -180,10 +178,9 @@ async def asyncio_detailed( ## Response Reports whether any lidar is available, the fraction of the domain covered, - the surveys that would be read with what each contributes, and the - estimated point count against the per-fetch budget. `datasets[].name` - values can be passed as `datasets` when creating the point cloud to pin the - fetch. + the surveys that would be read with what each contributes, and roughly how + many points a fetch would return. `datasets[].name` values can be passed as + `datasets` when creating the point cloud to pin the fetch. ## Error Responses @@ -231,10 +228,9 @@ async def asyncio( ## Response Reports whether any lidar is available, the fraction of the domain covered, - the surveys that would be read with what each contributes, and the - estimated point count against the per-fetch budget. `datasets[].name` - values can be passed as `datasets` when creating the point cloud to pin the - fetch. + the surveys that would be read with what each contributes, and roughly how + many points a fetch would return. `datasets[].name` values can be passed as + `datasets` when creating the point cloud to pin the fetch. ## Error Responses diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/create_3dep_point_cloud.py b/fastfuels_sdk/v2/client_library/api/point_clouds/create_3dep_point_cloud.py index 68ef124..7f57ed3 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/create_3dep_point_cloud.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/create_3dep_point_cloud.py @@ -130,9 +130,8 @@ def sync_detailed( ## Error Responses - - **422**: No 3DEP lidar covers this domain, a pinned acquisition is - unknown or does not overlap the domain, or the fetch would exceed the - point budget. + - **422**: No 3DEP lidar covers this domain, or a pinned acquisition is + unknown or does not overlap the domain. - **429**: A quota was exceeded. - **503**: The USGS 3DEP catalog is temporarily unreachable. @@ -219,9 +218,8 @@ def sync( ## Error Responses - - **422**: No 3DEP lidar covers this domain, a pinned acquisition is - unknown or does not overlap the domain, or the fetch would exceed the - point budget. + - **422**: No 3DEP lidar covers this domain, or a pinned acquisition is + unknown or does not overlap the domain. - **429**: A quota was exceeded. - **503**: The USGS 3DEP catalog is temporarily unreachable. @@ -303,9 +301,8 @@ async def asyncio_detailed( ## Error Responses - - **422**: No 3DEP lidar covers this domain, a pinned acquisition is - unknown or does not overlap the domain, or the fetch would exceed the - point budget. + - **422**: No 3DEP lidar covers this domain, or a pinned acquisition is + unknown or does not overlap the domain. - **429**: A quota was exceeded. - **503**: The USGS 3DEP catalog is temporarily unreachable. @@ -390,9 +387,8 @@ async def asyncio( ## Error Responses - - **422**: No 3DEP lidar covers this domain, a pinned acquisition is - unknown or does not overlap the domain, or the fetch would exceed the - point budget. + - **422**: No 3DEP lidar covers this domain, or a pinned acquisition is + unknown or does not overlap the domain. - **429**: A quota was exceeded. - **503**: The USGS 3DEP catalog is temporarily unreachable. diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/delete_point_cloud.py b/fastfuels_sdk/v2/client_library/api/point_clouds/delete_point_cloud.py index 5837222..803fb69 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/delete_point_cloud.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/delete_point_cloud.py @@ -63,23 +63,30 @@ def sync_detailed( ) -> Response[Any | HTTPValidationError]: """Delete a point cloud - # Delete Point Cloud + # Delete a Point Cloud - Permanently deletes a point cloud by its unique identifier, including the - stored point data in GCS. This action cannot be undone. + Deletes a point-cloud resource. This action cannot be undone through the + API. Its GCS artifact becomes orphaned and is reclaimed asynchronously by + the storage cleanup service; callers should treat the point cloud as deleted + as soon as this endpoint returns. + + Deleting a point cloud does not delete grids or inventories that were + derived from it. Those resources retain their recorded provenance, although + the source point cloud can no longer be queried. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns HTTP 204 No Content with an empty response body. + HTTP `204 No Content` with an empty response body. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -113,23 +120,30 @@ def sync( ) -> Any | HTTPValidationError | None: """Delete a point cloud - # Delete Point Cloud + # Delete a Point Cloud + + Deletes a point-cloud resource. This action cannot be undone through the + API. Its GCS artifact becomes orphaned and is reclaimed asynchronously by + the storage cleanup service; callers should treat the point cloud as deleted + as soon as this endpoint returns. - Permanently deletes a point cloud by its unique identifier, including the - stored point data in GCS. This action cannot be undone. + Deleting a point cloud does not delete grids or inventories that were + derived from it. Those resources retain their recorded provenance, although + the source point cloud can no longer be queried. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns HTTP 204 No Content with an empty response body. + HTTP `204 No Content` with an empty response body. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -158,23 +172,30 @@ async def asyncio_detailed( ) -> Response[Any | HTTPValidationError]: """Delete a point cloud - # Delete Point Cloud + # Delete a Point Cloud - Permanently deletes a point cloud by its unique identifier, including the - stored point data in GCS. This action cannot be undone. + Deletes a point-cloud resource. This action cannot be undone through the + API. Its GCS artifact becomes orphaned and is reclaimed asynchronously by + the storage cleanup service; callers should treat the point cloud as deleted + as soon as this endpoint returns. + + Deleting a point cloud does not delete grids or inventories that were + derived from it. Those resources retain their recorded provenance, although + the source point cloud can no longer be queried. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns HTTP 204 No Content with an empty response body. + HTTP `204 No Content` with an empty response body. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -206,23 +227,30 @@ async def asyncio( ) -> Any | HTTPValidationError | None: """Delete a point cloud - # Delete Point Cloud + # Delete a Point Cloud + + Deletes a point-cloud resource. This action cannot be undone through the + API. Its GCS artifact becomes orphaned and is reclaimed asynchronously by + the storage cleanup service; callers should treat the point cloud as deleted + as soon as this endpoint returns. - Permanently deletes a point cloud by its unique identifier, including the - stored point data in GCS. This action cannot be undone. + Deleting a point cloud does not delete grids or inventories that were + derived from it. Those resources retain their recorded provenance, although + the source point cloud can no longer be queried. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns HTTP 204 No Content with an empty response body. + HTTP `204 No Content` with an empty response body. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud.py b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud.py index 25df4e8..8e0dbb2 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud.py @@ -63,24 +63,31 @@ def sync_detailed( *, client: AuthenticatedClient, ) -> Response[HTTPValidationError | PointCloud]: - """Get a point cloud by ID + r"""Get a point cloud by ID - # Get Point Cloud + # Get a Point Cloud - Retrieves a specific point cloud resource by its unique identifier. + Returns one point-cloud resource by ID. This endpoint returns resource + metadata and processing state; it does not return the individual points. + Use `/data/metadata` and the tile data endpoints for point values after the + resource reaches `status=\"completed\"`. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique 32-character hex identifier. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns the point cloud resource. + The complete point-cloud resource, including its acquisition `type`, + `source` provenance, processing `status`, georeference, content summary, + checksum, and user-editable metadata. Derived fields such as `georeference` + and `summary` are null until processing completes. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -112,24 +119,31 @@ def sync( *, client: AuthenticatedClient, ) -> HTTPValidationError | PointCloud | None: - """Get a point cloud by ID + r"""Get a point cloud by ID - # Get Point Cloud + # Get a Point Cloud - Retrieves a specific point cloud resource by its unique identifier. + Returns one point-cloud resource by ID. This endpoint returns resource + metadata and processing state; it does not return the individual points. + Use `/data/metadata` and the tile data endpoints for point values after the + resource reaches `status=\"completed\"`. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique 32-character hex identifier. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns the point cloud resource. + The complete point-cloud resource, including its acquisition `type`, + `source` provenance, processing `status`, georeference, content summary, + checksum, and user-editable metadata. Derived fields such as `georeference` + and `summary` are null until processing completes. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -156,24 +170,31 @@ async def asyncio_detailed( *, client: AuthenticatedClient, ) -> Response[HTTPValidationError | PointCloud]: - """Get a point cloud by ID + r"""Get a point cloud by ID - # Get Point Cloud + # Get a Point Cloud - Retrieves a specific point cloud resource by its unique identifier. + Returns one point-cloud resource by ID. This endpoint returns resource + metadata and processing state; it does not return the individual points. + Use `/data/metadata` and the tile data endpoints for point values after the + resource reaches `status=\"completed\"`. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique 32-character hex identifier. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns the point cloud resource. + The complete point-cloud resource, including its acquisition `type`, + `source` provenance, processing `status`, georeference, content summary, + checksum, and user-editable metadata. Derived fields such as `georeference` + and `summary` are null until processing completes. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -203,24 +224,31 @@ async def asyncio( *, client: AuthenticatedClient, ) -> HTTPValidationError | PointCloud | None: - """Get a point cloud by ID + r"""Get a point cloud by ID - # Get Point Cloud + # Get a Point Cloud - Retrieves a specific point cloud resource by its unique identifier. + Returns one point-cloud resource by ID. This endpoint returns resource + metadata and processing state; it does not return the individual points. + Use `/data/metadata` and the tile data endpoints for point values after the + resource reaches `status=\"completed\"`. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique 32-character hex identifier. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Response - Returns the point cloud resource. + The complete point-cloud resource, including its acquisition `type`, + `source` provenance, processing `status`, georeference, content summary, + checksum, and user-editable metadata. Derived fields such as `georeference` + and `summary` are null until processing completes. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_binary.py b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_binary.py new file mode 100644 index 0000000..4238723 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_binary.py @@ -0,0 +1,603 @@ +from http import HTTPStatus +from typing import Any, cast +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> dict[str, Any]: + + params: dict[str, Any] = {} + + json_lod: int | None | Unset + if isinstance(lod, Unset): + json_lod = UNSET + else: + json_lod = lod + params["lod"] = json_lod + + json_classes: None | str | Unset + if isinstance(classes, Unset): + json_classes = UNSET + else: + json_classes = classes + params["classes"] = json_classes + + json_columns: None | str | Unset + if isinstance(columns, Unset): + json_columns = UNSET + else: + json_columns = columns + params["columns"] = json_columns + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/domains/{domain_id}/pointclouds/{point_cloud_id}/data/{tile_x}/{tile_y}/binary".format( + domain_id=quote(str(domain_id), safe=""), + point_cloud_id=quote(str(point_cloud_id), safe=""), + tile_x=quote(str(tile_x), safe=""), + tile_y=quote(str(tile_y), safe=""), + ), + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | str | None: + if response.status_code == 200: + response_200 = cast(str, response.content) + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | str]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> Response[HTTPValidationError | str]: + """Get point-cloud tile data (binary) + + # Get Point-Cloud Tile Data as Binary + + Returns selected columns from one occupied point-cloud tile as raw + little-endian typed arrays. This is the compact counterpart to the JSON + endpoint and is intended for clients that can construct NumPy, JavaScript, + Rust, or C/C++ typed arrays directly from response bytes. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first to discover valid tiles, cumulative LOD costs, available columns, and + coordinate scaling. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Omit it for all classes. + - **columns**: Optional comma-separated column projection in the exact + desired block order, for example `?columns=X,Y,Z`. Omit it for all public + stored columns. + + ## Response Body + + The body is one contiguous column block after another in `X-Data-Columns` + order. Every block contains `X-Data-Count` values and uses the corresponding + dtype in `X-Data-Dtypes`. All multi-byte values are little-endian. + + For example, these headers: + + ```text + X-Data-Columns: X,Z,classification + X-Data-Dtypes: int32,int32,uint8 + X-Data-Count: 1000 + ``` + + describe `4000` bytes of X values, followed by `4000` bytes of Z values, + followed by `1000` classification bytes. In general, each block occupies: + + `X-Data-Count * sizeof(corresponding dtype)` + + Slice the body at the cumulative block sizes. Values with the same position + within each block describe the same point. + + ## Response Headers + + - **X-Data-Columns**: Comma-separated column block order. + - **X-Data-Dtypes**: Comma-separated NumPy dtype for each column block. + - **X-Data-Count**: Number of values in every block. + - **X-Data-Tile**: Requested tile as `tile_x,tile_y`. + - **X-Data-Bounds**: Horizontal tile bounds as + `min_x,min_y,max_x,max_y`. + - **X-Data-LOD**: Inclusive LOD ceiling used for the response. + - **X-Data-Classes**: Comma-separated selected ASPRS classes, or `all` when + no class filter was supplied. + - **X-Data-Scales** and **X-Data-Offsets**: X/Y/Z coordinate encoding. + Decode coordinate axis `i` with + `stored_integer * scale[i] + offset[i]`. + + These headers are exposed through CORS, so browser JavaScript can read them. + + Binary responses are capped at 30 MiB. If a request is too large, lower + `lod` or select fewer classes or columns. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected binary column blocks exceed the + 30 MiB response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns in the desired binary block + order, such as `X,Y,Z`. Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | str] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + lod=lod, + classes=classes, + columns=columns, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> HTTPValidationError | str | None: + """Get point-cloud tile data (binary) + + # Get Point-Cloud Tile Data as Binary + + Returns selected columns from one occupied point-cloud tile as raw + little-endian typed arrays. This is the compact counterpart to the JSON + endpoint and is intended for clients that can construct NumPy, JavaScript, + Rust, or C/C++ typed arrays directly from response bytes. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first to discover valid tiles, cumulative LOD costs, available columns, and + coordinate scaling. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Omit it for all classes. + - **columns**: Optional comma-separated column projection in the exact + desired block order, for example `?columns=X,Y,Z`. Omit it for all public + stored columns. + + ## Response Body + + The body is one contiguous column block after another in `X-Data-Columns` + order. Every block contains `X-Data-Count` values and uses the corresponding + dtype in `X-Data-Dtypes`. All multi-byte values are little-endian. + + For example, these headers: + + ```text + X-Data-Columns: X,Z,classification + X-Data-Dtypes: int32,int32,uint8 + X-Data-Count: 1000 + ``` + + describe `4000` bytes of X values, followed by `4000` bytes of Z values, + followed by `1000` classification bytes. In general, each block occupies: + + `X-Data-Count * sizeof(corresponding dtype)` + + Slice the body at the cumulative block sizes. Values with the same position + within each block describe the same point. + + ## Response Headers + + - **X-Data-Columns**: Comma-separated column block order. + - **X-Data-Dtypes**: Comma-separated NumPy dtype for each column block. + - **X-Data-Count**: Number of values in every block. + - **X-Data-Tile**: Requested tile as `tile_x,tile_y`. + - **X-Data-Bounds**: Horizontal tile bounds as + `min_x,min_y,max_x,max_y`. + - **X-Data-LOD**: Inclusive LOD ceiling used for the response. + - **X-Data-Classes**: Comma-separated selected ASPRS classes, or `all` when + no class filter was supplied. + - **X-Data-Scales** and **X-Data-Offsets**: X/Y/Z coordinate encoding. + Decode coordinate axis `i` with + `stored_integer * scale[i] + offset[i]`. + + These headers are exposed through CORS, so browser JavaScript can read them. + + Binary responses are capped at 30 MiB. If a request is too large, lower + `lod` or select fewer classes or columns. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected binary column blocks exceed the + 30 MiB response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns in the desired binary block + order, such as `X,Y,Z`. Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | str + """ + + return sync_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + client=client, + lod=lod, + classes=classes, + columns=columns, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> Response[HTTPValidationError | str]: + """Get point-cloud tile data (binary) + + # Get Point-Cloud Tile Data as Binary + + Returns selected columns from one occupied point-cloud tile as raw + little-endian typed arrays. This is the compact counterpart to the JSON + endpoint and is intended for clients that can construct NumPy, JavaScript, + Rust, or C/C++ typed arrays directly from response bytes. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first to discover valid tiles, cumulative LOD costs, available columns, and + coordinate scaling. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Omit it for all classes. + - **columns**: Optional comma-separated column projection in the exact + desired block order, for example `?columns=X,Y,Z`. Omit it for all public + stored columns. + + ## Response Body + + The body is one contiguous column block after another in `X-Data-Columns` + order. Every block contains `X-Data-Count` values and uses the corresponding + dtype in `X-Data-Dtypes`. All multi-byte values are little-endian. + + For example, these headers: + + ```text + X-Data-Columns: X,Z,classification + X-Data-Dtypes: int32,int32,uint8 + X-Data-Count: 1000 + ``` + + describe `4000` bytes of X values, followed by `4000` bytes of Z values, + followed by `1000` classification bytes. In general, each block occupies: + + `X-Data-Count * sizeof(corresponding dtype)` + + Slice the body at the cumulative block sizes. Values with the same position + within each block describe the same point. + + ## Response Headers + + - **X-Data-Columns**: Comma-separated column block order. + - **X-Data-Dtypes**: Comma-separated NumPy dtype for each column block. + - **X-Data-Count**: Number of values in every block. + - **X-Data-Tile**: Requested tile as `tile_x,tile_y`. + - **X-Data-Bounds**: Horizontal tile bounds as + `min_x,min_y,max_x,max_y`. + - **X-Data-LOD**: Inclusive LOD ceiling used for the response. + - **X-Data-Classes**: Comma-separated selected ASPRS classes, or `all` when + no class filter was supplied. + - **X-Data-Scales** and **X-Data-Offsets**: X/Y/Z coordinate encoding. + Decode coordinate axis `i` with + `stored_integer * scale[i] + offset[i]`. + + These headers are exposed through CORS, so browser JavaScript can read them. + + Binary responses are capped at 30 MiB. If a request is too large, lower + `lod` or select fewer classes or columns. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected binary column blocks exceed the + 30 MiB response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns in the desired binary block + order, such as `X,Y,Z`. Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | str] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + lod=lod, + classes=classes, + columns=columns, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> HTTPValidationError | str | None: + """Get point-cloud tile data (binary) + + # Get Point-Cloud Tile Data as Binary + + Returns selected columns from one occupied point-cloud tile as raw + little-endian typed arrays. This is the compact counterpart to the JSON + endpoint and is intended for clients that can construct NumPy, JavaScript, + Rust, or C/C++ typed arrays directly from response bytes. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first to discover valid tiles, cumulative LOD costs, available columns, and + coordinate scaling. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Omit it for all classes. + - **columns**: Optional comma-separated column projection in the exact + desired block order, for example `?columns=X,Y,Z`. Omit it for all public + stored columns. + + ## Response Body + + The body is one contiguous column block after another in `X-Data-Columns` + order. Every block contains `X-Data-Count` values and uses the corresponding + dtype in `X-Data-Dtypes`. All multi-byte values are little-endian. + + For example, these headers: + + ```text + X-Data-Columns: X,Z,classification + X-Data-Dtypes: int32,int32,uint8 + X-Data-Count: 1000 + ``` + + describe `4000` bytes of X values, followed by `4000` bytes of Z values, + followed by `1000` classification bytes. In general, each block occupies: + + `X-Data-Count * sizeof(corresponding dtype)` + + Slice the body at the cumulative block sizes. Values with the same position + within each block describe the same point. + + ## Response Headers + + - **X-Data-Columns**: Comma-separated column block order. + - **X-Data-Dtypes**: Comma-separated NumPy dtype for each column block. + - **X-Data-Count**: Number of values in every block. + - **X-Data-Tile**: Requested tile as `tile_x,tile_y`. + - **X-Data-Bounds**: Horizontal tile bounds as + `min_x,min_y,max_x,max_y`. + - **X-Data-LOD**: Inclusive LOD ceiling used for the response. + - **X-Data-Classes**: Comma-separated selected ASPRS classes, or `all` when + no class filter was supplied. + - **X-Data-Scales** and **X-Data-Offsets**: X/Y/Z coordinate encoding. + Decode coordinate axis `i` with + `stored_integer * scale[i] + offset[i]`. + + These headers are exposed through CORS, so browser JavaScript can read them. + + Binary responses are capped at 30 MiB. If a request is too large, lower + `lod` or select fewer classes or columns. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected binary column blocks exceed the + 30 MiB response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns in the desired binary block + order, such as `X,Y,Z`. Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | str + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + client=client, + lod=lod, + classes=classes, + columns=columns, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_json.py b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_json.py new file mode 100644 index 0000000..34384dc --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_json.py @@ -0,0 +1,649 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...models.point_cloud_tile_data_response import PointCloudTileDataResponse +from ...types import UNSET, Response, Unset + + +def _get_kwargs( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> dict[str, Any]: + + params: dict[str, Any] = {} + + json_lod: int | None | Unset + if isinstance(lod, Unset): + json_lod = UNSET + else: + json_lod = lod + params["lod"] = json_lod + + json_classes: None | str | Unset + if isinstance(classes, Unset): + json_classes = UNSET + else: + json_classes = classes + params["classes"] = json_classes + + json_columns: None | str | Unset + if isinstance(columns, Unset): + json_columns = UNSET + else: + json_columns = columns + params["columns"] = json_columns + + params = {k: v for k, v in params.items() if v is not UNSET and v is not None} + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/domains/{domain_id}/pointclouds/{point_cloud_id}/data/{tile_x}/{tile_y}".format( + domain_id=quote(str(domain_id), safe=""), + point_cloud_id=quote(str(point_cloud_id), safe=""), + tile_x=quote(str(tile_x), safe=""), + tile_y=quote(str(tile_y), safe=""), + ), + "params": params, + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | PointCloudTileDataResponse | None: + if response.status_code == 200: + response_200 = PointCloudTileDataResponse.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | PointCloudTileDataResponse]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> Response[HTTPValidationError | PointCloudTileDataResponse]: + r"""Get point-cloud tile data (JSON) + + # Get Point-Cloud Tile Data as JSON + + Returns selected columns from one occupied point-cloud tile as columnar + JSON. Use this representation for inspection, small previews, and clients + that do not need the more compact binary response. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first. Its `tiles` array supplies valid tile coordinates and exact + cumulative point counts, while its `columns` object supplies the available + names and dtypes. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. They are indices in the point cloud's own tiling, + not projected map coordinates. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. Valid values are `0` through `lod_levels - 1` from the metadata + response. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Duplicate values are ignored. Omit it to retain + every classification. + - **columns**: Optional comma-separated column projection in the desired + response order, for example `?columns=X,Y,Z`. Omit it to return all + public stored columns. + + ## Response + + The response is columnar: arrays in `data` have equal length and values at + the same array index describe the same point. + + For example, this is the complete response for tile `(-1, 0)` from the + `static-test-blackfoot-3dep` point cloud with + `?lod=5&classes=1,2&columns=X,Y,Z,classification`: + + ```json + { + \"tile_x\": -1, + \"tile_y\": 0, + \"bounds\": [ + 293711.08485993545, + 5198981.669894749, + 294094.99218481116, + 5199365.577219625 + ], + \"lod\": 5, + \"classes\": [1, 2], + \"scales\": [0.001, 0.001, 0.001], + \"offsets\": [294094.0, 5198981.0, 0.0], + \"columns\": { + \"X\": \"int32\", + \"Y\": \"int32\", + \"Z\": \"int32\", + \"classification\": \"uint8\" + }, + \"data\": { + \"X\": [992, 992], + \"Y\": [346497, 64586], + \"Z\": [1077190, 1051360], + \"classification\": [1, 2] + } + } + ``` + + `X`, `Y`, and `Z` remain stored integers so the response is exact and does + not expand them to float64. Decode coordinate axis `i` with: + + `coordinate = stored_integer * scales[i] + offsets[i]` + + The echoed `lod`, `classes`, `columns`, and tile bounds make the response + self-describing. When `classes` was omitted, the response field is `null`. + + JSON responses are capped at 1,000,000 numeric values, calculated as rows + multiplied by selected columns. If a request is too large, lower `lod`, + select fewer classes or columns, or use the `/binary` endpoint. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected rows and columns exceed the JSON + response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns to return, such as `X,Y,Z`. + Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | PointCloudTileDataResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + lod=lod, + classes=classes, + columns=columns, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> HTTPValidationError | PointCloudTileDataResponse | None: + r"""Get point-cloud tile data (JSON) + + # Get Point-Cloud Tile Data as JSON + + Returns selected columns from one occupied point-cloud tile as columnar + JSON. Use this representation for inspection, small previews, and clients + that do not need the more compact binary response. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first. Its `tiles` array supplies valid tile coordinates and exact + cumulative point counts, while its `columns` object supplies the available + names and dtypes. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. They are indices in the point cloud's own tiling, + not projected map coordinates. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. Valid values are `0` through `lod_levels - 1` from the metadata + response. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Duplicate values are ignored. Omit it to retain + every classification. + - **columns**: Optional comma-separated column projection in the desired + response order, for example `?columns=X,Y,Z`. Omit it to return all + public stored columns. + + ## Response + + The response is columnar: arrays in `data` have equal length and values at + the same array index describe the same point. + + For example, this is the complete response for tile `(-1, 0)` from the + `static-test-blackfoot-3dep` point cloud with + `?lod=5&classes=1,2&columns=X,Y,Z,classification`: + + ```json + { + \"tile_x\": -1, + \"tile_y\": 0, + \"bounds\": [ + 293711.08485993545, + 5198981.669894749, + 294094.99218481116, + 5199365.577219625 + ], + \"lod\": 5, + \"classes\": [1, 2], + \"scales\": [0.001, 0.001, 0.001], + \"offsets\": [294094.0, 5198981.0, 0.0], + \"columns\": { + \"X\": \"int32\", + \"Y\": \"int32\", + \"Z\": \"int32\", + \"classification\": \"uint8\" + }, + \"data\": { + \"X\": [992, 992], + \"Y\": [346497, 64586], + \"Z\": [1077190, 1051360], + \"classification\": [1, 2] + } + } + ``` + + `X`, `Y`, and `Z` remain stored integers so the response is exact and does + not expand them to float64. Decode coordinate axis `i` with: + + `coordinate = stored_integer * scales[i] + offsets[i]` + + The echoed `lod`, `classes`, `columns`, and tile bounds make the response + self-describing. When `classes` was omitted, the response field is `null`. + + JSON responses are capped at 1,000,000 numeric values, calculated as rows + multiplied by selected columns. If a request is too large, lower `lod`, + select fewer classes or columns, or use the `/binary` endpoint. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected rows and columns exceed the JSON + response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns to return, such as `X,Y,Z`. + Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | PointCloudTileDataResponse + """ + + return sync_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + client=client, + lod=lod, + classes=classes, + columns=columns, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> Response[HTTPValidationError | PointCloudTileDataResponse]: + r"""Get point-cloud tile data (JSON) + + # Get Point-Cloud Tile Data as JSON + + Returns selected columns from one occupied point-cloud tile as columnar + JSON. Use this representation for inspection, small previews, and clients + that do not need the more compact binary response. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first. Its `tiles` array supplies valid tile coordinates and exact + cumulative point counts, while its `columns` object supplies the available + names and dtypes. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. They are indices in the point cloud's own tiling, + not projected map coordinates. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. Valid values are `0` through `lod_levels - 1` from the metadata + response. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Duplicate values are ignored. Omit it to retain + every classification. + - **columns**: Optional comma-separated column projection in the desired + response order, for example `?columns=X,Y,Z`. Omit it to return all + public stored columns. + + ## Response + + The response is columnar: arrays in `data` have equal length and values at + the same array index describe the same point. + + For example, this is the complete response for tile `(-1, 0)` from the + `static-test-blackfoot-3dep` point cloud with + `?lod=5&classes=1,2&columns=X,Y,Z,classification`: + + ```json + { + \"tile_x\": -1, + \"tile_y\": 0, + \"bounds\": [ + 293711.08485993545, + 5198981.669894749, + 294094.99218481116, + 5199365.577219625 + ], + \"lod\": 5, + \"classes\": [1, 2], + \"scales\": [0.001, 0.001, 0.001], + \"offsets\": [294094.0, 5198981.0, 0.0], + \"columns\": { + \"X\": \"int32\", + \"Y\": \"int32\", + \"Z\": \"int32\", + \"classification\": \"uint8\" + }, + \"data\": { + \"X\": [992, 992], + \"Y\": [346497, 64586], + \"Z\": [1077190, 1051360], + \"classification\": [1, 2] + } + } + ``` + + `X`, `Y`, and `Z` remain stored integers so the response is exact and does + not expand them to float64. Decode coordinate axis `i` with: + + `coordinate = stored_integer * scales[i] + offsets[i]` + + The echoed `lod`, `classes`, `columns`, and tile bounds make the response + self-describing. When `classes` was omitted, the response field is `null`. + + JSON responses are capped at 1,000,000 numeric values, calculated as rows + multiplied by selected columns. If a request is too large, lower `lod`, + select fewer classes or columns, or use the `/binary` endpoint. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected rows and columns exceed the JSON + response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns to return, such as `X,Y,Z`. + Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | PointCloudTileDataResponse] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + lod=lod, + classes=classes, + columns=columns, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + point_cloud_id: str, + tile_x: int, + tile_y: int, + *, + client: AuthenticatedClient, + lod: int | None | Unset = UNSET, + classes: None | str | Unset = UNSET, + columns: None | str | Unset = UNSET, +) -> HTTPValidationError | PointCloudTileDataResponse | None: + r"""Get point-cloud tile data (JSON) + + # Get Point-Cloud Tile Data as JSON + + Returns selected columns from one occupied point-cloud tile as columnar + JSON. Use this representation for inspection, small previews, and clients + that do not need the more compact binary response. + + Call `GET /domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata` + first. Its `tiles` array supplies valid tile coordinates and exact + cumulative point counts, while its `columns` object supplies the available + names and dtypes. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + - **tile_x** and **tile_y**: Integer coordinates of an occupied tile from + the metadata response. They are indices in the point cloud's own tiling, + not projected map coordinates. + + ## Query Parameters + + - **lod**: Inclusive LOD ceiling. `lod=0` returns the coarsest sample; + `lod=k` returns levels `0` through `k`; omitting it returns the complete + tile. Valid values are `0` through `lod_levels - 1` from the metadata + response. + - **classes**: Optional comma-separated ASPRS classification filter, for + example `?classes=2,5`. Duplicate values are ignored. Omit it to retain + every classification. + - **columns**: Optional comma-separated column projection in the desired + response order, for example `?columns=X,Y,Z`. Omit it to return all + public stored columns. + + ## Response + + The response is columnar: arrays in `data` have equal length and values at + the same array index describe the same point. + + For example, this is the complete response for tile `(-1, 0)` from the + `static-test-blackfoot-3dep` point cloud with + `?lod=5&classes=1,2&columns=X,Y,Z,classification`: + + ```json + { + \"tile_x\": -1, + \"tile_y\": 0, + \"bounds\": [ + 293711.08485993545, + 5198981.669894749, + 294094.99218481116, + 5199365.577219625 + ], + \"lod\": 5, + \"classes\": [1, 2], + \"scales\": [0.001, 0.001, 0.001], + \"offsets\": [294094.0, 5198981.0, 0.0], + \"columns\": { + \"X\": \"int32\", + \"Y\": \"int32\", + \"Z\": \"int32\", + \"classification\": \"uint8\" + }, + \"data\": { + \"X\": [992, 992], + \"Y\": [346497, 64586], + \"Z\": [1077190, 1051360], + \"classification\": [1, 2] + } + } + ``` + + `X`, `Y`, and `Z` remain stored integers so the response is exact and does + not expand them to float64. Decode coordinate axis `i` with: + + `coordinate = stored_integer * scales[i] + offsets[i]` + + The echoed `lod`, `classes`, `columns`, and tile bounds make the response + self-describing. When `classes` was omitted, the response field is `null`. + + JSON responses are capped at 1,000,000 numeric values, calculated as rows + multiplied by selected columns. If a request is too large, lower `lod`, + select fewer classes or columns, or use the `/binary` endpoint. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **413 Content Too Large**: The selected rows and columns exceed the JSON + response limit. + - **422 Unprocessable Entity**: The cloud is not completed; the tile, LOD, + class, or column selection is invalid; or stored data is unreadable or + inconsistent with its index. + + Args: + domain_id (str): + point_cloud_id (str): + tile_x (int): Horizontal tile index from `GET /data/metadata`. + tile_y (int): Vertical tile index from `GET /data/metadata`. + lod (int | None | Unset): Inclusive LOD ceiling. Omit to read the complete tile. Valid + values are `0` through `lod_levels - 1` from `/data/metadata`. + classes (None | str | Unset): Comma-separated ASPRS classification codes to retain, such + as `2,5`. Omit to retain every class. + columns (None | str | Unset): Comma-separated stored columns to return, such as `X,Y,Z`. + Omit to return every public stored column. + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | PointCloudTileDataResponse + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + tile_x=tile_x, + tile_y=tile_y, + client=client, + lod=lod, + classes=classes, + columns=columns, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_metadata.py b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_metadata.py new file mode 100644 index 0000000..cae6a07 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/get_point_cloud_data_metadata.py @@ -0,0 +1,419 @@ +from http import HTTPStatus +from typing import Any +from urllib.parse import quote + +import httpx + +from ... import errors +from ...client import AuthenticatedClient, Client +from ...models.http_validation_error import HTTPValidationError +from ...models.point_cloud_data_metadata import PointCloudDataMetadata +from ...types import Response + + +def _get_kwargs( + domain_id: str, + point_cloud_id: str, +) -> dict[str, Any]: + + _kwargs: dict[str, Any] = { + "method": "get", + "url": "/domains/{domain_id}/pointclouds/{point_cloud_id}/data/metadata".format( + domain_id=quote(str(domain_id), safe=""), + point_cloud_id=quote(str(point_cloud_id), safe=""), + ), + } + + return _kwargs + + +def _parse_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> HTTPValidationError | PointCloudDataMetadata | None: + if response.status_code == 200: + response_200 = PointCloudDataMetadata.from_dict(response.json()) + + return response_200 + + if response.status_code == 422: + response_422 = HTTPValidationError.from_dict(response.json()) + + return response_422 + + if client.raise_on_unexpected_status: + raise errors.UnexpectedStatus(response.status_code, response.content) + else: + return None + + +def _build_response( + *, client: AuthenticatedClient | Client, response: httpx.Response +) -> Response[HTTPValidationError | PointCloudDataMetadata]: + return Response( + status_code=HTTPStatus(response.status_code), + content=response.content, + headers=response.headers, + parsed=_parse_response(client=client, response=response), + ) + + +def sync_detailed( + domain_id: str, + point_cloud_id: str, + *, + client: AuthenticatedClient, +) -> Response[HTTPValidationError | PointCloudDataMetadata]: + """Get point-cloud tile metadata + + # Get Point-Cloud Data Metadata + + Returns the public read index for a completed point cloud without returning + any point values. Call this endpoint first to discover the occupied tiles, + stored columns and dtypes, coordinate encoding, and the number of points + each level of detail (LOD) would return. + + A typical client workflow is: + + 1. Read this metadata once. + 2. Select one of the entries in `tiles`. + 3. Choose an LOD whose cumulative point count fits the client workload. + 4. Request that tile from the JSON or binary data endpoint. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + + ## Response + + - **tile_m**: Width and height of each tile in the units of `crs`. + - **lod_levels**: Number of available cumulative LOD selections. The + current format has six levels, numbered `0` through `5`. + - **crs**: Coordinate reference system for the decoded point coordinates + and all reported bounds. + - **bounds**: Horizontal point-cloud extent as + `[min_x, min_y, max_x, max_y]`. + - **scales** and **offsets**: Three values in X/Y/Z order used to decode + stored integer coordinates: + + `coordinate = stored_integer * scale + offset` + + - **columns**: Stored public column names mapped to their NumPy-compatible + dtypes. `X`, `Y`, and `Z` are encoded integers; `classification` contains + ASPRS classification codes. Other columns, such as `intensity`, are + source-dependent. + - **tiles**: Occupied tiles only. Empty positions in the tiling are omitted. + Each entry contains its integer `tile_x` and `tile_y`, horizontal + `bounds`, and `points_by_lod`. + + `points_by_lod[k]` is the number of rows returned by `lod=k` before an + optional classification filter. Counts are cumulative: LOD 0 is the + coarsest sample, each higher value includes every preceding level, and the + final value is the complete tile. A sparse boundary tile may legitimately + repeat counts across several LODs when it contains too few points to + populate every level. + + Internal GCS object names, Parquet part paths, row-group offsets, and byte + ranges are deliberately not part of the API response. The server uses that + storage index to satisfy tile requests; clients only need this stable tile + catalogue. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **422 Unprocessable Entity**: The point cloud is not completed, its + resource metadata does not match its stored data, or the stored Parquet + index is missing or malformed. Re-create the point cloud before retrying. + + Args: + domain_id (str): + point_cloud_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | PointCloudDataMetadata] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + ) + + response = client.get_httpx_client().request( + **kwargs, + ) + + return _build_response(client=client, response=response) + + +def sync( + domain_id: str, + point_cloud_id: str, + *, + client: AuthenticatedClient, +) -> HTTPValidationError | PointCloudDataMetadata | None: + """Get point-cloud tile metadata + + # Get Point-Cloud Data Metadata + + Returns the public read index for a completed point cloud without returning + any point values. Call this endpoint first to discover the occupied tiles, + stored columns and dtypes, coordinate encoding, and the number of points + each level of detail (LOD) would return. + + A typical client workflow is: + + 1. Read this metadata once. + 2. Select one of the entries in `tiles`. + 3. Choose an LOD whose cumulative point count fits the client workload. + 4. Request that tile from the JSON or binary data endpoint. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + + ## Response + + - **tile_m**: Width and height of each tile in the units of `crs`. + - **lod_levels**: Number of available cumulative LOD selections. The + current format has six levels, numbered `0` through `5`. + - **crs**: Coordinate reference system for the decoded point coordinates + and all reported bounds. + - **bounds**: Horizontal point-cloud extent as + `[min_x, min_y, max_x, max_y]`. + - **scales** and **offsets**: Three values in X/Y/Z order used to decode + stored integer coordinates: + + `coordinate = stored_integer * scale + offset` + + - **columns**: Stored public column names mapped to their NumPy-compatible + dtypes. `X`, `Y`, and `Z` are encoded integers; `classification` contains + ASPRS classification codes. Other columns, such as `intensity`, are + source-dependent. + - **tiles**: Occupied tiles only. Empty positions in the tiling are omitted. + Each entry contains its integer `tile_x` and `tile_y`, horizontal + `bounds`, and `points_by_lod`. + + `points_by_lod[k]` is the number of rows returned by `lod=k` before an + optional classification filter. Counts are cumulative: LOD 0 is the + coarsest sample, each higher value includes every preceding level, and the + final value is the complete tile. A sparse boundary tile may legitimately + repeat counts across several LODs when it contains too few points to + populate every level. + + Internal GCS object names, Parquet part paths, row-group offsets, and byte + ranges are deliberately not part of the API response. The server uses that + storage index to satisfy tile requests; clients only need this stable tile + catalogue. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **422 Unprocessable Entity**: The point cloud is not completed, its + resource metadata does not match its stored data, or the stored Parquet + index is missing or malformed. Re-create the point cloud before retrying. + + Args: + domain_id (str): + point_cloud_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | PointCloudDataMetadata + """ + + return sync_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + client=client, + ).parsed + + +async def asyncio_detailed( + domain_id: str, + point_cloud_id: str, + *, + client: AuthenticatedClient, +) -> Response[HTTPValidationError | PointCloudDataMetadata]: + """Get point-cloud tile metadata + + # Get Point-Cloud Data Metadata + + Returns the public read index for a completed point cloud without returning + any point values. Call this endpoint first to discover the occupied tiles, + stored columns and dtypes, coordinate encoding, and the number of points + each level of detail (LOD) would return. + + A typical client workflow is: + + 1. Read this metadata once. + 2. Select one of the entries in `tiles`. + 3. Choose an LOD whose cumulative point count fits the client workload. + 4. Request that tile from the JSON or binary data endpoint. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + + ## Response + + - **tile_m**: Width and height of each tile in the units of `crs`. + - **lod_levels**: Number of available cumulative LOD selections. The + current format has six levels, numbered `0` through `5`. + - **crs**: Coordinate reference system for the decoded point coordinates + and all reported bounds. + - **bounds**: Horizontal point-cloud extent as + `[min_x, min_y, max_x, max_y]`. + - **scales** and **offsets**: Three values in X/Y/Z order used to decode + stored integer coordinates: + + `coordinate = stored_integer * scale + offset` + + - **columns**: Stored public column names mapped to their NumPy-compatible + dtypes. `X`, `Y`, and `Z` are encoded integers; `classification` contains + ASPRS classification codes. Other columns, such as `intensity`, are + source-dependent. + - **tiles**: Occupied tiles only. Empty positions in the tiling are omitted. + Each entry contains its integer `tile_x` and `tile_y`, horizontal + `bounds`, and `points_by_lod`. + + `points_by_lod[k]` is the number of rows returned by `lod=k` before an + optional classification filter. Counts are cumulative: LOD 0 is the + coarsest sample, each higher value includes every preceding level, and the + final value is the complete tile. A sparse boundary tile may legitimately + repeat counts across several LODs when it contains too few points to + populate every level. + + Internal GCS object names, Parquet part paths, row-group offsets, and byte + ranges are deliberately not part of the API response. The server uses that + storage index to satisfy tile requests; clients only need this stable tile + catalogue. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **422 Unprocessable Entity**: The point cloud is not completed, its + resource metadata does not match its stored data, or the stored Parquet + index is missing or malformed. Re-create the point cloud before retrying. + + Args: + domain_id (str): + point_cloud_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + Response[HTTPValidationError | PointCloudDataMetadata] + """ + + kwargs = _get_kwargs( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + ) + + response = await client.get_async_httpx_client().request(**kwargs) + + return _build_response(client=client, response=response) + + +async def asyncio( + domain_id: str, + point_cloud_id: str, + *, + client: AuthenticatedClient, +) -> HTTPValidationError | PointCloudDataMetadata | None: + """Get point-cloud tile metadata + + # Get Point-Cloud Data Metadata + + Returns the public read index for a completed point cloud without returning + any point values. Call this endpoint first to discover the occupied tiles, + stored columns and dtypes, coordinate encoding, and the number of points + each level of detail (LOD) would return. + + A typical client workflow is: + + 1. Read this metadata once. + 2. Select one of the entries in `tiles`. + 3. Choose an LOD whose cumulative point count fits the client workload. + 4. Request that tile from the JSON or binary data endpoint. + + ## Path Parameters + + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. + + ## Response + + - **tile_m**: Width and height of each tile in the units of `crs`. + - **lod_levels**: Number of available cumulative LOD selections. The + current format has six levels, numbered `0` through `5`. + - **crs**: Coordinate reference system for the decoded point coordinates + and all reported bounds. + - **bounds**: Horizontal point-cloud extent as + `[min_x, min_y, max_x, max_y]`. + - **scales** and **offsets**: Three values in X/Y/Z order used to decode + stored integer coordinates: + + `coordinate = stored_integer * scale + offset` + + - **columns**: Stored public column names mapped to their NumPy-compatible + dtypes. `X`, `Y`, and `Z` are encoded integers; `classification` contains + ASPRS classification codes. Other columns, such as `intensity`, are + source-dependent. + - **tiles**: Occupied tiles only. Empty positions in the tiling are omitted. + Each entry contains its integer `tile_x` and `tile_y`, horizontal + `bounds`, and `points_by_lod`. + + `points_by_lod[k]` is the number of rows returned by `lod=k` before an + optional classification filter. Counts are cumulative: LOD 0 is the + coarsest sample, each higher value includes every preceding level, and the + final value is the complete tile. A sparse boundary tile may legitimately + repeat counts across several LODs when it contains too few points to + populate every level. + + Internal GCS object names, Parquet part paths, row-group offsets, and byte + ranges are deliberately not part of the API response. The server uses that + storage index to satisfy tile requests; clients only need this stable tile + catalogue. + + ## Error Responses + + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. + - **422 Unprocessable Entity**: The point cloud is not completed, its + resource metadata does not match its stored data, or the stored Parquet + index is missing or malformed. Re-create the point cloud before retrying. + + Args: + domain_id (str): + point_cloud_id (str): + + Raises: + errors.UnexpectedStatus: If the server returns an undocumented status code and Client.raise_on_unexpected_status is True. + httpx.TimeoutException: If the request takes longer than Client.timeout. + + Returns: + HTTPValidationError | PointCloudDataMetadata + """ + + return ( + await asyncio_detailed( + domain_id=domain_id, + point_cloud_id=point_cloud_id, + client=client, + ) + ).parsed diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds.py b/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds.py index 39fe51a..907cccf 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds.py @@ -130,28 +130,35 @@ def sync_detailed( ) -> Response[HTTPValidationError | ListPointCloudsResponse]: """List point clouds in a domain - # List Point Clouds (Domain) + # List Point Clouds in a Domain - Retrieves a paginated list of the point clouds within a single domain - belonging to the authenticated user. + Returns a paginated list of point clouds owned by the authenticated caller + within one domain. ## Path Parameters - - **domain_id**: (string) The domain to list point clouds for. + - **domain_id**: Domain whose point clouds should be listed. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. + + ## Error Responses + + - **404 Not Found**: The domain does not exist or is not accessible to the + caller. Args: domain_id (str): @@ -162,7 +169,7 @@ def sync_detailed( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -205,28 +212,35 @@ def sync( ) -> HTTPValidationError | ListPointCloudsResponse | None: """List point clouds in a domain - # List Point Clouds (Domain) + # List Point Clouds in a Domain - Retrieves a paginated list of the point clouds within a single domain - belonging to the authenticated user. + Returns a paginated list of point clouds owned by the authenticated caller + within one domain. ## Path Parameters - - **domain_id**: (string) The domain to list point clouds for. + - **domain_id**: Domain whose point clouds should be listed. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. + + ## Error Responses + + - **404 Not Found**: The domain does not exist or is not accessible to the + caller. Args: domain_id (str): @@ -237,7 +251,7 @@ def sync( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -275,28 +289,35 @@ async def asyncio_detailed( ) -> Response[HTTPValidationError | ListPointCloudsResponse]: """List point clouds in a domain - # List Point Clouds (Domain) + # List Point Clouds in a Domain - Retrieves a paginated list of the point clouds within a single domain - belonging to the authenticated user. + Returns a paginated list of point clouds owned by the authenticated caller + within one domain. ## Path Parameters - - **domain_id**: (string) The domain to list point clouds for. + - **domain_id**: Domain whose point clouds should be listed. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. + + ## Error Responses + + - **404 Not Found**: The domain does not exist or is not accessible to the + caller. Args: domain_id (str): @@ -307,7 +328,7 @@ async def asyncio_detailed( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -348,28 +369,35 @@ async def asyncio( ) -> HTTPValidationError | ListPointCloudsResponse | None: """List point clouds in a domain - # List Point Clouds (Domain) + # List Point Clouds in a Domain - Retrieves a paginated list of the point clouds within a single domain - belonging to the authenticated user. + Returns a paginated list of point clouds owned by the authenticated caller + within one domain. ## Path Parameters - - **domain_id**: (string) The domain to list point clouds for. + - **domain_id**: Domain whose point clouds should be listed. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. + + ## Error Responses + + - **404 Not Found**: The domain does not exist or is not accessible to the + caller. Args: domain_id (str): @@ -380,7 +408,7 @@ async def asyncio( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds_cross_domain.py b/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds_cross_domain.py index 5315597..70d7c42 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds_cross_domain.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/list_point_clouds_cross_domain.py @@ -125,24 +125,27 @@ def sync_detailed( ) -> Response[HTTPValidationError | ListPointCloudsResponse]: """List point clouds across all domains - # List Point Clouds (All Domains) + # List Point Clouds Across All Domains - Retrieves a paginated list of every point cloud belonging to the - authenticated user, across all of their domains. + Returns a paginated list of every point cloud owned by the authenticated + caller, regardless of which domain contains it. Use the domain-scoped list + endpoint when the caller already knows the domain. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. Args: page (int | Unset): The page number to retrieve (zero-indexed). Default: 0. @@ -152,7 +155,7 @@ def sync_detailed( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -193,24 +196,27 @@ def sync( ) -> HTTPValidationError | ListPointCloudsResponse | None: """List point clouds across all domains - # List Point Clouds (All Domains) + # List Point Clouds Across All Domains - Retrieves a paginated list of every point cloud belonging to the - authenticated user, across all of their domains. + Returns a paginated list of every point cloud owned by the authenticated + caller, regardless of which domain contains it. Use the domain-scoped list + endpoint when the caller already knows the domain. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. Args: page (int | Unset): The page number to retrieve (zero-indexed). Default: 0. @@ -220,7 +226,7 @@ def sync( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -256,24 +262,27 @@ async def asyncio_detailed( ) -> Response[HTTPValidationError | ListPointCloudsResponse]: """List point clouds across all domains - # List Point Clouds (All Domains) + # List Point Clouds Across All Domains - Retrieves a paginated list of every point cloud belonging to the - authenticated user, across all of their domains. + Returns a paginated list of every point cloud owned by the authenticated + caller, regardless of which domain contains it. Use the domain-scoped list + endpoint when the caller already knows the domain. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. Args: page (int | Unset): The page number to retrieve (zero-indexed). Default: 0. @@ -283,7 +292,7 @@ async def asyncio_detailed( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: @@ -322,24 +331,27 @@ async def asyncio( ) -> HTTPValidationError | ListPointCloudsResponse | None: """List point clouds across all domains - # List Point Clouds (All Domains) + # List Point Clouds Across All Domains - Retrieves a paginated list of every point cloud belonging to the - authenticated user, across all of their domains. + Returns a paginated list of every point cloud owned by the authenticated + caller, regardless of which domain contains it. Use the domain-scoped list + endpoint when the caller already knows the domain. ## Query Parameters - - **page**: (integer, optional) Page number (zero-indexed). Default: 0. - - **size**: (integer, optional) Items per page (1-1000). Default: 100. - - **sort_by**: (string, optional) Field to sort by: `created_on`, `modified_on`, `name`. - - **sort_order**: (string, optional) Sort direction: `ascending`, `descending`. - - **type**: (string, optional) Filter by acquisition type: `als` or `tls`. - - **source**: (string, optional) Filter by source name (e.g., `3dep`, `upload`). - - **tag**: (string, optional) Filter point clouds that contain this tag. + - **page**: Zero-indexed page number. Defaults to `0`. + - **size**: Results per page, from `1` through `1000`. Defaults to `100`. + - **sort_by**: Sort by `created_on`, `modified_on`, or `name`. + - **sort_order**: Sort in `ascending` or `descending` order. + - **type**: Keep only airborne (`als`) or terrestrial (`tls`) clouds. + - **source**: Keep only clouds from a source such as `3dep` or `upload`. + - **tag**: Keep only clouds whose `tags` array contains this value. ## Response - Returns a paginated list of point clouds with metadata. + A standard paginated response. `point_clouds` contains the resources on the + requested page; `current_page`, `page_size`, and `total_items` describe the + page and the complete filtered result set. Args: page (int | Unset): The page number to retrieve (zero-indexed). Default: 0. @@ -349,7 +361,7 @@ async def asyncio( descending). type_ (None | PointCloudType | Unset): Filter point clouds by acquisition type (`als` or `tls`). - source (None | str | Unset): Filter point clouds by source name (e.g., `3dep`, `upload`). + source (None | str | Unset): Filter point clouds by source name (for example, `3dep`). tag (None | str | Unset): Filter point clouds that contain this tag. Raises: diff --git a/fastfuels_sdk/v2/client_library/api/point_clouds/update_point_cloud.py b/fastfuels_sdk/v2/client_library/api/point_clouds/update_point_cloud.py index b18f008..d870ee7 100644 --- a/fastfuels_sdk/v2/client_library/api/point_clouds/update_point_cloud.py +++ b/fastfuels_sdk/v2/client_library/api/point_clouds/update_point_cloud.py @@ -75,42 +75,43 @@ def sync_detailed( ) -> Response[HTTPValidationError | PointCloud]: """Update a point cloud - # Update Point Cloud + # Update a Point Cloud - Updates the metadata of an existing point cloud. Only the fields provided in - the request body are modified. + Updates the user-editable metadata of an existing point cloud. Only fields + present in the request body are changed. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Request Body - All fields are optional: + Every field is optional: - - **name**: (string) New name for the point cloud. - - **description**: (string) New description. - - **tags**: (array of strings) New tags (replaces existing). + - **name**: New human-readable name. + - **description**: New free-text description. + - **tags**: Replacement tag list. Supplying an empty list removes all tags. - ## What Cannot Be Updated + Omitted fields retain their current values. - The following are immutable through this endpoint: + ## Immutable Fields - - **id**, **domain_id**, **type**, **source**, **georeference** - - **created_on** (creation timestamp is permanent) - - **checksum** (changes only when the point cloud's content is rebuilt, never - via metadata updates) + This endpoint cannot alter stored point data or derived/provenance fields, + including `id`, `domain_id`, `type`, `source`, `georeference`, `summary`, + `status`, `created_on`, or `checksum`. A metadata-only update therefore does + not make resources derived from the point cloud stale. - The **modified_on** field is updated automatically. + `modified_on` is updated automatically. ## Response - Returns the updated point cloud resource. + The updated point-cloud resource. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -151,42 +152,43 @@ def sync( ) -> HTTPValidationError | PointCloud | None: """Update a point cloud - # Update Point Cloud + # Update a Point Cloud - Updates the metadata of an existing point cloud. Only the fields provided in - the request body are modified. + Updates the user-editable metadata of an existing point cloud. Only fields + present in the request body are changed. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Request Body - All fields are optional: + Every field is optional: - - **name**: (string) New name for the point cloud. - - **description**: (string) New description. - - **tags**: (array of strings) New tags (replaces existing). + - **name**: New human-readable name. + - **description**: New free-text description. + - **tags**: Replacement tag list. Supplying an empty list removes all tags. - ## What Cannot Be Updated + Omitted fields retain their current values. - The following are immutable through this endpoint: + ## Immutable Fields - - **id**, **domain_id**, **type**, **source**, **georeference** - - **created_on** (creation timestamp is permanent) - - **checksum** (changes only when the point cloud's content is rebuilt, never - via metadata updates) + This endpoint cannot alter stored point data or derived/provenance fields, + including `id`, `domain_id`, `type`, `source`, `georeference`, `summary`, + `status`, `created_on`, or `checksum`. A metadata-only update therefore does + not make resources derived from the point cloud stale. - The **modified_on** field is updated automatically. + `modified_on` is updated automatically. ## Response - Returns the updated point cloud resource. + The updated point-cloud resource. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -222,42 +224,43 @@ async def asyncio_detailed( ) -> Response[HTTPValidationError | PointCloud]: """Update a point cloud - # Update Point Cloud + # Update a Point Cloud - Updates the metadata of an existing point cloud. Only the fields provided in - the request body are modified. + Updates the user-editable metadata of an existing point cloud. Only fields + present in the request body are changed. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Request Body - All fields are optional: + Every field is optional: - - **name**: (string) New name for the point cloud. - - **description**: (string) New description. - - **tags**: (array of strings) New tags (replaces existing). + - **name**: New human-readable name. + - **description**: New free-text description. + - **tags**: Replacement tag list. Supplying an empty list removes all tags. - ## What Cannot Be Updated + Omitted fields retain their current values. - The following are immutable through this endpoint: + ## Immutable Fields - - **id**, **domain_id**, **type**, **source**, **georeference** - - **created_on** (creation timestamp is permanent) - - **checksum** (changes only when the point cloud's content is rebuilt, never - via metadata updates) + This endpoint cannot alter stored point data or derived/provenance fields, + including `id`, `domain_id`, `type`, `source`, `georeference`, `summary`, + `status`, `created_on`, or `checksum`. A metadata-only update therefore does + not make resources derived from the point cloud stale. - The **modified_on** field is updated automatically. + `modified_on` is updated automatically. ## Response - Returns the updated point cloud resource. + The updated point-cloud resource. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): @@ -296,42 +299,43 @@ async def asyncio( ) -> HTTPValidationError | PointCloud | None: """Update a point cloud - # Update Point Cloud + # Update a Point Cloud - Updates the metadata of an existing point cloud. Only the fields provided in - the request body are modified. + Updates the user-editable metadata of an existing point cloud. Only fields + present in the request body are changed. ## Path Parameters - - **domain_id**: (string) The domain the point cloud belongs to. - - **point_cloud_id**: (string) The unique identifier of the point cloud. + - **domain_id**: Domain the point cloud belongs to. + - **point_cloud_id**: Unique point-cloud identifier. ## Request Body - All fields are optional: + Every field is optional: - - **name**: (string) New name for the point cloud. - - **description**: (string) New description. - - **tags**: (array of strings) New tags (replaces existing). + - **name**: New human-readable name. + - **description**: New free-text description. + - **tags**: Replacement tag list. Supplying an empty list removes all tags. - ## What Cannot Be Updated + Omitted fields retain their current values. - The following are immutable through this endpoint: + ## Immutable Fields - - **id**, **domain_id**, **type**, **source**, **georeference** - - **created_on** (creation timestamp is permanent) - - **checksum** (changes only when the point cloud's content is rebuilt, never - via metadata updates) + This endpoint cannot alter stored point data or derived/provenance fields, + including `id`, `domain_id`, `type`, `source`, `georeference`, `summary`, + `status`, `created_on`, or `checksum`. A metadata-only update therefore does + not make resources derived from the point cloud stale. - The **modified_on** field is updated automatically. + `modified_on` is updated automatically. ## Response - Returns the updated point cloud resource. + The updated point-cloud resource. ## Error Responses - - **404 Not Found**: The point cloud does not exist or the user does not have access. + - **404 Not Found**: The point cloud does not exist, belongs to another + domain, or is not accessible to the caller. Args: domain_id (str): diff --git a/fastfuels_sdk/v2/client_library/models/__init__.py b/fastfuels_sdk/v2/client_library/models/__init__.py index 1be370f..34919b0 100644 --- a/fastfuels_sdk/v2/client_library/models/__init__.py +++ b/fastfuels_sdk/v2/client_library/models/__init__.py @@ -5,6 +5,7 @@ from .allometry_biomass_source_component_states import ( AllometryBiomassSourceComponentStates, ) +from .allometry_canopy_biomass_source import AllometryCanopyBiomassSource from .allometry_max_crown_radius_source import AllometryMaxCrownRadiusSource from .application import Application from .application_quota_overrides_type_0 import ApplicationQuotaOverridesType0 @@ -18,8 +19,38 @@ from .biomass_component_state import BiomassComponentState from .biomass_equations import BiomassEquations from .biomass_unit import BiomassUnit +from .canopy_allometry_max_crown_radius_source import ( + CanopyAllometryMaxCrownRadiusSource, +) +from .canopy_available_fuel import CanopyAvailableFuel +from .canopy_biomass_equations import CanopyBiomassEquations +from .canopy_branchwood import CanopyBranchwood +from .canopy_branchwood_size_partition import CanopyBranchwoodSizePartition +from .canopy_cbd_depth import CanopyCbdDepth +from .canopy_cbd_load_over_depth import CanopyCbdLoadOverDepth +from .canopy_cbd_running_mean import CanopyCbdRunningMean +from .canopy_cbh_mean import CanopyCbhMean +from .canopy_cbh_minimum import CanopyCbhMinimum +from .canopy_cbh_percentile import CanopyCbhPercentile +from .canopy_cc_cover_fraction import CanopyCcCoverFraction +from .canopy_cc_crown_overlap import CanopyCcCrownOverlap +from .canopy_cc_crown_union import CanopyCcCrownUnion +from .canopy_chm_height_percentile import CanopyChmHeightPercentile +from .canopy_crown_width_equations import CanopyCrownWidthEquations +from .canopy_fuelcalc_crown_class_adjustment import CanopyFuelcalcCrownClassAdjustment +from .canopy_horizontal_distribution import CanopyHorizontalDistribution +from .canopy_no_crown_class_adjustment import CanopyNoCrownClassAdjustment +from .canopy_profile_threshold import CanopyProfileThreshold +from .canopy_running_mean_edge import CanopyRunningMeanEdge +from .canopy_species_inclusion import CanopySpeciesInclusion +from .canopy_vertical_distribution import CanopyVerticalDistribution from .categorical_band_summary import CategoricalBandSummary from .categorical_column_summary import CategoricalColumnSummary +from .chm_max_aggregation import ChmMaxAggregation +from .chm_mean_aggregation import ChmMeanAggregation +from .chm_median_aggregation import ChmMedianAggregation +from .chm_percentile_aggregation import ChmPercentileAggregation +from .chm_spike_filter import ChmSpikeFilter from .chunks import Chunks from .chunks_count_by_axis_type_0 import ChunksCountByAxisType0 from .column import Column @@ -41,11 +72,13 @@ from .create_fbfm_13_lookup_request import CreateFbfm13LookupRequest from .create_fbfm_40_lookup_request import CreateFbfm40LookupRequest from .create_fccs_lookup_request import CreateFccsLookupRequest +from .create_fosberg_fuel_moisture_request import CreateFosbergFuelMoistureRequest from .create_gdam_inventory_request import CreateGdamInventoryRequest from .create_gdam_inventory_request_impute_columns_item import ( CreateGdamInventoryRequestImputeColumnsItem, ) from .create_geo_tiff_upload_request import CreateGeoTIFFUploadRequest +from .create_inventory_canopy_request import CreateInventoryCanopyRequest from .create_inventory_upload_request import CreateInventoryUploadRequest from .create_key_request import CreateKeyRequest from .create_key_response import CreateKeyResponse @@ -56,6 +89,7 @@ from .create_landfire_topography_request import CreateLandfireTopographyRequest from .create_layerset_rasterize_request import CreateLayersetRasterizeRequest from .create_layerset_request_body import CreateLayersetRequestBody +from .create_leaflux_irradiance_request import CreateLeafluxIrradianceRequest from .create_meta_chm_request import CreateMetaChmRequest from .create_naip_chm_request import CreateNaipChmRequest from .create_netcdf_upload_request import CreateNetcdfUploadRequest @@ -107,6 +141,7 @@ from .fia_species_group_share import FIASpeciesGroupShare from .field_source import FieldSource from .fine_biomass_config import FineBiomassConfig +from .fuel_moisture_month import FuelMoistureMonth from .geo_json_crs import GeoJsonCRS from .geo_json_crs_properties import GeoJsonCRSProperties from .geo_json_feature import GeoJsonFeature @@ -148,6 +183,8 @@ from .inventory_attribute import InventoryAttribute from .inventory_basal_area_treatment import InventoryBasalAreaTreatment from .inventory_biomass_column import InventoryBiomassColumn +from .inventory_canopy_band import InventoryCanopyBand +from .inventory_column_canopy_biomass_source import InventoryColumnCanopyBiomassSource from .inventory_column_mapping import InventoryColumnMapping from .inventory_column_max_crown_radius_source import ( InventoryColumnMaxCrownRadiusSource, @@ -198,6 +235,7 @@ from .landfire_fbfm_13_version import LandfireFbfm13Version from .landfire_fbfm_40_version import LandfireFbfm40Version from .landfire_fccs_version import LandfireFccsVersion +from .landfire_season import LandfireSeason from .landfire_topography_version import LandfireTopographyVersion from .landscape_export_alignment_domain_target import ( LandscapeExportAlignmentDomainTarget, @@ -212,6 +250,7 @@ from .layerset_crs_properties import LayersetCrsProperties from .layerset_feature import LayersetFeature from .layerset_properties import LayersetProperties +from .leaflux_band import LeafluxBand from .line_string import LineString from .list_applications_response import ListApplicationsResponse from .list_domains_response import ListDomainsResponse @@ -233,11 +272,17 @@ from .overlap_method import OverlapMethod from .point import Point from .point_cloud import PointCloud +from .point_cloud_data_metadata import PointCloudDataMetadata +from .point_cloud_data_metadata_columns import PointCloudDataMetadataColumns from .point_cloud_georeference import PointCloudGeoreference from .point_cloud_sort_field import PointCloudSortField from .point_cloud_source import PointCloudSource from .point_cloud_summary import PointCloudSummary from .point_cloud_three_dep_coverage_response import PointCloudThreeDepCoverageResponse +from .point_cloud_tile_data_response import PointCloudTileDataResponse +from .point_cloud_tile_data_response_columns import PointCloudTileDataResponseColumns +from .point_cloud_tile_data_response_data import PointCloudTileDataResponseData +from .point_cloud_tile_metadata import PointCloudTileMetadata from .point_cloud_type import PointCloudType from .point_cloud_upload_created_response import PointCloudUploadCreatedResponse from .point_cloud_upload_spec import PointCloudUploadSpec @@ -252,6 +297,7 @@ from .quicfire_export_request_moist_merge import QuicfireExportRequestMoistMerge from .quota_exceeded_detail import QuotaExceededDetail from .quotas import Quotas +from .relative_elevation import RelativeElevation from .remove_action import RemoveAction from .resampling_method import ResamplingMethod from .resolution_3d import Resolution3D @@ -292,6 +338,7 @@ "Access", "AllometryBiomassSource", "AllometryBiomassSourceComponentStates", + "AllometryCanopyBiomassSource", "AllometryMaxCrownRadiusSource", "Application", "ApplicationQuotaOverridesType0", @@ -305,8 +352,36 @@ "BiomassComponentState", "BiomassEquations", "BiomassUnit", + "CanopyAllometryMaxCrownRadiusSource", + "CanopyAvailableFuel", + "CanopyBiomassEquations", + "CanopyBranchwood", + "CanopyBranchwoodSizePartition", + "CanopyCbdDepth", + "CanopyCbdLoadOverDepth", + "CanopyCbdRunningMean", + "CanopyCbhMean", + "CanopyCbhMinimum", + "CanopyCbhPercentile", + "CanopyCcCoverFraction", + "CanopyCcCrownOverlap", + "CanopyCcCrownUnion", + "CanopyChmHeightPercentile", + "CanopyCrownWidthEquations", + "CanopyFuelcalcCrownClassAdjustment", + "CanopyHorizontalDistribution", + "CanopyNoCrownClassAdjustment", + "CanopyProfileThreshold", + "CanopyRunningMeanEdge", + "CanopySpeciesInclusion", + "CanopyVerticalDistribution", "CategoricalBandSummary", "CategoricalColumnSummary", + "ChmMaxAggregation", + "ChmMeanAggregation", + "ChmMedianAggregation", + "ChmPercentileAggregation", + "ChmSpikeFilter", "Chunks", "ChunksCountByAxisType0", "Column", @@ -328,9 +403,11 @@ "CreateFbfm13LookupRequest", "CreateFbfm40LookupRequest", "CreateFccsLookupRequest", + "CreateFosbergFuelMoistureRequest", "CreateGdamInventoryRequest", "CreateGdamInventoryRequestImputeColumnsItem", "CreateGeoTIFFUploadRequest", + "CreateInventoryCanopyRequest", "CreateInventoryUploadRequest", "CreateKeyRequest", "CreateKeyResponse", @@ -341,6 +418,7 @@ "CreateLandfireTopographyRequest", "CreateLayersetRasterizeRequest", "CreateLayersetRequestBody", + "CreateLeafluxIrradianceRequest", "CreateMetaChmRequest", "CreateNaipChmRequest", "CreateNetcdfUploadRequest", @@ -390,6 +468,7 @@ "FeatureType", "FieldSource", "FineBiomassConfig", + "FuelMoistureMonth", "GeoJsonCRS", "GeoJsonCRSProperties", "GeoJsonFeature", @@ -427,6 +506,8 @@ "InventoryAttribute", "InventoryBasalAreaTreatment", "InventoryBiomassColumn", + "InventoryCanopyBand", + "InventoryColumnCanopyBiomassSource", "InventoryColumnMapping", "InventoryColumnMaxCrownRadiusSource", "InventoryColumnsBiomassSource", @@ -467,6 +548,7 @@ "LandfireFbfm13Version", "LandfireFbfm40Version", "LandfireFccsVersion", + "LandfireSeason", "LandfireTopographyVersion", "LandscapeExportAlignmentDomainTarget", "LandscapeExportAlignmentGridTarget", @@ -477,6 +559,7 @@ "LayersetCrsProperties", "LayersetFeature", "LayersetProperties", + "LeafluxBand", "LineString", "ListApplicationsResponse", "ListDomainsResponse", @@ -498,11 +581,17 @@ "OverlapMethod", "Point", "PointCloud", + "PointCloudDataMetadata", + "PointCloudDataMetadataColumns", "PointCloudGeoreference", "PointCloudSortField", "PointCloudSource", "PointCloudSummary", "PointCloudThreeDepCoverageResponse", + "PointCloudTileDataResponse", + "PointCloudTileDataResponseColumns", + "PointCloudTileDataResponseData", + "PointCloudTileMetadata", "PointCloudType", "PointCloudUploadCreatedResponse", "PointCloudUploadSpec", @@ -515,6 +604,7 @@ "QuicfireExportRequestMoistMerge", "QuotaExceededDetail", "Quotas", + "RelativeElevation", "RemoveAction", "ResamplingMethod", "Resolution3D", diff --git a/fastfuels_sdk/v2/client_library/models/allometry_canopy_biomass_source.py b/fastfuels_sdk/v2/client_library/models/allometry_canopy_biomass_source.py new file mode 100644 index 0000000..2c956af --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/allometry_canopy_biomass_source.py @@ -0,0 +1,78 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.canopy_biomass_equations import CanopyBiomassEquations +from ..types import UNSET, Unset + +T = TypeVar("T", bound="AllometryCanopyBiomassSource") + + +@_attrs_define +class AllometryCanopyBiomassSource: + """Estimate each tree's crown biomass from allometric equations. + + The equations produce crown component biomass (foliage, branchwood); + the `available_fuel` settings then reduce those components to the + available canopy fuel used in the profile. + + Attributes: + type_ (Literal['allometry'] | Unset): Default: 'allometry'. + equations (CanopyBiomassEquations | Unset): Allometric equation families for estimating crown biomass. + + A superset of the voxelize biomass equations: ``brown_1978`` (Brown, + "Weight and Density of Crowns of Rocky Mountain Conifers", INT-197) is + offered here because it is the equation set behind FuelCalc and the + LANDFIRE canopy layers. It is scoped to Interior West conifers and is + intended for compatibility studies, not as a national default. + """ + + type_: Literal["allometry"] | Unset = "allometry" + equations: CanopyBiomassEquations | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + type_ = self.type_ + + equations: str | Unset = UNSET + if not isinstance(self.equations, Unset): + equations = self.equations.value + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if type_ is not UNSET: + field_dict["type"] = type_ + if equations is not UNSET: + field_dict["equations"] = equations + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + type_ = cast(Literal["allometry"] | Unset, d.pop("type", UNSET)) + if type_ != "allometry" and not isinstance(type_, Unset): + raise ValueError(f"type must match const 'allometry', got '{type_}'") + + _equations = d.pop("equations", UNSET) + equations: CanopyBiomassEquations | Unset + if isinstance(_equations, Unset): + equations = UNSET + else: + equations = CanopyBiomassEquations(_equations) + + allometry_canopy_biomass_source = cls( + type_=type_, + equations=equations, + ) + + return allometry_canopy_biomass_source diff --git a/fastfuels_sdk/v2/client_library/models/canopy_allometry_max_crown_radius_source.py b/fastfuels_sdk/v2/client_library/models/canopy_allometry_max_crown_radius_source.py new file mode 100644 index 0000000..99f500a --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_allometry_max_crown_radius_source.py @@ -0,0 +1,82 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.canopy_crown_width_equations import CanopyCrownWidthEquations +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyAllometryMaxCrownRadiusSource") + + +@_attrs_define +class CanopyAllometryMaxCrownRadiusSource: + """Compute each tree's maximum crown radius from allometric equations. + + Supersets the voxelize allometry source with an `equations` choice, + because canopy cover and crown biomass are separate axes here: which + allometry supplies the radius is independent of how a cover method + treats crowns that overlap, so a run can vary either without + confounding the other. + + Attributes: + type_ (Literal['allometry'] | Unset): Default: 'allometry'. + equations (CanopyCrownWidthEquations | Unset): Allometric equation families for a tree's maximum crown radius. + + ``purves`` (Purves et al. 2007) is the national default and the one + the tree voxelization endpoint uses; it varies with height and + crown ratio as well as diameter. ``crookston_stage`` (Crookston & + Stage 1999, RMRS-GTR-24, reached through FVS) is the crown width + behind FuelCalc's canopy cover — diameter alone above breast + height, with regionally fitted coefficients — and is offered for + compatibility studies. + """ + + type_: Literal["allometry"] | Unset = "allometry" + equations: CanopyCrownWidthEquations | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + type_ = self.type_ + + equations: str | Unset = UNSET + if not isinstance(self.equations, Unset): + equations = self.equations.value + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if type_ is not UNSET: + field_dict["type"] = type_ + if equations is not UNSET: + field_dict["equations"] = equations + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + type_ = cast(Literal["allometry"] | Unset, d.pop("type", UNSET)) + if type_ != "allometry" and not isinstance(type_, Unset): + raise ValueError(f"type must match const 'allometry', got '{type_}'") + + _equations = d.pop("equations", UNSET) + equations: CanopyCrownWidthEquations | Unset + if isinstance(_equations, Unset): + equations = UNSET + else: + equations = CanopyCrownWidthEquations(_equations) + + canopy_allometry_max_crown_radius_source = cls( + type_=type_, + equations=equations, + ) + + return canopy_allometry_max_crown_radius_source diff --git a/fastfuels_sdk/v2/client_library/models/canopy_available_fuel.py b/fastfuels_sdk/v2/client_library/models/canopy_available_fuel.py new file mode 100644 index 0000000..c50dad7 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_available_fuel.py @@ -0,0 +1,77 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.canopy_branchwood import CanopyBranchwood + + +T = TypeVar("T", bound="CanopyAvailableFuel") + + +@_attrs_define +class CanopyAvailableFuel: + """Which crown biomass counts as available canopy fuel. + + Available fuel is the mass consumed in the flaming front of a crown + fire: foliage plus a fraction of the fine (0-1/4 inch) branchwood. + Fine branchwood is the finest class any published crown allometry + resolves, so the size line is fixed; the caller-adjustable choices are + the fractions and how the fine class is obtained from the biomass + equations. + + Attributes: + foliage_fraction (float | Unset): Fraction of foliage biomass counted as available fuel. Default: 1.0. + branchwood (CanopyBranchwood | Unset): Branchwood availability: the size basis and how much of it counts. + + `fraction` multiplies the branchwood mass `size_partition` produces — + the fine (0-1/4 inch) class under `equations` and `brown_proportions`, + or total branchwood under `none` — so the fraction's referent is always + an explicit choice, never an artifact of the biomass source. + """ + + foliage_fraction: float | Unset = 1.0 + branchwood: CanopyBranchwood | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + foliage_fraction = self.foliage_fraction + + branchwood: dict[str, Any] | Unset = UNSET + if not isinstance(self.branchwood, Unset): + branchwood = self.branchwood.to_dict() + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if foliage_fraction is not UNSET: + field_dict["foliage_fraction"] = foliage_fraction + if branchwood is not UNSET: + field_dict["branchwood"] = branchwood + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.canopy_branchwood import CanopyBranchwood + + d = dict(src_dict) + foliage_fraction = d.pop("foliage_fraction", UNSET) + + _branchwood = d.pop("branchwood", UNSET) + branchwood: CanopyBranchwood | Unset + if isinstance(_branchwood, Unset): + branchwood = UNSET + else: + branchwood = CanopyBranchwood.from_dict(_branchwood) + + canopy_available_fuel = cls( + foliage_fraction=foliage_fraction, + branchwood=branchwood, + ) + + return canopy_available_fuel diff --git a/fastfuels_sdk/v2/client_library/models/canopy_biomass_equations.py b/fastfuels_sdk/v2/client_library/models/canopy_biomass_equations.py new file mode 100644 index 0000000..96e2191 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_biomass_equations.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class CanopyBiomassEquations(str, Enum): + BROWN_1978 = "brown_1978" + JENKINS = "jenkins" + NSVB = "nsvb" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_branchwood.py b/fastfuels_sdk/v2/client_library/models/canopy_branchwood.py new file mode 100644 index 0000000..f6b94a1 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_branchwood.py @@ -0,0 +1,102 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define + +from ..models.canopy_branchwood_size_partition import CanopyBranchwoodSizePartition +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyBranchwood") + + +@_attrs_define +class CanopyBranchwood: + """Branchwood availability: the size basis and how much of it counts. + + `fraction` multiplies the branchwood mass `size_partition` produces — + the fine (0-1/4 inch) class under `equations` and `brown_proportions`, + or total branchwood under `none` — so the fraction's referent is always + an explicit choice, never an artifact of the biomass source. + + Attributes: + size_partition (CanopyBranchwoodSizePartition | None | Unset): Size basis for the branchwood fraction. Omitted + (`null`), it resolves by equation family: `equations` for `brown_1978` (which reports the fine class directly), + `none` for `nsvb` and `jenkins` — total branchwood, the only basis that prices every species, since the + `brown_proportions` fine-share crosswalk covers ~16 species and errors on common ones (quaking aspen, jack pine, + water birch). Set `brown_proportions` explicitly to reduce an `nsvb`/`jenkins` total to the fine class where the + species are covered. + fraction (float | None | Unset): Fraction of the size basis counted as available fuel. Omitted (`null`), it + resolves by partition: 0.5 against the fine class (`equations` / `brown_proportions`), the Brown & Reinhardt + (1991) / FuelCalc consumed fraction, and 0.075 against total branchwood (`none`), which folds the fine-branch + share and the consumed share into one number. Conrad et al. (2024) measured species-specific consumable + fractions of the fine class from 0.0 to 0.99. + """ + + size_partition: CanopyBranchwoodSizePartition | None | Unset = UNSET + fraction: float | None | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + size_partition: None | str | Unset + if isinstance(self.size_partition, Unset): + size_partition = UNSET + elif isinstance(self.size_partition, CanopyBranchwoodSizePartition): + size_partition = self.size_partition.value + else: + size_partition = self.size_partition + + fraction: float | None | Unset + if isinstance(self.fraction, Unset): + fraction = UNSET + else: + fraction = self.fraction + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if size_partition is not UNSET: + field_dict["size_partition"] = size_partition + if fraction is not UNSET: + field_dict["fraction"] = fraction + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + + def _parse_size_partition( + data: object, + ) -> CanopyBranchwoodSizePartition | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + size_partition_type_0 = CanopyBranchwoodSizePartition(data) + + return size_partition_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(CanopyBranchwoodSizePartition | None | Unset, data) + + size_partition = _parse_size_partition(d.pop("size_partition", UNSET)) + + def _parse_fraction(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + fraction = _parse_fraction(d.pop("fraction", UNSET)) + + canopy_branchwood = cls( + size_partition=size_partition, + fraction=fraction, + ) + + return canopy_branchwood diff --git a/fastfuels_sdk/v2/client_library/models/canopy_branchwood_size_partition.py b/fastfuels_sdk/v2/client_library/models/canopy_branchwood_size_partition.py new file mode 100644 index 0000000..619d104 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_branchwood_size_partition.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class CanopyBranchwoodSizePartition(str, Enum): + BROWN_PROPORTIONS = "brown_proportions" + EQUATIONS = "equations" + NONE = "none" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbd_depth.py b/fastfuels_sdk/v2/client_library/models/canopy_cbd_depth.py new file mode 100644 index 0000000..cd026ca --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbd_depth.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class CanopyCbdDepth(str, Enum): + BIOMASS_PERCENTILE = "biomass_percentile" + CANOPY_DEPTH = "canopy_depth" + HEIGHT_PERCENTILE = "height_percentile" + MEAN_CROWN_LENGTH = "mean_crown_length" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbd_load_over_depth.py b/fastfuels_sdk/v2/client_library/models/canopy_cbd_load_over_depth.py new file mode 100644 index 0000000..f4761c6 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbd_load_over_depth.py @@ -0,0 +1,85 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.canopy_cbd_depth import CanopyCbdDepth +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCbdLoadOverDepth") + + +@_attrs_define +class CanopyCbdLoadOverDepth: + """CBD as canopy fuel load divided by a canopy depth. + + The van Wagner-consistent average-density convention (load over + depth). Produces systematically lower values than the running-mean + maximum. Cruz et al. (2003) used this convention with + `mean_crown_length` as the depth and foliage-only available fuel. + + Attributes: + method (Literal['load_over_depth'] | Unset): Default: 'load_over_depth'. + depth (CanopyCbdDepth | Unset): Depth definitions for the load-over-depth CBD method. + + `canopy_depth` is chm - cbh (the van Wagner convention), computed from + the requested `cbh`/`chm` threshold settings — those bands must be + requested with the `bulk_density_threshold` method so the depth's + definition is recorded on the grid. `mean_crown_length` is the mean of + per-tree crown lengths (the depth Cruz et al. 2003 used). + `biomass_percentile` is the height span holding the central 80% of + canopy biomass (10th to 90th percentile; Albini 1996). + `height_percentile` is the 90th-percentile tree height minus the + median crown base height. + """ + + method: Literal["load_over_depth"] | Unset = "load_over_depth" + depth: CanopyCbdDepth | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + method = self.method + + depth: str | Unset = UNSET + if not isinstance(self.depth, Unset): + depth = self.depth.value + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if depth is not UNSET: + field_dict["depth"] = depth + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["load_over_depth"] | Unset, d.pop("method", UNSET)) + if method != "load_over_depth" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'load_over_depth', got '{method}'" + ) + + _depth = d.pop("depth", UNSET) + depth: CanopyCbdDepth | Unset + if isinstance(_depth, Unset): + depth = UNSET + else: + depth = CanopyCbdDepth(_depth) + + canopy_cbd_load_over_depth = cls( + method=method, + depth=depth, + ) + + return canopy_cbd_load_over_depth diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbd_running_mean.py b/fastfuels_sdk/v2/client_library/models/canopy_cbd_running_mean.py new file mode 100644 index 0000000..816c6a7 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbd_running_mean.py @@ -0,0 +1,115 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.canopy_running_mean_edge import CanopyRunningMeanEdge +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCbdRunningMean") + + +@_attrs_define +class CanopyCbdRunningMean: + """CBD as the maximum of a running mean of the vertical fuel profile. + + The effective-CBD convention used by FuelCalc, FFE-FVS, and NEXUS. + Published window depths vary by implementation: 1.524 m (5 ft, the + FuelCalc 1.7 User Guide; its Appendix D reads unsmoothed layers, i.e. + `null`), 3.9624 m (13 ft, FFE-FVS), 4.572 m (15 ft, the original + FuelCalc method in RMRS-P-41; early NEXUS used 4.5 m), and 3.0 m + (Reinhardt et al. 2006; current NEXUS). The default 3.0 m follows + Reinhardt et al. (2006). + + Attributes: + method (Literal['maximum_running_mean'] | Unset): Default: 'maximum_running_mean'. + window (float | None | Unset): Running-mean window depth in meters, rounded internally to a whole number of + profile layers. `null` disables smoothing, making CBD the maximum single-layer value. Default: 3.0. + edge (CanopyRunningMeanEdge | Unset): What a running mean takes to lie past the ends of the profile. + + The three answers in use agree everywhere except the lowest and + highest few layers, which is exactly where the threshold heights are + read, so the choice can move a reported `cbh` or `chm` by a layer + and shift `cbd` in a canopy that reaches the ground. + + `fixed_depth` divides by the window depth at every height, padding + with zeros at both ends, so a slab of fuel reports the same bulk + density wherever it sits — the reading Reinhardt et al. (2006) + define. `ground_clamped` shortens the window where it would run + below the ground and divides by what it actually covered, while + still dividing by the full depth above the canopy; this is + FuelCalc's, and it concentrates density against the ground while + letting it fall away above the crowns. `truncated` divides by + whatever the window covered at either end, which is FFE-FVS's and + inflates the topmost layers. + """ + + method: Literal["maximum_running_mean"] | Unset = "maximum_running_mean" + window: float | None | Unset = 3.0 + edge: CanopyRunningMeanEdge | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + method = self.method + + window: float | None | Unset + if isinstance(self.window, Unset): + window = UNSET + else: + window = self.window + + edge: str | Unset = UNSET + if not isinstance(self.edge, Unset): + edge = self.edge.value + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if window is not UNSET: + field_dict["window"] = window + if edge is not UNSET: + field_dict["edge"] = edge + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["maximum_running_mean"] | Unset, d.pop("method", UNSET)) + if method != "maximum_running_mean" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'maximum_running_mean', got '{method}'" + ) + + def _parse_window(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + window = _parse_window(d.pop("window", UNSET)) + + _edge = d.pop("edge", UNSET) + edge: CanopyRunningMeanEdge | Unset + if isinstance(_edge, Unset): + edge = UNSET + else: + edge = CanopyRunningMeanEdge(_edge) + + canopy_cbd_running_mean = cls( + method=method, + window=window, + edge=edge, + ) + + return canopy_cbd_running_mean diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbh_mean.py b/fastfuels_sdk/v2/client_library/models/canopy_cbh_mean.py new file mode 100644 index 0000000..01b9564 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbh_mean.py @@ -0,0 +1,67 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCbhMean") + + +@_attrs_define +class CanopyCbhMean: + """CBH as the mean of the per-tree crown base heights in each cell. + + Van Wagner's (1977) stand-mean definition — a plain summary of the + per-tree crown bases (`height * (1 - crown_ratio)`), one tree one vote + unless `weight_by_available_fuel` is set. A distinct convention from + the `bulk_density_threshold` profile method; the two diverge where tree + height varies within a cell. + + Attributes: + method (Literal['mean'] | Unset): Default: 'mean'. + weight_by_available_fuel (bool | Unset): Weight the mean by each tree's available canopy fuel so heavier crowns + pull it, rather than one tree one vote. Van Wagner's original mean is unweighted (the default). Default: False. + """ + + method: Literal["mean"] | Unset = "mean" + weight_by_available_fuel: bool | Unset = False + + def to_dict(self) -> dict[str, Any]: + method = self.method + + weight_by_available_fuel = self.weight_by_available_fuel + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if weight_by_available_fuel is not UNSET: + field_dict["weight_by_available_fuel"] = weight_by_available_fuel + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["mean"] | Unset, d.pop("method", UNSET)) + if method != "mean" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'mean', got '{method}'") + + weight_by_available_fuel = d.pop("weight_by_available_fuel", UNSET) + + canopy_cbh_mean = cls( + method=method, + weight_by_available_fuel=weight_by_available_fuel, + ) + + return canopy_cbh_mean diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbh_minimum.py b/fastfuels_sdk/v2/client_library/models/canopy_cbh_minimum.py new file mode 100644 index 0000000..64d8e4f --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbh_minimum.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCbhMinimum") + + +@_attrs_define +class CanopyCbhMinimum: + """CBH as the lowest per-tree crown base height in each cell. + + The most conservative characterization — any tree can carry fire into + the canopy (Mast et al. 2026), suited to risk-averse screening and + firefighter-safety assessments. + + Attributes: + method (Literal['minimum'] | Unset): Default: 'minimum'. + """ + + method: Literal["minimum"] | Unset = "minimum" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["minimum"] | Unset, d.pop("method", UNSET)) + if method != "minimum" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'minimum', got '{method}'") + + canopy_cbh_minimum = cls( + method=method, + ) + + return canopy_cbh_minimum diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cbh_percentile.py b/fastfuels_sdk/v2/client_library/models/canopy_cbh_percentile.py new file mode 100644 index 0000000..6cd187e --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cbh_percentile.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCbhPercentile") + + +@_attrs_define +class CanopyCbhPercentile: + """CBH as a percentile of the per-tree crown base heights in each cell. + + A conservative alternative to the mean for multi-storied stands, where + a mean hides low ladder fuel. 50 is the median; 20 and 25 (first + quartile) are the lower-tail aggregations used for conservative + screening (Fulé et al. 2002; Mast et al. 2026). + + Attributes: + percentile (float): Percentile of per-tree crown base heights. 50 is the median; 20 and 25 (first quartile) are + common conservative lower-tail choices. + method (Literal['percentile'] | Unset): Default: 'percentile'. + """ + + percentile: float + method: Literal["percentile"] | Unset = "percentile" + + def to_dict(self) -> dict[str, Any]: + percentile = self.percentile + + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "percentile": percentile, + } + ) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + percentile = d.pop("percentile") + + method = cast(Literal["percentile"] | Unset, d.pop("method", UNSET)) + if method != "percentile" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'percentile', got '{method}'") + + canopy_cbh_percentile = cls( + percentile=percentile, + method=method, + ) + + return canopy_cbh_percentile diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cc_cover_fraction.py b/fastfuels_sdk/v2/client_library/models/canopy_cc_cover_fraction.py new file mode 100644 index 0000000..32f6555 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cc_cover_fraction.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCcCoverFraction") + + +@_attrs_define +class CanopyCcCoverFraction: + """Canopy cover as the fraction of the cell with canopy above a height. + + A CHM-style cover measure, distinct from projected crown cover: it + reports where canopy *surface* exceeds `height_threshold` rather than + the projection of all suspended canopy. + + Attributes: + method (Literal['cover_fraction'] | Unset): Default: 'cover_fraction'. + height_threshold (float | Unset): Height in meters above which canopy counts as cover. Default: 2.0. + """ + + method: Literal["cover_fraction"] | Unset = "cover_fraction" + height_threshold: float | Unset = 2.0 + + def to_dict(self) -> dict[str, Any]: + method = self.method + + height_threshold = self.height_threshold + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if height_threshold is not UNSET: + field_dict["height_threshold"] = height_threshold + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["cover_fraction"] | Unset, d.pop("method", UNSET)) + if method != "cover_fraction" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'cover_fraction', got '{method}'" + ) + + height_threshold = d.pop("height_threshold", UNSET) + + canopy_cc_cover_fraction = cls( + method=method, + height_threshold=height_threshold, + ) + + return canopy_cc_cover_fraction diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_overlap.py b/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_overlap.py new file mode 100644 index 0000000..0d01399 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_overlap.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCcCrownOverlap") + + +@_attrs_define +class CanopyCcCrownOverlap: + """Canopy cover from the Crookston-Stage random-overlap correction. + + ``100 * (1 - exp(-total_crown_area / cell_area))`` — the expected cover + if stems were randomly placed. This is the FuelCalc estimator; it + ignores actual stem positions. + + Attributes: + method (Literal['crown_overlap'] | Unset): Default: 'crown_overlap'. + """ + + method: Literal["crown_overlap"] | Unset = "crown_overlap" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["crown_overlap"] | Unset, d.pop("method", UNSET)) + if method != "crown_overlap" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'crown_overlap', got '{method}'") + + canopy_cc_crown_overlap = cls( + method=method, + ) + + return canopy_cc_crown_overlap diff --git a/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_union.py b/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_union.py new file mode 100644 index 0000000..b6958b7 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_cc_crown_union.py @@ -0,0 +1,55 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyCcCrownUnion") + + +@_attrs_define +class CanopyCcCrownUnion: + """Canopy cover as the geometric union of projected crown areas. + + Measures the vertically projected crown cover directly from stem + positions and crown radii, respecting the inventory's actual spatial + pattern (clumping, gaps). + + Attributes: + method (Literal['crown_union'] | Unset): Default: 'crown_union'. + """ + + method: Literal["crown_union"] | Unset = "crown_union" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["crown_union"] | Unset, d.pop("method", UNSET)) + if method != "crown_union" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'crown_union', got '{method}'") + + canopy_cc_crown_union = cls( + method=method, + ) + + return canopy_cc_crown_union diff --git a/fastfuels_sdk/v2/client_library/models/canopy_chm_height_percentile.py b/fastfuels_sdk/v2/client_library/models/canopy_chm_height_percentile.py new file mode 100644 index 0000000..51796e0 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_chm_height_percentile.py @@ -0,0 +1,62 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyChmHeightPercentile") + + +@_attrs_define +class CanopyChmHeightPercentile: + """Canopy height as a percentile of tree heights in the cell. + + Attributes: + method (Literal['height_percentile'] | Unset): Default: 'height_percentile'. + percentile (float | Unset): Tree-height percentile reported as canopy height. Default: 99.0. + """ + + method: Literal["height_percentile"] | Unset = "height_percentile" + percentile: float | Unset = 99.0 + + def to_dict(self) -> dict[str, Any]: + method = self.method + + percentile = self.percentile + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if percentile is not UNSET: + field_dict["percentile"] = percentile + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["height_percentile"] | Unset, d.pop("method", UNSET)) + if method != "height_percentile" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'height_percentile', got '{method}'" + ) + + percentile = d.pop("percentile", UNSET) + + canopy_chm_height_percentile = cls( + method=method, + percentile=percentile, + ) + + return canopy_chm_height_percentile diff --git a/fastfuels_sdk/v2/client_library/models/canopy_crown_width_equations.py b/fastfuels_sdk/v2/client_library/models/canopy_crown_width_equations.py new file mode 100644 index 0000000..0f44044 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_crown_width_equations.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CanopyCrownWidthEquations(str, Enum): + CROOKSTON_STAGE = "crookston_stage" + PURVES = "purves" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_fuelcalc_crown_class_adjustment.py b/fastfuels_sdk/v2/client_library/models/canopy_fuelcalc_crown_class_adjustment.py new file mode 100644 index 0000000..6891024 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_fuelcalc_crown_class_adjustment.py @@ -0,0 +1,79 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyFuelcalcCrownClassAdjustment") + + +@_attrs_define +class CanopyFuelcalcCrownClassAdjustment: + """Multiply crown weight by the FuelCalc species x crown-class factors. + + The factor is selected per tree from the inventory's crown class + (`fia_crown_class_code`, FIA CCLCD). A tree whose code is missing — a null + value, or an inventory that carries no crown class at all — takes the factor + selected by `missing_crown_class`. With the `other_none` fallback that is a + global 0.5 multiplier for most species — a large, deliberate reduction that + reproduces how FuelCalc treats trees of unknown crown class. + + Attributes: + method (Literal['fuelcalc_table'] | Unset): Default: 'fuelcalc_table'. + missing_crown_class (Literal['other_none'] | Unset): Factor column applied to trees whose `fia_crown_class_code` + is missing — every tree in an inventory that carries no crown class. `other_none` is FuelCalc's Other/none + column (0.5 for most species). Default: 'other_none'. + """ + + method: Literal["fuelcalc_table"] | Unset = "fuelcalc_table" + missing_crown_class: Literal["other_none"] | Unset = "other_none" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + missing_crown_class = self.missing_crown_class + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if missing_crown_class is not UNSET: + field_dict["missing_crown_class"] = missing_crown_class + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["fuelcalc_table"] | Unset, d.pop("method", UNSET)) + if method != "fuelcalc_table" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'fuelcalc_table', got '{method}'" + ) + + missing_crown_class = cast( + Literal["other_none"] | Unset, d.pop("missing_crown_class", UNSET) + ) + if missing_crown_class != "other_none" and not isinstance( + missing_crown_class, Unset + ): + raise ValueError( + f"missing_crown_class must match const 'other_none', got '{missing_crown_class}'" + ) + + canopy_fuelcalc_crown_class_adjustment = cls( + method=method, + missing_crown_class=missing_crown_class, + ) + + return canopy_fuelcalc_crown_class_adjustment diff --git a/fastfuels_sdk/v2/client_library/models/canopy_horizontal_distribution.py b/fastfuels_sdk/v2/client_library/models/canopy_horizontal_distribution.py new file mode 100644 index 0000000..35d6bee --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_horizontal_distribution.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CanopyHorizontalDistribution(str, Enum): + CROWN_PROJECTED = "crown_projected" + STEM = "stem" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_no_crown_class_adjustment.py b/fastfuels_sdk/v2/client_library/models/canopy_no_crown_class_adjustment.py new file mode 100644 index 0000000..0d6580a --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_no_crown_class_adjustment.py @@ -0,0 +1,51 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyNoCrownClassAdjustment") + + +@_attrs_define +class CanopyNoCrownClassAdjustment: + """Apply no crown-class adjustment: every tree keeps its full crown weight. + + Attributes: + method (Literal['none'] | Unset): Default: 'none'. + """ + + method: Literal["none"] | Unset = "none" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["none"] | Unset, d.pop("method", UNSET)) + if method != "none" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'none', got '{method}'") + + canopy_no_crown_class_adjustment = cls( + method=method, + ) + + return canopy_no_crown_class_adjustment diff --git a/fastfuels_sdk/v2/client_library/models/canopy_profile_threshold.py b/fastfuels_sdk/v2/client_library/models/canopy_profile_threshold.py new file mode 100644 index 0000000..4e74c90 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_profile_threshold.py @@ -0,0 +1,154 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.canopy_running_mean_edge import CanopyRunningMeanEdge +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CanopyProfileThreshold") + + +@_attrs_define +class CanopyProfileThreshold: + """A height where the vertical fuel profile crosses a bulk-density threshold. + + Shared by `cbh` (lowest crossing) and `chm` (highest crossing), so the + two bands are consistent by construction (cbh <= chm). The effective + threshold is ``min(relative_threshold_fraction * CBD_max, threshold)`` + — FuelCalc's rule, in which the relative branch engages whenever the + cell's maximum profile density is below threshold / fraction (0.12 + kg/m**3 at the defaults). `CBD_max` is the maximum of the profile + this method scans, so `smoothing_window` changes it; it is + independent of the `cbd` band's own window, which is a separate + setting. + + Attributes: + method (Literal['bulk_density_threshold'] | Unset): Default: 'bulk_density_threshold'. + threshold (float | Unset): Bulk-density threshold ceiling in kg/m**3. Default 0.012, the FuelCalc value, also + used by LANDFIRE to define canopy base height. Published alternatives: 0.011 (Scott & Reinhardt 2001 / FFE-FVS, + 30 lb/acre/ft), 0.037 (Sando & Wick 1972, 100 lb/acre/ft), 0.074 (Williams 1977). Default: 0.012. + relative_threshold_fraction (float | None | Unset): Fraction of the cell's maximum profile density used as the + threshold when that is lower than `threshold`. `null` applies `threshold` flat, with no relative branch. + Default: 0.1. + smoothing_window (float | None | Unset): Running-mean window in meters applied to the profile before locating + threshold crossings. `null` reads raw layers. FuelCalc 1.7 crosses a 1.524 m (5 ft) running mean, the same + window it reduces CBD over; FFE-FVS crosses 0.9144 m (3 ft); the original FuelCalc method (RMRS-P-41) crosses + its full 4.572 m (15 ft) window. + smoothing_edge (CanopyRunningMeanEdge | Unset): What a running mean takes to lie past the ends of the profile. + + The three answers in use agree everywhere except the lowest and + highest few layers, which is exactly where the threshold heights are + read, so the choice can move a reported `cbh` or `chm` by a layer + and shift `cbd` in a canopy that reaches the ground. + + `fixed_depth` divides by the window depth at every height, padding + with zeros at both ends, so a slab of fuel reports the same bulk + density wherever it sits — the reading Reinhardt et al. (2006) + define. `ground_clamped` shortens the window where it would run + below the ground and divides by what it actually covered, while + still dividing by the full depth above the canopy; this is + FuelCalc's, and it concentrates density against the ground while + letting it fall away above the crowns. `truncated` divides by + whatever the window covered at either end, which is FFE-FVS's and + inflates the topmost layers. + """ + + method: Literal["bulk_density_threshold"] | Unset = "bulk_density_threshold" + threshold: float | Unset = 0.012 + relative_threshold_fraction: float | None | Unset = 0.1 + smoothing_window: float | None | Unset = UNSET + smoothing_edge: CanopyRunningMeanEdge | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + method = self.method + + threshold = self.threshold + + relative_threshold_fraction: float | None | Unset + if isinstance(self.relative_threshold_fraction, Unset): + relative_threshold_fraction = UNSET + else: + relative_threshold_fraction = self.relative_threshold_fraction + + smoothing_window: float | None | Unset + if isinstance(self.smoothing_window, Unset): + smoothing_window = UNSET + else: + smoothing_window = self.smoothing_window + + smoothing_edge: str | Unset = UNSET + if not isinstance(self.smoothing_edge, Unset): + smoothing_edge = self.smoothing_edge.value + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + if threshold is not UNSET: + field_dict["threshold"] = threshold + if relative_threshold_fraction is not UNSET: + field_dict["relative_threshold_fraction"] = relative_threshold_fraction + if smoothing_window is not UNSET: + field_dict["smoothing_window"] = smoothing_window + if smoothing_edge is not UNSET: + field_dict["smoothing_edge"] = smoothing_edge + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["bulk_density_threshold"] | Unset, d.pop("method", UNSET)) + if method != "bulk_density_threshold" and not isinstance(method, Unset): + raise ValueError( + f"method must match const 'bulk_density_threshold', got '{method}'" + ) + + threshold = d.pop("threshold", UNSET) + + def _parse_relative_threshold_fraction(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + relative_threshold_fraction = _parse_relative_threshold_fraction( + d.pop("relative_threshold_fraction", UNSET) + ) + + def _parse_smoothing_window(data: object) -> float | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(float | None | Unset, data) + + smoothing_window = _parse_smoothing_window(d.pop("smoothing_window", UNSET)) + + _smoothing_edge = d.pop("smoothing_edge", UNSET) + smoothing_edge: CanopyRunningMeanEdge | Unset + if isinstance(_smoothing_edge, Unset): + smoothing_edge = UNSET + else: + smoothing_edge = CanopyRunningMeanEdge(_smoothing_edge) + + canopy_profile_threshold = cls( + method=method, + threshold=threshold, + relative_threshold_fraction=relative_threshold_fraction, + smoothing_window=smoothing_window, + smoothing_edge=smoothing_edge, + ) + + return canopy_profile_threshold diff --git a/fastfuels_sdk/v2/client_library/models/canopy_running_mean_edge.py b/fastfuels_sdk/v2/client_library/models/canopy_running_mean_edge.py new file mode 100644 index 0000000..304bb12 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_running_mean_edge.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class CanopyRunningMeanEdge(str, Enum): + FIXED_DEPTH = "fixed_depth" + GROUND_CLAMPED = "ground_clamped" + TRUNCATED = "truncated" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_species_inclusion.py b/fastfuels_sdk/v2/client_library/models/canopy_species_inclusion.py new file mode 100644 index 0000000..d45fabb --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_species_inclusion.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CanopySpeciesInclusion(str, Enum): + ALL_SPECIES = "all_species" + FUELCALC_DEFAULT = "fuelcalc_default" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/canopy_vertical_distribution.py b/fastfuels_sdk/v2/client_library/models/canopy_vertical_distribution.py new file mode 100644 index 0000000..9bf6154 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/canopy_vertical_distribution.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class CanopyVerticalDistribution(str, Enum): + REINHARDT_2006 = "reinhardt_2006" + UNIFORM = "uniform" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/chm_max_aggregation.py b/fastfuels_sdk/v2/client_library/models/chm_max_aggregation.py new file mode 100644 index 0000000..faeb10b --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/chm_max_aggregation.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ChmMaxAggregation") + + +@_attrs_define +class ChmMaxAggregation: + """Tallest return in the cell. + + The height of whatever the cell's tallest thing is, which is a crown at a + cell narrower than one and a stand's tallest tree at a cell wider than one. + + Attributes: + method (Literal['max'] | Unset): Default: 'max'. + """ + + method: Literal["max"] | Unset = "max" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["max"] | Unset, d.pop("method", UNSET)) + if method != "max" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'max', got '{method}'") + + chm_max_aggregation = cls( + method=method, + ) + + return chm_max_aggregation diff --git a/fastfuels_sdk/v2/client_library/models/chm_mean_aggregation.py b/fastfuels_sdk/v2/client_library/models/chm_mean_aggregation.py new file mode 100644 index 0000000..894e8b3 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/chm_mean_aggregation.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ChmMeanAggregation") + + +@_attrs_define +class ChmMeanAggregation: + """Average height of every return in the cell. + + Weighted by how the returns fall, so a cell that is half canopy and half + gap reads between the two rather than at the canopy. + + Attributes: + method (Literal['mean'] | Unset): Default: 'mean'. + """ + + method: Literal["mean"] | Unset = "mean" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["mean"] | Unset, d.pop("method", UNSET)) + if method != "mean" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'mean', got '{method}'") + + chm_mean_aggregation = cls( + method=method, + ) + + return chm_mean_aggregation diff --git a/fastfuels_sdk/v2/client_library/models/chm_median_aggregation.py b/fastfuels_sdk/v2/client_library/models/chm_median_aggregation.py new file mode 100644 index 0000000..b4e18de --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/chm_median_aggregation.py @@ -0,0 +1,54 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ChmMedianAggregation") + + +@_attrs_define +class ChmMedianAggregation: + """Height half the cell's returns lie below. + + The same statistic as `percentile` at 50, spelled the way it is usually + asked for. + + Attributes: + method (Literal['median'] | Unset): Default: 'median'. + """ + + method: Literal["median"] | Unset = "median" + + def to_dict(self) -> dict[str, Any]: + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update({}) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + method = cast(Literal["median"] | Unset, d.pop("method", UNSET)) + if method != "median" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'median', got '{method}'") + + chm_median_aggregation = cls( + method=method, + ) + + return chm_median_aggregation diff --git a/fastfuels_sdk/v2/client_library/models/chm_percentile_aggregation.py b/fastfuels_sdk/v2/client_library/models/chm_percentile_aggregation.py new file mode 100644 index 0000000..d88f831 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/chm_percentile_aggregation.py @@ -0,0 +1,63 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ChmPercentileAggregation") + + +@_attrs_define +class ChmPercentileAggregation: + """Height a given fraction of the cell's returns lie below. + + Attributes: + percentile (float): Rank to take, as a percentage of the cell's returns. 100 is the tallest return and 50 the + median. Between two returns the height is interpolated linearly. + method (Literal['percentile'] | Unset): Default: 'percentile'. + """ + + percentile: float + method: Literal["percentile"] | Unset = "percentile" + + def to_dict(self) -> dict[str, Any]: + percentile = self.percentile + + method = self.method + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "percentile": percentile, + } + ) + if method is not UNSET: + field_dict["method"] = method + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + percentile = d.pop("percentile") + + method = cast(Literal["percentile"] | Unset, d.pop("method", UNSET)) + if method != "percentile" and not isinstance(method, Unset): + raise ValueError(f"method must match const 'percentile', got '{method}'") + + chm_percentile_aggregation = cls( + percentile=percentile, + method=method, + ) + + return chm_percentile_aggregation diff --git a/fastfuels_sdk/v2/client_library/models/chm_spike_filter.py b/fastfuels_sdk/v2/client_library/models/chm_spike_filter.py new file mode 100644 index 0000000..7d02a52 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/chm_spike_filter.py @@ -0,0 +1,87 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +from ..types import UNSET, Unset + +T = TypeVar("T", bound="ChmSpikeFilter") + + +@_attrs_define +class ChmSpikeFilter: + """Removal of lone spurious returns from a point-cloud canopy height model. + + Under the default `max` aggregation a cell takes the tallest return that + falls in it, so one bad return — a bird, haze, a multiple-time-around + artifact — becomes the cell's height unless the cloud classified it as + noise. Many do not. + + Such a return leaves a shape real canopy cannot: a single cell towering + over everything around it. Both fields are the two halves of that shape, in + meters, so they mean the same thing at any `alignment.resolution`. A `mean`, + `median`, or `percentile` aggregation already resists a lone return, so this + filter is aimed at the `max` case. + + Attributes: + min_canopy_footprint_m (float | Unset): Narrowest ground footprint real canopy can occupy, in meters. A cell is + judged against everything within this distance, and only a cell narrower than it can be rejected — so the filter + does not run at all once `alignment.resolution` reaches this value, where one cell holds a stand rather than a + crown. Default: 3.0. + min_prominence_m (float | Unset): How far above every neighbour a cell must rise to be rejected, in meters. + Measured noise returns stood 40-80 m above their surroundings; a real crown's peak is within a few meters of the + cells beside it. Default: 25.0. + """ + + min_canopy_footprint_m: float | Unset = 3.0 + min_prominence_m: float | Unset = 25.0 + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + min_canopy_footprint_m = self.min_canopy_footprint_m + + min_prominence_m = self.min_prominence_m + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update({}) + if min_canopy_footprint_m is not UNSET: + field_dict["min_canopy_footprint_m"] = min_canopy_footprint_m + if min_prominence_m is not UNSET: + field_dict["min_prominence_m"] = min_prominence_m + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + min_canopy_footprint_m = d.pop("min_canopy_footprint_m", UNSET) + + min_prominence_m = d.pop("min_prominence_m", UNSET) + + chm_spike_filter = cls( + min_canopy_footprint_m=min_canopy_footprint_m, + min_prominence_m=min_prominence_m, + ) + + chm_spike_filter.additional_properties = d + return chm_spike_filter + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/create_fosberg_fuel_moisture_request.py b/fastfuels_sdk/v2/client_library/models/create_fosberg_fuel_moisture_request.py new file mode 100644 index 0000000..163622a --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/create_fosberg_fuel_moisture_request.py @@ -0,0 +1,144 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define + +from ..models.fuel_moisture_month import FuelMoistureMonth +from ..models.relative_elevation import RelativeElevation +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CreateFosbergFuelMoistureRequest") + + +@_attrs_define +class CreateFosbergFuelMoistureRequest: + """Request body for a Fosberg 1-hour dead fuel moisture content grid. + + Does not extend CreateSourceGridRequestBase: this is a grid -> grid + derivation with no external raster and no alignment input. The output + inherits the topography grid's domain, CRS, transform, and georeference. + + Attributes: + source_topography_grid_id (str): ID of a completed 2D topography grid with `slope` and `aspect` bands (both in + degrees). + source_irradiance_grid_id (str): ID of a completed leaflux irradiance grid with an `irradiance.surface.relative` + band. Per-cell shading is derived as 1 - irradiance.surface.relative. + dry_bulb_temp (float): Dry-bulb air temperature in degrees Fahrenheit (the Fosberg table lineage is Fahrenheit). + Must be >= 10. + relative_humidity (float): Relative humidity as a percent (0-100). + time (int): Local time of day in 24-hour HHMM form (e.g. 1200 for noon). Restricted to 0800-1959; the model has + no daytime table outside that window. + month (FuelMoistureMonth): Month of the burn scenario, selecting the Fosberg correction table. + name (str | Unset): Default: ''. + description (str | Unset): Default: ''. + tags (list[str] | Unset): + elevation (RelativeElevation | Unset): Site elevation relative to the reference weather station. + + This is a Fosberg correction category, NOT the topography elevation band: + `below` = 1000-2000 ft below the station, `near` = within 1000 ft (no + correction), `above` = 1000-2000 ft above the station. + """ + + source_topography_grid_id: str + source_irradiance_grid_id: str + dry_bulb_temp: float + relative_humidity: float + time: int + month: FuelMoistureMonth + name: str | Unset = "" + description: str | Unset = "" + tags: list[str] | Unset = UNSET + elevation: RelativeElevation | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + source_topography_grid_id = self.source_topography_grid_id + + source_irradiance_grid_id = self.source_irradiance_grid_id + + dry_bulb_temp = self.dry_bulb_temp + + relative_humidity = self.relative_humidity + + time = self.time + + month = self.month.value + + name = self.name + + description = self.description + + tags: list[str] | Unset = UNSET + if not isinstance(self.tags, Unset): + tags = self.tags + + elevation: str | Unset = UNSET + if not isinstance(self.elevation, Unset): + elevation = self.elevation.value + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "source_topography_grid_id": source_topography_grid_id, + "source_irradiance_grid_id": source_irradiance_grid_id, + "dry_bulb_temp": dry_bulb_temp, + "relative_humidity": relative_humidity, + "time": time, + "month": month, + } + ) + if name is not UNSET: + field_dict["name"] = name + if description is not UNSET: + field_dict["description"] = description + if tags is not UNSET: + field_dict["tags"] = tags + if elevation is not UNSET: + field_dict["elevation"] = elevation + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + source_topography_grid_id = d.pop("source_topography_grid_id") + + source_irradiance_grid_id = d.pop("source_irradiance_grid_id") + + dry_bulb_temp = d.pop("dry_bulb_temp") + + relative_humidity = d.pop("relative_humidity") + + time = d.pop("time") + + month = FuelMoistureMonth(d.pop("month")) + + name = d.pop("name", UNSET) + + description = d.pop("description", UNSET) + + tags = cast(list[str], d.pop("tags", UNSET)) + + _elevation = d.pop("elevation", UNSET) + elevation: RelativeElevation | Unset + if isinstance(_elevation, Unset): + elevation = UNSET + else: + elevation = RelativeElevation(_elevation) + + create_fosberg_fuel_moisture_request = cls( + source_topography_grid_id=source_topography_grid_id, + source_irradiance_grid_id=source_irradiance_grid_id, + dry_bulb_temp=dry_bulb_temp, + relative_humidity=relative_humidity, + time=time, + month=month, + name=name, + description=description, + tags=tags, + elevation=elevation, + ) + + return create_fosberg_fuel_moisture_request diff --git a/fastfuels_sdk/v2/client_library/models/create_inventory_canopy_request.py b/fastfuels_sdk/v2/client_library/models/create_inventory_canopy_request.py new file mode 100644 index 0000000..0e4ce15 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/create_inventory_canopy_request.py @@ -0,0 +1,752 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast + +from attrs import define as _attrs_define + +from ..models.canopy_horizontal_distribution import CanopyHorizontalDistribution +from ..models.canopy_species_inclusion import CanopySpeciesInclusion +from ..models.canopy_vertical_distribution import CanopyVerticalDistribution +from ..models.inventory_canopy_band import InventoryCanopyBand +from ..types import UNSET, Unset + +if TYPE_CHECKING: + from ..models.allometry_canopy_biomass_source import AllometryCanopyBiomassSource + from ..models.canopy_allometry_max_crown_radius_source import ( + CanopyAllometryMaxCrownRadiusSource, + ) + from ..models.canopy_available_fuel import CanopyAvailableFuel + from ..models.canopy_cbd_load_over_depth import CanopyCbdLoadOverDepth + from ..models.canopy_cbd_running_mean import CanopyCbdRunningMean + from ..models.canopy_cbh_mean import CanopyCbhMean + from ..models.canopy_cbh_minimum import CanopyCbhMinimum + from ..models.canopy_cbh_percentile import CanopyCbhPercentile + from ..models.canopy_cc_cover_fraction import CanopyCcCoverFraction + from ..models.canopy_cc_crown_overlap import CanopyCcCrownOverlap + from ..models.canopy_cc_crown_union import CanopyCcCrownUnion + from ..models.canopy_chm_height_percentile import CanopyChmHeightPercentile + from ..models.canopy_fuelcalc_crown_class_adjustment import ( + CanopyFuelcalcCrownClassAdjustment, + ) + from ..models.canopy_no_crown_class_adjustment import CanopyNoCrownClassAdjustment + from ..models.canopy_profile_threshold import CanopyProfileThreshold + from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget + from ..models.grid_alignment_grid_target import GridAlignmentGridTarget + from ..models.grid_alignment_native_target import GridAlignmentNativeTarget + from ..models.inventory_column_canopy_biomass_source import ( + InventoryColumnCanopyBiomassSource, + ) + from ..models.inventory_column_max_crown_radius_source import ( + InventoryColumnMaxCrownRadiusSource, + ) + + +T = TypeVar("T", bound="CreateInventoryCanopyRequest") + + +@_attrs_define +class CreateInventoryCanopyRequest: + """Request body for creating a canopy fuel grid from a tree inventory. + + Only live trees contribute canopy fuel: the worker reads live + inventory records only, matching FuelCalc, which excludes dead trees + from all calculations. + + Does not extend CreateGridRequestBase: like DUET and the 3D voxel + grids, inventory-derived grids do not support modifications — apply + treatments and modifications to the inventory before deriving canopy + metrics. + + Attributes: + source_inventory_id (str): ID of a completed tree inventory in this domain to derive canopy metrics from. + biomass_source (AllometryCanopyBiomassSource | InventoryColumnCanopyBiomassSource | Unset): Where each tree's + crown fuel mass comes from: allometric equations (default: NSVB) or an inventory column carrying precomputed + available canopy fuel. + available_fuel (CanopyAvailableFuel | None | Unset): How crown biomass reduces to available canopy fuel. Applies + only with an allometry biomass source; resolved to `null` when the biomass source is an inventory column. + species_inclusion (CanopySpeciesInclusion | Unset): Which species contribute canopy fuel to the profile. + + FuelCalc excludes most hardwoods from canopy fuel calculations by + default; FastFuels includes every species unless told otherwise. With + `fuelcalc_default`, the persisted source records the exclusion table + the worker applied. + crown_class_adjustment (CanopyFuelcalcCrownClassAdjustment | CanopyNoCrownClassAdjustment | Unset): Crown-weight + adjustment for canopy position. `none` (default) applies no adjustment; `fuelcalc_table` applies the FuelCalc + species x crown-class factors. + min_tree_height (float | Unset): Trees shorter than this height in meters contribute no canopy fuel. Default 0.0 + includes every tree. FFE-FVS and the original FuelCalc method (RMRS-P-41) exclude trees under 1.83 m (6 ft) as + surface fuel; FuelCalc 1.7 includes small trees down to 0.3 m via Brown's small-tree tables. Default: 0.0. + vertical_distribution (CanopyVerticalDistribution | Unset): How each tree's available fuel is distributed from + crown base to top. + + `reinhardt_2006` uses the species cumulative-fraction cubics fit from + destructive sampling (Reinhardt et al. 2006, CJFR 36), applied through + the published FuelCalc species crosswalk; species the crosswalk maps to + no cubic (including all hardwoods) distribute uniformly. `uniform` + spreads mass evenly over the crown for every tree — the FFE-FVS + assumption. + layer_depth (float | Unset): Vertical profile layer depth in meters. Default 0.3048 m (1 ft, the FuelCalc + layer). Affects CBD smoothing and where threshold crossings land. Default: 0.3048. + horizontal_distribution (CanopyHorizontalDistribution | Unset): How a tree's available fuel is attributed to + output cells. + + `crown_projected` splits each tree's mass across the cells its + projected crown overlaps, in proportion to the overlap area. + `stem` assigns the whole mass to the cell containing the stem — the + closest analogue to FuelCalc's plot computation. + max_crown_radius_source (CanopyAllometryMaxCrownRadiusSource | InventoryColumnMaxCrownRadiusSource | Unset): + Source of each tree's maximum crown radius, used by `crown_projected` attribution and the geometric canopy cover + methods. Defaults to the `purves` allometry; set `{"type": "allometry", "equations": "crookston_stage"}` for the + crown widths FuelCalc uses, or `{"type": "inventory_column", "column": ...}` to read a per-tree radius in meters + (e.g. derived from LiDAR). + cbd (CanopyCbdLoadOverDepth | CanopyCbdRunningMean | None | Unset): Canopy bulk density method. Defaults to the + maximum 3.0 m running mean of the profile when the `cbd` band is requested. + cbh (CanopyCbhMean | CanopyCbhMinimum | CanopyCbhPercentile | CanopyProfileThreshold | None | Unset): Canopy + base height method. Defaults to the lowest profile-threshold crossing when the `cbh` band is requested. + chm (CanopyChmHeightPercentile | CanopyProfileThreshold | None | Unset): Canopy height method. Defaults to the + highest profile-threshold crossing when the `chm` band is requested. + cc (CanopyCcCoverFraction | CanopyCcCrownOverlap | CanopyCcCrownUnion | None | Unset): Canopy cover method. + Defaults to the geometric crown union when the `cc` band is requested. + name (str | Unset): Default: ''. + description (str | Unset): Default: ''. + tags (list[str] | Unset): + bands (list[InventoryCanopyBand] | Unset): Which output bands to produce. Defaults to the four LANDFIRE-parity + canopy bands; add `cfl` for canopy fuel load. + alignment (GridAlignmentDomainTarget | GridAlignmentGridTarget | GridAlignmentNativeTarget | Unset): Output + lattice. Against the domain (`target: 'domain'`, the default) `resolution` defaults to 30 m — an inventory has + no native cell size to inherit. Against another grid (`target: 'grid'`) omitting `resolution` matches that + grid's lattice exactly. `target: 'native'` is not supported. + """ + + source_inventory_id: str + biomass_source: ( + AllometryCanopyBiomassSource | InventoryColumnCanopyBiomassSource | Unset + ) = UNSET + available_fuel: CanopyAvailableFuel | None | Unset = UNSET + species_inclusion: CanopySpeciesInclusion | Unset = UNSET + crown_class_adjustment: ( + CanopyFuelcalcCrownClassAdjustment | CanopyNoCrownClassAdjustment | Unset + ) = UNSET + min_tree_height: float | Unset = 0.0 + vertical_distribution: CanopyVerticalDistribution | Unset = UNSET + layer_depth: float | Unset = 0.3048 + horizontal_distribution: CanopyHorizontalDistribution | Unset = UNSET + max_crown_radius_source: ( + CanopyAllometryMaxCrownRadiusSource + | InventoryColumnMaxCrownRadiusSource + | Unset + ) = UNSET + cbd: CanopyCbdLoadOverDepth | CanopyCbdRunningMean | None | Unset = UNSET + cbh: ( + CanopyCbhMean + | CanopyCbhMinimum + | CanopyCbhPercentile + | CanopyProfileThreshold + | None + | Unset + ) = UNSET + chm: CanopyChmHeightPercentile | CanopyProfileThreshold | None | Unset = UNSET + cc: ( + CanopyCcCoverFraction | CanopyCcCrownOverlap | CanopyCcCrownUnion | None | Unset + ) = UNSET + name: str | Unset = "" + description: str | Unset = "" + tags: list[str] | Unset = UNSET + bands: list[InventoryCanopyBand] | Unset = UNSET + alignment: ( + GridAlignmentDomainTarget + | GridAlignmentGridTarget + | GridAlignmentNativeTarget + | Unset + ) = UNSET + + def to_dict(self) -> dict[str, Any]: + from ..models.allometry_canopy_biomass_source import ( + AllometryCanopyBiomassSource, + ) + from ..models.canopy_allometry_max_crown_radius_source import ( + CanopyAllometryMaxCrownRadiusSource, + ) + from ..models.canopy_available_fuel import CanopyAvailableFuel + from ..models.canopy_cbd_load_over_depth import CanopyCbdLoadOverDepth + from ..models.canopy_cbd_running_mean import CanopyCbdRunningMean + from ..models.canopy_cbh_mean import CanopyCbhMean + from ..models.canopy_cbh_minimum import CanopyCbhMinimum + from ..models.canopy_cbh_percentile import CanopyCbhPercentile + from ..models.canopy_cc_cover_fraction import CanopyCcCoverFraction + from ..models.canopy_cc_crown_overlap import CanopyCcCrownOverlap + from ..models.canopy_cc_crown_union import CanopyCcCrownUnion + from ..models.canopy_chm_height_percentile import CanopyChmHeightPercentile + from ..models.canopy_no_crown_class_adjustment import ( + CanopyNoCrownClassAdjustment, + ) + from ..models.canopy_profile_threshold import CanopyProfileThreshold + from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget + from ..models.grid_alignment_native_target import GridAlignmentNativeTarget + + source_inventory_id = self.source_inventory_id + + biomass_source: dict[str, Any] | Unset + if isinstance(self.biomass_source, Unset): + biomass_source = UNSET + elif isinstance(self.biomass_source, AllometryCanopyBiomassSource): + biomass_source = self.biomass_source.to_dict() + else: + biomass_source = self.biomass_source.to_dict() + + available_fuel: dict[str, Any] | None | Unset + if isinstance(self.available_fuel, Unset): + available_fuel = UNSET + elif isinstance(self.available_fuel, CanopyAvailableFuel): + available_fuel = self.available_fuel.to_dict() + else: + available_fuel = self.available_fuel + + species_inclusion: str | Unset = UNSET + if not isinstance(self.species_inclusion, Unset): + species_inclusion = self.species_inclusion.value + + crown_class_adjustment: dict[str, Any] | Unset + if isinstance(self.crown_class_adjustment, Unset): + crown_class_adjustment = UNSET + elif isinstance(self.crown_class_adjustment, CanopyNoCrownClassAdjustment): + crown_class_adjustment = self.crown_class_adjustment.to_dict() + else: + crown_class_adjustment = self.crown_class_adjustment.to_dict() + + min_tree_height = self.min_tree_height + + vertical_distribution: str | Unset = UNSET + if not isinstance(self.vertical_distribution, Unset): + vertical_distribution = self.vertical_distribution.value + + layer_depth = self.layer_depth + + horizontal_distribution: str | Unset = UNSET + if not isinstance(self.horizontal_distribution, Unset): + horizontal_distribution = self.horizontal_distribution.value + + max_crown_radius_source: dict[str, Any] | Unset + if isinstance(self.max_crown_radius_source, Unset): + max_crown_radius_source = UNSET + elif isinstance( + self.max_crown_radius_source, CanopyAllometryMaxCrownRadiusSource + ): + max_crown_radius_source = self.max_crown_radius_source.to_dict() + else: + max_crown_radius_source = self.max_crown_radius_source.to_dict() + + cbd: dict[str, Any] | None | Unset + if isinstance(self.cbd, Unset): + cbd = UNSET + elif isinstance(self.cbd, CanopyCbdRunningMean) or isinstance( + self.cbd, CanopyCbdLoadOverDepth + ): + cbd = self.cbd.to_dict() + else: + cbd = self.cbd + + cbh: dict[str, Any] | None | Unset + if isinstance(self.cbh, Unset): + cbh = UNSET + elif ( + isinstance(self.cbh, CanopyProfileThreshold) + or isinstance(self.cbh, CanopyCbhMean) + or isinstance(self.cbh, CanopyCbhPercentile) + or isinstance(self.cbh, CanopyCbhMinimum) + ): + cbh = self.cbh.to_dict() + else: + cbh = self.cbh + + chm: dict[str, Any] | None | Unset + if isinstance(self.chm, Unset): + chm = UNSET + elif isinstance(self.chm, CanopyProfileThreshold) or isinstance( + self.chm, CanopyChmHeightPercentile + ): + chm = self.chm.to_dict() + else: + chm = self.chm + + cc: dict[str, Any] | None | Unset + if isinstance(self.cc, Unset): + cc = UNSET + elif ( + isinstance(self.cc, CanopyCcCrownUnion) + or isinstance(self.cc, CanopyCcCrownOverlap) + or isinstance(self.cc, CanopyCcCoverFraction) + ): + cc = self.cc.to_dict() + else: + cc = self.cc + + name = self.name + + description = self.description + + tags: list[str] | Unset = UNSET + if not isinstance(self.tags, Unset): + tags = self.tags + + bands: list[str] | Unset = UNSET + if not isinstance(self.bands, Unset): + bands = [] + for bands_item_data in self.bands: + bands_item = bands_item_data.value + bands.append(bands_item) + + alignment: dict[str, Any] | Unset + if isinstance(self.alignment, Unset): + alignment = UNSET + elif isinstance(self.alignment, GridAlignmentDomainTarget) or isinstance( + self.alignment, GridAlignmentNativeTarget + ): + alignment = self.alignment.to_dict() + else: + alignment = self.alignment.to_dict() + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "source_inventory_id": source_inventory_id, + } + ) + if biomass_source is not UNSET: + field_dict["biomass_source"] = biomass_source + if available_fuel is not UNSET: + field_dict["available_fuel"] = available_fuel + if species_inclusion is not UNSET: + field_dict["species_inclusion"] = species_inclusion + if crown_class_adjustment is not UNSET: + field_dict["crown_class_adjustment"] = crown_class_adjustment + if min_tree_height is not UNSET: + field_dict["min_tree_height"] = min_tree_height + if vertical_distribution is not UNSET: + field_dict["vertical_distribution"] = vertical_distribution + if layer_depth is not UNSET: + field_dict["layer_depth"] = layer_depth + if horizontal_distribution is not UNSET: + field_dict["horizontal_distribution"] = horizontal_distribution + if max_crown_radius_source is not UNSET: + field_dict["max_crown_radius_source"] = max_crown_radius_source + if cbd is not UNSET: + field_dict["cbd"] = cbd + if cbh is not UNSET: + field_dict["cbh"] = cbh + if chm is not UNSET: + field_dict["chm"] = chm + if cc is not UNSET: + field_dict["cc"] = cc + if name is not UNSET: + field_dict["name"] = name + if description is not UNSET: + field_dict["description"] = description + if tags is not UNSET: + field_dict["tags"] = tags + if bands is not UNSET: + field_dict["bands"] = bands + if alignment is not UNSET: + field_dict["alignment"] = alignment + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.allometry_canopy_biomass_source import ( + AllometryCanopyBiomassSource, + ) + from ..models.canopy_allometry_max_crown_radius_source import ( + CanopyAllometryMaxCrownRadiusSource, + ) + from ..models.canopy_available_fuel import CanopyAvailableFuel + from ..models.canopy_cbd_load_over_depth import CanopyCbdLoadOverDepth + from ..models.canopy_cbd_running_mean import CanopyCbdRunningMean + from ..models.canopy_cbh_mean import CanopyCbhMean + from ..models.canopy_cbh_minimum import CanopyCbhMinimum + from ..models.canopy_cbh_percentile import CanopyCbhPercentile + from ..models.canopy_cc_cover_fraction import CanopyCcCoverFraction + from ..models.canopy_cc_crown_overlap import CanopyCcCrownOverlap + from ..models.canopy_cc_crown_union import CanopyCcCrownUnion + from ..models.canopy_chm_height_percentile import CanopyChmHeightPercentile + from ..models.canopy_fuelcalc_crown_class_adjustment import ( + CanopyFuelcalcCrownClassAdjustment, + ) + from ..models.canopy_no_crown_class_adjustment import ( + CanopyNoCrownClassAdjustment, + ) + from ..models.canopy_profile_threshold import CanopyProfileThreshold + from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget + from ..models.grid_alignment_grid_target import GridAlignmentGridTarget + from ..models.grid_alignment_native_target import GridAlignmentNativeTarget + from ..models.inventory_column_canopy_biomass_source import ( + InventoryColumnCanopyBiomassSource, + ) + from ..models.inventory_column_max_crown_radius_source import ( + InventoryColumnMaxCrownRadiusSource, + ) + + d = dict(src_dict) + source_inventory_id = d.pop("source_inventory_id") + + def _parse_biomass_source( + data: object, + ) -> AllometryCanopyBiomassSource | InventoryColumnCanopyBiomassSource | Unset: + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + biomass_source_type_0 = AllometryCanopyBiomassSource.from_dict(data) + + return biomass_source_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + if not isinstance(data, dict): + raise TypeError() + biomass_source_type_1 = InventoryColumnCanopyBiomassSource.from_dict(data) + + return biomass_source_type_1 + + biomass_source = _parse_biomass_source(d.pop("biomass_source", UNSET)) + + def _parse_available_fuel(data: object) -> CanopyAvailableFuel | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + available_fuel_type_0 = CanopyAvailableFuel.from_dict(data) + + return available_fuel_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(CanopyAvailableFuel | None | Unset, data) + + available_fuel = _parse_available_fuel(d.pop("available_fuel", UNSET)) + + _species_inclusion = d.pop("species_inclusion", UNSET) + species_inclusion: CanopySpeciesInclusion | Unset + if isinstance(_species_inclusion, Unset): + species_inclusion = UNSET + else: + species_inclusion = CanopySpeciesInclusion(_species_inclusion) + + def _parse_crown_class_adjustment( + data: object, + ) -> CanopyFuelcalcCrownClassAdjustment | CanopyNoCrownClassAdjustment | Unset: + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + crown_class_adjustment_type_0 = CanopyNoCrownClassAdjustment.from_dict( + data + ) + + return crown_class_adjustment_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + if not isinstance(data, dict): + raise TypeError() + crown_class_adjustment_type_1 = ( + CanopyFuelcalcCrownClassAdjustment.from_dict(data) + ) + + return crown_class_adjustment_type_1 + + crown_class_adjustment = _parse_crown_class_adjustment( + d.pop("crown_class_adjustment", UNSET) + ) + + min_tree_height = d.pop("min_tree_height", UNSET) + + _vertical_distribution = d.pop("vertical_distribution", UNSET) + vertical_distribution: CanopyVerticalDistribution | Unset + if isinstance(_vertical_distribution, Unset): + vertical_distribution = UNSET + else: + vertical_distribution = CanopyVerticalDistribution(_vertical_distribution) + + layer_depth = d.pop("layer_depth", UNSET) + + _horizontal_distribution = d.pop("horizontal_distribution", UNSET) + horizontal_distribution: CanopyHorizontalDistribution | Unset + if isinstance(_horizontal_distribution, Unset): + horizontal_distribution = UNSET + else: + horizontal_distribution = CanopyHorizontalDistribution( + _horizontal_distribution + ) + + def _parse_max_crown_radius_source( + data: object, + ) -> ( + CanopyAllometryMaxCrownRadiusSource + | InventoryColumnMaxCrownRadiusSource + | Unset + ): + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + max_crown_radius_source_type_0 = ( + CanopyAllometryMaxCrownRadiusSource.from_dict(data) + ) + + return max_crown_radius_source_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + if not isinstance(data, dict): + raise TypeError() + max_crown_radius_source_type_1 = ( + InventoryColumnMaxCrownRadiusSource.from_dict(data) + ) + + return max_crown_radius_source_type_1 + + max_crown_radius_source = _parse_max_crown_radius_source( + d.pop("max_crown_radius_source", UNSET) + ) + + def _parse_cbd( + data: object, + ) -> CanopyCbdLoadOverDepth | CanopyCbdRunningMean | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + cbd_type_0_type_0 = CanopyCbdRunningMean.from_dict(data) + + return cbd_type_0_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cbd_type_0_type_1 = CanopyCbdLoadOverDepth.from_dict(data) + + return cbd_type_0_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast( + CanopyCbdLoadOverDepth | CanopyCbdRunningMean | None | Unset, data + ) + + cbd = _parse_cbd(d.pop("cbd", UNSET)) + + def _parse_cbh( + data: object, + ) -> ( + CanopyCbhMean + | CanopyCbhMinimum + | CanopyCbhPercentile + | CanopyProfileThreshold + | None + | Unset + ): + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + cbh_type_0_type_0 = CanopyProfileThreshold.from_dict(data) + + return cbh_type_0_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cbh_type_0_type_1 = CanopyCbhMean.from_dict(data) + + return cbh_type_0_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cbh_type_0_type_2 = CanopyCbhPercentile.from_dict(data) + + return cbh_type_0_type_2 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cbh_type_0_type_3 = CanopyCbhMinimum.from_dict(data) + + return cbh_type_0_type_3 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast( + CanopyCbhMean + | CanopyCbhMinimum + | CanopyCbhPercentile + | CanopyProfileThreshold + | None + | Unset, + data, + ) + + cbh = _parse_cbh(d.pop("cbh", UNSET)) + + def _parse_chm( + data: object, + ) -> CanopyChmHeightPercentile | CanopyProfileThreshold | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + chm_type_0_type_0 = CanopyProfileThreshold.from_dict(data) + + return chm_type_0_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + chm_type_0_type_1 = CanopyChmHeightPercentile.from_dict(data) + + return chm_type_0_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast( + CanopyChmHeightPercentile | CanopyProfileThreshold | None | Unset, data + ) + + chm = _parse_chm(d.pop("chm", UNSET)) + + def _parse_cc( + data: object, + ) -> ( + CanopyCcCoverFraction + | CanopyCcCrownOverlap + | CanopyCcCrownUnion + | None + | Unset + ): + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + cc_type_0_type_0 = CanopyCcCrownUnion.from_dict(data) + + return cc_type_0_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cc_type_0_type_1 = CanopyCcCrownOverlap.from_dict(data) + + return cc_type_0_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + cc_type_0_type_2 = CanopyCcCoverFraction.from_dict(data) + + return cc_type_0_type_2 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast( + CanopyCcCoverFraction + | CanopyCcCrownOverlap + | CanopyCcCrownUnion + | None + | Unset, + data, + ) + + cc = _parse_cc(d.pop("cc", UNSET)) + + name = d.pop("name", UNSET) + + description = d.pop("description", UNSET) + + tags = cast(list[str], d.pop("tags", UNSET)) + + _bands = d.pop("bands", UNSET) + bands: list[InventoryCanopyBand] | Unset = UNSET + if _bands is not UNSET: + bands = [] + for bands_item_data in _bands: + bands_item = InventoryCanopyBand(bands_item_data) + + bands.append(bands_item) + + def _parse_alignment( + data: object, + ) -> ( + GridAlignmentDomainTarget + | GridAlignmentGridTarget + | GridAlignmentNativeTarget + | Unset + ): + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + alignment_type_0 = GridAlignmentDomainTarget.from_dict(data) + + return alignment_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + alignment_type_1 = GridAlignmentNativeTarget.from_dict(data) + + return alignment_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + if not isinstance(data, dict): + raise TypeError() + alignment_type_2 = GridAlignmentGridTarget.from_dict(data) + + return alignment_type_2 + + alignment = _parse_alignment(d.pop("alignment", UNSET)) + + create_inventory_canopy_request = cls( + source_inventory_id=source_inventory_id, + biomass_source=biomass_source, + available_fuel=available_fuel, + species_inclusion=species_inclusion, + crown_class_adjustment=crown_class_adjustment, + min_tree_height=min_tree_height, + vertical_distribution=vertical_distribution, + layer_depth=layer_depth, + horizontal_distribution=horizontal_distribution, + max_crown_radius_source=max_crown_radius_source, + cbd=cbd, + cbh=cbh, + chm=chm, + cc=cc, + name=name, + description=description, + tags=tags, + bands=bands, + alignment=alignment, + ) + + return create_inventory_canopy_request diff --git a/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_13_request.py b/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_13_request.py index 29d8d82..023d6a7 100644 --- a/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_13_request.py +++ b/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_13_request.py @@ -49,7 +49,7 @@ class CreateLandfireFbfm13Request: alignment target. Default `target="domain"` anchors output cells to the domain origin so cross-source composition works by construction. `target="native"` preserves the source pixel anchor. `target="grid"` aligns to an existing grid by id. - version (LandfireFbfm13Version | Unset): Available LANDFIRE FBFM13 data versions. + version (LandfireFbfm13Version | Unset): remove_non_burnable (list[NonBurnableFuelModel] | None | Unset): """ diff --git a/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_40_request.py b/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_40_request.py index dfd542d..cdde952 100644 --- a/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_40_request.py +++ b/fastfuels_sdk/v2/client_library/models/create_landfire_fbfm_40_request.py @@ -7,6 +7,7 @@ from attrs import field as _attrs_field from ..models.landfire_fbfm_40_version import LandfireFbfm40Version +from ..models.landfire_season import LandfireSeason from ..models.non_burnable_fuel_model import NonBurnableFuelModel from ..types import UNSET, Unset @@ -49,8 +50,11 @@ class CreateLandfireFbfm40Request: alignment target. Default `target="domain"` anchors output cells to the domain origin so cross-source composition works by construction. `target="native"` preserves the source pixel anchor. `target="grid"` aligns to an existing grid by id. - version (LandfireFbfm40Version | Unset): Available LANDFIRE FBFM40 data versions. + version (LandfireFbfm40Version | Unset): remove_non_burnable (list[NonBurnableFuelModel] | None | Unset): + season (LandfireSeason | None | Unset): LANDFIRE Seasonal Fuels window: ES (early spring), SP (spring), SU + (summer), FA (fall). When set, fetches seasonal fuels from the LANDFIRE Product Service for the given season + instead of the staged annual release. """ name: str | Unset = "" @@ -66,6 +70,7 @@ class CreateLandfireFbfm40Request: ) = UNSET version: LandfireFbfm40Version | Unset = UNSET remove_non_burnable: list[NonBurnableFuelModel] | None | Unset = UNSET + season: LandfireSeason | None | Unset = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -117,6 +122,14 @@ def to_dict(self) -> dict[str, Any]: else: remove_non_burnable = self.remove_non_burnable + season: None | str | Unset + if isinstance(self.season, Unset): + season = UNSET + elif isinstance(self.season, LandfireSeason): + season = self.season.value + else: + season = self.season + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update({}) @@ -136,6 +149,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["version"] = version if remove_non_burnable is not UNSET: field_dict["remove_non_burnable"] = remove_non_burnable + if season is not UNSET: + field_dict["season"] = season return field_dict @@ -233,6 +248,23 @@ def _parse_remove_non_burnable( d.pop("remove_non_burnable", UNSET) ) + def _parse_season(data: object) -> LandfireSeason | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, str): + raise TypeError() + season_type_0 = LandfireSeason(data) + + return season_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(LandfireSeason | None | Unset, data) + + season = _parse_season(d.pop("season", UNSET)) + create_landfire_fbfm_40_request = cls( name=name, description=description, @@ -242,6 +274,7 @@ def _parse_remove_non_burnable( alignment=alignment, version=version, remove_non_burnable=remove_non_burnable, + season=season, ) create_landfire_fbfm_40_request.additional_properties = d diff --git a/fastfuels_sdk/v2/client_library/models/create_landfire_fccs_request.py b/fastfuels_sdk/v2/client_library/models/create_landfire_fccs_request.py index 796ad33..1b3aea1 100644 --- a/fastfuels_sdk/v2/client_library/models/create_landfire_fccs_request.py +++ b/fastfuels_sdk/v2/client_library/models/create_landfire_fccs_request.py @@ -48,7 +48,7 @@ class CreateLandfireFccsRequest: alignment target. Default `target="domain"` anchors output cells to the domain origin so cross-source composition works by construction. `target="native"` preserves the source pixel anchor. `target="grid"` aligns to an existing grid by id. - version (LandfireFccsVersion | Unset): Available LANDFIRE FCCS data versions. + version (LandfireFccsVersion | Unset): remove_bare_ground (bool | Unset): Default: False. """ diff --git a/fastfuels_sdk/v2/client_library/models/create_leaflux_irradiance_request.py b/fastfuels_sdk/v2/client_library/models/create_leaflux_irradiance_request.py new file mode 100644 index 0000000..42df6f9 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/create_leaflux_irradiance_request.py @@ -0,0 +1,141 @@ +from __future__ import annotations + +import datetime +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define + +from ..models.leaflux_band import LeafluxBand +from ..types import UNSET, Unset + +T = TypeVar("T", bound="CreateLeafluxIrradianceRequest") + + +@_attrs_define +class CreateLeafluxIrradianceRequest: + """Request body for creating a LeafLux irradiance grid from a 3D fuel grid. + + Does not extend CreateGridRequestBase because 3D grids do not support + modifications. This is a grid -> grid derivation aligned to the source + grid's geometry, so there is no resolution input. + + Attributes: + source_grid_id (str): ID of a completed 3D grid that has a `leaf_area_density` band. + date_time (datetime.datetime): UTC instant at which to compute irradiance. + name (str | Unset): Default: ''. + description (str | Unset): Default: ''. + tags (list[str] | Unset): + source_terrain_grid_id (None | str | Unset): (optional) 2D terrain grid in the same domain, used for surface + irradiance. + bands (list[LeafluxBand] | Unset): Which output bands to produce. Defaults to `irradiance.surface.relative`. + extinction_coefficient (float | Unset): Beer-Lambert extinction coefficient (leaflux `extn`). Default: 0.5. + """ + + source_grid_id: str + date_time: datetime.datetime + name: str | Unset = "" + description: str | Unset = "" + tags: list[str] | Unset = UNSET + source_terrain_grid_id: None | str | Unset = UNSET + bands: list[LeafluxBand] | Unset = UNSET + extinction_coefficient: float | Unset = 0.5 + + def to_dict(self) -> dict[str, Any]: + source_grid_id = self.source_grid_id + + date_time = self.date_time.isoformat() + + name = self.name + + description = self.description + + tags: list[str] | Unset = UNSET + if not isinstance(self.tags, Unset): + tags = self.tags + + source_terrain_grid_id: None | str | Unset + if isinstance(self.source_terrain_grid_id, Unset): + source_terrain_grid_id = UNSET + else: + source_terrain_grid_id = self.source_terrain_grid_id + + bands: list[str] | Unset = UNSET + if not isinstance(self.bands, Unset): + bands = [] + for bands_item_data in self.bands: + bands_item = bands_item_data.value + bands.append(bands_item) + + extinction_coefficient = self.extinction_coefficient + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "source_grid_id": source_grid_id, + "date_time": date_time, + } + ) + if name is not UNSET: + field_dict["name"] = name + if description is not UNSET: + field_dict["description"] = description + if tags is not UNSET: + field_dict["tags"] = tags + if source_terrain_grid_id is not UNSET: + field_dict["source_terrain_grid_id"] = source_terrain_grid_id + if bands is not UNSET: + field_dict["bands"] = bands + if extinction_coefficient is not UNSET: + field_dict["extinction_coefficient"] = extinction_coefficient + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + source_grid_id = d.pop("source_grid_id") + + date_time = datetime.datetime.fromisoformat(d.pop("date_time")) + + name = d.pop("name", UNSET) + + description = d.pop("description", UNSET) + + tags = cast(list[str], d.pop("tags", UNSET)) + + def _parse_source_terrain_grid_id(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + source_terrain_grid_id = _parse_source_terrain_grid_id( + d.pop("source_terrain_grid_id", UNSET) + ) + + _bands = d.pop("bands", UNSET) + bands: list[LeafluxBand] | Unset = UNSET + if _bands is not UNSET: + bands = [] + for bands_item_data in _bands: + bands_item = LeafluxBand(bands_item_data) + + bands.append(bands_item) + + extinction_coefficient = d.pop("extinction_coefficient", UNSET) + + create_leaflux_irradiance_request = cls( + source_grid_id=source_grid_id, + date_time=date_time, + name=name, + description=description, + tags=tags, + source_terrain_grid_id=source_terrain_grid_id, + bands=bands, + extinction_coefficient=extinction_coefficient, + ) + + return create_leaflux_irradiance_request diff --git a/fastfuels_sdk/v2/client_library/models/create_point_cloud_chm_request.py b/fastfuels_sdk/v2/client_library/models/create_point_cloud_chm_request.py index a53606c..130ffd6 100644 --- a/fastfuels_sdk/v2/client_library/models/create_point_cloud_chm_request.py +++ b/fastfuels_sdk/v2/client_library/models/create_point_cloud_chm_request.py @@ -9,6 +9,11 @@ from ..types import UNSET, Unset if TYPE_CHECKING: + from ..models.chm_max_aggregation import ChmMaxAggregation + from ..models.chm_mean_aggregation import ChmMeanAggregation + from ..models.chm_median_aggregation import ChmMedianAggregation + from ..models.chm_percentile_aggregation import ChmPercentileAggregation + from ..models.chm_spike_filter import ChmSpikeFilter from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget from ..models.grid_alignment_grid_target import GridAlignmentGridTarget from ..models.grid_alignment_native_target import GridAlignmentNativeTarget @@ -52,6 +57,11 @@ class CreatePointCloudChmRequest: alignment target. Default `target="domain"` anchors output cells to the domain origin so cross-source composition works by construction. `target="native"` preserves the source pixel anchor. `target="grid"` aligns to an existing grid by id. + spike_filter (ChmSpikeFilter | None | Unset): Removal of lone spurious returns. Omit for the defaults; send + `null` to keep every return, leaving unclassified noise in the grid. + aggregation (ChmMaxAggregation | ChmMeanAggregation | ChmMedianAggregation | ChmPercentileAggregation | Unset): + Statistic each cell reduces the heights above ground of its returns with. Carries `percentile` only on `method: + percentile`. """ source_point_cloud_id: str @@ -66,9 +76,21 @@ class CreatePointCloudChmRequest: | GridAlignmentNativeTarget | Unset ) = UNSET + spike_filter: ChmSpikeFilter | None | Unset = UNSET + aggregation: ( + ChmMaxAggregation + | ChmMeanAggregation + | ChmMedianAggregation + | ChmPercentileAggregation + | Unset + ) = UNSET additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: + from ..models.chm_max_aggregation import ChmMaxAggregation + from ..models.chm_mean_aggregation import ChmMeanAggregation + from ..models.chm_median_aggregation import ChmMedianAggregation + from ..models.chm_spike_filter import ChmSpikeFilter from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget from ..models.grid_alignment_native_target import GridAlignmentNativeTarget @@ -101,6 +123,26 @@ def to_dict(self) -> dict[str, Any]: else: alignment = self.alignment.to_dict() + spike_filter: dict[str, Any] | None | Unset + if isinstance(self.spike_filter, Unset): + spike_filter = UNSET + elif isinstance(self.spike_filter, ChmSpikeFilter): + spike_filter = self.spike_filter.to_dict() + else: + spike_filter = self.spike_filter + + aggregation: dict[str, Any] | Unset + if isinstance(self.aggregation, Unset): + aggregation = UNSET + elif ( + isinstance(self.aggregation, ChmMaxAggregation) + or isinstance(self.aggregation, ChmMeanAggregation) + or isinstance(self.aggregation, ChmMedianAggregation) + ): + aggregation = self.aggregation.to_dict() + else: + aggregation = self.aggregation.to_dict() + field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -120,11 +162,20 @@ def to_dict(self) -> dict[str, Any]: field_dict["extent_buffer_cells"] = extent_buffer_cells if alignment is not UNSET: field_dict["alignment"] = alignment + if spike_filter is not UNSET: + field_dict["spike_filter"] = spike_filter + if aggregation is not UNSET: + field_dict["aggregation"] = aggregation return field_dict @classmethod def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.chm_max_aggregation import ChmMaxAggregation + from ..models.chm_mean_aggregation import ChmMeanAggregation + from ..models.chm_median_aggregation import ChmMedianAggregation + from ..models.chm_percentile_aggregation import ChmPercentileAggregation + from ..models.chm_spike_filter import ChmSpikeFilter from ..models.grid_alignment_domain_target import GridAlignmentDomainTarget from ..models.grid_alignment_grid_target import GridAlignmentGridTarget from ..models.grid_alignment_native_target import GridAlignmentNativeTarget @@ -184,6 +235,66 @@ def _parse_alignment( alignment = _parse_alignment(d.pop("alignment", UNSET)) + def _parse_spike_filter(data: object) -> ChmSpikeFilter | None | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + spike_filter_type_0 = ChmSpikeFilter.from_dict(data) + + return spike_filter_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(ChmSpikeFilter | None | Unset, data) + + spike_filter = _parse_spike_filter(d.pop("spike_filter", UNSET)) + + def _parse_aggregation( + data: object, + ) -> ( + ChmMaxAggregation + | ChmMeanAggregation + | ChmMedianAggregation + | ChmPercentileAggregation + | Unset + ): + if isinstance(data, Unset): + return data + try: + if not isinstance(data, dict): + raise TypeError() + aggregation_type_0 = ChmMaxAggregation.from_dict(data) + + return aggregation_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + aggregation_type_1 = ChmMeanAggregation.from_dict(data) + + return aggregation_type_1 + except (TypeError, ValueError, AttributeError, KeyError): + pass + try: + if not isinstance(data, dict): + raise TypeError() + aggregation_type_2 = ChmMedianAggregation.from_dict(data) + + return aggregation_type_2 + except (TypeError, ValueError, AttributeError, KeyError): + pass + if not isinstance(data, dict): + raise TypeError() + aggregation_type_3 = ChmPercentileAggregation.from_dict(data) + + return aggregation_type_3 + + aggregation = _parse_aggregation(d.pop("aggregation", UNSET)) + create_point_cloud_chm_request = cls( source_point_cloud_id=source_point_cloud_id, name=name, @@ -192,6 +303,8 @@ def _parse_alignment( modifications=modifications, extent_buffer_cells=extent_buffer_cells, alignment=alignment, + spike_filter=spike_filter, + aggregation=aggregation, ) create_point_cloud_chm_request.additional_properties = d diff --git a/fastfuels_sdk/v2/client_library/models/dense_grid_data.py b/fastfuels_sdk/v2/client_library/models/dense_grid_data.py index ddda21f..40b379a 100644 --- a/fastfuels_sdk/v2/client_library/models/dense_grid_data.py +++ b/fastfuels_sdk/v2/client_library/models/dense_grid_data.py @@ -20,11 +20,12 @@ class DenseGridData: """ Attributes: format_ (Literal['dense']): - values (list[float | int]): + values (list[float | int | None]): Flat list of every cell in the chunk. `null` marks a cell with no data — a + float band stores those as NaN, which JSON cannot represent. """ format_: Literal["dense"] - values: list[float | int] + values: list[float | int | None] additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -32,7 +33,7 @@ def to_dict(self) -> dict[str, Any]: values = [] for values_item_data in self.values: - values_item: float | int + values_item: float | int | None values_item = values_item_data values.append(values_item) @@ -58,8 +59,10 @@ def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: _values = d.pop("values") for values_item_data in _values: - def _parse_values_item(data: object) -> float | int: - return cast(float | int, data) + def _parse_values_item(data: object) -> float | int | None: + if data is None: + return data + return cast(float | int | None, data) values_item = _parse_values_item(values_item_data) diff --git a/fastfuels_sdk/v2/client_library/models/fuel_moisture_month.py b/fastfuels_sdk/v2/client_library/models/fuel_moisture_month.py new file mode 100644 index 0000000..a5f16f9 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/fuel_moisture_month.py @@ -0,0 +1,19 @@ +from enum import Enum + + +class FuelMoistureMonth(str, Enum): + APRIL = "April" + AUGUST = "August" + DECEMBER = "December" + FEBRUARY = "February" + JANUARY = "January" + JULY = "July" + JUNE = "June" + MARCH = "March" + MAY = "May" + NOVEMBER = "November" + OCTOBER = "October" + SEPTEMBER = "September" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/inventory_canopy_band.py b/fastfuels_sdk/v2/client_library/models/inventory_canopy_band.py new file mode 100644 index 0000000..63d3289 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/inventory_canopy_band.py @@ -0,0 +1,12 @@ +from enum import Enum + + +class InventoryCanopyBand(str, Enum): + CBD = "cbd" + CBH = "cbh" + CC = "cc" + CFL = "cfl" + CHM = "chm" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/inventory_column_canopy_biomass_source.py b/fastfuels_sdk/v2/client_library/models/inventory_column_canopy_biomass_source.py new file mode 100644 index 0000000..14debe3 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/inventory_column_canopy_biomass_source.py @@ -0,0 +1,84 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import ( + Any, + Literal, + Self, + TypeVar, + cast, +) + +from attrs import define as _attrs_define + +from ..models.biomass_unit import BiomassUnit +from ..types import UNSET, Unset + +T = TypeVar("T", bound="InventoryColumnCanopyBiomassSource") + + +@_attrs_define +class InventoryColumnCanopyBiomassSource: + """Read each tree's available canopy fuel directly from an inventory column. + + The column value is used as-is: it must already be the per-tree mass of + canopy fuel available to a crown fire (foliage plus the burnable fine + branchwood). This bypasses allometry, `available_fuel`, species + inclusion, and crown-class adjustment for fuel magnitude. + + Attributes: + column (str): Inventory column holding per-tree available canopy fuel. + type_ (Literal['inventory_column'] | Unset): Default: 'inventory_column'. + unit (BiomassUnit | Unset): Accepted inventory biomass units. + """ + + column: str + type_: Literal["inventory_column"] | Unset = "inventory_column" + unit: BiomassUnit | Unset = UNSET + + def to_dict(self) -> dict[str, Any]: + column = self.column + + type_ = self.type_ + + unit: str | Unset = UNSET + if not isinstance(self.unit, Unset): + unit = self.unit.value + + field_dict: dict[str, Any] = {} + + field_dict.update( + { + "column": column, + } + ) + if type_ is not UNSET: + field_dict["type"] = type_ + if unit is not UNSET: + field_dict["unit"] = unit + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + column = d.pop("column") + + type_ = cast(Literal["inventory_column"] | Unset, d.pop("type", UNSET)) + if type_ != "inventory_column" and not isinstance(type_, Unset): + raise ValueError(f"type must match const 'inventory_column', got '{type_}'") + + _unit = d.pop("unit", UNSET) + unit: BiomassUnit | Unset + if isinstance(_unit, Unset): + unit = UNSET + else: + unit = BiomassUnit(_unit) + + inventory_column_canopy_biomass_source = cls( + column=column, + type_=type_, + unit=unit, + ) + + return inventory_column_canopy_biomass_source diff --git a/fastfuels_sdk/v2/client_library/models/inventory_column_mapping.py b/fastfuels_sdk/v2/client_library/models/inventory_column_mapping.py index b0e2563..02e250c 100644 --- a/fastfuels_sdk/v2/client_library/models/inventory_column_mapping.py +++ b/fastfuels_sdk/v2/client_library/models/inventory_column_mapping.py @@ -24,6 +24,7 @@ class InventoryColumnMapping: height (None | str | Unset): fia_species_code (None | str | Unset): fia_status_code (None | str | Unset): + fia_crown_class_code (None | str | Unset): dbh (None | str | Unset): crown_ratio (None | str | Unset): """ @@ -33,6 +34,7 @@ class InventoryColumnMapping: height: None | str | Unset = UNSET fia_species_code: None | str | Unset = UNSET fia_status_code: None | str | Unset = UNSET + fia_crown_class_code: None | str | Unset = UNSET dbh: None | str | Unset = UNSET crown_ratio: None | str | Unset = UNSET @@ -67,6 +69,12 @@ def to_dict(self) -> dict[str, Any]: else: fia_status_code = self.fia_status_code + fia_crown_class_code: None | str | Unset + if isinstance(self.fia_crown_class_code, Unset): + fia_crown_class_code = UNSET + else: + fia_crown_class_code = self.fia_crown_class_code + dbh: None | str | Unset if isinstance(self.dbh, Unset): dbh = UNSET @@ -92,6 +100,8 @@ def to_dict(self) -> dict[str, Any]: field_dict["fia_species_code"] = fia_species_code if fia_status_code is not UNSET: field_dict["fia_status_code"] = fia_status_code + if fia_crown_class_code is not UNSET: + field_dict["fia_crown_class_code"] = fia_crown_class_code if dbh is not UNSET: field_dict["dbh"] = dbh if crown_ratio is not UNSET: @@ -148,6 +158,17 @@ def _parse_fia_status_code(data: object) -> None | str | Unset: fia_status_code = _parse_fia_status_code(d.pop("fia_status_code", UNSET)) + def _parse_fia_crown_class_code(data: object) -> None | str | Unset: + if data is None: + return data + if isinstance(data, Unset): + return data + return cast(None | str | Unset, data) + + fia_crown_class_code = _parse_fia_crown_class_code( + d.pop("fia_crown_class_code", UNSET) + ) + def _parse_dbh(data: object) -> None | str | Unset: if data is None: return data @@ -172,6 +193,7 @@ def _parse_crown_ratio(data: object) -> None | str | Unset: height=height, fia_species_code=fia_species_code, fia_status_code=fia_status_code, + fia_crown_class_code=fia_crown_class_code, dbh=dbh, crown_ratio=crown_ratio, ) diff --git a/fastfuels_sdk/v2/client_library/models/landfire_fbfm_40_version.py b/fastfuels_sdk/v2/client_library/models/landfire_fbfm_40_version.py index 30d20d4..352f28d 100644 --- a/fastfuels_sdk/v2/client_library/models/landfire_fbfm_40_version.py +++ b/fastfuels_sdk/v2/client_library/models/landfire_fbfm_40_version.py @@ -7,6 +7,7 @@ class LandfireFbfm40Version(str, Enum): VALUE_2 = "2022" VALUE_3 = "2023" VALUE_4 = "2024" + VALUE_5 = "2025" def __str__(self) -> str: return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/landfire_season.py b/fastfuels_sdk/v2/client_library/models/landfire_season.py new file mode 100644 index 0000000..77ab28d --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/landfire_season.py @@ -0,0 +1,11 @@ +from enum import Enum + + +class LandfireSeason(str, Enum): + ES = "ES" + FA = "FA" + SP = "SP" + SU = "SU" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/leaflux_band.py b/fastfuels_sdk/v2/client_library/models/leaflux_band.py new file mode 100644 index 0000000..1ea743c --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/leaflux_band.py @@ -0,0 +1,9 @@ +from enum import Enum + + +class LeafluxBand(str, Enum): + IRRADIANCE_CANOPY_RELATIVE = "irradiance.canopy.relative" + IRRADIANCE_SURFACE_RELATIVE = "irradiance.surface.relative" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata.py b/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata.py new file mode 100644 index 0000000..70aabad --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata.py @@ -0,0 +1,180 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.point_cloud_data_metadata_columns import PointCloudDataMetadataColumns + from ..models.point_cloud_tile_metadata import PointCloudTileMetadata + + +T = TypeVar("T", bound="PointCloudDataMetadata") + + +@_attrs_define +class PointCloudDataMetadata: + """Public tile index used to discover and budget point-data requests. + + Attributes: + tile_m (float): Tile width and height in the horizontal units of `crs`. + lod_levels (int): Number of cumulative levels of detail. Valid `lod` query values are `0` through `lod_levels - + 1`; omitting `lod` reads the final, complete level. + crs (str): Coordinate reference system for decoded X/Y coordinates and all reported horizontal bounds. + bounds (list[float]): Overall horizontal point extent as `[min_x, min_y, max_x, max_y]` in `crs`. + scales (list[float]): Coordinate scale factors in X/Y/Z order. Decode axis `i` with `stored_integer * scales[i] + + offsets[i]`. + offsets (list[float]): Coordinate offsets in X/Y/Z order. Decode axis `i` with `stored_integer * scales[i] + + offsets[i]`. + columns (PointCloudDataMetadataColumns): Public stored column names mapped to NumPy-compatible dtypes. Use these + names in the comma-separated `columns` query parameter. X/Y/Z remain scaled integers on the wire. + tiles (list[PointCloudTileMetadata]): Occupied tiles sorted by `(tile_x, tile_y)`. Empty positions are omitted; + only listed tile coordinates are valid data requests. + """ + + tile_m: float + lod_levels: int + crs: str + bounds: list[float] + scales: list[float] + offsets: list[float] + columns: PointCloudDataMetadataColumns + tiles: list[PointCloudTileMetadata] + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + tile_m = self.tile_m + + lod_levels = self.lod_levels + + crs = self.crs + + bounds = [] + for bounds_item_data in self.bounds: + bounds_item: float + bounds_item = bounds_item_data + bounds.append(bounds_item) + + scales = [] + for scales_item_data in self.scales: + scales_item: float + scales_item = scales_item_data + scales.append(scales_item) + + offsets = [] + for offsets_item_data in self.offsets: + offsets_item: float + offsets_item = offsets_item_data + offsets.append(offsets_item) + + columns = self.columns.to_dict() + + tiles = [] + for tiles_item_data in self.tiles: + tiles_item = tiles_item_data.to_dict() + tiles.append(tiles_item) + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "tile_m": tile_m, + "lod_levels": lod_levels, + "crs": crs, + "bounds": bounds, + "scales": scales, + "offsets": offsets, + "columns": columns, + "tiles": tiles, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.point_cloud_data_metadata_columns import ( + PointCloudDataMetadataColumns, + ) + from ..models.point_cloud_tile_metadata import PointCloudTileMetadata + + d = dict(src_dict) + tile_m = d.pop("tile_m") + + lod_levels = d.pop("lod_levels") + + crs = d.pop("crs") + + bounds = [] + _bounds = d.pop("bounds") + for bounds_item_data in _bounds: + + def _parse_bounds_item(data: object) -> float: + return cast(float, data) + + bounds_item = _parse_bounds_item(bounds_item_data) + + bounds.append(bounds_item) + + scales = [] + _scales = d.pop("scales") + for scales_item_data in _scales: + + def _parse_scales_item(data: object) -> float: + return cast(float, data) + + scales_item = _parse_scales_item(scales_item_data) + + scales.append(scales_item) + + offsets = [] + _offsets = d.pop("offsets") + for offsets_item_data in _offsets: + + def _parse_offsets_item(data: object) -> float: + return cast(float, data) + + offsets_item = _parse_offsets_item(offsets_item_data) + + offsets.append(offsets_item) + + columns = PointCloudDataMetadataColumns.from_dict(d.pop("columns")) + + tiles = [] + _tiles = d.pop("tiles") + for tiles_item_data in _tiles: + tiles_item = PointCloudTileMetadata.from_dict(tiles_item_data) + + tiles.append(tiles_item) + + point_cloud_data_metadata = cls( + tile_m=tile_m, + lod_levels=lod_levels, + crs=crs, + bounds=bounds, + scales=scales, + offsets=offsets, + columns=columns, + tiles=tiles, + ) + + point_cloud_data_metadata.additional_properties = d + return point_cloud_data_metadata + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata_columns.py b/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata_columns.py new file mode 100644 index 0000000..1bda150 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_data_metadata_columns.py @@ -0,0 +1,50 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PointCloudDataMetadataColumns") + + +@_attrs_define +class PointCloudDataMetadataColumns: + """Public stored column names mapped to NumPy-compatible dtypes. Use these names in the comma-separated `columns` query + parameter. X/Y/Z remain scaled integers on the wire. + + """ + + additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + point_cloud_data_metadata_columns = cls() + + point_cloud_data_metadata_columns.additional_properties = d + return point_cloud_data_metadata_columns + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> str: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: str) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_three_dep_coverage_response.py b/fastfuels_sdk/v2/client_library/models/point_cloud_three_dep_coverage_response.py index 1366f4f..c488a96 100644 --- a/fastfuels_sdk/v2/client_library/models/point_cloud_three_dep_coverage_response.py +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_three_dep_coverage_response.py @@ -26,18 +26,12 @@ class PointCloudThreeDepCoverageResponse: Empty when no lidar covers the domain. estimated_point_count (int): Approximate total number of points a fetch would return, summed across acquisitions. - point_budget (int): Maximum number of points a single fetch may return. Shrink the domain if the estimate - exceeds it. - exceeds_point_budget (bool): Whether the estimate is over `point_budget`. When true, a create request for this - domain is rejected, so check this before committing to a fetch. """ available: bool coverage_fraction: float datasets: list[ThreeDepDatasetCoverage] estimated_point_count: int - point_budget: int - exceeds_point_budget: bool additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -52,10 +46,6 @@ def to_dict(self) -> dict[str, Any]: estimated_point_count = self.estimated_point_count - point_budget = self.point_budget - - exceeds_point_budget = self.exceeds_point_budget - field_dict: dict[str, Any] = {} field_dict.update(self.additional_properties) field_dict.update( @@ -64,8 +54,6 @@ def to_dict(self) -> dict[str, Any]: "coverage_fraction": coverage_fraction, "datasets": datasets, "estimated_point_count": estimated_point_count, - "point_budget": point_budget, - "exceeds_point_budget": exceeds_point_budget, } ) @@ -89,17 +77,11 @@ def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: estimated_point_count = d.pop("estimated_point_count") - point_budget = d.pop("point_budget") - - exceeds_point_budget = d.pop("exceeds_point_budget") - point_cloud_three_dep_coverage_response = cls( available=available, coverage_fraction=coverage_fraction, datasets=datasets, estimated_point_count=estimated_point_count, - point_budget=point_budget, - exceeds_point_budget=exceeds_point_budget, ) point_cloud_three_dep_coverage_response.additional_properties = d diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response.py b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response.py new file mode 100644 index 0000000..9b4acb3 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response.py @@ -0,0 +1,204 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import TYPE_CHECKING, Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +if TYPE_CHECKING: + from ..models.point_cloud_tile_data_response_columns import ( + PointCloudTileDataResponseColumns, + ) + from ..models.point_cloud_tile_data_response_data import ( + PointCloudTileDataResponseData, + ) + + +T = TypeVar("T", bound="PointCloudTileDataResponse") + + +@_attrs_define +class PointCloudTileDataResponse: + """Columnar JSON values for one point-cloud tile selection. + + Attributes: + tile_x (int): Requested horizontal tile index. + tile_y (int): Requested vertical tile index. + bounds (list[float]): Horizontal tile bounds as `[min_x, min_y, max_x, max_y]` in the point cloud's CRS. + lod (int): Inclusive LOD ceiling used for this response. The response contains stored levels `0` through this + value. + classes (list[int] | None): Sorted ASPRS classes retained by the request, or null when no classification filter + was applied. + scales (list[float]): Coordinate scales in X/Y/Z order. Decode coordinate axis `i` with `stored_integer * + scales[i] + offsets[i]`. + offsets (list[float]): Coordinate offsets in X/Y/Z order. + columns (PointCloudTileDataResponseColumns): Returned column names mapped to their NumPy-compatible dtypes. Only + requested columns are present. + data (PointCloudTileDataResponseData): Columnar point values. Every array has equal length, and values at the + same array index describe the same point. X/Y/Z values are stored integers decoded with `scales` and `offsets`. + """ + + tile_x: int + tile_y: int + bounds: list[float] + lod: int + classes: list[int] | None + scales: list[float] + offsets: list[float] + columns: PointCloudTileDataResponseColumns + data: PointCloudTileDataResponseData + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + tile_x = self.tile_x + + tile_y = self.tile_y + + bounds = [] + for bounds_item_data in self.bounds: + bounds_item: float + bounds_item = bounds_item_data + bounds.append(bounds_item) + + lod = self.lod + + classes: list[int] | None + if isinstance(self.classes, list): + classes = self.classes + + else: + classes = self.classes + + scales = [] + for scales_item_data in self.scales: + scales_item: float + scales_item = scales_item_data + scales.append(scales_item) + + offsets = [] + for offsets_item_data in self.offsets: + offsets_item: float + offsets_item = offsets_item_data + offsets.append(offsets_item) + + columns = self.columns.to_dict() + + data = self.data.to_dict() + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "tile_x": tile_x, + "tile_y": tile_y, + "bounds": bounds, + "lod": lod, + "classes": classes, + "scales": scales, + "offsets": offsets, + "columns": columns, + "data": data, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + from ..models.point_cloud_tile_data_response_columns import ( + PointCloudTileDataResponseColumns, + ) + from ..models.point_cloud_tile_data_response_data import ( + PointCloudTileDataResponseData, + ) + + d = dict(src_dict) + tile_x = d.pop("tile_x") + + tile_y = d.pop("tile_y") + + bounds = [] + _bounds = d.pop("bounds") + for bounds_item_data in _bounds: + + def _parse_bounds_item(data: object) -> float: + return cast(float, data) + + bounds_item = _parse_bounds_item(bounds_item_data) + + bounds.append(bounds_item) + + lod = d.pop("lod") + + def _parse_classes(data: object) -> list[int] | None: + if data is None: + return data + try: + if not isinstance(data, list): + raise TypeError() + classes_type_0 = cast(list[int], data) + + return classes_type_0 + except (TypeError, ValueError, AttributeError, KeyError): + pass + return cast(list[int] | None, data) + + classes = _parse_classes(d.pop("classes")) + + scales = [] + _scales = d.pop("scales") + for scales_item_data in _scales: + + def _parse_scales_item(data: object) -> float: + return cast(float, data) + + scales_item = _parse_scales_item(scales_item_data) + + scales.append(scales_item) + + offsets = [] + _offsets = d.pop("offsets") + for offsets_item_data in _offsets: + + def _parse_offsets_item(data: object) -> float: + return cast(float, data) + + offsets_item = _parse_offsets_item(offsets_item_data) + + offsets.append(offsets_item) + + columns = PointCloudTileDataResponseColumns.from_dict(d.pop("columns")) + + data = PointCloudTileDataResponseData.from_dict(d.pop("data")) + + point_cloud_tile_data_response = cls( + tile_x=tile_x, + tile_y=tile_y, + bounds=bounds, + lod=lod, + classes=classes, + scales=scales, + offsets=offsets, + columns=columns, + data=data, + ) + + point_cloud_tile_data_response.additional_properties = d + return point_cloud_tile_data_response + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_columns.py b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_columns.py new file mode 100644 index 0000000..77765b0 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_columns.py @@ -0,0 +1,47 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PointCloudTileDataResponseColumns") + + +@_attrs_define +class PointCloudTileDataResponseColumns: + """Returned column names mapped to their NumPy-compatible dtypes. Only requested columns are present.""" + + additional_properties: dict[str, str] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + point_cloud_tile_data_response_columns = cls() + + point_cloud_tile_data_response_columns.additional_properties = d + return point_cloud_tile_data_response_columns + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> str: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: str) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_data.py b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_data.py new file mode 100644 index 0000000..5380735 --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_data_response_data.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PointCloudTileDataResponseData") + + +@_attrs_define +class PointCloudTileDataResponseData: + """Columnar point values. Every array has equal length, and values at the same array index describe the same point. + X/Y/Z values are stored integers decoded with `scales` and `offsets`. + + """ + + additional_properties: dict[str, list[int]] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + + field_dict: dict[str, Any] = {} + for prop_name, prop in self.additional_properties.items(): + field_dict[prop_name] = prop + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + point_cloud_tile_data_response_data = cls() + + additional_properties = {} + for prop_name, prop_dict in d.items(): + additional_property = cast(list[int], prop_dict) + + additional_properties[prop_name] = additional_property + + point_cloud_tile_data_response_data.additional_properties = ( + additional_properties + ) + return point_cloud_tile_data_response_data + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> list[int]: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: list[int]) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/point_cloud_tile_metadata.py b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_metadata.py new file mode 100644 index 0000000..9b30bca --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/point_cloud_tile_metadata.py @@ -0,0 +1,104 @@ +from __future__ import annotations + +from collections.abc import Mapping +from typing import Any, Self, TypeVar, cast + +from attrs import define as _attrs_define +from attrs import field as _attrs_field + +T = TypeVar("T", bound="PointCloudTileMetadata") + + +@_attrs_define +class PointCloudTileMetadata: + """Location and cumulative LOD costs for one occupied point-cloud tile. + + Attributes: + tile_x (int): Horizontal tile index. Pass this value as `tile_x` to a point-cloud data endpoint. Negative + indices are valid for boundary points that fall just west of the tiling origin. + tile_y (int): Vertical tile index. Pass this value as `tile_y` to a point-cloud data endpoint. Negative indices + are valid. + bounds (list[float]): Horizontal tile bounds as `[min_x, min_y, max_x, max_y]` in the metadata response's `crs`. + A boundary tile can extend beyond the point cloud's overall bounds. + points_by_lod (list[int]): Cumulative point counts by LOD. Element `k` is the exact number of rows returned by + `lod=k` before optional classification filtering. Counts never decrease; the final value is the complete tile. + Repeated values are valid for sparse tiles. + """ + + tile_x: int + tile_y: int + bounds: list[float] + points_by_lod: list[int] + additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) + + def to_dict(self) -> dict[str, Any]: + tile_x = self.tile_x + + tile_y = self.tile_y + + bounds = [] + for bounds_item_data in self.bounds: + bounds_item: float + bounds_item = bounds_item_data + bounds.append(bounds_item) + + points_by_lod = self.points_by_lod + + field_dict: dict[str, Any] = {} + field_dict.update(self.additional_properties) + field_dict.update( + { + "tile_x": tile_x, + "tile_y": tile_y, + "bounds": bounds, + "points_by_lod": points_by_lod, + } + ) + + return field_dict + + @classmethod + def from_dict(cls, src_dict: Mapping[str, Any]) -> Self: + d = dict(src_dict) + tile_x = d.pop("tile_x") + + tile_y = d.pop("tile_y") + + bounds = [] + _bounds = d.pop("bounds") + for bounds_item_data in _bounds: + + def _parse_bounds_item(data: object) -> float: + return cast(float, data) + + bounds_item = _parse_bounds_item(bounds_item_data) + + bounds.append(bounds_item) + + points_by_lod = cast(list[int], d.pop("points_by_lod")) + + point_cloud_tile_metadata = cls( + tile_x=tile_x, + tile_y=tile_y, + bounds=bounds, + points_by_lod=points_by_lod, + ) + + point_cloud_tile_metadata.additional_properties = d + return point_cloud_tile_metadata + + @property + def additional_keys(self) -> list[str]: + return list(self.additional_properties.keys()) + + def __getitem__(self, key: str) -> Any: + return self.additional_properties[key] + + def __setitem__(self, key: str, value: Any) -> None: + self.additional_properties[key] = value + + def __delitem__(self, key: str) -> None: + del self.additional_properties[key] + + def __contains__(self, key: str) -> bool: + return key in self.additional_properties diff --git a/fastfuels_sdk/v2/client_library/models/relative_elevation.py b/fastfuels_sdk/v2/client_library/models/relative_elevation.py new file mode 100644 index 0000000..6bd39cb --- /dev/null +++ b/fastfuels_sdk/v2/client_library/models/relative_elevation.py @@ -0,0 +1,10 @@ +from enum import Enum + + +class RelativeElevation(str, Enum): + ABOVE = "above" + BELOW = "below" + NEAR = "near" + + def __str__(self) -> str: + return str(self.value) diff --git a/fastfuels_sdk/v2/client_library/models/sparse_grid_data.py b/fastfuels_sdk/v2/client_library/models/sparse_grid_data.py index 00ad40e..118190d 100644 --- a/fastfuels_sdk/v2/client_library/models/sparse_grid_data.py +++ b/fastfuels_sdk/v2/client_library/models/sparse_grid_data.py @@ -20,15 +20,17 @@ class SparseGridData: """ Attributes: format_ (Literal['sparse']): - fill_value (float | int | None): + fill_value (float | int | None): The band's fill value; cells holding it are omitted from `indices`. `null` when + the fill is nodata (a float band's NaN) or the band declares none. indices (list[int]): - values (list[float | int]): + values (list[float | int | None]): Values of the cells named by `indices`. `null` marks a cell with no data, + which only arises when the band declares no fill value and so every cell is listed. """ format_: Literal["sparse"] fill_value: float | int | None indices: list[int] - values: list[float | int] + values: list[float | int | None] additional_properties: dict[str, Any] = _attrs_field(init=False, factory=dict) def to_dict(self) -> dict[str, Any]: @@ -41,7 +43,7 @@ def to_dict(self) -> dict[str, Any]: values = [] for values_item_data in self.values: - values_item: float | int + values_item: float | int | None values_item = values_item_data values.append(values_item) @@ -78,8 +80,10 @@ def _parse_fill_value(data: object) -> float | int | None: _values = d.pop("values") for values_item_data in _values: - def _parse_values_item(data: object) -> float | int: - return cast(float | int, data) + def _parse_values_item(data: object) -> float | int | None: + if data is None: + return data + return cast(float | int | None, data) values_item = _parse_values_item(values_item_data) diff --git a/fastfuels_sdk/v2/v2_api_design.md b/fastfuels_sdk/v2/v2_api_design.md index b02b076..99fa6c6 100644 --- a/fastfuels_sdk/v2/v2_api_design.md +++ b/fastfuels_sdk/v2/v2_api_design.md @@ -292,7 +292,7 @@ operations have no SDK surface. Ordered by what unblocks a user workflow. - **3DEP point clouds** — `ff.point_clouds.create_point_cloud_from_3dep( domain, datasets=)` + `ff.point_clouds.check_3dep_coverage(domain)` (returns `available`, `coverage_fraction`, `estimated_point_count`, - `point_budget`, `exceeds_point_budget`, per-acquisition `datasets`). + per-acquisition `datasets`). Mirrors the existing `grids.check_3dep_coverage` pre-flight pattern; this is the first non-upload point cloud source. - **Point cloud → CHM** — `ff.grids.create_canopy_height_grid_from_point_cloud( diff --git a/tests/v2/test_point_clouds.py b/tests/v2/test_point_clouds.py index 6de60a7..45201af 100644 --- a/tests/v2/test_point_clouds.py +++ b/tests/v2/test_point_clouds.py @@ -94,8 +94,6 @@ def test_coverage_preflight(self, covered_3dep_domain): assert coverage.available is True assert coverage.coverage_fraction == pytest.approx(1.0, abs=1e-3) assert coverage.estimated_point_count > 0 - assert coverage.point_budget > 0 - assert coverage.exceeds_point_budget is False assert coverage.datasets def test_create_with_pinned_dataset(self, covered_3dep_domain): From 788ce8b762f5990955ac4f8f5e01b4903d535626 Mon Sep 17 00:00:00 2001 From: amarcozzi Date: Wed, 26 Aug 2026 10:20:51 -0600 Subject: [PATCH 2/2] Ignore the .nodeterm/ canvas directory --- .gitignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitignore b/.gitignore index 1d4f1ab..df4713c 100644 --- a/.gitignore +++ b/.gitignore @@ -148,3 +148,6 @@ tests/tmp/ #Code workspace *.code-workspace .run/ + +# nodeterm canvas +.nodeterm/