This notebook edits a disposable HEC-RAS land-cover sidecar exclusively
through the public native RASMapper polygon API. It demonstrates list,
add, update, delete, one-member MultiPolygon normalization, true
multipart and interior-ring rejection, durable backups, native geometry
recomputation, geometry base/region override precedence, and a complete
hydraulic solve.
Acceptance is based on the final solver-owned Manning and WSE arrays,
not on the sidecar write alone.
Existing class only; native save/readback; durable backup
Update
RasMap.update_land_classification_polygon()
One hole-free polygon and existing class persist by index
Delete
RasMap.delete_land_classification_polygon()
Native removal; class table remains intact
Reject unsupported topology
Add/update normalization
True multipart and interior rings fail before backup/native mutation
Recompute
RasMap.recompute_property_tables()
HEC-RAS resamples the sidecar into geometry tables
Verify
RasCmdr.compute_plan()
Required final arrays and completed hydraulic solve
Raw Shapely coordinates are interpreted in the sidecar CRS. A
one-member, hole-free MultiPolygon is normalized to its polygon. True
multipart geometry and interior rings fail before mutation. HEC-RAS
6.0 through 7.0.1 can persist a ring but its classification resampler
fills the hole, so the public mutation API does not pretend the topology
is supported. HEC-RAS 5.x users should use durable geometry Manning
regions because this sidecar polygon API is qualified for HEC-RAS 6.x
and 7.0.x.
Python
# ruff: noqa: E402frompathlibimportPathimportsysimportloggingimporthashlibimportwarningsforcandidatein[Path.cwd(),*Path.cwd().parents]:if((candidate/"ras_commander"/"__init__.py").exists()and(candidate/"examples").exists()):REPO_ROOT=candidate.resolve()breakelse:raiseRuntimeError("Could not locate the ras-commander checkout")repo_root_string=str(REPO_ROOT)ifrepo_root_stringinsys.path:sys.path.remove(repo_root_string)sys.path.insert(0,repo_root_string)importmatplotlib.pyplotaspltfrommatplotlib.colorsimportTwoSlopeNormimportnumpyasnpimportpandasaspdimportrasteriofromIPython.displayimportdisplayfromshapely.geometryimportLineString,MultiPolygon,Polygon,boximportras_commanderfromras_commanderimportRasCmdr,RasExamples,RasMap,init_ras_projectfromras_commander.geomimportGeomLandCover,GeomLateralfromras_commander.hdfimport(HdfBase,HdfBndry,HdfLandCover,HdfMesh,HdfPlan,HdfResultsBreach,HdfResultsMesh,HdfResultsPlan,)defsha256_file(path):digest=hashlib.sha256()withPath(path).open("rb")assource:forblockiniter(lambda:source.read(1024*1024),b""):digest.update(block)returndigest.hexdigest()defbackup_glob_state(layer_hdf_path):layer_hdf_path=Path(layer_hdf_path)pattern=f"{layer_hdf_path.stem}*.backup{layer_hdf_path.suffix}"return{candidate.name:(candidate.stat().st_size,sha256_file(candidate),)forcandidateinsorted(layer_hdf_path.parent.glob(pattern))}# Some mixed GDAL/PROJ Windows environments emit this known pyproj# database-location warning even when the authoritative HDF CRS reads# successfully. Suppress only that exact warning family; all other# geospatial and solver warnings remain visible.warnings.filterwarnings("ignore",message=r".*proj_create_from_database.*proj\.db.*",category=Warning,module=r"pyproj(\..*)?",)logging.getLogger("ras_commander").setLevel(logging.WARNING)forlogger_nameinlist(logging.root.manager.loggerDict):iflogger_name.startswith("ras_commander"):logging.getLogger(logger_name).setLevel(logging.WARNING)plt.rcParams.update({"figure.dpi":140,"savefig.dpi":180,"axes.titlesize":13,"axes.labelsize":10,"legend.fontsize":8,})RAS_VERSION="7.0"PLAN_NUMBER="03"GEOMETRY_NUMBER="09"MESH_NAME="BaldEagleCr"FINAL_CLASS="Evergreen Forest"TRANSIENT_CLASS="Barren Land Rock-Sand-Clay"DELETION_CLASS="Developed, Open Space"RAS_COMMANDER_VERSION=ras_commander.__version__RAS_COMMANDER_REVISION=next((str(getattr(ras_commander,attribute))forattributein("__commit__","__git_revision__")ifgetattr(ras_commander,attribute,None)),"not exposed by package metadata",)WORK_ROOT=REPO_ROOT/"working"/"notebook_runs"/"CLB-903"/"213"WORK_ROOT.mkdir(parents=True,exist_ok=True)
Plan 03 is an unsteady, single-2D-area Bald Eagle Creek dam-break
simulation. Its forcing comprises one upstream flow hydrograph, two
downstream normal-depth lines, and a gate-opening schedule. There is
no stage hydrograph. Independent disposable project copies preserve a
true baseline for final-array comparison.
After the baseline solve, public geometry APIs supply the three mapped
boundary-condition lines and the 41-point Sayers Dam connection.
They are plotted over the registered Terrain50 companion raster with
the proposed override footprint. The setup cell suppresses only the
known pyproj proj.db location warning; the authoritative CRS is still
read and displayed from HDF, and all other warnings remain visible.
Python
available_examples=RasExamples.get_example_projects(RAS_VERSION)baseline_path=Path(RasExamples.extract_project("BaldEagleCrkMulti2D",output_path=WORK_ROOT,suffix="213_baseline",))modified_path=Path(RasExamples.extract_project("BaldEagleCrkMulti2D",output_path=WORK_ROOT,suffix="213_modified",))baseline_ras=init_ras_project(baseline_path,RAS_VERSION,ras_object="new",accept_tcu=True,)modified_ras=init_ras_project(modified_path,RAS_VERSION,ras_object="new",accept_tcu=True,)baseline_plan=baseline_ras.plan_df.loc[baseline_ras.plan_df["plan_number"]==PLAN_NUMBER].iloc[0]modified_geom=modified_ras.geom_df.loc[modified_ras.geom_df["geom_number"]==GEOMETRY_NUMBER].iloc[0]baseline_geom=baseline_ras.geom_df.loc[baseline_ras.geom_df["geom_number"]==GEOMETRY_NUMBER].iloc[0]baseline_plan_hdf=(baseline_path/f"{baseline_ras.project_name}.p{PLAN_NUMBER}.hdf")modified_plan_hdf=(modified_path/f"{modified_ras.project_name}.p{PLAN_NUMBER}.hdf")modified_geometry_hdf=Path(modified_geom["hdf_path"])modified_geometry_path=Path(modified_geom["full_path"])baseline_geometry_hdf=Path(baseline_geom["hdf_path"])baseline_geometry_path=Path(baseline_geom["full_path"])flow_number=str(baseline_plan["unsteady_number"]).zfill(2)context=pd.DataFrame([{"item":"requested HEC-RAS runtime","value":RAS_VERSION},{"item":"plan Program Version","value":baseline_plan.get("Program Version","not available",),},{"item":"ras-commander version","value":RAS_COMMANDER_VERSION,},{"item":"ras-commander revision","value":RAS_COMMANDER_REVISION,},{"item":"plan","value":f"p{PLAN_NUMBER}: {baseline_plan['Plan Title']}"},{"item":"geometry","value":f"g{GEOMETRY_NUMBER}"},{"item":"unsteady flow","value":f"u{flow_number}",},{"item":"2D flow area","value":MESH_NAME},{"item":"simulation period","value":baseline_plan["Simulation Date"]},{"item":"computation interval","value":baseline_plan["Computation Interval"]},{"item":"mapping interval","value":baseline_plan["Mapping Interval"]},{"item":"model description","value":baseline_plan["description"]},])display(context)boundary_context=baseline_ras.boundaries_df.loc[baseline_ras.boundaries_df["unsteady_number"]==flow_number,["bc_type","hydrograph_type","river_reach_name","river_station","storage_area_name","area_2d","bc_line_name",],].drop_duplicates()boundary_types=boundary_context["bc_type"].fillna("")hydrograph_types=boundary_context["hydrograph_type"].fillna("")assertint(boundary_types.eq("Normal Depth").sum())==2assertint(boundary_types.eq("Flow Hydrograph").sum())==1assertint(boundary_types.eq("Gate Opening").sum())==1upstream_flow_boundary=boundary_context.loc[boundary_types.eq("Flow Hydrograph")].iloc[0]assertupstream_flow_boundary["area_2d"]==MESH_NAMEassertupstream_flow_boundary["bc_line_name"]=="Upstream Inflow"assertnotboundary_types.str.contains("Stage Hydrograph",case=False).any()assertnothydrograph_types.str.contains("Stage Hydrograph",case=False).any()display(boundary_context.reset_index(drop=True))# Normalize the baseline through the same native association and# property-table path used for the modified condition.baseline_landcover=Path(RasMap.list_landcover_layers(baseline_path,ras_object=baseline_ras).iloc[0]["resolved_path"])baseline_terrain=Path(RasMap.list_terrain_layers(baseline_path,ras_object=baseline_ras).iloc[0]["resolved_path"])baseline_infiltration=Path(RasMap.list_infiltration_layers(baseline_path,ras_object=baseline_ras).iloc[0]["resolved_path"])baseline_association_result=RasMap.associate_geometry_layers(baseline_path,baseline_geometry_hdf,landcover_hdf_path=baseline_landcover,infiltration_hdf_path=baseline_infiltration,terrain_hdf_path=baseline_terrain,hecras_version=RAS_VERSION,ras_object=baseline_ras,)baseline_recomputed_hdf=RasMap.recompute_property_tables(baseline_path,baseline_geometry_hdf,hecras_version=RAS_VERSION,audit_mannings=True,ras_object=baseline_ras,)assertbaseline_recomputed_hdf==baseline_geometry_hdf
The baseline run must complete and contain both final cell and face
Manning arrays. This prevents a successful process exit from standing
in for the requested solver evidence. Exact HDF build, flow filename,
CRS, and mesh-population metadata are read only after that HDF exists.
Python
baseline_result=RasCmdr.compute_plan(PLAN_NUMBER,ras_object=baseline_ras,force_rerun=True,num_cores=2,verify=True,)assertbaseline_result.success,"Baseline HEC-RAS computation failed."assertbaseline_result.completion_verifiedisTruebaseline_cells=HdfLandCover.get_preprocessed_mannings_n(baseline_plan_hdf,mesh_name=MESH_NAME,).rename(columns={"mannings_n":"baseline_n"})baseline_wse=HdfResultsMesh.get_mesh_max_ws(baseline_plan_hdf)baseline_faces=HdfMesh.get_mesh_face_property_tables(baseline_plan_hdf)[MESH_NAME].sort_values(["Face ID","Elevation"]).reset_index(drop=True)baseline_audit=HdfLandCover.audit_final_mannings_n(baseline_plan_hdf,mesh_name=MESH_NAME,tolerance=1.0e-4,)plan_hdf_information=HdfPlan.get_plan_information(baseline_plan_hdf)project_crs=HdfBase.get_projection(baseline_plan_hdf)postcompute_cell_points=HdfMesh.get_mesh_cell_points(baseline_plan_hdf)postcompute_cell_points=postcompute_cell_points.loc[postcompute_cell_points["mesh_name"]==MESH_NAME]postcompute_cell_polygons=HdfMesh.get_mesh_cell_polygons(baseline_plan_hdf)postcompute_cell_polygons=postcompute_cell_polygons.loc[postcompute_cell_polygons["mesh_name"]==MESH_NAME]assertlen(postcompute_cell_points)==19_597assertlen(postcompute_cell_polygons)==18_066baseline_hdf_info=HdfResultsPlan.get_unsteady_info(baseline_plan_hdf)baseline_hdf_summary=HdfResultsPlan.get_unsteady_summary(baseline_plan_hdf)baseline_exact_hdf_build=str(baseline_hdf_info.iloc[0]["Program Version"])assertbaseline_exact_hdf_build=="HEC-RAS 7.0 April 2026"bc_lines=HdfBndry.get_bc_lines(baseline_plan_hdf)assertlen(bc_lines)==3assertset(bc_lines["Name"])=={"DSNormalDepth","DS2NormalD","Upstream Inflow",}assertbc_lines.geometry.notna().all()connection_coordinates=GeomLateral.get_connection_line_coords(baseline_geometry_path,"Sayers Dam",)assertlist(connection_coordinates.columns)==["X","Y"]assertlen(connection_coordinates)==41connection_line=LineString(connection_coordinates[["X","Y"]].to_numpy())assertlen(connection_line.coords)==41baseline_terrain_layers=RasMap.list_terrain_layers(baseline_path,ras_object=baseline_ras,)assertlen(baseline_terrain_layers)==1assertbaseline_terrain_layers.iloc[0]["name"]=="Terrain50"registered_terrain_hdf=Path(baseline_terrain_layers.iloc[0]["resolved_path"])assertregistered_terrain_hdf==baseline_terrainterrain_companion_vrt=registered_terrain_hdf.with_suffix(".vrt")assertterrain_companion_vrt.is_file()withrasterio.open(terrain_companion_vrt)asterrain_dataset:preview_scale=max(terrain_dataset.width,terrain_dataset.height,)/900preview_height=max(1,round(terrain_dataset.height/preview_scale),)preview_width=max(1,round(terrain_dataset.width/preview_scale),)terrain_preview=terrain_dataset.read(1,out_shape=(preview_height,preview_width),masked=True,)terrain_extent=(terrain_dataset.bounds.left,terrain_dataset.bounds.right,terrain_dataset.bounds.bottom,terrain_dataset.bounds.top,)terrain_raster_crs=terrain_dataset.crsassertterrain_raster_crsisnotNonecontext_override_polygon=box(2_063_347.2881,352_125.3883,2_083_124.6947,367_770.6226,)assertcontext_override_polygon.is_validdisplay(baseline_audit)display(pd.DataFrame([{"exact HDF build":baseline_exact_hdf_build,"flow file":plan_hdf_information["Flow Filename"],"project CRS":str(project_crs),"mesh cell centers":len(postcompute_cell_points),"reconstructable cell polygons":len(postcompute_cell_polygons),"solution":baseline_hdf_summary.iloc[0]["Solution"],"run time window":baseline_hdf_summary.iloc[0]["Run Time Window"],}]))display(pd.DataFrame([{"registered terrain":registered_terrain_hdf.stem,"companion raster":terrain_companion_vrt.name,"mapped BC lines":len(bc_lines),"Sayers Dam connection vertices":len(connection_coordinates),}]))fig,context_ax=plt.subplots(figsize=(12,8),constrained_layout=True,)terrain_image=context_ax.imshow(terrain_preview,extent=terrain_extent,origin="upper",cmap="terrain",alpha=0.72,)fig.colorbar(terrain_image,ax=context_ax,shrink=0.75,label="Terrain50 elevation (ft)",)postcompute_cell_points.plot(ax=context_ax,color="white",edgecolor="0.25",linewidth=0.1,markersize=1.0,alpha=0.45,label=f"{MESH_NAME} cell centers",)boundary_colors={"Upstream Inflow":"#0072B2","DSNormalDepth":"#7B3294","DS2NormalD":"#C51B7D",}forboundaryinbc_lines.itertuples(index=False):color=boundary_colors[boundary.Name]x_values,y_values=boundary.geometry.xycontext_ax.plot(x_values,y_values,color=color,linewidth=2.2,label=boundary.Name,)midpoint=boundary.geometry.interpolate(0.5,normalized=True)context_ax.annotate(boundary.Name,xy=(midpoint.x,midpoint.y),xytext=(5,5),textcoords="offset points",fontsize=8,color=color,bbox={"boxstyle":"round,pad=0.2","facecolor":"white","edgecolor":color,"alpha":0.9,},)connection_x,connection_y=connection_line.xycontext_ax.plot(connection_x,connection_y,color="#D55E00",linewidth=2.4,label="Sayers Dam connection (41 points)",)connection_midpoint=connection_line.interpolate(0.5,normalized=True,)context_ax.annotate("Sayers Dam",xy=(connection_midpoint.x,connection_midpoint.y),xytext=(8,-14),textcoords="offset points",fontsize=8,color="#A34100",arrowprops={"arrowstyle":"->","color":"#A34100"},)override_x,override_y=context_override_polygon.exterior.xycontext_ax.fill(override_x,override_y,facecolor="#F0E442",edgecolor="black",linewidth=1.8,alpha=0.35,hatch="///",label="proposed land-classification override",)model_minx,model_miny,model_maxx,model_maxy=(postcompute_cell_points.total_bounds)map_padding=0.06*max(model_maxx-model_minx,model_maxy-model_miny,)context_ax.set_xlim(model_minx-map_padding,model_maxx+map_padding)context_ax.set_ylim(model_miny-map_padding,model_maxy+map_padding)scale_length_ft=20_000scale_x=model_minx+0.04*(model_maxx-model_minx)scale_y=model_miny+0.05*(model_maxy-model_miny)context_ax.plot([scale_x,scale_x+scale_length_ft],[scale_y,scale_y],color="black",linewidth=3,)context_ax.text(scale_x+scale_length_ft/2,scale_y+1_500,"20,000 ft",ha="center",va="bottom",fontsize=8,)context_ax.annotate("N",xy=(0.95,0.90),xytext=(0.95,0.78),xycoords="axes fraction",ha="center",fontweight="bold",arrowprops={"arrowstyle":"-|>","linewidth":1.5},)context_ax.set_title("Baseline model context — Terrain50, mapped BCs, dam, and override")context_ax.set_xlabel("State Plane Easting (ft)")context_ax.set_ylabel("State Plane Northing (ft)")context_ax.set_aspect("equal")context_ax.legend(loc="upper left",framealpha=0.92)plt.show()
Text Only
2026-07-25 15:07:39 - rasterio._err - WARNING - CPLE_AppDefined:PROJ: proj_create_from_database: C:\Program Files (x86)\HEC\HEC-RAS\7.0.1\GDAL\common\data\proj.db lacks DATABASE.LAYOUT.VERSION.MAJOR / DATABASE.LAYOUT.VERSION.MINOR metadata. It comes from another PROJ installation.
2026-07-25 15:07:39 - rasterio._err - WARNING - CPLE_AppDefined:PROJ: proj_create_from_name: C:\Program Files (x86)\HEC\HEC-RAS\7.0.1\GDAL\common\data\proj.db lacks DATABASE.LAYOUT.VERSION.MAJOR / DATABASE.LAYOUT.VERSION.MINOR metadata. It comes from another PROJ installation.
2026-07-25 15:07:40 - rasterio._err - WARNING - CPLE_AppDefined:PROJ: proj_create_from_database: C:\Program Files (x86)\HEC\HEC-RAS\7.0.1\GDAL\common\data\proj.db lacks DATABASE.LAYOUT.VERSION.MAJOR / DATABASE.LAYOUT.VERSION.MINOR metadata. It comes from another PROJ installation.
2026-07-25 15:07:40 - rasterio._err - WARNING - CPLE_AppDefined:PROJ: proj_create_from_name: C:\Program Files (x86)\HEC\HEC-RAS\7.0.1\GDAL\common\data\proj.db lacks DATABASE.LAYOUT.VERSION.MAJOR / DATABASE.LAYOUT.VERSION.MINOR metadata. It comes from another PROJ installation.
hdf_path
mesh_name
complete_geometry
landcover_filename
landcover_layer_name
cell_value_count
cell_distinct_count
cell_distinct_values
face_value_count
face_distinct_count
face_distinct_values
missing_expected_values
passed
failure_reason
0
C:\Users\bill\.config\superpowers\worktrees\ra...
BaldEagleCr
True
.\Land Classification\LandCover.hdf
LandCover
19597
10
(0.029999999329447746, 0.03500000014901161, 0....
434012
10
(0.029999999329447746, 0.03500000014901161, 0....
()
True
exact HDF build
flow file
project CRS
mesh cell centers
reconstructable cell polygons
solution
run time window
0
HEC-RAS 7.0 April 2026
BaldEagleDamBrk.u13
EPSG:2271
19597
18066
Unsteady Finished Successfully
25JUL2026 15:04:53 to 25JUL2026 15:07:36
registered terrain
companion raster
mapped BC lines
Sayers Dam connection vertices
0
Terrain50
Terrain50.vrt
3
41
Discover the sidecar, geometry overrides, and supported polygon¶
The final polygon targets an existing Evergreen Forest class so the
operation changes only the classification override footprint. The
sidecar class value is not necessarily the final solver value: the
geometry's durable base and regional LCMann tables take precedence
during preprocessing. No class values or custom HDF datasets are
authored.
The update deliberately passes MultiPolygon([polygon]); the API
normalizes its one effective, hole-free part. Each successful mutation
must produce a distinct durable backup.
Delete a transient polygon and reject unsupported topology¶
Deleting a polygon does not delete its class definition. True multipart
input and interior rings are rejected before backup or native save.
HEC-RAS can persist a ring, but its 6.0-7.0.1 land-cover resampler
flattens it; fail-closed behavior prevents a misleading sidecar.
Python
transient_polygon=box(2_055_000,345_000,2_058_000,348_000)transient=RasMap.add_land_classification_polygon(sidecar_hdf,transient_polygon,class_name=DELETION_CLASS,hecras_version=RAS_VERSION,ras_object=modified_ras,)transient_index=int(transient["polygon_index"].max())backup_paths.append(Path(transient.attrs["backup_path"]))after_delete=RasMap.delete_land_classification_polygon(sidecar_hdf,polygon_index=transient_index,hecras_version=RAS_VERSION,ras_object=modified_ras,)backup_paths.append(Path(after_delete.attrs["backup_path"]))assertlen(after_delete)==initial_count+1assertDELETION_CLASSinafter_delete.attrs["removed_class_names"]assertDELETION_CLASSinset(HdfLandCover.get_landcover_raster_map(sidecar_hdf)["class_name"])before_hole_add_hash=sha256_file(sidecar_hdf)before_hole_add_backups=backup_glob_state(sidecar_hdf)try:RasMap.add_land_classification_polygon(sidecar_hdf,unsupported_hole_polygon,class_name=FINAL_CLASS,hecras_version=RAS_VERSION,ras_object=modified_ras,)exceptNotImplementedErrorasexc:hole_error=str(exc)else:raiseAssertionError("Interior-ring polygon was not rejected")assert"interior rings"inhole_error.casefold()assertsha256_file(sidecar_hdf)==before_hole_add_hashassertbackup_glob_state(sidecar_hdf)==before_hole_add_backupsbefore_hole_update_hash=sha256_file(sidecar_hdf)before_hole_update_backups=backup_glob_state(sidecar_hdf)try:RasMap.update_land_classification_polygon(sidecar_hdf,added_index,polygon=unsupported_hole_polygon,hecras_version=RAS_VERSION,ras_object=modified_ras,)exceptNotImplementedErrorasexc:update_hole_error=str(exc)else:raiseAssertionError("Interior-ring polygon update was not rejected")assert"interior rings"inupdate_hole_error.casefold()assertsha256_file(sidecar_hdf)==before_hole_update_hashassert(backup_glob_state(sidecar_hdf)==before_hole_update_backups)true_multipart=MultiPolygon([box(2_050_000,340_000,2_051_000,341_000),box(2_052_000,342_000,2_053_000,343_000),])before_multipart_hash=sha256_file(sidecar_hdf)before_multipart_backups=backup_glob_state(sidecar_hdf)try:RasMap.add_land_classification_polygon(sidecar_hdf,true_multipart,class_name=FINAL_CLASS,hecras_version=RAS_VERSION,ras_object=modified_ras,)exceptValueErrorasexc:multipart_error=str(exc)else:raiseAssertionError("True multipart polygon was not rejected")assert"multipart"inmultipart_error.casefold()assertsha256_file(sidecar_hdf)==before_multipart_hashassert(backup_glob_state(sidecar_hdf)==before_multipart_backups)assertall(path.exists()andpath.stat().st_size>0forpathinbackup_paths)assertlen(set(backup_paths))==len(backup_paths)final_polygons=RasMap.list_land_classification_polygons(sidecar_hdf)final_feature=final_polygons.loc[final_polygons["polygon_index"]==added_index].iloc[0]assertfinal_feature.geometry.equals(final_polygon)assertlen(final_polygons)==initial_count+1for_,originalinoriginal_polygon_snapshot.iterrows():current=final_polygons.loc[final_polygons["polygon_index"]==original["polygon_index"]].iloc[0]assertcurrent["class_name"]==original["class_name"]assertcurrent.geometry.equals(original.geometry)display(pd.DataFrame([{"operation":"add/update/delete","durable_backup_count":len(backup_paths),"final_polygon_count":len(final_polygons),"hole_count":len(final_feature.geometry.interiors),"hole_add_rejected_before_backup":True,"hole_add_rejection":hole_error,"hole_update_rejected_before_backup":True,"hole_update_rejection":update_hole_error,"multipart_add_rejected_before_backup":True,"multipart_add_rejection":multipart_error,}]))
operation
durable_backup_count
final_polygon_count
hole_count
hole_add_rejected_before_backup
hole_add_rejection
hole_update_rejected_before_backup
hole_update_rejection
multipart_add_rejected_before_backup
multipart_add_rejection
0
add/update/delete
4
2
0
True
HEC-RAS 6.0 through 7.0.1 land-cover classific...
True
HEC-RAS 6.0 through 7.0.1 land-cover classific...
True
Classification polygon input must be one polyg...
Preserve associations and recompute native property tables¶
The terrain association is retained while the edited land-cover
sidecar is associated explicitly. HEC-RAS, not ras-commander, resamples
the polygon and rebuilds the geometry property tables.
The complete plan is rerun with required final arrays. The classification
sidecar is only an input; the plan HDF is the authoritative acceptance
surface. Public breach-result APIs then compare initiation, peak,
geometry progression, and the complete breach-flow trace. This
distinguishes local hydraulic response to roughness from a change in
breach timing or formation.
The solver-owned Manning array and cell-center catalog each contain
19,597 cells. Public polygon reconstruction succeeds for 18,066 and
leaves 1,531 unreconstructable/ghost cells. Checked left joins retain
the complete population. Polygon-backed cells use full-cell
covered/boundary/disjoint gates; the 1,531 remainder uses an explicitly
labeled cell-center fallback. Every center-located off-polygon cell
must remain unchanged.
Fully covered and center-fallback interior cells must resolve to the
geometry base/regional values for Evergreen Forest. Native
regeneration must keep non-Manning face columns materially stable
within the documented distribution tolerance. The full solve must also
produce finite WSE results. The expected 5,924 changed maximum-WSE
values include cells beyond the override because roughness modifies
local conveyance and the nonlinear solution propagates that response
through connected cells and peak timing. The independently unchanged
breach initiation, peak, and geometry progression show that this is
hydraulic response, not a shifted breach schedule. WSE sensitivity is
secondary evidence rather than proof of polygon persistence.
Python
cell_points=HdfMesh.get_mesh_cell_points(modified_plan_hdf)cell_points=cell_points.loc[cell_points["mesh_name"]==MESH_NAME][["cell_id","geometry"]].reset_index(drop=True)cell_polygons=HdfMesh.get_mesh_cell_polygons(modified_plan_hdf)cell_polygons=cell_polygons.loc[cell_polygons["mesh_name"]==MESH_NAME][["cell_id","geometry"]].rename(columns={"geometry":"cell_polygon"}).reset_index(drop=True)expected_cell_count=19_597expected_polygon_count=18_066expected_center_only_count=1_531assertcell_points["cell_id"].is_uniqueassertcell_polygons["cell_id"].is_uniqueassertbaseline_cells["cell_id"].is_uniqueassertmodified_cells["cell_id"].is_uniqueassertlen(cell_points)==expected_cell_countassertlen(cell_polygons)==expected_polygon_countassertlen(baseline_cells)==expected_cell_countassertlen(modified_cells)==expected_cell_countassertset(cell_points["cell_id"])==set(baseline_cells["cell_id"])assertset(cell_points["cell_id"])==set(modified_cells["cell_id"])comparison=(cell_points.merge(cell_polygons,on="cell_id",how="left",validate="one_to_one",indicator="polygon_join",))assertlen(comparison)==expected_cell_countpolygon_backed=comparison["polygon_join"]=="both"center_only=comparison["polygon_join"]=="left_only"assertint(polygon_backed.sum())==expected_polygon_countassertint(center_only.sum())==expected_center_only_countcomparison=comparison.drop(columns=["polygon_join"])defmerge_complete_cell_table(left,right,value_column,indicator_name,):assertright["cell_id"].is_uniquemerged=left.merge(right[["cell_id",value_column]],on="cell_id",how="left",validate="one_to_one",indicator=indicator_name,)assertlen(merged)==expected_cell_countassert(merged[indicator_name]=="both").all()assertmerged[value_column].notna().all()returnmerged.drop(columns=[indicator_name])comparison=merge_complete_cell_table(comparison,baseline_cells,"baseline_n","baseline_n_join",)comparison=merge_complete_cell_table(comparison,modified_cells,"modified_n","modified_n_join",)polygon_backed=comparison["cell_polygon"].notna()center_only=~polygon_backedassertint(polygon_backed.sum())==expected_polygon_countassertint(center_only.sum())==expected_center_only_countcomparison["delta_n"]=(comparison["modified_n"]-comparison["baseline_n"])polygon_covered=pd.Series(False,index=comparison.index)polygon_disjoint=pd.Series(False,index=comparison.index)polygon_covered.loc[polygon_backed]=comparison.loc[polygon_backed,"cell_polygon"].map(final_polygon.covers).to_numpy()polygon_disjoint.loc[polygon_backed]=comparison.loc[polygon_backed,"cell_polygon"].map(final_polygon.disjoint).to_numpy()polygon_boundary_overlap=(polygon_backed&~polygon_covered&~polygon_disjoint)assertint((polygon_covered|polygon_boundary_overlap|polygon_disjoint).sum())==expected_polygon_countassertcomparison.loc[polygon_boundary_overlap,"cell_polygon"].map(final_polygon.boundary.intersects).all()center_covered=comparison["geometry"].map(final_polygon.covers)center_disjoint=comparison["geometry"].map(final_polygon.disjoint)assert(center_covered|center_disjoint).all()center_only_inside=center_only¢er_coveredcenter_only_off_polygon=center_only¢er_disjointassertint((center_only_inside|center_only_off_polygon).sum())==expected_center_only_countcomparison["coverage_basis"]="cell polygon"comparison.loc[center_only,"coverage_basis"]="cell center fallback"comparison["zone"]="boundary overlap (cell polygon)"comparison.loc[polygon_covered,"zone"]="polygon interior (cell polygon)"comparison.loc[polygon_disjoint,"zone"]="off-polygon (cell polygon)"comparison.loc[center_only_inside,"zone"]="inside polygon (cell-center fallback)"comparison.loc[center_only_off_polygon,"zone"]="off-polygon (cell-center fallback)"baseline_wse_mesh=baseline_wse.loc[baseline_wse["mesh_name"]==MESH_NAME,["cell_id","maximum_water_surface"],].rename(columns={"maximum_water_surface":"baseline_wse"})modified_wse_mesh=modified_wse.loc[modified_wse["mesh_name"]==MESH_NAME,["cell_id","maximum_water_surface"],].rename(columns={"maximum_water_surface":"modified_wse"})cell_order_before_wse=comparison["cell_id"].tolist()comparison=merge_complete_cell_table(comparison,baseline_wse_mesh,"baseline_wse","baseline_wse_join",)comparison=merge_complete_cell_table(comparison,modified_wse_mesh,"modified_wse","modified_wse_join",)assertcomparison["cell_id"].tolist()==cell_order_before_wsecomparison["delta_wse"]=(comparison["modified_wse"]-comparison["baseline_wse"])changed=comparison["delta_n"].abs()>1.0e-4assertpolygon_covered.any()assertpolygon_disjoint.any()assertpolygon_boundary_overlap.any()assertcenter_only_inside.any()assertcenter_only_off_polygon.any()assertint(changed.sum())==2_976assertint((changed&polygon_covered).sum())>0assertnotchanged[polygon_disjoint].any()assertnotchanged[center_disjoint].any()defmatches_expected_effective_n(values):returnnp.column_stack([np.isclose(values,value,atol=1.0e-4)forvalueinexpected_effective_n]).any(axis=1)core_values=comparison.loc[polygon_covered,"modified_n"].to_numpy()assertmatches_expected_effective_n(core_values).all()center_fallback_values=comparison.loc[center_only_inside,"modified_n"].to_numpy()assertmatches_expected_effective_n(center_fallback_values).all()assertbaseline_faces.shape==modified_faces.shapegeometry_drift={}maximum_drift_caps={"Elevation":0.01,"Area":1.0,"Wetted Perimeter":0.1,}forcolumnin["Elevation","Area","Wetted Perimeter"]:absolute_delta=np.abs(baseline_faces[column]-modified_faces[column])assertnp.isfinite(absolute_delta.to_numpy()).all()geometry_drift[column]={"maximum_absolute_delta":float(absolute_delta.max()),"p999_absolute_delta":float(absolute_delta.quantile(0.999)),"rows_above_0.01":int((absolute_delta>0.01).sum()),}# Native regeneration can rebuild isolated top rows or round# stored float geometry values differently. Require 99.9% of# rows to remain within 0.01 and bound exceptional rows to less# than 0.01% of the complete face table.assertgeometry_drift[column]["p999_absolute_delta"]<0.01assert(geometry_drift[column]["maximum_absolute_delta"]<maximum_drift_caps[column])assert(geometry_drift[column]["rows_above_0.01"]/len(baseline_faces))<0.0001changed_face_rows=int((np.abs(modified_faces["Manning's n"]-baseline_faces["Manning's n"])>1.0e-4).sum())assertchanged_face_rows>0valid_wse=(np.isfinite(comparison["delta_wse"])&(comparison["baseline_wse"]>-100)&(comparison["modified_wse"]>-100))hydraulic_changed=(valid_wse&(comparison["delta_wse"].abs()>1.0e-4))hydraulic_cells=int(hydraulic_changed.sum())assertvalid_wse.any()asserthydraulic_cells==5_924off_polygon_hydraulic_cells=int((hydraulic_changed&comparison["zone"].str.startswith("off-polygon")).sum())assertoff_polygon_hydraulic_cells==4_002coverage_summary=(comparison.groupby("coverage_basis",observed=True).agg(cells=("cell_id","size"),changed_manning_cells=("delta_n",lambdavalues:int((values.abs()>1.0e-4).sum())),changed_wse_cells=("delta_wse",lambdavalues:int((values.abs()>1.0e-4).sum())),).reset_index())assertint(coverage_summary["cells"].sum())==expected_cell_countassertint(coverage_summary["changed_manning_cells"].sum())==2_976zone_summary=(comparison.groupby("zone",observed=True).agg(cells=("cell_id","size"),changed_manning_cells=("delta_n",lambdavalues:int((values.abs()>1.0e-4).sum())),mean_baseline_n=("baseline_n","mean"),mean_modified_n=("modified_n","mean"),maximum_abs_wse_delta_ft=("delta_wse",lambdavalues:float(values[np.isfinite(values)].abs().max())),).reset_index())display(coverage_summary)display(zone_summary)display(pd.DataFrame([{"final cell Manning rows":expected_cell_count,"final cell Manning rows changed":int(changed.sum()),"polygon-backed cells":int(polygon_backed.sum()),"center-only fallback cells":int(center_only.sum()),"center-located off-polygon Manning rows changed":int((changed¢er_disjoint).sum()),"final face Manning rows changed":changed_face_rows,"maximum non-Manning rows with drift > 0.01":max(value["rows_above_0.01"]forvalueingeometry_drift.values()),"maximum 99.9th-percentile non-Manning drift":max(value["p999_absolute_delta"]forvalueingeometry_drift.values()),"maximum non-Manning absolute drift":max(value["maximum_absolute_delta"]forvalueingeometry_drift.values()),"finite WSE comparison cells":int(valid_wse.sum()),"cells with |delta WSE| > 0.0001 ft":hydraulic_cells,"changed WSE cells in off-polygon zones":(off_polygon_hydraulic_cells),}]))
Native RASMapper persisted add, update, and delete operations with a
unique durable backup for every successful mutation.
A one-member hole-free MultiPolygon was normalized; true multipart
add and interior-ring add/update failed before backup or mutation,
as verified by unchanged backup inventories and sidecar hashes.
The class table remained intact after polygon deletion.
HEC-RAS retained the native BaldEagleCr terrain association and
the exact one-row 2D-area association catalog, recomputed the geometry
tables, completed both hydraulic solves with the exact
HEC-RAS 7.0 April 2026 build, and wrote the required final arrays.
Checked joins retained all 19,597 Manning cells: 18,066
polygon-backed cells plus 1,531 explicitly labeled center-only
fallback cells. Exactly 2,976 final cell Manning values changed, and
every center-located off-polygon cell remained unchanged.
Fully covered and center-fallback interior cells resolved to the
geometry base/regional Evergreen Forest values. Non-Manning face
properties remained materially stable within the native regeneration
tolerance.
Exactly 5,924 maximum-WSE values changed, including 4,002 cells in
off-polygon zones. The unchanged 03JAN1999 02:22:30 breach
initiation, shared 530,807.25-cfs peak and time, and effectively
identical breach geometry progression show that these differences
are nonlinear/local conveyance and peak-response propagation, not a
breach-timing change. The maximum breach-flow trace difference was
only about 71.19 cfs (0.013% of peak).
HEC-RAS 6.0-7.0.1 cannot hydraulically honor classification-polygon
holes; represent an exclusion by adding explicit, non-overlapping
hole-free polygons rather than relying on an interior ring.