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

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion services/griddle/griddle/dispatch.py
Original file line number Diff line number Diff line change
Expand Up @@ -146,6 +146,7 @@ def handle_landfire(
remove_non_burnable = source.get("remove_non_burnable")
return landfire.fetch_fbfm13(
domain_gdf,
progress,
version,
remove_non_burnable=remove_non_burnable,
extent_buffer_cells=extent_buffer_cells,
Expand All @@ -159,20 +160,21 @@ def handle_landfire(
season = source.get("season")
return landfire.fetch_fbfm40(
domain_gdf,
progress,
version,
remove_non_burnable=remove_non_burnable,
extent_buffer_cells=extent_buffer_cells,
alignment=alignment,
target_grid_doc=target_grid_doc,
season=season,
progress=progress,
)
case "fccs":
version = source.get("version", LANDFIRE_VERSIONS["fccs"]["default"])
progress(f"Fetching LANDFIRE {product} v{version}...", 10)
remove_bare_ground = source.get("remove_bare_ground", False)
return landfire.fetch_fccs(
domain_gdf,
progress,
version,
remove_bare_ground=remove_bare_ground,
extent_buffer_cells=extent_buffer_cells,
Expand Down
85 changes: 61 additions & 24 deletions services/griddle/griddle/handlers/landfire.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ def _landfire_cog_url(product: str, version: str) -> str:
return f"gs://{RASTERS_BUCKET}/LF{version}_{product}_CONUS.tif"


def _needs_lfps(product: str, version: str) -> bool:
"""Whether `version` is only served on-demand via LFPS, not as a staged COG."""
return version in LANDFIRE_VERSIONS[product].get("lfps_available", ())


def _fetch_landfire_raster(
roi: gpd.GeoDataFrame,
url: str,
Expand Down Expand Up @@ -140,6 +145,7 @@ def _to_dataset(variables: dict[str, DataArray]) -> xr.Dataset:

def fetch_fbfm13(
roi: gpd.GeoDataFrame,
progress: Callable[[str, int | None], None],
version: str = LANDFIRE_VERSIONS["fbfm13"]["default"],
remove_non_burnable: list[str] | None = None,
extent_buffer_cells: int = 0,
Expand All @@ -159,23 +165,38 @@ def fetch_fbfm13(
``{"target": "domain"}`` when omitted.
target_grid_doc: Loaded grid document used when
``alignment["target"] == "grid"``.
progress: Progress callback, only used when `version` is fetched via
LFPS (submit/wait/download reports through it).

Returns:
Dataset with a single "fbfm13" variable (int16 categorical codes,
1-13 plus non-burnable 91/92/93/98/99)
"""
product = "fbfm13"
validate_landfire_version(product, version)
alignment = alignment or {"target": "domain"}
url = _landfire_cog_url(product, version)
data = _fetch_landfire_raster(
roi,
url,
extent_buffer_cells,
alignment,
target_grid_doc,
is_categorical=True,
)
product = "fbfm13"
if _needs_lfps(product, version):
source = landfire_lfps.fetch_lfps(
roi,
product,
version,
alignment,
target_grid_doc,
extent_buffer_cells,
progress,
)
else:
validate_landfire_version(product, version)
source = nullcontext(_landfire_cog_url(product, version))

with source as url:
data = _fetch_landfire_raster(
roi,
url,
extent_buffer_cells,
alignment,
target_grid_doc,
is_categorical=True,
)

if remove_non_burnable:
non_burnable_keys = [NB_CODE_MAP[code] for code in remove_non_burnable]
Expand All @@ -187,12 +208,12 @@ def fetch_fbfm13(

def fetch_fbfm40(
roi: gpd.GeoDataFrame,
progress: Callable[[str, int | None], None],
version: str = LANDFIRE_VERSIONS["fbfm40"]["default"],
remove_non_burnable: list[str] | None = None,
extent_buffer_cells: int = 0,
alignment: dict | None = None,
target_grid_doc: dict | None = None,
progress: Callable[[str, int | None], None] | None = None,
season: str | None = None,
) -> xr.Dataset:
"""Fetch LANDFIRE FBFM40 fuel model codes.
Expand All @@ -218,7 +239,7 @@ def fetch_fbfm40(
"""
alignment = alignment or {"target": "domain"}
product = "fbfm40"
if season is not None:
if season is not None or _needs_lfps(product, version):
source = landfire_lfps.fetch_lfps(
roi,
product,
Expand Down Expand Up @@ -254,6 +275,7 @@ def fetch_fbfm40(

def fetch_fccs(
roi: gpd.GeoDataFrame,
progress: Callable[[str, int | None], None],
version: str = LANDFIRE_VERSIONS["fccs"]["default"],
remove_bare_ground: bool = False,
extent_buffer_cells: int = 0,
Expand All @@ -273,22 +295,37 @@ def fetch_fccs(
``{"target": "domain"}`` when omitted.
target_grid_doc: Loaded grid document used when
``alignment["target"] == "grid"``.
progress: Progress callback, only used when `version` is fetched via
LFPS (submit/wait/download reports through it).

Returns:
Dataset with a single "fccs" variable (int32 categorical codes)
"""
product = "fccs"
validate_landfire_version(product, version)
alignment = alignment or {"target": "domain"}
url = _landfire_cog_url(product, version)
data = _fetch_landfire_raster(
roi,
url,
extent_buffer_cells,
alignment,
target_grid_doc,
is_categorical=True,
)
product = "fccs"
if _needs_lfps(product, version):
source = landfire_lfps.fetch_lfps(
roi,
product,
version,
alignment,
target_grid_doc,
extent_buffer_cells,
progress,
)
else:
validate_landfire_version(product, version)
source = nullcontext(_landfire_cog_url(product, version))

with source as url:
data = _fetch_landfire_raster(
roi,
url,
extent_buffer_cells,
alignment,
target_grid_doc,
is_categorical=True,
)

if remove_bare_ground:
filtered = _remove_non_burnable_blocks(data.values, [0])
Expand Down
17 changes: 14 additions & 3 deletions services/griddle/tests/handlers/test_alignment_handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,9 +81,11 @@ class TestLandfireAlignmentDomain:
def test_domain_target_passes_destination_kwargs(self, mock_cls):
mock_cls.return_value = _mock_raster()
roi = _domain_gdf()
progress = MagicMock()

fetch_fbfm40(
roi,
progress,
version="2024",
alignment={"target": "domain", "resolution": 2.0},
)
Expand All @@ -103,8 +105,9 @@ def test_domain_target_passes_destination_kwargs(self, mock_cls):
def test_domain_target_default_uses_source_native_resolution(self, mock_cls):
mock_cls.return_value = _mock_raster(source_resolution=30.0)
roi = _domain_gdf()
progress = MagicMock()

fetch_fbfm40(roi, version="2024", alignment={"target": "domain"})
fetch_fbfm40(roi, progress, version="2024", alignment={"target": "domain"})

kwargs = mock_cls.return_value.extract_window.call_args[1]
transform = kwargs["destination_transform"]
Expand All @@ -122,8 +125,9 @@ def test_domain_target_default_uses_source_native_in_roi_crs_units(self, mock_cl
native_in_roi_resolution=30.0,
)
roi = _domain_gdf()
progress = MagicMock()

fetch_fbfm40(roi, version="2024", alignment={"target": "domain"})
fetch_fbfm40(roi, progress, version="2024", alignment={"target": "domain"})

kwargs = mock_cls.return_value.extract_window.call_args[1]
transform = kwargs["destination_transform"]
Expand All @@ -140,8 +144,9 @@ class TestLandfireAlignmentNative:
def test_native_target_no_resolution_passes_no_destination(self, mock_cls):
mock_cls.return_value = _mock_raster()
roi = _domain_gdf()
progress = MagicMock()

fetch_fbfm40(roi, version="2024", alignment={"target": "native"})
fetch_fbfm40(roi, progress, version="2024", alignment={"target": "native"})

kwargs = mock_cls.return_value.extract_window.call_args[1]
assert "destination_transform" not in kwargs
Expand All @@ -154,9 +159,11 @@ def test_native_target_no_resolution_passes_no_destination(self, mock_cls):
def test_native_target_with_resolution_passes_resolution(self, mock_cls):
mock_cls.return_value = _mock_raster()
roi = _domain_gdf()
progress = MagicMock()

fetch_fbfm40(
roi,
progress,
version="2024",
alignment={"target": "native", "resolution": 5.0},
)
Expand All @@ -173,6 +180,7 @@ class TestLandfireAlignmentGrid:
def test_grid_target_exact_match(self, mock_cls):
mock_cls.return_value = _mock_raster()
roi = _domain_gdf()
progress = MagicMock()
target_grid_doc = {
"georeference": {
"crs": "EPSG:32611",
Expand All @@ -183,6 +191,7 @@ def test_grid_target_exact_match(self, mock_cls):

fetch_fbfm40(
roi,
progress,
version="2024",
alignment={"target": "grid", "grid_id": "x"},
target_grid_doc=target_grid_doc,
Expand All @@ -199,6 +208,7 @@ def test_grid_target_exact_match(self, mock_cls):
def test_grid_target_with_resolution_recomputes_shape(self, mock_cls):
mock_cls.return_value = _mock_raster()
roi = _domain_gdf()
progress = MagicMock()
# Target grid: 30m cells, 10x10, anchored lower-left at (720100, 5190200).
target_grid_doc = {
"georeference": {
Expand All @@ -210,6 +220,7 @@ def test_grid_target_with_resolution_recomputes_shape(self, mock_cls):

fetch_fbfm40(
roi,
progress,
version="2024",
alignment={"target": "grid", "grid_id": "x", "resolution": 1.0},
target_grid_doc=target_grid_doc,
Expand Down
Loading
Loading