Skip to content

ras2fim-2D flood inundation mapping (optional)

RasFim maps water-surface elevation (WSE) profiles defined on HEC-RAS 2D cells to depth and WSE rasters with the ras2fim-2D Step 05 method. It maps a batch of profiles against one shared terrain, accepts a bounding box or a polygon area of interest (AOI), and returns a product manifest DataFrame.

The feature is optional. Install the Python extra and the matching Linux x86-64 or Windows x86-64 engine wheel into the same environment:

Bash
pip install "ras-commander[fim]"
pip install /path/to/ras2fim_engine-0.1.0-py3-none-<platform>.whl
ras2fim-batch method

The repository wheels/ folder includes both tested wheels, checksums, and BUILD.json provenance. See wheels/README.md for the exact platform filenames. Wheels are rebuilt by the Engine wheels workflow and available as its platform artifacts with SHA-256 checksums. Linux wheels target glibc 2.28 or newer. The wheel installs ras2fim-batch on the environment's PATH; no Rust compiler or system GDAL is needed for the native engine. The Python GIS stack remains required. Source builds are also supported. The workflow does not publish to PyPI. Installed-binary synthetic mapping tests run on both platforms; retained real-model agreement evidence remains Linux-only.

Method

The method comes from ras2fim-2D Step 05 (Andy Carter, PE, Center for Water and the Environment, The University of Texas at Austin; BSD 3-Clause). For each profile:

  1. Wet cells are the cells whose profile WSE is not NaN.
  2. Samples are the wet cell centres in ascending cell_idx order, followed by the wet-cell polygon vertices outside the convex hull of those centres. Vertices that round to the same 0.01 model units are merged with their mean WSE.
  3. WSE is interpolated linearly on the SciPy Delaunay triangulation of the samples over np.arange axes that span the wet-cell union at the terrain pixel width, then masked to that union.
  4. Depth is WSE minus the terrain sampled at each pixel centre; negative depths become NaN. WSE is then terrain plus depth where depth is finite.
  5. Depth and WSE are reprojected with nearest-neighbour resampling to the output CRS on a grid whose origin is snapped to a multiple of the output resolution.
  6. The floodplain polygon covers pixels with 0 < depth <= 10000 in the model vertical units.

Not RAS Mapper stored maps

This method differs from RAS Mapper stored maps (RasProcess.store_maps()), which use HEC-RAS's own rendering of the hydraulic results. The two produce different rasters; do not substitute one for the other.

Engine

The interpolation, terrain, and reprojection arithmetic runs in the ras2fim-batch engine. Python prepares the global samples, the SciPy Delaunay topology, the query axes, the wet mask, and the explicit final grid, then validates each engine output before writing products. The engine source and build instructions are in rust/step05/ of the RAS Commander repository. The tested scope is x86-64 Linux; a Windows engine build is not qualified.

RasFim resolves the engine in this order:

  1. the engine_path= argument;
  2. the RAS_COMMANDER_FIM_ENGINE environment variable;
  3. ras2fim-batch on PATH.

A missing engine raises FimEngineNotFoundError. RAS Commander never builds, downloads, or installs the engine, and it has no Python fallback for this method.

Python
from ras_commander import RasFim

info = RasFim.get_engine_info("/opt/ras2fim/ras2fim-batch")
print(info["method"])

Map a ras2fim-2D catalog

A ras2fim-2D hydraulic-results GeoPackage stores one polygon per cell in layer 02_cells_wsel_ar, with cell_idx and one wsel_* column per flow profile. Cell centres come from the plan HDF through HdfMesh.get_mesh_cell_points().

Python
from ras_commander import RasFim

manifest = RasFim.map_ras2fim_results(
    "results.gpkg",
    "model.p01.hdf",
    "terrain.tif",
    "fim_out",                      # new or empty folder
    engine_path="/opt/ras2fim/ras2fim-batch",
    output_crs="EPSG:3857",
    output_resolution=3.0,
    threads=8,
)
print(manifest[["profile", "status", "flooded_pixels", "depth_tif"]])

Map cells from a plan HDF

get_cells_from_plan_hdf() builds a one-profile cell table from the plan's maximum WSE and maximum depth. A cell is wet when its maximum depth exceeds min_depth. Frames from several plans on the same geometry can be joined on cell_idx to map them as one batch.

Python
cells = RasFim.get_cells_from_plan_hdf("model.p01.hdf", profile_name="wsel_max")
manifest = RasFim.map_profiles(
    cells, "terrain.tif", "fim_max",
    hdf_path="model.p01.hdf",
    profiles=["wsel_max"],
)

Any GeoDataFrame with cell_idx, cell polygons, and numeric profile columns in the terrain CRS can be mapped the same way. Pass centers= (a GeoDataFrame of cell_idx and centre points) instead of hdf_path= when the centres come from elsewhere. When an HDF has more than one 2D flow area, pass mesh_name=.

Area of interest

Pass either bbox=(xmin, ymin, xmax, ymax) or polygon=. A polygon can be a Shapely Polygon or MultiPolygon, a GeoDataFrame, or a vector file (use polygon_layer= for a multi-layer file). Holes and multiple parts are kept, and file features are unioned. A bbox without aoi_crs is in output_crs; a Shapely polygon requires aoi_crs; a file or frame CRS that conflicts with aoi_crs is an error.

Python
manifest = RasFim.map_ras2fim_results(
    "results.gpkg", "model.p01.hdf", "terrain.tif", "fim_reach",
    polygon="reach.gpkg", polygon_layer="reach",
)

The AOI selects pixels of the final output grid whose centres lie inside it. It never changes sample preparation, triangulation, query axes, the wet mask, terrain sampling, the grid origin, or the reprojection, so an AOI product is the crop of the full product. Products cover the AOI window; pixels outside a polygon are NaN.

Wet-cell discovery, sample preparation, and triangulation always use every wet cell of the profile, and the engine's lookup warp spans the full output grid. AOI runtime therefore does not shrink in proportion to the AOI area.

Terrain-anchored grids and coplanar samples (opt-in)

Two keywords depart from the upstream Step 05 recipe. Both default to the upstream behaviour and are recorded in fim_manifest.json under options.

grid_anchor="terrain" places the query axes and the final grid on the terrain pixel lattice. Upstream starts the query axes at the wet-union bounds and snaps the final origin to a multiple of output_resolution, so the terrain pixel each output pixel takes depends on the phase of the wet-union bounds. On one 1 m terrain whose origin is not a multiple of the pixel size, that put every output pixel of a reach one terrain column away from the terrain pixel it lies on, and moved results when the cell set changed. With the anchor, each output pixel takes the terrain pixel it lies on, products stack with the terrain without resampling, and equal lattice positions give bit-equal transforms. The anchor needs a north-up terrain with square pixels, output_crs equal to the results CRS, and output_resolution equal to the terrain pixel size; anything else raises FimUnsupportedError. Grid edges are integer lattice positions, and the grid is clamped to the terrain extent where the wet cells reach past it (those pixels would only read terrain nodata). Samples and triangulation are unchanged.

coplanar="ignore" maps a profile whose samples include points Qhull reports as coplanar or duplicate (for example two perimeter vertices closer than the 0.01-unit merge) without those points, as upstream griddata does. The default "error" fails such a profile. The count per profile is in the manifest's coplanar_samples.

Python
manifest = RasFim.map_profiles(
    cells, "terrain.tif", "fim_reach", centers=centers,
    output_crs=cells.crs, output_resolution=terrain_pixel_size,
    polygon=reach_polygon, aoi_crs=cells.crs,
    grid_anchor="terrain", coplanar="ignore",
)

Batches, resources, and outputs

Keyword Meaning
profiles Profile columns; default all columns starting with profile_prefix ("wsel")
profiles_per_request Profiles prepared and sent per engine call (default 4)
max_inflight_profiles Profiles the engine evaluates concurrently in one call (default 1); they share one decoded-terrain cache and the threads pool
threads Engine thread pool; default min(cpu_count - 1, 16)
max_cache_mib Engine decoded-terrain cache budget (default 64)
max_profile_mib Engine per-profile planned working-set budget (default 1024); a profile over budget fails before allocation
nudge_method "lookup" (default) or "full_warp"
keep_work Keep engine requests and raw outputs in output_folder/.fim_work

The engine budgets are admission checks on the engine's planned allocations. They are not a resident-memory limit for the engine or the Python process. Each profile in flight counts against max_profile_mib.

Each profile writes the ras2fim-2D product names into output_folder:

  • 09_<profile>_depth_nudge.tif and 10_<profile>_wsel_nudge.tif: float32, LZW-compressed, NaN nodata;
  • 11_<profile>_floodplain_ar.gpkg: one floodplain (Multi)Polygon, written when at least one pixel is flooded.

fim_manifest.json records the engine path and method record, the options, the AOI, the input file identities, engine reports, and one entry per profile. The returned DataFrame has the same rows. Its columns are declared as fim_product_manifest in ras_commander/schemas.py, which also feeds the published machine-readable DataFrame surface:

Column Meaning
profile, status Profile name; complete, no_flooded_pixels, empty_aoi, failed, or not_run
wet_cells, samples Wet cells and interpolation samples used for the global preparation
depth_tif, wse_tif, floodplain_gpkg Product paths (None when not written)
col_off, row_off, width, height, transform, crs Written window on the full output grid
depth_finite_pixels, flooded_pixels Finite depth pixels and floodplain pixels
error Diagnostic for failed or not_run rows

Errors and inputs

  • output_folder must be new or empty; no existing file is replaced.
  • Profile names use letters, digits, underscores, dots, or hyphens and must be unique ignoring case. Caller centres require unique integer cell_idx values and non-empty point geometries.
  • Input files are opened read-only, and caller DataFrames are not modified. Their size and modification time are checked after mapping.
  • The hydraulic results and the terrain must share a CRS; the output CRS needs an EPSG code.
  • Coplanar samples, infinite WSE, grids that extend beyond the terrain, and similar inputs raise FimUnsupportedError or mark the profile failed.
  • An engine exit other than 0 or 2, or an output that is missing, sized incorrectly, has inconsistent finite counts, or has values outside a polygon mask, fails the profile or raises FimEngineError.
  • Typed errors isolate a failed profile from the others in its batch; they do not sandbox the engine process or bound its operating-system resources.
  • After writing the manifest, map_profiles() raises FimMappingError when any profile failed; error.manifest holds the DataFrame. Pass raise_on_failure=False to return it instead.

The FIM engine comparison records the agreement checks and the measurement scope. Its throughput observations come from the ras2fim-2D adapter, not from RasFim, and do not establish a general speedup.

Complete source reference

Static namespace for the optional ras2fim-2D FIM mapping method.

All methods are static; do not instantiate.

find_engine staticmethod

Python
find_engine(engine_path: Optional[PathLike] = None) -> Path

Return the path of the ras2fim-batch engine executable.

Resolution order: engine_path, then the RAS_COMMANDER_FIM_ENGINE environment variable, then ras2fim-batch on PATH.

Raises:

Type Description
FimEngineNotFoundError

No executable was found. The engine is never built or installed automatically.

get_engine_info staticmethod

Python
get_engine_info(engine_path: Optional[PathLike] = None) -> Dict[str, Any]

Return the engine path and its method record (ras2fim-batch method).

get_cells_from_results_gpkg staticmethod

Python
get_cells_from_results_gpkg(results_gpkg: PathLike, layer: str = RAS2FIM_CELLS_LAYER) -> 'gpd.GeoDataFrame'

Read a ras2fim-2D hydraulic-results cell layer.

The ras2fim-2D layer 02_cells_wsel_ar holds one polygon per cell with cell_idx and WSE profile columns (wsel_*), NaN where the cell is dry for that profile. The file is opened read-only.

Returns:

Type Description
GeoDataFrame

The layer as stored.

get_cell_centers staticmethod

Python
get_cell_centers(hdf_path: PathLike, cell_ids: Optional[Iterable[int]] = None, *, mesh_name: Optional[str] = None) -> 'gpd.GeoDataFrame'

Return HEC-RAS 2D cell centres for the FIM method.

Centres are the computational cell centres (HdfMesh.get_mesh_cell_points), not polygon centroids.

Parameters:

Name Type Description Default
hdf_path PathLike

Plan or geometry HDF that contains the 2D mesh geometry.

required
cell_ids Optional[Iterable[int]]

Zero-based cell indices to return. None returns every cell. Duplicates are removed and the result is sorted by cell_idx.

None
mesh_name Optional[str]

2D flow area name. Required when the HDF has more than one.

None

Returns:

Type Description
GeoDataFrame

Cell indices and point geometries.

Raises:

Type Description
FimUnsupportedError

No mesh, an ambiguous or unknown mesh, or a requested cell that is not in the mesh.

get_cells_from_plan_hdf staticmethod

Python
get_cells_from_plan_hdf(hdf_path: PathLike, *, mesh_name: Optional[str] = None, profile_name: str = 'wsel_max', min_depth: float = 0.0) -> 'gpd.GeoDataFrame'

Build a one-profile cell table from a plan HDF's maximum results.

Uses HdfMesh.get_mesh_cell_polygons(strict=True), HdfResultsMesh.get_mesh_max_ws and HdfResultsMesh.get_mesh_max_depth. A cell is wet when its maximum depth is greater than min_depth; dry cells get NaN.

Returns:

Type Description
GeoDataFrame

Cell indices, profile WSE and polygon geometries. Frames from plans on the same geometry can be joined on cell_idx to map a batch of profiles.

resolve_aoi staticmethod

Python
resolve_aoi(bbox: Optional[Sequence[float]] = None, polygon: Any = None, *, polygon_layer: Optional[str] = None, aoi_crs: Any = None, default_crs: Any = None) -> Optional[FimAoi]

Validate a bounding box OR polygon area of interest.

Parameters:

Name Type Description Default
bbox Optional[Sequence[float]]

(xmin, ymin, xmax, ymax). Its CRS is aoi_crs, else default_crs.

None
polygon Any

A Shapely Polygon/MultiPolygon (needs aoi_crs), a GeoDataFrame, or a vector file path (GeoJSON, GeoPackage, ...). Holes and multiple parts are kept; features are unioned.

None
polygon_layer Optional[str]

Layer of a multi-layer polygon file.

None
aoi_crs Any

CRS of the AOI. Must agree with a frame or file CRS.

None
default_crs Any

CRS used for a bbox without aoi_crs.

None

Returns:

Type Description
Optional[FimAoi]

FimAoi, or None when neither bbox nor polygon is given.

Raises:

Type Description
FimAoiError

Both or invalid AOIs, a missing or conflicting CRS, invalid or empty geometry.

prepare_profile staticmethod

Python
prepare_profile(cells, centers, profile: str, pixel_size: float, lattice_origin: Optional[Sequence[float]] = None) -> FimPreparedProfile

Prepare the global interpolation samples and axes for one profile.

This is the preparation map_profiles sends to the engine, exposed for inspection and qualification. It always uses every wet cell. Inputs are not modified. lattice_origin (a terrain pixel corner (x, y)) gives the grid_anchor="terrain" query axes.

map_profiles staticmethod

Python
map_profiles(cells, terrain_path: PathLike, output_folder: PathLike, *, profiles: Optional[Sequence[str]] = None, profile_prefix: str = DEFAULT_PROFILE_PREFIX, centers=None, hdf_path: Optional[PathLike] = None, mesh_name: Optional[str] = None, output_crs: Any = 'EPSG:3857', output_resolution: float = 3.0, bbox: Optional[Sequence[float]] = None, polygon: Any = None, polygon_layer: Optional[str] = None, aoi_crs: Any = None, aoi: Optional[FimAoi] = None, engine_path: Optional[PathLike] = None, threads: Optional[int] = None, profiles_per_request: int = 4, max_inflight_profiles: int = 1, max_cache_mib: int = 64, max_profile_mib: int = 1024, nudge_method: str = 'lookup', grid_anchor: str = 'ras2fim', coplanar: str = 'error', terrain_crs_fnv1a64: Optional[str] = None, write_floodplain: bool = True, keep_work: bool = False, timeout: Optional[float] = None, raise_on_failure: bool = True) -> pd.DataFrame

Map a batch of cell WSE profiles to depth and WSE rasters.

Every profile shares one terrain and one engine session per request of profiles_per_request profiles. Sample preparation, Delaunay topology, query axes and the wet mask always use every wet cell of a profile. An AOI only selects pixels of the final output grid, so an AOI product equals the crop of the full product. The preparation cost therefore does not shrink with the AOI.

Parameters:

Name Type Description Default
cells GeoDataFrame

Cell polygons with cell_idx and WSE profile columns (NaN = dry), in the terrain CRS. Not modified.

required
terrain_path PathLike

Single-band terrain GeoTIFF, same CRS as cells. Its pixel width is the interpolation pixel size.

required
output_folder PathLike

New or empty folder for products and the manifest.

required
profiles Optional[Sequence[str]]

Profile columns to map. Default: columns starting with profile_prefix.

None
centers GeoDataFrame

Unique integer cell_idx and centre points. Give either centers or hdf_path.

None
hdf_path Optional[PathLike]

Plan or geometry HDF; centres are read through :meth:get_cell_centers.

None
mesh_name Optional[str]

2D flow area for hdf_path (required for several).

None
output_crs Any

CRS of the final grid (must have an EPSG code).

'EPSG:3857'
output_resolution float

Final pixel size in output_crs units.

3.0
bbox Optional[Sequence[float]]

Bounding box; without aoi_crs it is in output_crs.

None
polygon Any

Polygon area of interest; see :meth:resolve_aoi.

None
polygon_layer Optional[str]

Layer name for a polygon file.

None
aoi_crs Any

CRS of the area of interest.

None
aoi Optional[FimAoi]

A :class:FimAoi from :meth:resolve_aoi (instead of the bbox/polygon arguments).

None
engine_path Optional[PathLike]

Engine executable; see :meth:find_engine.

None
threads Optional[int]

Engine threads. Default min(cpu_count - 1, 16).

None
profiles_per_request int

Profiles prepared and run per engine call. This bounds the prepared inputs on disk at one time.

4
max_inflight_profiles int

Profiles the engine evaluates concurrently within one call (default 1). Concurrent profiles share one decoded-terrain cache and the threads pool; each counts against max_profile_mib.

1
max_cache_mib int

Engine decoded-terrain cache budget.

64
max_profile_mib int

Engine per-profile working-set budget; a profile whose planned peak exceeds it fails before allocation.

1024
nudge_method str

"lookup" (one full-grid index warp, AOI pixels gathered from it) or "full_warp".

'lookup'
grid_anchor str

"ras2fim" (default, the upstream grids: query axes from the wet-union bounds, final origin snapped to a multiple of output_resolution) or "terrain": both grids on the terrain pixel lattice, so each pixel takes the terrain pixel it lies on and products stack with the terrain. Needs a north-up terrain with square pixels, output_crs equal to the results CRS and output_resolution equal to the terrain pixel size. Not the upstream Step 05 grid.

'ras2fim'
coplanar str

"error" (default) fails a profile whose samples include points Qhull reports as coplanar or duplicate; "ignore" maps it without them, as upstream griddata does, and records the count in the manifest.

'error'
terrain_crs_fnv1a64 Optional[str]

Engine WKT hash to accept when the engine cannot identify the terrain EPSG code.

None
write_floodplain bool

Write product 11.

True
keep_work bool

Keep the engine requests and raw outputs in output_folder/.fim_work for review.

False
timeout Optional[float]

Seconds allowed per engine call (default no limit).

None
raise_on_failure bool

Raise :class:FimMappingError when any profile fails (default). The manifest is written first either way.

True

Returns:

Type Description
DataFrame

DataFrame with one row per profile (fim_product_manifest

DataFrame

schema). status is complete, no_flooded_pixels,

DataFrame

empty_aoi, failed, or not_run (a fatal engine error

DataFrame

stopped the batch sequence). The manifest is also written to

DataFrame

fim_manifest.json before any error is raised.

Raises:

Type Description
FimEngineNotFoundError

No engine executable.

FimEngineError

The engine rejected a request or crashed.

FimUnsupportedError

Input outside the engine contract.

FimAoiError

Invalid area of interest.

FimMappingError

One or more profiles failed.

FileExistsError

output_folder is not empty.

map_ras2fim_results staticmethod

Python
map_ras2fim_results(results_gpkg: PathLike, hdf_path: PathLike, terrain_path: PathLike, output_folder: PathLike, **kwargs) -> pd.DataFrame

Map every wsel_* profile of a ras2fim-2D hydraulic-results GeoPackage.

Reads layer 02_cells_wsel_ar, takes cell centres from hdf_path, and calls :meth:map_profiles with kwargs.

A validated bounding-box or polygon area of interest.

Attributes:

Name Type Description
kind str

"bbox" or "polygon".

geometry Any

Shapely box, Polygon, or MultiPolygon in crs. Holes and multiple parts are kept.

crs Any

:class:pyproj.CRS of geometry.

source str

Human-readable origin, recorded in the product manifest.

describe

Python
describe() -> dict

Return a JSON-serializable summary.

Global interpolation inputs for one WSE profile.

Attributes:

Name Type Description
profile str

Profile (column) name.

x, (y, z)

Sample coordinates and WSE in upstream order (wet centres, then merged hull-exterior vertices).

x_axis, y_axis

Exact np.arange query axes. The effective step can differ from res in the last bits.

xmin, (ymin, res)

Raster-01 origin and pixel size.

wet_union Any

Shapely union of the wet cell polygons.

crs Any

CRS of the hydraulic results (and of all coordinates above).

wet_cell_count int

Number of wet cells (centres) used.

grid_anchor str

"ras2fim" (origin at the wet-union bounds, upstream) or "terrain" (origin snapped down onto the terrain lattice).

lattice_origin Optional[Tuple[float, float]]

Terrain pixel corner of a terrain-anchored profile.

lattice_index Optional[Tuple[int, int]]

Integer (column, row) lattice index of (xmin, ymin) from lattice_origin; edges are always origin + k * res.

shape property

Python
shape: Tuple[int, int]

(rows, cols) of the raster-01 grid.