Skip to content

1D Breakout Extraction

RasBreakout1D creates independent, steady-flow HEC-RAS projects from either a continuous slice of one 1D river reach or an accepted chain of adjacent source reaches. Selection and extraction are separate, so a station range, polygon, network edge, or explicit cross-section set can feed the fail-closed writers and validation workflow.

Catalog multiple source models

Use catalog_sources() before planning a network edge that may cross more than one source project. Caller-defined IDs remain stable throughout coverage, seam, and later geometry-provenance tables.

Python
from ras_commander import RasBreakout1D

catalog = RasBreakout1D.catalog_sources(
    {
        "ble-upstream": upstream_ras,
        "ble-downstream": downstream_ras,
    },
    model_footprints=footprints_gdf,
    analysis_crs="EPSG:2277",
)

catalog.write("working/ble-breakout-source-catalog")

source_models values must be initialized RasPrj instances with steady 1D plans. The returned Breakout1DSourceCatalog contains:

Property Contents
models_df Project/plan/geometry/flow paths, exact geometry hash, units, version, and profile schema
footprints_gdf Deduplicated model footprints used by extent-first coverage
centerlines_gdf River centerlines with globally unique composite reach IDs
cross_sections_gdf GIS cut lines with globally unique composite XS IDs

Supplied footprints are authoritative. Without them, the catalog prefers the geometry-HDF footprint and falls back to the convex hull of legacy text-file centerlines and cut lines. A projected CRS is required. Exact duplicate geometry hashes remain visible in models_df through duplicate_of but are excluded from the spatial tables by default.

Catalog persistence uses one ordinary Parquet table and three GeoParquet tables; it does not require GeoPackage, SQLite, or a long-running database. Load it later with Breakout1DSourceCatalog.read().

Plan one network edge across multiple models

plan_network_edge() turns model footprints into candidates, then confirms the best source reach in each model with deliberately small, interpretable checks: at least two cross-section intersections, station sequence agreement with the directed edge, an optional mean centerline-offset limit, and a fail-closed cross-model handoff check. It does not invoke the advanced seven-signal conflation scorer.

Python
plan = RasBreakout1D.plan_network_edge(
    catalog,
    nwm_flowlines_gdf,
    edge_id="5790954",
    adapter="nwm",
    max_centerline_offset=500.0,
    max_cross_centerline_xs=1,
)

print(plan.status)
print(plan.reach_assignments_df)
print(plan.source_slices_df)
print(plan.seams_df)
print(plan.handoff_diagnostics_df)

The Breakout1DPlan keeps the full audit trail:

Property Contents
reach_assignments_df Best reach per footprint candidate, confirmation status, and reason codes
edge_coverage Directed coverage parts after rejecting footprint-only false positives
source_slices_df Minimum-switch upstream-to-downstream source ownership intervals
seams_df Overlap, touching, or gap transitions with a provisional handoff point
handoff_diagnostics_df Centerline continuity plus IDs and counts of source cross sections intersecting both selected centerlines
source_models_df Distinct source projects selected by the interval plan, ordered upstream to downstream

The network geometry's coordinate order defines direction. Cross-section river stations must decrease as edge measure increases; a conflicting sequence is rejected explicitly. A footprint can contribute multiple disconnected coverage slices, so plan output reports both distinct model count and slice count.

For every cross-model seam, plan_network_edge() checks the complete source cross-section sets against both selected centerlines. The default permits at most one cross section to intersect both lines. Two or more produce plan.status == "multi_source_handoff_conflict", plan.join_ready == False, and the reason code MULTIPLE_XS_INTERSECT_BOTH_CENTERLINES. This rejects the former overlapping tributary/main-stem example before a writer can treat it as an adjacent source reach. Set max_cross_centerline_xs explicitly only when a different reviewed threshold is warranted.

An overlap midpoint is only an extent-planning seam. The geometry assembler chooses a centerline intersection or documented nearest connection, rejects intersecting cross-source cut lines, preserves complete geometry blocks and flow-change locations, reconciles stations, and validates profile, CRS, and units compatibility.

The joined reach must also carry explicit reach-length evidence. Run RasGeometryCompute.assess_flow_path_policy() on the provisional destination geometry before changing overbank lengths. A 1% comparison against flow paths regenerated on an isolated copy selects one of two policies:

  • regenerate_and_recompute when every usable regenerated LOB/ROB interval reproduces its stored source value within tolerance.
  • preserve_and_recompute_only_at_join_boundary when any interval fails, or when stored overbank lengths differ from channel lengths but no source flow paths span the reach.

Under the preserve policy, only the new interval between the join-adjacent cross sections is recomputed. The audit returns regenerated left/right segments clipped to those two cut lines so their geometry and measured length can be reviewed and stored as GeoParquet. Existing source flow-path lengths remain unchanged.

Run RasGeometryCompute.audit_main_channel_lengths() separately. Channel-length differences are informative centerline QA and do not by themselves authorize overwriting overbank routing evidence. After the final centerline is accepted, assemble_network_edge() restations all retained nodes, applies the selected flow-path evidence, recomputes channel and join intervals, and rewrites station-keyed flow and boundary references before validation and optional execution.

Assemble adjacent source reaches

assemble_network_edge() consumes the exact catalog, plan, and initialized source-model mapping used during planning:

Python
provisional = RasBreakout1D.assemble_network_edge(
    source_models,
    catalog,
    plan,
    "working/nwm-5790954-provisional",
    destination_river="NWM 5790954",
    destination_reach="Main",
)

assert provisional.validation.is_valid
display(provisional.station_map_gdf)
display(provisional.seams_gdf)

The writer resolves every provisional footprint seam to the source river centerlines. An actual intersection is preferred; otherwise it records and writes the shortest straight connector. It then:

  • assigns intersecting cross sections to the source owning each directed edge interval;
  • preserves each complete continuous source node slice, including intervening inline structures and all cross-section payload data;
  • rejects any retained cut lines from different sources that intersect;
  • clips and joins the river centerlines;
  • restations every retained cross section and structure from the new downstream terminus;
  • recomputes every main-channel interval along the joined centerline;
  • rewrites retained steady flow-change locations and endpoint boundary blocks to the destination river/reach; and
  • verifies source SHA-256 values before and after writing.

The public station_map_gdf is the authoritative block-level provenance table. It contains original and destination stations, written LOB/channel/ROB lengths, domain-membership flags, join-adjacent flags, and a hash of every unchanged node payload. seams_gdf records the actual join method, connector distance, source/destination join stations, and the three written join lengths. Both tables are suitable for direct map and QA figure generation.

Computation and raster-export buffers

When the planned network edge is fully covered, omitted buffer distances default to 10% upstream and 25% downstream of the assembled main-channel length across the directly intersected target span. Explicit upstream_buffer_distance and downstream_buffer_distance values override those defaults. Expansion stops at the available source terminus.

The geometry file contains the larger computational domain. In station_map_gdf, in_direct_domain identifies the strict target span and in_inundation_domain adds inundation_overlap_xs=1 downstream when another cross section is available. Requested and applied distances/counts are retained in station_map_gdf.attrs, allowing a later raster export to use the smaller domain without removing the hydraulic transition length.

Two-pass overbank finalization

A first call without flow_path_policy_results is intentionally provisional. It preserves source LOB/ROB lengths within each source and uses the newly computed channel distance for the cross-source interval. The validation report marks only flow_path_lengths_finalized as a warning; all structural checks must still pass.

Compile the provisional geometry, audit each join, then write a separate final destination with the evidence keyed by seam index:

Python
policy = RasGeometryCompute.assess_flow_path_policy(
    provisional_geom_hdf,
    tolerance_fraction=0.01,
    join_upstream_xs=(river, reach, upstream_join_rs),
    join_downstream_xs=(river, reach, downstream_join_rs),
    review_segments_path="working/join-0-flow-paths.parquet",
)

final = RasBreakout1D.assemble_network_edge(
    source_models,
    catalog,
    plan,
    "working/nwm-5790954-final",
    destination_river=river,
    destination_reach=reach,
    flow_path_policy_results={0: policy},
)

assert final.reach_lengths_finalized

For regenerate_and_recompute, audited regenerated LOB/ROB values replace every usable destination interval. For preserve_and_recompute_only_at_join_boundary, source overbank values remain unchanged and each join receives the measured left/right lengths from its two clipped review segments. Inputs must cover every seam and carry one consistent recommendation.

Distinguish inherited diagnostics from join defects

Compile the complete original source plans before interpreting diagnostics from the joined model. Compute into isolated destination folders with RasCmdr.compute_plan(..., dest_folder=...) so the source projects remain unchanged. For station-specific diagnostics, use station_map_gdf to map each destination river station back to source_geometry_id and source_station.

Non-station diagnostics such as an edge-line self-intersection require one more control: extract and compute each exact retained source slice independently, without a cross-model join. A diagnostic is pre-existing only when it reproduces in the corresponding complete source plan and retained slice. An unmatched diagnostic, or any diagnostic on either join-adjacent cross section, is treated as join/assembly-introduced and should be remediated before accepting the breakout.

Example 236 demonstrates this isolation on the Texas/FEMA Walnut Creek pair. It maps all six final cross-section diagnostics to the same original source stations and reproduces the edge-line diagnostic in the retained downstream slice. None is join-adjacent. The notebook therefore records those findings as inherited source QA while failing closed if the provenance assertions ever change.

Select a reach slice

Python
from ras_commander import RasBreakout1D

selection = RasBreakout1D.select_by_stations(
    source_geometry_file,
    river="White River",
    reach="Main Stem",
    upstream_station=24000,
    downstream_station=12000,
)

Other selectors accept:

  • a contiguous cross-section station set with select_by_cross_sections();
  • a Shapely polygon intersecting the desired cut lines with select_by_polygon();
  • a Shapely network edge and optional search tolerance with select_by_network_edge() (select_by_network_segment() is an alias).

Polygon and network geometries must use the geometry file's coordinate system. An omitted river/reach is accepted only when the selection resolves to exactly one reach.

Network-edge selection includes one additional downstream cross section by default. This matches Ripple1D's shared-boundary convention for reach-sized submodels and provides an internal target reach with a downstream boundary section. Pass downstream_overlap_xs=0 for the directly intersected span only, or a larger integer for a wider transition zone.

Optional upstream_buffer_distance and downstream_buffer_distance values expand a network-edge selection using the source geometry's main-channel reach lengths. These values use HEC-RAS reach-length/model units; by contrast, tolerance uses the network geometry's spatial coordinate-system units. Expansion retains the first cross section at or beyond each requested distance and stops at the available upstream or downstream terminus.

Separate computation and inundation domains

Use select_domains_by_network_edge() when a model should retain hydraulic transition length without expanding its published inundation footprint:

Python
domains = RasBreakout1D.select_domains_by_network_edge(
    source_geometry_file,
    target_edge.geometry,
    river="White River",
    reach="Main Stem",
    inside_fraction=target_edge.inside_fraction,
)

computation_selection = domains.computation_selection
inundation_selection = domains.inundation_selection

When inside_fraction indicates that the network edge is fully inside the model, omitted buffer distances default to 10% of the full source-reach main channel length upstream and 25% downstream. Explicit distances override those defaults independently. Partial edges receive no automatic distance buffer.

The inundation selection remains the directly intersected span plus one shared downstream cross section by default. The computation selection always contains that strict export selection and extends to the first cross section meeting each hydraulic buffer distance, or to the source-model terminus. This allows the larger project to absorb boundary effects while a later raster-clipping step uses the smaller, overlapping export domain.

inundation_overlap_xs records the requested overlap and inundation_overlap_xs_applied records how many downstream sections were available before the model terminus. The *_buffer_applied distances report only the hydraulic distance expansion; they do not count the strict raster overlap that is unioned into the computation selection.

Extract and validate

Python
result = RasBreakout1D.extract_selection(
    source_ras,
    r"D:\models\white_river_breakout",
    selection,
    plan_number="01",
    destination_name="white_river_breakout",
    boundary_mode="auto",
)

assert result.validation.is_valid
print(result.project_file)
print(result.boundary_provenance)

The destination receives its own .prj, .p01, .g01, and .f01 files and an initialized RasPrj. The geometry writer preserves complete retained cross-section and inline-structure blocks, including Manning's n, bank stations, levees, ineffective areas, blocked obstructions, and HTAB settings. It clips the one-reach Reach XY centerline to the retained boundary-section crossings, keeps upstream reach lengths, and zeroes the downstream retained section's L/Ch/R reach lengths because no downstream section remains. Steady-flow change locations inside the retained range are preserved, and the active source flow is propagated to the new upstream limit.

The validation dataframe checks project/plan/geometry/flow relationships, retained stations and blocks, flow profiles and change locations, reach-length termination, boundary data, and source-geometry immutability. Use result.validation.raise_for_errors() when validating separately.

Downstream boundaries

boundary_mode controls an internal downstream cut:

Mode Behavior
auto Use source steady-plan WSE results when available; otherwise preserve the source boundary and record the fallback.
source_results Require usable steady-plan HDF results and write known WSE values by profile.
preserve Preserve the source reach boundary.

Pass downstream_boundary={...} to explicitly override those modes with a boundary definition accepted by RasSteady.write_flow_file().

Run and compare

Execution is always routed through RasCmdr:

Python
compute_result = RasBreakout1D.run(result, verify=True)

geometry_delta = RasBreakout1D.compare_geometry(
    source_geometry_file,
    result.geometry_file,
    result.selection,
)

results_delta = RasBreakout1D.compare_results(
    source_plan_hdf,
    destination_plan_hdf,
    result.selection,
)

compare_geometry() reports exact retained cross-section payload agreement and whether intervening structure blocks match. compare_results() reports numeric differences at retained cross sections for matching steady profiles after both plans have completed.

MVP boundaries

The workflow intentionally fails closed for branched/multi-reach or junction assemblies, repeated non-contiguous source ownership, unsteady or sediment plans, lateral structures, cross-source cut-line intersections, incompatible profiles/units/CRSs, and selections with fewer than two cross sections. The single-source writer preserves its legacy clipping fallback. The multi-source writer requires usable GIS cut lines and one monotonic adjacent main-stem source chain.

RasBreakout1D

Static workflow for extracting or assembling a 1D steady breakout.

catalog_sources staticmethod

Python
catalog_sources(source_models: Mapping[str, RasPrj], *, plan_numbers: Optional[Mapping[str, Union[str, int]]] = None, model_footprints: Optional[GeoDataFrame] = None, analysis_crs: Optional[Any] = None, deduplicate: bool = True) -> Breakout1DSourceCatalog

Catalog steady 1D source models for network-edge planning.

source_models maps stable caller-defined model IDs to initialized :class:RasPrj objects. Exact duplicate geometry files are retained in models_df for provenance but excluded from the spatial catalog by default. Supplied footprints are authoritative. When omitted, an HDF project footprint is preferred and legacy text geometry falls back to the convex hull of its centerlines and cross-section cut lines.

The returned tables use one projected analysis_crs and can be persisted with :meth:Breakout1DSourceCatalog.write as GeoParquet.

plan_network_edge staticmethod

Python
plan_network_edge(source_catalog: Breakout1DSourceCatalog, network_edges: Union[GeoDataFrame, str, Path], *, edge_id: Optional[Union[str, int]] = None, adapter: Union[str, NetworkAdapter] = 'auto', network_edges_layer: Optional[str] = None, min_xs_intersections: int = 2, xs_tolerance: float = 0.0, max_centerline_offset: Optional[float] = None, gap_tolerance: float = 0.0, max_cross_centerline_xs: int = 1) -> Breakout1DPlan

Plan source-model coverage for one directed network edge.

Footprints generate candidate models. A model is retained only when one source reach has at least min_xs_intersections cut lines meeting the edge and those sections form a monotonic upstream-to-downstream sequence in edge-coordinate order. The extent-first coverage planner then chooses a deterministic minimum-switch model chain.

The returned seam points are provisional footprint handoffs. Geometry assembly must still evaluate source-centerline intersections, duplicate cut lines, structures, station identifiers, and steady-flow continuity.

A cross-model handoff fails closed when more than max_cross_centerline_xs source cross sections intersect both selected river centerlines. This prevents the overlapping tributary/main-stem geometry seen in invalid candidates from being treated as join-ready.

assemble_network_edge staticmethod

Python
assemble_network_edge(source_models: Mapping[str, RasPrj], source_catalog: Breakout1DSourceCatalog, breakout_plan: Breakout1DPlan, destination: Union[str, Path], *, destination_name: Optional[str] = None, destination_river: Optional[str] = None, destination_reach: str = 'Main', upstream_buffer_distance: Optional[float] = None, downstream_buffer_distance: Optional[float] = None, inundation_overlap_xs: int = 1, boundary_mode: str = 'auto', downstream_boundary: Optional[Mapping[str, Any]] = None, flow_path_policy_results: Optional[Mapping[int, 'FlowPathPolicyResult']] = None, run: bool = False, verify_run: bool = True, compute_kwargs: Optional[Mapping[str, Any]] = None) -> Breakout1DAssemblyResult

Write one restationed reach from adjacent source-model reaches.

The directed network edge and accepted source order come from breakout_plan. Cross sections intersecting the edge are assigned to the source owning that edge interval. When the edge is fully covered, omitted hydraulic buffers default to 10 percent upstream and 25 percent downstream of the assembled main-channel length represented by the direct target span. inundation_overlap_xs marks the smaller raster-export domain without reducing the computational model.

Complete retained node payloads are copied from the source geometries; only Type RM Length L Ch R is rewritten. Every retained node is restationed from the downstream end of the joined centerline, main channel lengths are recomputed from that line, and station-keyed steady flow changes are rewritten to the destination reach.

With no flow_path_policy_results, source LOB/ROB lengths are preserved within each source and the new cross-source interval uses its channel length provisionally. For a final geometry, pass one :class:FlowPathPolicyResult per seam, keyed by zero-based seam index, after auditing a provisional assembled geometry. Regenerate-policy evidence replaces all LOB/ROB lengths; preserve-policy evidence replaces only the corresponding join interval with the clipped review-segment lengths.

select_by_stations staticmethod

Python
select_by_stations(geom_file: Union[str, Path], river: str, reach: str, upstream_station: Union[str, float, int], downstream_station: Union[str, float, int]) -> Breakout1DSelection

Select every cross section between inclusive station bounds.

select_by_cross_sections staticmethod

Python
select_by_cross_sections(geom_file: Union[str, Path], river: str, reach: str, stations: Sequence[Union[str, float, int]]) -> Breakout1DSelection

Resolve a supplied, contiguous cross-section set on one reach.

Non-contiguous sets fail closed. A later multi-segment workflow can define how gaps should be reconnected without weakening this contract.

select_by_polygon staticmethod

Python
select_by_polygon(geom_file: Union[str, Path], polygon: Any, *, river: Optional[str] = None, reach: Optional[str] = None) -> Breakout1DSelection

Select the continuous reach span whose XS cut lines intersect a polygon.

The polygon and geometry cut lines must use the same coordinate system. If river and reach are omitted, the intersections must resolve to exactly one reach.

select_by_network_edge staticmethod

Python
select_by_network_edge(geom_file: Union[str, Path], network_edge: Any, *, river: Optional[str] = None, reach: Optional[str] = None, tolerance: float = 0.0, downstream_overlap_xs: int = 1, upstream_buffer_distance: float = 0.0, downstream_buffer_distance: float = 0.0) -> Breakout1DSelection

Select the continuous XS span associated with a network edge.

network_edge must be a Shapely-like line in the geometry coordinate system. A positive tolerance buffers it in spatial coordinate-system units before testing intersections. Optional upstream and downstream buffer distances use HEC-RAS main-channel reach-length/model units. The first cross section at or beyond each requested distance is retained, or selection stops at the reach terminus.

By default, the selection also includes the next cross section downstream of the distance-buffered span. That shared boundary section preserves the Ripple1D breakout convention and gives an internal reach a usable downstream boundary.

Set downstream_overlap_xs=0 to retain only directly intersected cross sections. Values greater than one are supported for workflows that need a wider shared transition zone; the selection stops at the source reach boundary when fewer downstream sections are available.

select_domains_by_network_edge staticmethod

Python
select_domains_by_network_edge(geom_file: Union[str, Path], network_edge: Any, *, river: Optional[str] = None, reach: Optional[str] = None, tolerance: float = 0.0, inside_fraction: Optional[float] = None, upstream_buffer_distance: Optional[float] = None, downstream_buffer_distance: Optional[float] = None, upstream_buffer_fraction: float = 0.1, downstream_buffer_fraction: float = 0.25, inundation_overlap_xs: int = 1, fully_inside_tolerance: float = 1e-09) -> Breakout1DDomainSelection

Resolve nested hydraulic-computation and raster-export domains.

Explicit buffer distances override percentage defaults independently. When inside_fraction indicates that the network edge is fully inside the model, omitted distances default to 10% upstream and 25% downstream of the source reach's main-channel length. For partial edges, omitted distances resolve to zero. Expansion stops at the available reach termini.

The inundation selection requests inundation_overlap_xs shared downstream cross sections and reports how many were available. The computation selection always contains that strict export selection, even when no hydraulic distance buffer is requested.

select_network_edge_domains staticmethod

Python
select_network_edge_domains(geom_file: Union[str, Path], network_edge: Any, *, river: Optional[str] = None, reach: Optional[str] = None, tolerance: float = 0.0, inside_fraction: Optional[float] = None, upstream_buffer_distance: Optional[float] = None, downstream_buffer_distance: Optional[float] = None, upstream_buffer_fraction: float = 0.1, downstream_buffer_fraction: float = 0.25, inundation_overlap_xs: int = 1, fully_inside_tolerance: float = 1e-09) -> Breakout1DDomainSelection

Compatibility alias for :meth:select_domains_by_network_edge.

select_by_network_segment staticmethod

Python
select_by_network_segment(geom_file: Union[str, Path], segment: Any, *, river: Optional[str] = None, reach: Optional[str] = None, tolerance: float = 0.0, downstream_overlap_xs: int = 1, upstream_buffer_distance: float = 0.0, downstream_buffer_distance: float = 0.0) -> Breakout1DSelection

Alias for :meth:select_by_network_edge.

extract_reach staticmethod

Python
extract_reach(source_ras: RasPrj, destination: Union[str, Path], river: str, reach: str, upstream_station: Union[str, float, int], downstream_station: Union[str, float, int], *, plan_number: Optional[Union[str, int]] = None, destination_name: Optional[str] = None, boundary_mode: str = 'auto', source_plan_hdf: Optional[Union[str, Path]] = None, downstream_boundary: Optional[Mapping[str, Any]] = None, run: bool = False, verify_run: bool = True, compute_kwargs: Optional[Mapping[str, Any]] = None) -> Breakout1DResult

Extract one station-bounded reach into a new steady project.

extract_selection staticmethod

Python
extract_selection(source_ras: RasPrj, destination: Union[str, Path], selection: Breakout1DSelection, *, plan_number: Optional[Union[str, int]] = None, destination_name: Optional[str] = None, boundary_mode: str = 'auto', source_plan_hdf: Optional[Union[str, Path]] = None, downstream_boundary: Optional[Mapping[str, Any]] = None, run: bool = False, verify_run: bool = True, compute_kwargs: Optional[Mapping[str, Any]] = None) -> Breakout1DResult

Extract a previously resolved one-reach selection.

boundary_mode='auto' uses source-plan WSE at an internal downstream cut when results exist, otherwise preserves the source reach boundary and records that fallback in the result. 'source_results' requires a usable steady plan HDF. 'preserve' always keeps the source reach boundary. downstream_boundary overrides all three modes.

validate staticmethod

Python
validate(source_ras: RasPrj, destination_ras: RasPrj, selection: Breakout1DSelection, *, source_plan_number: Optional[Union[str, int]] = None, source_geometry_sha256: Optional[str] = None, boundary_provenance: str = 'unknown') -> Breakout1DValidationReport

Validate project links, retained blocks, reach lengths, and flow data.

run staticmethod

Python
run(breakout: Union[Breakout1DResult, Breakout1DAssemblyResult, RasPrj], *, plan_number: Union[str, int] = '01', verify: bool = True, **compute_kwargs: Any) -> Any

Explicitly run a destination breakout through RasCmdr.

compare_geometry staticmethod

Python
compare_geometry(source_geometry: Union[str, Path], destination_geometry: Union[str, Path], selection: Breakout1DSelection) -> pd.DataFrame

Compare complete retained source/destination geometry node blocks.

compare_results staticmethod

Python
compare_results(source_plan_hdf: Union[str, Path], destination_plan_hdf: Union[str, Path], selection: Breakout1DSelection) -> pd.DataFrame

Compare steady results at retained sections and return numeric deltas.