Skip to content

mapping

Result raster generation for HEC-RAS projects using the RASMapper engine.

Overview

The mapping module generates georeferenced raster files (GeoTIFF) from completed HEC-RAS plan results. With ras-commander 0.99.0 or newer it drives the canonical RasMap.store_all_maps(mode="selected") API, which deploys isolated RasStoreMapHelper.exe processes and preserves the correct water-surface render mode. ras-commander 0.98.2 remains supported through the serial compatibility path.

On Linux, RasProcess.exe runs under Wine. See the Linux/Wine Setup guide.

Supported Map Types

Map Type CLI Flag Default Description
wse --wse/--no-wse On Water Surface Elevation
depth --depth/--no-depth On Depth
velocity --velocity/--no-velocity On Velocity
froude --froude Off Froude Number
shear_stress --shear-stress Off Shear Stress
depth_x_velocity --dv Off Depth x Velocity
depth_x_velocity_sq --dv-sq Off Depth x Velocity²
inundation_boundary --inundation-boundary Off Inundation Boundary (shapefile)
arrival_time --arrival-time Off Arrival Time (hours, whole-simulation)
duration --duration Off Inundation Duration (hours, whole-simulation)
percent_inundated --percent-inundated Off Percent Time Inundated (whole-simulation)

--recession is accepted for compatibility but ignored with a warning — RasMapperLib has no recession map type, and only RasMapperLib-native outputs are produced.

Whole-simulation map types

arrival_time, duration, and percent_inundated are computed over the entire simulation — the --profile option does not apply to them. Their filenames carry the --arrival-depth wet/dry threshold instead of the profile name, e.g. Arrival Time (0.1ft hrs).tif. They work with any ras-commander version: newer versions generate them natively via store_maps(); on older versions ras2cng pre-injects the stored-map entries into the .rasmap (restored afterwards) so the same StoreAllMaps run produces them.

Generating these types causes RasMapperLib to build a PostProcessing.hdf cache that can exceed the plan HDF in size; ras2cng deletes it from the output directory unless --keep-postprocessing is passed.

Local performance policy

The default DEFAULT_LOCAL_MAP_PERFORMANCE preset is tuned for local processing on the 8-core, 31.8 GiB development workstation:

  • memory-aware automatic helper selection (max_workers=None);
  • enforced memory admission with an 8192 MiB / 25 percent reserve;
  • one GDAL thread per helper to prevent nested oversubscription;
  • a 64 MiB GDAL cache cap charged to every helper's memory estimate.

Only independent WSE, Depth, and Velocity products run concurrently. Large terrain estimates and products that require shared ordered state automatically use one helper. On the Spring River fixture, the measured estimate is about 11.2 GiB per helper, so this 31.8 GiB machine remains serial; a machine with more available memory can admit two or three map helpers without changing the call.

Use --map-workers 1 for a controlled serial comparison, or set the helper ceiling, reserve, and cache explicitly:

ras2cng map model.prj ./maps \
  --map-workers 2 \
  --map-reserve-memory-mb 8192 \
  --map-gdal-cache-mb 64

Python callers can provide the full typed policy without adding a second map function:

from ras_commander import StoreMapPerformanceOptions
from ras2cng import generate_result_maps

results = generate_result_maps(
    "model.prj",
    "maps",
    performance=StoreMapPerformanceOptions(
        max_workers=None,
        reserve_memory_mb=8192,
        gdal_cachemax_mb=64,
    ),
)

Render Mode

The --render-mode option controls how the water surface is rendered to raster grids. This is critical for pixel-perfect output — HEC-RAS 6.x's RasProcess.exe ignores the render mode from the .rasmap file, so ras-commander uses RasStoreMapHelper.exe to set the mode explicitly before generating maps.

Mode Flag Description
horizontal --render-mode horizontal Flat water surface within each mesh cell (default)
sloping --render-mode sloping Interpolated sloping water surface
slopingPretty --render-mode slopingPretty Sloping with depth-weighted face reduction (HEC-RAS 6.4+)

If --render-mode is not specified, the mode is read from the project's .rasmap file (defaults to horizontal if not set).

# Generate maps with sloping render mode
ras2cng map /path/to/project /output/maps --render-mode sloping

# Archive with slopingPretty render mode (requires HEC-RAS 6.4+)
ras2cng archive /path/to/project ./archive/ --results --map --render-mode slopingPretty

Post-Processing Options

  • Minimum depth threshold (--min-depth): Set pixels below a depth threshold to NoData
  • WGS84 reprojection (--wgs84): Reproject output rasters to EPSG:4326 using rasterio
  • Cloud Optimized GeoTIFF (--cog): Convert output to COG using gdal_translate

API Reference

ras2cng.mapping.MapResult dataclass

Result of map generation for a single plan.

Source code in ras2cng/mapping.py
@dataclass
class MapResult:
    """Result of map generation for a single plan."""
    plan_id: str
    plan_number: str
    map_types: dict[str, list[Path]] = field(default_factory=dict)  # {"depth": [Path(...)]}
    output_dir: Path = field(default_factory=lambda: Path("."))
    errors: list[str] = field(default_factory=list)

ras2cng.mapping.generate_result_maps(project_path, output_dir, *, plans=None, profile='Max', wse=True, depth=True, velocity=True, froude=False, shear_stress=False, depth_x_velocity=False, depth_x_velocity_sq=False, inundation_boundary=False, arrival_time=False, duration=False, recession=False, percent_inundated=False, arrival_depth=0.0, terrain_name=None, ras_version=None, rasprocess_path=None, render_mode=None, min_depth=0.0, reproject_wgs84=False, convert_cog=False, timeout=10800, skip_errors=True, keep_postprocessing=False, performance=None)

Generate result rasters for plans in a HEC-RAS project.

Uses the canonical RasMap.store_all_maps(mode="selected") API with RasStoreMapHelper.exe to generate raw TIFs from completed plan HDF files. With ras-commander 0.99.0 or newer, the default is memory-aware local auto parallelism. ras-commander 0.98.2 retains its serial compatibility path.

Parameters:

Name Type Description Default
project_path Path

Path to .prj file or project directory

required
output_dir Path

Directory for output raster files

required
plans Optional[list[str]]

Plan IDs to process (e.g. ["p01", "p02"]). None = all with results

None
profile str

Output profile: "Max", "Min", or timestamp

'Max'
wse bool

Generate Water Surface Elevation rasters

True
depth bool

Generate Depth rasters

True
velocity bool

Generate Velocity rasters

True
froude bool

Generate Froude Number rasters

False
shear_stress bool

Generate Shear Stress rasters

False
depth_x_velocity bool

Generate Depth x Velocity rasters

False
depth_x_velocity_sq bool

Generate Depth x Velocity² rasters

False
inundation_boundary bool

Generate Inundation Boundary polygon (shapefile)

False
arrival_time bool

Generate Arrival Time rasters (hours; whole-simulation, ignores profile)

False
duration bool

Generate Duration rasters (hours; whole-simulation)

False
recession bool

Not supported — RasMapperLib has no recession map type; a warning is printed and the flag is ignored

False
percent_inundated bool

Generate Percent Time Inundated rasters

False
arrival_depth float

Wet/dry depth threshold (model vertical units) for arrival/duration/recession/percent_inundated (default: 0.0)

0.0
terrain_name Optional[str]

Specific terrain name from rasmap to use for mapping

None
ras_version Optional[str]

HEC-RAS version (auto-detected if None)

None
rasprocess_path Optional[Path]

Path to HEC-RAS install directory (for helper deployment)

None
render_mode Optional[str]

Water surface render mode: "horizontal", "sloping", or "slopingPretty". If None, reads from the .rasmap file (default: horizontal).

None
min_depth float

Minimum depth threshold for depth rasters (default: 0.0)

0.0
reproject_wgs84 bool

Reproject output rasters to WGS84

False
convert_cog bool

Convert output to Cloud Optimized GeoTIFF

False
timeout int

Per-plan timeout in seconds (default: 10800 = 3 hours)

10800
skip_errors bool

If True, log and continue past errors

True
keep_postprocessing bool

Keep the PostProcessing.hdf cache RasMapperLib creates for derived map types (can exceed the plan HDF in size). Default: delete it from the output directory.

False
performance Optional['StoreMapPerformanceOptions']

Optional ras-commander StoreMap performance policy. None selects :data:DEFAULT_LOCAL_MAP_PERFORMANCE when the canonical API is available. Pass StoreMapPerformanceOptions(max_workers=1) for the legacy serial execution policy.

None

Returns:

Type Description
list[MapResult]

List of MapResult, one per processed plan

Source code in ras2cng/mapping.py
def generate_result_maps(
    project_path: Path,
    output_dir: Path,
    *,
    plans: Optional[list[str]] = None,
    profile: str = "Max",
    wse: bool = True,
    depth: bool = True,
    velocity: bool = True,
    froude: bool = False,
    shear_stress: bool = False,
    depth_x_velocity: bool = False,
    depth_x_velocity_sq: bool = False,
    inundation_boundary: bool = False,
    arrival_time: bool = False,
    duration: bool = False,
    recession: bool = False,
    percent_inundated: bool = False,
    arrival_depth: float = 0.0,
    terrain_name: Optional[str] = None,
    ras_version: Optional[str] = None,
    rasprocess_path: Optional[Path] = None,
    render_mode: Optional[str] = None,
    min_depth: float = 0.0,
    reproject_wgs84: bool = False,
    convert_cog: bool = False,
    timeout: int = 10800,
    skip_errors: bool = True,
    keep_postprocessing: bool = False,
    performance: Optional["StoreMapPerformanceOptions"] = None,
) -> list[MapResult]:
    """Generate result rasters for plans in a HEC-RAS project.

    Uses the canonical ``RasMap.store_all_maps(mode="selected")`` API with
    RasStoreMapHelper.exe to generate raw TIFs from completed plan HDF files.
    With ras-commander 0.99.0 or newer, the default is memory-aware local auto
    parallelism. ras-commander 0.98.2 retains its serial compatibility path.

    Args:
        project_path: Path to .prj file or project directory
        output_dir: Directory for output raster files
        plans: Plan IDs to process (e.g. ["p01", "p02"]). None = all with results
        profile: Output profile: "Max", "Min", or timestamp
        wse: Generate Water Surface Elevation rasters
        depth: Generate Depth rasters
        velocity: Generate Velocity rasters
        froude: Generate Froude Number rasters
        shear_stress: Generate Shear Stress rasters
        depth_x_velocity: Generate Depth x Velocity rasters
        depth_x_velocity_sq: Generate Depth x Velocity² rasters
        inundation_boundary: Generate Inundation Boundary polygon (shapefile)
        arrival_time: Generate Arrival Time rasters (hours; whole-simulation,
            ignores `profile`)
        duration: Generate Duration rasters (hours; whole-simulation)
        recession: Not supported — RasMapperLib has no recession map type;
            a warning is printed and the flag is ignored
        percent_inundated: Generate Percent Time Inundated rasters
        arrival_depth: Wet/dry depth threshold (model vertical units) for
            arrival/duration/recession/percent_inundated (default: 0.0)
        terrain_name: Specific terrain name from rasmap to use for mapping
        ras_version: HEC-RAS version (auto-detected if None)
        rasprocess_path: Path to HEC-RAS install directory (for helper deployment)
        render_mode: Water surface render mode: "horizontal", "sloping", or "slopingPretty".
            If None, reads from the .rasmap file (default: horizontal).
        min_depth: Minimum depth threshold for depth rasters (default: 0.0)
        reproject_wgs84: Reproject output rasters to WGS84
        convert_cog: Convert output to Cloud Optimized GeoTIFF
        timeout: Per-plan timeout in seconds (default: 10800 = 3 hours)
        skip_errors: If True, log and continue past errors
        keep_postprocessing: Keep the PostProcessing.hdf cache RasMapperLib
            creates for derived map types (can exceed the plan HDF in size).
            Default: delete it from the output directory.
        performance: Optional ras-commander StoreMap performance policy. None
            selects :data:`DEFAULT_LOCAL_MAP_PERFORMANCE` when the canonical
            API is available. Pass ``StoreMapPerformanceOptions(max_workers=1)``
            for the legacy serial execution policy.

    Returns:
        List of MapResult, one per processed plan
    """
    from ras2cng.project import resolve_project_path

    project_path = Path(project_path)
    output_dir = Path(output_dir)
    output_dir.mkdir(parents=True, exist_ok=True)

    project_dir, prj_file = resolve_project_path(project_path)

    console.print(f"\n[bold cyan]ras2cng map[/bold cyan] -> {output_dir}")
    console.print(f"  Project : {prj_file.name}")
    console.print(f"  Profile : {profile}")

    optimized_store_maps = _supports_optimized_store_maps()
    if performance is not None and not optimized_store_maps:
        raise RuntimeError(
            "performance requires ras-commander>=0.99.0; upgrade ras-commander "
            "or omit performance to use the serial compatibility path"
        )
    if performance is not None:
        effective_performance = performance
    elif optimized_store_maps:
        effective_performance = DEFAULT_LOCAL_MAP_PERFORMANCE
    else:
        effective_performance = None
    if effective_performance is not None:
        worker_label = (
            "auto"
            if effective_performance.max_workers is None
            else str(effective_performance.max_workers)
        )
        console.print(
            f"  Mapping : {worker_label} helper(s), "
            f"{effective_performance.memory_policy} memory admission"
        )
    else:
        console.print("  Mapping : serial compatibility path")

    # Configure RasProcess
    _configure_rasprocess(rasprocess_path, ras_version)

    # Initialize project (pass ras_version to avoid auto-detecting old versions from plan files)
    init_kwargs = dict(ras_object="new", load_results_summary=True)
    if ras_version:
        init_kwargs["ras_version"] = ras_version
    ras = init_ras_project(project_dir, **init_kwargs)
    if rasprocess_path:
        exe_path = Path(rasprocess_path)
        ras.ras_exe_path = str(
            exe_path
            if exe_path.suffix.lower() == ".exe"
            else exe_path / "RasProcess.exe"
        )

    # RasMapperLib has no recession MapType (verified 6.6/7.0.1) — only
    # RasMapperLib-native products are generated.
    if recession:
        console.print(
            "  [yellow]Warning:[/yellow] recession has no RasMapperLib map type "
            "- skipping (arrival_time and duration are available)"
        )
        recession = False

    # Build list of requested map types
    requested_types = _build_requested_types(
        wse=wse, depth=depth, velocity=velocity,
        froude=froude, shear_stress=shear_stress,
        depth_x_velocity=depth_x_velocity,
        depth_x_velocity_sq=depth_x_velocity_sq,
        inundation_boundary=inundation_boundary,
        arrival_time=arrival_time, duration=duration,
        percent_inundated=percent_inundated,
    )

    if not requested_types:
        console.print("[yellow]Warning:[/yellow] No map types selected")
        return []

    if "inundation_boundary" in requested_types and (reproject_wgs84 or convert_cog):
        console.print(
            "  [yellow]Note:[/yellow] raster post-processing (--wgs84/--cog) does "
            "not apply to the inundation boundary shapefile - it stays in the "
            "model CRS"
        )

    console.print(f"  Map types: {', '.join(requested_types)}")

    # Determine which plans to process
    plan_filter = set(plans) if plans else None
    plan_rows = ras.plan_df if ras.plan_df is not None and not ras.plan_df.empty else None

    if plan_rows is None:
        console.print("[yellow]Warning:[/yellow] No plans found in project")
        return []

    results: list[MapResult] = []

    for _, row in plan_rows.iterrows():
        plan_num = str(row.get("plan_number", "")).zfill(2)
        plan_id = f"p{plan_num}"

        if plan_filter and plan_id not in plan_filter:
            continue

        plan_hdf = project_dir / f"{ras.project_name}.p{plan_num}.hdf"
        if not plan_hdf.exists():
            console.print(f"  [{plan_id}] No HDF results - skipping")
            continue

        plan_output = output_dir / plan_id
        plan_output.mkdir(parents=True, exist_ok=True)

        map_result = MapResult(
            plan_id=plan_id,
            plan_number=plan_num,
            output_dir=plan_output,
        )

        console.print(f"  [{plan_id}] Generating maps...")

        try:
            # Build boolean flags for RasProcess.store_maps()
            type_flags = {t: (t in requested_types) for t in MAP_TYPE_VARIABLES}

            plan_run_started = time.time() - 2  # filesystem mtime slack

            result_dict = _generate_plan_maps(
                ras=ras,
                plan_number=plan_num,
                profile=profile,
                output_dir=plan_output,
                terrain_name=terrain_name,
                render_mode=render_mode,
                timeout=timeout,
                arrival_depth=arrival_depth,
                ras_version=ras_version,
                performance=effective_performance,
                **type_flags,
            )

            # Shapefile outputs (inundation boundary) are moved by store_maps
            # but not included in its TIF-oriented return dict — glob them,
            # restricted to files produced by this run.
            if "inundation_boundary" in requested_types and not result_dict.get("inundation_boundary"):
                shp_paths = sorted(
                    p for p in plan_output.glob("*.shp")
                    if p.stat().st_mtime >= plan_run_started
                )
                if shp_paths:
                    result_dict["inundation_boundary"] = shp_paths

            for map_type in requested_types:
                tif_paths = result_dict.get(map_type, [])

                # Raster post-processing does not apply to shapefile outputs
                if map_type != "inundation_boundary":
                    # Post-process: depth threshold
                    if map_type == "depth" and min_depth > 0.0:
                        tif_paths = _apply_depth_threshold(tif_paths, min_depth)

                    # Post-process: reproject to WGS84
                    if reproject_wgs84:
                        tif_paths = _reproject_tifs(tif_paths, "EPSG:4326")

                    # Post-process: convert to COG
                    if convert_cog:
                        tif_paths = _convert_to_cog(tif_paths)

                if tif_paths:
                    map_result.map_types[map_type] = tif_paths
                    console.print(f"    {map_type}: {len(tif_paths)} raster(s)")

            postprocessing_hdf = plan_output / "PostProcessing.hdf"
            if postprocessing_hdf.exists() and not keep_postprocessing:
                size_mb = postprocessing_hdf.stat().st_size / 1e6
                postprocessing_hdf.unlink()
                console.print(f"    Removed PostProcessing.hdf cache ({size_mb:.0f} MB)")

        except Exception as e:
            error_msg = f"plan {plan_id}: {e}"
            map_result.errors.append(error_msg)
            console.print(f"    [yellow]Warning:[/yellow] {error_msg}")
            if not skip_errors:
                raise

        results.append(map_result)

    total_maps = sum(
        sum(len(paths) for paths in r.map_types.values())
        for r in results
    )
    total_errors = sum(len(r.errors) for r in results)
    console.print(f"\n[green]OK[/green] Generated {total_maps} raster(s) from {len(results)} plan(s)")
    if total_errors:
        console.print(f"  [yellow]{total_errors} error(s)[/yellow]")

    return results

ras2cng.mapping.DEFAULT_LOCAL_MAP_PERFORMANCE = StoreMapPerformanceOptions(max_workers=None, memory_policy='enforce', reserve_memory_mb=8192, reserve_memory_fraction=0.25, gdal_num_threads_per_helper=1, gdal_cachemax_mb=64) if StoreMapPerformanceOptions is not None else None module-attribute

ras2cng.mapping.MAP_TYPE_VARIABLES = {'wse': 'Water Surface', 'depth': 'Depth', 'velocity': 'Velocity', 'froude': 'Froude Number', 'shear_stress': 'Shear Stress', 'depth_x_velocity': 'Depth x Velocity', 'depth_x_velocity_sq': 'Depth x Velocity²', 'inundation_boundary': 'Inundation Boundary', 'arrival_time': 'Arrival Time', 'duration': 'Duration', 'recession': 'Recession', 'percent_inundated': 'Percent Time Inundated'} module-attribute