Skip to content

Structures and Metadata from Geometry Files

This notebook demonstrates parsing bridge, culvert, inline weir, and geometry metadata from HEC-RAS plain text geometry files, including: - GeomMetadata: Efficient geometry element counts (HDF-first with text fallback) - GeomBridge: Bridge deck, piers, coefficients, and HTAB parameters - GeomCulvert: Culvert inventory across entire geometry files - GeomInlineWeir: Inline weir profiles and gate configurations

Overview

HEC-RAS geometry files contain inline structures (bridges, culverts, inline weirs) that control hydraulic behavior at specific cross-section locations. This notebook demonstrates the geom subpackage modules for extracting structure data.

What You'll Learn

  • Get a quick summary of geometry contents with GeomMetadata
  • Extract bridge deck geometry, pier data, and hydraulic coefficients with GeomBridge
  • List all culverts and their dimensions with GeomCulvert
  • Read inline weir profiles and gate configurations with GeomInlineWeir

LLM Forward Approach

  • Verification: Compare extracted DataFrames against HEC-RAS GUI
  • Visual Outputs: Plot bridge deck profiles and weir crest lines
  • Audit Trail: Log all geometry file paths and structure counts

Reference Documentation

Python
# =============================================================================
# DEVELOPMENT MODE TOGGLE
# =============================================================================
from pathlib import Path
import sys

USE_LOCAL_SOURCE = True  # <-- TOGGLE THIS

if USE_LOCAL_SOURCE:
    local_path = str(Path.cwd().parent)
    if local_path not in sys.path:
        sys.path.insert(0, local_path)
    print(f"LOCAL SOURCE MODE: Loading from {local_path}/ras_commander")
else:
    print("PIP PACKAGE MODE: Loading installed ras-commander")

# Import RAS Commander modules
from ras_commander import (
    RasExamples, init_ras_project, RasPrj,
    GeomBridge, GeomCulvert, GeomInlineWeir, GeomLandCover,
)
from ras_commander.geom import GeomMetadata

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from IPython import display

# Verify which version loaded
import ras_commander
print(f"Loaded: {ras_commander.__file__}")
print(f"Working directory: {Path.cwd()}")

Parameters

Python
# =============================================================================
# PARAMETERS - Edit these to customize the notebook
# =============================================================================

# Projects containing structures
PROJECT_BRIDGE = "Bridge Hydraulics"             # Has bridge structures
PROJECT_CULVERT = "ConSpan Culvert"              # Has culvert structures
PROJECT_WEIR = "Example 12 - Inline Structure"   # Has inline weir with gates
PROJECT_2D = "BaldEagleCrkMulti2D"               # For metadata comparison

PROJECT_SUFFIX = "206"     # Folder suffix for extracted projects
RAS_VERSION = "7.0"        # HEC-RAS version

Setup: Extract Example Projects

Python
# Extract all projects needed for this notebook
project_paths = RasExamples.extract_project(
    [PROJECT_BRIDGE, PROJECT_CULVERT, PROJECT_WEIR, PROJECT_2D],
    suffix=PROJECT_SUFFIX
)

bridge_path = project_paths[0]
culvert_path = project_paths[1]
weir_path = project_paths[2]
bald_eagle_path = project_paths[3]

# Initialize each project
bridge_ras = RasPrj()
init_ras_project(bridge_path, RAS_VERSION, ras_object=bridge_ras)
print(f"Bridge project: {bridge_ras.project_name} ({len(bridge_ras.geom_df)} geom files)")

culvert_ras = RasPrj()
init_ras_project(culvert_path, RAS_VERSION, ras_object=culvert_ras)
print(f"Culvert project: {culvert_ras.project_name} ({len(culvert_ras.geom_df)} geom files)")

weir_ras = RasPrj()
init_ras_project(weir_path, RAS_VERSION, ras_object=weir_ras)
print(f"Weir project: {weir_ras.project_name} ({len(weir_ras.geom_df)} geom files)")

bald_eagle_ras = RasPrj()
init_ras_project(bald_eagle_path, RAS_VERSION, ras_object=bald_eagle_ras)
print(f"2D project: {bald_eagle_ras.project_name} ({len(bald_eagle_ras.geom_df)} geom files)")

Section 1: GeomMetadata - Geometry Element Counts

GeomMetadata.get_geometry_counts() provides a quick summary of geometry file contents. It prefers HDF-based extraction (~10-50ms) and falls back to plain text parsing (~100-500ms) when HDF is unavailable.

Python
# Get geometry counts for the bridge project
bridge_geom_row = bridge_ras.geom_df.iloc[0]
bridge_geom_path = Path(bridge_geom_row['full_path'])
bridge_hdf_path = Path(bridge_geom_row['hdf_path']) if pd.notna(bridge_geom_row.get('hdf_path')) else None

print(f"Geometry file: {bridge_geom_path.name}")
print(f"HDF available: {bridge_hdf_path is not None and bridge_hdf_path.exists()}")
print()

counts = GeomMetadata.get_geometry_counts(bridge_geom_path, bridge_hdf_path)

print("Geometry Element Counts:")
print(f"  Cross sections:      {counts['num_cross_sections']}")
print(f"  Bridges:             {counts['num_bridges']}")
print(f"  Culverts:            {counts['num_culverts']}")
print(f"  Inline weirs:        {counts['num_weirs']}")
print(f"  Gates:               {counts['num_gates']}")
print(f"  Inline structures:   {counts['num_inline_structures']} (bridges + culverts + weirs)")
print(f"  Lateral structures:  {counts['num_lateral_structures']}")
print(f"  SA/2D connections:   {counts['num_sa_2d_connections']}")
print(f"  Has 1D XS:           {counts['has_1d_xs']}")
print(f"  Has 2D mesh:         {counts['has_2d_mesh']}")
print(f"  Mesh areas:          {counts['mesh_area_names']}")
print(f"  Mesh cells:          {counts['mesh_cell_count']}")

Compare Counts Across Projects

Run get_geometry_counts() on multiple projects to see the range of geometry types.

Python
# Compare counts across all four projects
projects = {
    'Bridge Hydraulics': bridge_ras,
    'ConSpan Culvert': culvert_ras,
    'Inline Structure': weir_ras,
    'BaldEagleCrk 2D': bald_eagle_ras,
}

summary_rows = []
for name, ras_obj in projects.items():
    geom_row = ras_obj.geom_df.iloc[0]
    geom_path = Path(geom_row['full_path'])
    hdf_path = Path(geom_row['hdf_path']) if pd.notna(geom_row.get('hdf_path')) else None

    c = GeomMetadata.get_geometry_counts(geom_path, hdf_path)
    summary_rows.append({
        'Project': name,
        'XS': c['num_cross_sections'],
        'Bridges': c['num_bridges'],
        'Culverts': c['num_culverts'],
        'Weirs': c['num_weirs'],
        'Gates': c['num_gates'],
        'Laterals': c['num_lateral_structures'],
        '2D Areas': len(c['mesh_area_names']),
        'Mesh Cells': c['mesh_cell_count'],
    })

summary_df = pd.DataFrame(summary_rows)
print("Geometry Element Summary Across Projects:")
display.display(summary_df)

Section 2: GeomBridge - Bridge Extraction

The Bridge Hydraulics example project contains a bridge structure with deck geometry, piers, and hydraulic coefficients.

2.1 List All Bridges

Python
# List all bridges in the geometry file
bridges_df = GeomBridge.get_bridges(bridge_geom_path)

print(f"Bridges found: {len(bridges_df)}")
print()
display.display(bridges_df)

2.2 Bridge Deck Geometry

The deck profile defines the station-elevation points of the bridge deck (high chord) and low chord.

Python
# Extract deck geometry for the first bridge
br = bridges_df.iloc[0]
river = br['River']
reach = br['Reach']
rs = br['RS']

print(f"Bridge at {river}, {reach}, RS {rs}")
print()

deck_df = GeomBridge.get_deck(bridge_geom_path, river, reach, rs)
print(f"Deck points: {len(deck_df)}")
display.display(deck_df)
Python
# Visualize bridge deck profile
if len(deck_df) > 0:
    fig, ax = plt.subplots(figsize=(12, 5))

    if 'HighChord' in deck_df.columns and 'LowChord' in deck_df.columns:
        ax.plot(deck_df['Station'], deck_df['HighChord'], 'b-o', label='High Chord (Top of Deck)', markersize=4)
        ax.plot(deck_df['Station'], deck_df['LowChord'], 'r-o', label='Low Chord (Bottom of Deck)', markersize=4)
        ax.fill_between(deck_df['Station'], deck_df['LowChord'], deck_df['HighChord'],
                        alpha=0.3, color='gray', label='Deck Cross-Section')
    elif 'Elevation' in deck_df.columns:
        ax.plot(deck_df['Station'], deck_df['Elevation'], 'b-o', label='Deck Elevation', markersize=4)

    ax.set_xlabel('Station (ft)', fontsize=11)
    ax.set_ylabel('Elevation (ft)', fontsize=11)
    ax.set_title(f'Bridge Deck Profile: {river}, {reach}, RS {rs}', fontsize=13, fontweight='bold')
    ax.legend(fontsize=10)
    ax.grid(True, alpha=0.3)
    plt.tight_layout()
    plt.show()

2.3 Pier Data

Python
# Extract pier definitions
piers_df = GeomBridge.get_piers(bridge_geom_path, river, reach, rs)

print(f"Piers found: {len(piers_df)}")
if len(piers_df) > 0:
    print()
    display.display(piers_df)

2.4 Hydraulic Coefficients

Python
# Extract hydraulic coefficients
coeffs_df = GeomBridge.get_coefficients(bridge_geom_path, river, reach, rs)

print(f"Coefficient rows: {len(coeffs_df)}")
if len(coeffs_df) > 0:
    print()
    display.display(coeffs_df)

2.5 HTAB Parameters

Python
# Extract hydraulic table parameters
htab_df = GeomBridge.get_htab(bridge_geom_path, river, reach, rs)

print("HTAB Parameters (DataFrame):")
display.display(htab_df)

# Also available as dict (includes invert elevation)
htab_dict = GeomBridge.get_htab_dict(bridge_geom_path, river, reach, rs)
print()
print("HTAB Parameters (dict):")
for key, value in htab_dict.items():
    print(f"  {key}: {value}")

2.6 Approach Sections and Abutment

Python
# Extract approach sections (BR U and BR D cross sections)
approach_df = GeomBridge.get_approach_sections(bridge_geom_path, river, reach, rs)
print(f"Approach section points: {len(approach_df)}")
if len(approach_df) > 0:
    display.display(approach_df.head(10))

# Extract abutment geometry (not all bridges have abutments)
if br.get('HasAbutment', False):
    abutment_df = GeomBridge.get_abutment(bridge_geom_path, river, reach, rs)
    print(f"\nAbutment points: {len(abutment_df)}")
    if len(abutment_df) > 0:
        display.display(abutment_df)
else:
    print("\nNo abutment data for this bridge (HasAbutment=False)")

Section 3: GeomCulvert - Culvert Extraction

The ConSpan Culvert example project contains culvert structures. GeomCulvert.get_all() scans the entire geometry file for all culverts.

Python
# Get the culvert geometry file
culvert_geom_row = culvert_ras.geom_df.iloc[0]
culvert_geom_path = Path(culvert_geom_row['full_path'])

print(f"Geometry file: {culvert_geom_path.name}")
print()

# List all culverts in the entire geometry file
all_culverts = GeomCulvert.get_all(culvert_geom_path)

print(f"Total culverts found: {len(all_culverts)}")
if len(all_culverts) > 0:
    print(f"Columns: {list(all_culverts.columns)}")
    print()
    display.display(all_culverts)
Python
# Summarize culvert properties
if len(all_culverts) > 0:
    print("Culvert Summary:")

    # Group by shape type
    if 'ShapeName' in all_culverts.columns:
        print("\nCulverts by shape type:")
        print(all_culverts.groupby('ShapeName').size().to_string())

    # Show dimensions
    for _, culvert in all_culverts.iterrows():
        name = culvert.get('CulvertName', 'Unknown')
        shape = culvert.get('ShapeName', 'Unknown')
        span = culvert.get('Span', 0)
        rise = culvert.get('Rise', 0)
        length = culvert.get('Length', 0)
        mannings = culvert.get('ManningsN', 0)
        print(f"\n  {name} ({shape}):")
        print(f"    Span: {span:.1f} ft, Rise: {rise:.1f} ft")
        print(f"    Length: {length:.1f} ft")
        print(f"    Manning's n: {mannings}")
        if 'UpstreamInvert' in all_culverts.columns:
            print(f"    US Invert: {culvert['UpstreamInvert']:.2f} ft, DS Invert: {culvert['DownstreamInvert']:.2f} ft")

Section 4: GeomInlineWeir - Inline Weir and Gate Extraction

The Example 12 project contains an inline weir structure with gate groups. GeomInlineWeir extracts the weir profile and gate configurations.

4.1 List Inline Weirs

Python
# Get the weir geometry file
weir_geom_row = weir_ras.geom_df.iloc[0]
weir_geom_path = Path(weir_geom_row['full_path'])

print(f"Geometry file: {weir_geom_path.name}")
print()

# List all inline weirs
weirs_df = GeomInlineWeir.get_weirs(weir_geom_path)

print(f"Inline weirs found: {len(weirs_df)}")
if len(weirs_df) > 0:
    display.display(weirs_df)

4.2 Weir Crest Profile

The weir profile defines the station-elevation points along the weir crest.

Python
if len(weirs_df) > 0:
    # Get the first weir's location
    weir = weirs_df.iloc[0]
    w_river = weir['River']
    w_reach = weir['Reach']
    w_rs = weir['RS']

    print(f"Weir at {w_river}, {w_reach}, RS {w_rs}")
    print()

    # Extract weir profile
    profile_df = GeomInlineWeir.get_profile(weir_geom_path, w_river, w_reach, w_rs)

    print(f"Profile points: {len(profile_df)}")
    display.display(profile_df)
else:
    print("No inline weirs found")
Python
# Visualize weir crest profile
if len(weirs_df) > 0 and len(profile_df) > 0:
    fig, ax = plt.subplots(figsize=(12, 5))

    ax.plot(profile_df['Station'], profile_df['Elevation'], 'b-o',
            linewidth=2, markersize=5, label='Weir Crest')
    ax.fill_between(profile_df['Station'], profile_df['Elevation'],
                    profile_df['Elevation'].min() - 5,
                    alpha=0.3, color='brown')

    ax.set_xlabel('Station (ft)', fontsize=11)
    ax.set_ylabel('Elevation (ft)', fontsize=11)
    ax.set_title(f'Inline Weir Crest Profile: {w_river}, {w_reach}, RS {w_rs}',
                 fontsize=13, fontweight='bold')
    ax.legend(fontsize=10)
    ax.grid(True, alpha=0.3)

    # Add statistics
    crest_len = profile_df['Station'].max() - profile_df['Station'].min()
    stats_text = '\n'.join([
        f'Points: {len(profile_df)}',
        f'Length: {crest_len:.0f} ft',
        f'Elev: {profile_df["Elevation"].min():.1f} - {profile_df["Elevation"].max():.1f} ft',
    ])
    ax.text(0.02, 0.98, stats_text, transform=ax.transAxes,
            verticalalignment='top',
            bbox=dict(boxstyle='round', facecolor='wheat', alpha=0.5), fontsize=10)

    plt.tight_layout()
    plt.show()

4.3 Gate Configurations

Inline weirs may include gate groups that control flow. Each gate group has opening stations defining where along the weir the gate is located.

Python
if len(weirs_df) > 0:
    # Extract gate configurations
    gates_df = GeomInlineWeir.get_gates(weir_geom_path, w_river, w_reach, w_rs)

    print(f"Gate groups found: {len(gates_df)}")
    if len(gates_df) > 0:
        print()
        # Show gate parameters (excluding OpeningStations list for cleaner display)
        display_cols = [c for c in gates_df.columns if c != 'OpeningStations']
        display.display(gates_df[display_cols])

        # Show opening stations for each gate group
        print("\nGate opening stations:")
        for _, gate in gates_df.iterrows():
            name = gate.get('GateName', gate.get('Name', f'Gate {_}'))
            stations = gate.get('OpeningStations', [])
            n_openings = gate.get('NumOpenings', len(stations) if isinstance(stations, list) else 0)
            print(f"  {name}: {n_openings} openings")
            if isinstance(stations, list) and len(stations) > 0:
                for i, sta in enumerate(stations):
                    print(f"    Opening {i+1}: station {sta}")
    else:
        print("No gates defined for this weir")
else:
    print("No inline weirs found")

Summary

Methods Demonstrated

GeomMetadata (geometry element counting): - get_geometry_counts(geom_path, hdf_path) - Quick inventory of all geometry elements

GeomBridge (bridge structures): - get_bridges() - List all bridges with river/reach/RS - get_deck() - Deck high chord and low chord geometry - get_piers() - Pier widths and elevations - get_coefficients() - Hydraulic loss coefficients - get_htab() / get_htab_dict() - Hydraulic table parameters - get_approach_sections() - BR U/BR D cross sections - get_abutment() - Abutment geometry

GeomCulvert (culvert structures): - get_all() - List all culverts across entire geometry file - get_culverts() - List culverts at a specific bridge/culvert structure

GeomInlineWeir (inline weirs and gates): - get_weirs() - List all inline weirs - get_profile() - Station-elevation weir crest profile - get_gates() - Gate group configurations with opening stations

Key Patterns

  • All classes use static methods (no instantiation needed)
  • Methods accept geometry file path as first argument
  • Bridge/culvert/weir methods require river, reach, RS to identify the structure
  • All methods return DataFrames for consistent data handling
CLB Engineering Corporation  ·  LLM Forward Engineering
RAS Commander is a free and open-source project maintained by CLB Engineering Corporation. For agencies and firms seeking to modernize H&H workflows with LLM Forward approaches, contact CLB to partner with the engineers who wrote the automation.