Native RAS Mapper Terrain Export¶
Export a registered HEC-RAS terrain to one bounded, validated GeoTIFF with RasTerrain.export_rasmapper_terrain(). This example uses the portable Muncie project and its two-source TerrainWithChannel layer to demonstrate exact terrain selection, a source-aligned 2x grid, native stitch handling, the modification-rasterization option, structured results, and the machine-readable receipt.
The export is deliberately small and opt-in. It does not run a hydraulic simulation, modify the source terrain, or register the derivative back into the project.
What the native wrapper preserves¶
The API loads the selected TerrainLayer from the project's .rasmap, then delegates consolidation and optional vector-modification rasterization to RAS Mapper. This retains registered source priority, stitches, masks, and modification behavior rather than rebuilding those rules in Python. The supervised helper writes a unique partial TIFF, validates it semantically, promotes it only on success, and writes a JSON receipt.
RasTerrainMod remains useful for terrain profiles, volume curves, and small analytical samples. It is not a fallback for this production raster-export path.
Release and platform contract¶
The API checks an initialized RasPrj and its identifiable executable release before it creates output folders or starts native work.
| HEC-RAS release | Terrain-export status |
|---|---|
| 6.3 / 6.3.1 | Unsupported: the required bounded native contract is absent. |
| 6.4.0 | Unsupported because of the documented terrain-elevation defect. |
| 6.4.1, 6.5, 6.6 | Qualified on native Windows and under task-local Wine. |
| 6.7 beta builds | Unsupported; no final release was available to qualify. |
| 7.0.0 | Unsupported because of the documented terrain-modification export defect. |
| 7.0.1 | Qualified on native Windows and under task-local Wine. |
| 7.1 | Forward-open, not pre-qualified: an exact 7.1 installation may proceed, but the helper still verifies the managed method contract at runtime. |
This notebook defaults to 6.6 because it is the cross-platform-qualified release. On Windows the helper uses the matching local installation. On Linux it uses the Wine configuration established through RasProcess.configure_wine() and task-local process state; it supervises only the process tree it owns.
Setup and parameters¶
Keep RUN_NATIVE_EXPORT = False for routine documentation and source QA. To reproduce the committed evidence on a machine with a qualified HEC-RAS 6.6 installation (or configured task-local Wine runtime), set RAS_COMMANDER_RUN_NATIVE_TERRAIN_EXPORT=1 in the notebook kernel environment after reviewing the bounded parameters. Generated projects, rasters, receipts, and figures stay under the ignored working/ tree.
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
# True uses this checkout; False uses an installed ras-commander package.
USE_LOCAL_SOURCE = True
import sys
from pathlib import Path
REPO_ROOT = Path.cwd()
if REPO_ROOT.name.lower() == "examples":
REPO_ROOT = REPO_ROOT.parent
if USE_LOCAL_SOURCE:
local_path = str(REPO_ROOT)
if local_path not in sys.path:
sys.path.insert(0, local_path)
print("LOCAL SOURCE MODE: Loading from this checkout's ras_commander package")
else:
print("PIP PACKAGE MODE: Loading installed ras-commander")
LOCAL SOURCE MODE: Loading from this checkout's ras_commander package
from __future__ import annotations
import json
import os
from pathlib import Path
import pandas as pd
from IPython.display import display
from ras_commander import (
RasExamples,
RasMap,
RasTerrain,
TerrainExportResult,
init_ras_project,
)
RUN_NATIVE_EXPORT = False
if os.environ.get("RAS_COMMANDER_RUN_NATIVE_TERRAIN_EXPORT", "0") == "1":
RUN_NATIVE_EXPORT = True
OVERWRITE_EXISTING = False
PROJECT_SUFFIX = "native_terrain_export_931"
RAS_VERSION = "6.6"
TERRAIN_NAME = "TerrainWithChannel"
DOWNSAMPLE_FACTOR = 2
RASTERIZE_MODIFICATIONS = True
TIMEOUT_SECONDS = 180.0
EXPORT_EXTENT = (
404147.258781418,
1801881.85296284,
404307.258781418,
1802111.85296284,
)
RUN_ROOT = Path(
os.environ.get(
"RAS_COMMANDER_EXAMPLE_RUN_ROOT",
REPO_ROOT / "working" / "example_931_native_terrain_export",
)
)
RUN_ROOT.mkdir(parents=True, exist_ok=True)
OUTPUT_TIF = RUN_ROOT / "exports" / "muncie_terrain_with_channel_2x.tif"
REVIEW_FIGURE = RUN_ROOT / "review" / "muncie_terrain_export_evidence.png"
def run_path(path):
"""Return a portable label for an artifact under this run root."""
return str(Path("run") / Path(path).resolve().relative_to(RUN_ROOT.resolve()))
print("Run root: run")
print(f"Native export enabled: {RUN_NATIVE_EXPORT}")
print(f"Overwrite enabled: {OVERWRITE_EXISTING}")
print(f"HEC-RAS release: {RAS_VERSION}")
Run root: run
Native export enabled: True
Overwrite enabled: False
HEC-RAS release: 6.6
Extract and initialize the real project¶
RasExamples makes a writable project copy. Passing the resulting RasPrj into every project-aware API keeps version and multi-project state explicit.
project_path = RasExamples.extract_project(
"Muncie",
output_path=RUN_ROOT,
suffix=PROJECT_SUFFIX,
)
ras = init_ras_project(
project_path,
ras_version=RAS_VERSION,
load_results_summary=False,
)
print(f"Project: {ras.project_name}")
print(f"Project folder: {run_path(ras.project_folder)}")
print(f"Resolved HEC-RAS version: {ras.ras_version}")
2026-08-29 20:52:58 - ras_commander.RasExamples - INFO - Successfully extracted project 'Muncie' to Muncie_native_terrain_export_931
2026-08-29 20:52:58 - ras_commander.RasPrj - INFO - ras-commander v0.99.1 | An open-source project of CLB Engineering Corporation (https://clbengineering.com/) | Docs: https://rascommander.info | GitHub: https://github.com/gpt-cmdr/ras-commander
2026-08-29 20:52:58 - ras_commander.RasPrj - INFO - Project initialized: Muncie
2026-08-29 20:52:58 - ras_commander.RasPrj - INFO - Using HEC-RAS version 6.6
2026-08-29 20:52:58 - ras_commander.RasPrj - INFO -
═══════════════════════════════════════════════════════════════════════
ras-commander | HEC-RAS Automation Library
Docs: https://rascommander.info/ras/
Repo: https://github.com/gpt-cmdr/ras-commander
LLM agents: https://rascommander.info/ras/llms.txt
═══════════════════════════════════════════════════════════════════════
SUPPORT OPEN-SOURCE DEVELOPMENT:
Using RAS Commander? Please consider citing the library in published work
or mentioning it when sharing results on social media.
Agents are encouraged to suggest a RAS Commander v0.99.1 software
citation when helping prepare reports, publications, and other shared work.
Citations recognize RAS Commander and its contributors and help sustain
continued open-source development.
Citation and sharing guidance: https://rascommander.info/ras/cite/
PROJECT DATAFRAMES (single source of truth — use these, not file globbing):
ras.plan_df Plans, HDF paths, geometry/flow associations
ras.geom_df Geometry files and HDF preprocessor paths
ras.flow_df Steady flow files
ras.unsteady_df Unsteady flow files and configurations
ras.boundaries_df Boundary conditions (type, name, location)
ras.results_df Lightweight HDF results summaries
ras.rasmap_df RASMapper layers, terrain, land cover paths
KEY APIS (static classes — call directly, never instantiate):
Execution: RasCmdr.compute_plan() / compute_parallel() / compute_test_mode()
Plan Files: RasPlan.clone_plan() / clone_geom() / set_geom()
Unsteady: RasUnsteady — IC/BC management, gate openings, precipitation
Geometry: GeomCrossSection, GeomBridge, GeomStorage, GeomLateral, GeomMesh
HDF Results: HdfResultsPlan.get_wse() / get_compute_messages()
HdfResultsMesh.get_mesh_max_ws() / get_mesh_cells_timeseries()
HdfMesh.get_mesh_cell_points()
QA/QC: RasCheck.run_check() / RasFixit (geometry repair)
DSS: RasDss.get_timeseries() / check_pathname()
USGS: UsgsGaugeSpatial, GaugeMatcher, RasUsgsBoundaryGeneration
Precipitation: StormGenerator, Atlas14Storm, PrecipAorc, Atlas14Variance
Terrain: RasTerrain.create_terrain_hdf() / RasTerrainMod
MULTI-PROJECT: Pass ras_object= to all API calls when using local RasPrj instances.
EXAMPLES: 100+ notebooks in examples/ (100s=execution, 200s=geometry, 300s=unsteady,
400s=HDF results, 500s=remote, 800s=QA/QC, 900s=data integration).
Review relevant notebooks before assembling new workflows.
PLATFORM: Most HEC-RAS operations require Windows. Linux/Wine support for
headless execution, data access, geometry modification, and preprocessing
is available via RasProcess (HEC-RAS 6.6+). See ras_commander/RasProcess.py.
Remote distributed execution: ras_commander/remote/ (PsExec, Docker, SSH, cloud).
═══════════════════════════════════════════════════════════════════════
Project: Muncie
Project folder: run\Muncie_native_terrain_export_931
Resolved HEC-RAS version: 6.6
Inventory first, then select exactly¶
RasMap.list_terrain_layers() is the DataFrame source of truth for registered terrain selection. The export API accepts an omitted name only when exactly one terrain is registered; this example deliberately requires one exact, case-sensitive match.
terrain_layers = RasMap.list_terrain_layers(
project_path,
ras_object=ras,
)
terrain_layers_review = terrain_layers.copy()
for path_column in ("filename", "resolved_path", "rasmap_path"):
if path_column in terrain_layers_review.columns:
terrain_layers_review[path_column] = terrain_layers_review[path_column].map(
lambda value: Path(value).name if pd.notna(value) else value
)
display(terrain_layers_review)
selected_rows = terrain_layers.loc[terrain_layers["name"] == TERRAIN_NAME]
if len(selected_rows.index) != 1:
available = terrain_layers["name"].astype(str).tolist()
raise ValueError(
f"Expected one exact terrain named {TERRAIN_NAME!r}; found "
f"{len(selected_rows.index)}. Available terrains: {available}"
)
selected_terrain = selected_rows.iloc[0]
registered_before = terrain_layers[["name", "resolved_path"]].copy()
print(f"Selected terrain: {selected_terrain['name']}")
print(f"Registered terrain HDF: {Path(selected_terrain['resolved_path']).name}")
| name | filename | resolved_path | checked | type | resample_method | surface_on | |
|---|---|---|---|---|---|---|---|
| 0 | Terrain | Terrain.hdf | Terrain.hdf | True | TerrainLayer | near | True |
| 1 | TerrainWithChannel | TerrainWithChannel.hdf | TerrainWithChannel.hdf | True | TerrainLayer | near | True |
Selected terrain: TerrainWithChannel
Registered terrain HDF: TerrainWithChannel.hdf
Exact source-derived resolution and grid alignment¶
downsample_factor accepts only 1, 2, 4, or 8. Factor 1 consolidates at native resolution; factors 2, 4, and 8 multiply the authoritative source cell size exactly. The API does not round a US-survey-foot source to a nominal metre resolution. It snaps the requested extent outward to the authoritative source-grid origin and always uses nearest-neighbor for this path.
| Factor | Meaning | Exact output cell |
|---|---|---|
| 1 | Native consolidation | native_cell_size |
| 2 | Notebook execution example | native_cell_size * 2 |
| 4 | Coarser derivative | native_cell_size * 4 |
| 8 | Coarsest supported derivative | native_cell_size * 8 |
For multi-source terrain, the result inventories every source and identifies the authoritative grid. Source resolutions may differ and need not have integer ratios: RAS Mapper consolidates them onto the explicit source-derived output grid. Preflight rejects only missing, non-finite, or non-positive level-zero source grids.
Run the bounded native export¶
rasterize_modifications=True is passed to RAS Mapper's native resampleVecMods behavior. Muncie is used here to provide a portable two-source stitch example; use the terrain-modification notebook for an affected-versus-control-cell comparison across a known vector modification.
overwrite=False is the safe default. If either the requested TIFF or receipt already exists, the API raises rather than replacing it. Choose a new destination for a repeat run, or opt into replacement deliberately.
result = None
if RUN_NATIVE_EXPORT:
result = RasTerrain.export_rasmapper_terrain(
ras_project_path=project_path,
output_tif=OUTPUT_TIF,
terrain_name=TERRAIN_NAME,
extent=EXPORT_EXTENT,
downsample_factor=DOWNSAMPLE_FACTOR,
rasterize_modifications=RASTERIZE_MODIFICATIONS,
overwrite=OVERWRITE_EXISTING,
timeout_seconds=TIMEOUT_SECONDS,
ras_object=ras,
)
if not result:
raise RuntimeError(
f"Native terrain export failed: {result.error}; "
f"receipt: {run_path(result.receipt_path)}"
)
assert isinstance(result, TerrainExportResult)
print(result)
else:
print("Native export skipped. Set RUN_NATIVE_EXPORT = True after reviewing the parameters.")
print(f"Planned output: {run_path(OUTPUT_TIF)}")
print(f"Requested bounded extent: {EXPORT_EXTENT}")
TerrainExportResult(SUCCESS, terrain='TerrainWithChannel', factor=2x, time=1.4s)
Inspect the typed result and semantic validation¶
A successful TerrainExportResult is truthy only after both the GeoTIFF and receipt are promoted. The source inventory records priority, raster dimensions and extent, intersection with this window, and the authoritative-grid source. The validation dictionary records the actual grid, band semantics, value range, CRS presence, checksum, and absence of unexpected sidecars.
if result is not None:
result_summary = pd.DataFrame(
[
{
"result_type": type(result).__name__,
"success": bool(result),
"terrain_name": result.terrain_name,
"native_cell_size": result.native_cell_size,
"downsample_factor": result.downsample_factor,
"output_cell_size": result.output_cell_size,
"requested_extent": result.requested_extent,
"snapped_extent": result.snapped_extent,
"rasterize_modifications": result.rasterize_modifications,
"elapsed_seconds": result.elapsed_seconds,
"output_path": run_path(result.output_path),
}
]
)
validation_summary = pd.Series(result.validation, name="validated_value").to_frame()
source_inventory_review = result.source_inventory.copy()
source_inventory_review["filename"] = source_inventory_review["filename"].map(
lambda value: Path(value).name
)
display(result_summary)
display(source_inventory_review)
display(validation_summary)
assert result.validation["driver"] == "GTiff"
assert result.validation["band_count"] == 1
assert result.validation["data_type"] == "Float32"
assert result.validation["crs_present"] is True
assert result.validation["sidecars"] == []
assert result.source_inventory["intersects_output"].all()
assert int(result.source_inventory["authoritative_grid"].sum()) == 1
else:
print("Run the native export cell to populate result, source, and validation evidence.")
| result_type | success | terrain_name | native_cell_size | downsample_factor | output_cell_size | requested_extent | snapped_extent | rasterize_modifications | elapsed_seconds | output_path | |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 0 | TerrainExportResult | True | TerrainWithChannel | 5.0 | 2 | 10.0 | (404147.258781418, 1801881.85296284, 404307.25... | (404147.258781418, 1801881.85296284, 404307.25... | True | 1.424569 | run\exports\muncie_terrain_with_channel_2x.tif |
| index | filename | priority | columns | rows | extent | cell_sizes | levels | intersects_output | authoritative_grid | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 0 | TerrainWithChannel.ChannelOnly.tif | 0 | 1891 | 1155 | {'min_x': 404207.258781418, 'min_y': 1799936.8... | [5, 10, 20, 40] | 4 | True | True |
| 1 | 1 | TerrainWithChannel.muncie_clip.tif | 1 | 7892 | 4538 | {'min_x': 384977.84867792, 'min_y': 1788863.54... | [5, 10, 20, 40, 80, 160] | 6 | True | False |
| validated_value | |
|---|---|
| driver | GTiff |
| columns | 16 |
| rows | 23 |
| band_count | 1 |
| data_type | Float32 |
| nodata | -9999.0 |
| computed_min | 928.844 |
| computed_max | 933.219 |
| checksum | 4221 |
| geotransform | [404147.258781418, 10.0, 0.0, 1802111.85296284... |
| actual_extent | [404147.258781418, 1801881.85296284, 404307.25... |
| crs_present | True |
| sidecars | [] |
Inspect the machine-readable receipt¶
The receipt is suitable for automated review. It records the operation status, HEC-RAS release, selected project and terrain, requested and snapped grid details, source inventory, validation, helper messages, and completion state. This feature intentionally adds no model, input, output, installer, or executable hashes.
receipt_payload = None
if result is not None:
receipt_payload = json.loads(result.receipt_path.read_text(encoding="utf-8"))
receipt_summary = pd.DataFrame(
[
{
"schema_version": receipt_payload["schema_version"],
"operation": receipt_payload["operation"],
"status": receipt_payload["status"],
"hecras_version": receipt_payload["hecras_version"],
"terrain_name": receipt_payload["result"]["terrain_name"],
"source_count": len(receipt_payload["sources"]),
"resample_method": receipt_payload["native_helper"]["resample_method"],
"single_output": receipt_payload["native_helper"]["resample_to_one_rfi"],
"rasterize_modifications": receipt_payload["native_helper"]["rasterize_modifications"],
"receipt_path": run_path(result.receipt_path),
}
]
)
display(receipt_summary)
assert receipt_payload["status"] == "success"
assert receipt_payload["result"]["validation"] == result.validation
assert receipt_payload["native_helper"]["success"] is True
assert receipt_payload["native_helper"]["resample_method"] == "near"
assert receipt_payload["native_helper"]["resample_to_one_rfi"] is True
assert receipt_payload["native_helper"]["rasterize_modifications"] is True
else:
print("Run the native export cell to inspect its JSON receipt.")
| schema_version | operation | status | hecras_version | terrain_name | source_count | resample_method | single_output | rasterize_modifications | receipt_path | |
|---|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | rasmapper_terrain_export | success | 6.6 | TerrainWithChannel | 2 | near | True | True | run\exports\muncie_terrain_with_channel_2x.tif... |
Visual evidence: terrain, bounds, grid, and sources¶
The left panel shows the actual exported terrain with both requested and snapped bounds. The center panel exposes individual pixels, and the right panel annotates the validated raster contract and registered sources. The figure is embedded in this executed notebook for review; its separate high-resolution file remains under ignored working/ output and is not committed.
if result is not None:
import matplotlib.pyplot as plt
import rasterio
from matplotlib.patches import Rectangle
with rasterio.open(result.output_path) as dataset:
terrain = dataset.read(1, masked=True)
bounds = dataset.bounds
raster_extent = (bounds.left, bounds.right, bounds.bottom, bounds.top)
fig, (ax_map, ax_grid, ax_evidence) = plt.subplots(
1,
3,
figsize=(16, 5.5),
gridspec_kw={"width_ratios": [1.15, 1.0, 0.72]},
)
image = ax_map.imshow(
terrain,
extent=raster_extent,
origin="upper",
cmap="terrain",
)
requested = result.requested_extent
snapped = result.snapped_extent
ax_map.add_patch(
Rectangle(
(requested[0], requested[1]),
requested[2] - requested[0],
requested[3] - requested[1],
fill=False,
edgecolor="#c43c2f",
linewidth=2.0,
linestyle="--",
label="Requested extent",
)
)
ax_map.add_patch(
Rectangle(
(snapped[0], snapped[1]),
snapped[2] - snapped[0],
snapped[3] - snapped[1],
fill=False,
edgecolor="#1f3552",
linewidth=1.6,
label="Snapped output grid",
)
)
ax_map.set_title("Bounded native RAS Mapper terrain export")
ax_map.set_xlabel("Project X")
ax_map.set_ylabel("Project Y")
ax_map.legend(loc="lower right")
fig.colorbar(image, ax=ax_map, shrink=0.82, label="Elevation")
zoom_rows = min(8, terrain.shape[0])
zoom_columns = min(8, terrain.shape[1])
grid_image = ax_grid.imshow(
terrain[:zoom_rows, :zoom_columns],
origin="upper",
cmap="terrain",
)
ax_grid.set_xticks([value - 0.5 for value in range(zoom_columns + 1)], minor=True)
ax_grid.set_yticks([value - 0.5 for value in range(zoom_rows + 1)], minor=True)
ax_grid.grid(which="minor", color="white", linewidth=0.7, alpha=0.85)
ax_grid.tick_params(which="minor", bottom=False, left=False)
ax_grid.set_title("Upper-left pixels: exact aligned grid")
ax_grid.set_xlabel("Column")
ax_grid.set_ylabel("Row")
fig.colorbar(grid_image, ax=ax_grid, shrink=0.82, label="Elevation")
source_lines = []
for _, source in result.source_inventory.sort_values("index").iterrows():
marker = "authoritative" if bool(source["authoritative_grid"]) else "registered"
source_lines.append(f"{int(source['index'])}: {Path(source['filename']).name} ({marker})")
evidence_text = (
"VALIDATED OUTPUT\n"
f"{result.validation['columns']} x {result.validation['rows']} pixels\n"
f"Cell size: {result.output_cell_size:g}\n"
f"Downsample: {result.downsample_factor}x\n"
f"Band: {result.validation['data_type']}\n"
f"CRS present: {result.validation['crs_present']}\n"
f"Checksum: {result.validation['checksum']}\n"
f"Modifications: {result.rasterize_modifications}\n\n"
"REGISTERED SOURCES\n"
+ "\n".join(source_lines)
)
ax_evidence.set_title("Semantic and source evidence")
ax_evidence.axis("off")
ax_evidence.text(
0.0,
0.95,
evidence_text,
transform=ax_evidence.transAxes,
va="top",
fontsize=9.5,
family="monospace",
linespacing=1.35,
)
fig.suptitle(f"{result.terrain_name}: RAS Mapper {result.downsample_factor}x export")
fig.tight_layout()
REVIEW_FIGURE.parent.mkdir(parents=True, exist_ok=True)
fig.savefig(REVIEW_FIGURE, dpi=180, bbox_inches="tight")
plt.show()
print(f"Review figure: {run_path(REVIEW_FIGURE)}")
else:
print("Run the native export cell to generate raster and grid visual evidence.")

Review figure: run\review\muncie_terrain_export_evidence.png
Verify the source project was not mutated¶
The derivative is an external product, not a newly registered terrain. Re-inventorying the .rasmap should therefore return the same terrain names and HDF paths, and the output TIFF must not appear among registered paths.
if result is not None:
terrain_layers_after = RasMap.list_terrain_layers(
project_path,
ras_object=ras,
)
registered_after = terrain_layers_after[["name", "resolved_path"]].copy()
pd.testing.assert_frame_equal(
registered_before.reset_index(drop=True),
registered_after.reset_index(drop=True),
)
registered_paths = {
str(Path(path).resolve())
for path in terrain_layers_after["resolved_path"].dropna()
}
assert str(result.output_path.resolve()) not in registered_paths
print("Verified: registered terrain inventory is unchanged.")
print(f"Derivative remains external: {run_path(result.output_path)}")
else:
print("No export was run; the source project remains unchanged.")
Verified: registered terrain inventory is unchanged.
Derivative remains external: run\exports\muncie_terrain_with_channel_2x.tif
Review checklist¶
A reviewable run should show all of the following:
- one exact registered terrain selected from the DataFrame inventory;
- a truthy
TerrainExportResultand successful JSON receipt; - two intersecting Muncie sources with exactly one authoritative grid;
- an exact 2x output cell size and outward-snapped bounded extent;
- one Float32 GeoTIFF band, a present CRS, finite elevations and NoData metadata, and no unexpected sidecars;
- requested and snapped bounds visible on the terrain figure, plus a pixel-grid detail panel; and
- unchanged registered terrain inventory after export.
For modification-specific semantic evidence, compare modification-off and modification-on bounded exports across a known feature and confirm that affected cells change while control cells remain stable. That focused proof belongs in the terrain-modification example rather than being inferred from Muncie's stitched sources.