River-Network Steps and Tasks

This section of the Developer’s guide covers the code for the unified-mesh river workflow. The River-network tasks section describes the user-facing behavior, configuration options, algorithms, and output products.

The polaris.tasks.mesh.spherical.unified.river package separates the river workflow into source-level simplification, target-grid rasterization, optional diagnostics, and downstream base-mesh conditioning. The package makes available functions that return a dict of shared steps and thin task wrappers so the same implementation can be reused by standalone inspection tasks and by downstream unified-mesh tasks.

Available tasks

The helper polaris.tasks.mesh.spherical.unified.river.add_river_tasks() registers one standalone task for each mesh name in polaris.mesh.spherical.unified.UNIFIED_MESH_NAMES:

polaris.tasks.mesh.add_tasks.add_mesh_tasks() wires these tasks into the mesh component after the generic base-mesh tasks (see Base Mesh Task) and the shared coastline tasks (see Coastline Steps and Tasks).

Task structure and shared steps

UnifiedRiverNetworkTask layers shared dependencies in a fixed order:

  1. It requests the shared lat-lon topography step (see Combine Steps and Tasks) from polaris.tasks.e3sm.init.topo.combine.steps.get_lat_lon_topo_steps() and exposes it as combine_topo.

  2. It requests the shared coastline steps (see Coastline Steps and Tasks) from polaris.tasks.mesh.spherical.unified.coastline.get_unified_mesh_coastline_steps() and exposes them as coastline_compute and the optional viz_coastline_compute.

  3. It requests the full set of shared river steps from polaris.tasks.mesh.spherical.unified.river.get_unified_mesh_river_steps() and exposes polaris.tasks.mesh.spherical.unified.river.SimplifyRiverNetworkStep as river_simplify, polaris.tasks.mesh.spherical.unified.river.RasterizeRiverLatLonStep as river_rasterize, polaris.tasks.mesh.spherical.unified.river.VizRiverStep as viz_river_network, and polaris.tasks.mesh.spherical.unified.river.ClipRiverNetworkStep as river_clip.

This ordering keeps the river implementation dependent only on the explicit products it consumes: simplified HydroRIVERS geometry and the selected coastline dataset.

The shared-step factory in steps.py is the main extension point for other task families:

Implementation map

simplify.py

SimplifyRiverNetworkStep.setup() registers the HydroRIVERS archive as an input file through add_input_file() using the public URL from river_network.cfg. SimplifyRiverNetworkStep.run() unpacks the archive, simplifies the source network, and writes:

  • simplified_river_network.geojson

The public helper polaris.tasks.mesh.spherical.unified.river.simplify_river_network_feature_collection() contains the source-level retention logic. It builds canonical segments, validates downstream topology, and traverses upstream from all retained HydroRIVERS terminal segments while preserving main stems and significant tributaries. The resulting segment metadata keeps outlet_hyriv_id as basin-root provenance for future catchment grouping, and adds outlet_drainage_area and river_network_rank so individual networks can be selected from the single GeoJSON output without default per-network files.

The tributary-selection logic compares each non-primary tributary’s drainage area against the terminal root’s drainage area (not the primary upstream tributary’s area), matching the reference point used by the standalone implementation. Strahler stream-order-1 headwater tributaries skip the drainage-area check entirely and proceed directly to the distance-based fallback, consistent with the standalone’s more aggressive headwater pruning.

The internal RiverSegment dataclass is the canonical representation used by the simplification helpers. It is intentionally not exported as part of the public API. read_river_segments_from_feature_collection() and river_segments_to_feature_collection() preserve outlet_hyriv_id, outlet_drainage_area, and river_network_rank during GeoJSON round trips, and ClipRiverNetworkStep carries these fields through coastline conditioning.

rasterize.py

RasterizeRiverLatLonStep.setup() links the simplified source product and the selected coastline NetCDF file. RasterizeRiverLatLonStep.run() reads those inputs, calls polaris.tasks.mesh.spherical.unified.river.build_river_network_dataset(), and writes:

  • river_network.nc

build_river_network_dataset() is the public target-grid helper. It rasterizes river channels and writes river_channel_mask on the shared lat-lon grid. Outlet snapping and coastline reconciliation are deferred until after an MPAS base mesh exists.

clip.py

ClipRiverNetworkStep is produced through get_unified_mesh_river_steps() and is included in UnifiedRiverNetworkTask as well as downstream unified base-mesh consumers.

Its run() method reads the simplified river network and coastline products, then conditions the retained geometry for direct base-mesh use by:

  • densifying each river line at the coastline-grid scale before evaluating the selected coastline signed-distance field;

  • clipping only the line portions that fall inside the configured coastal exclusion band, with threshold-crossing points interpolated along each sampled line interval;

  • preserving all valid inland pieces, including multiple pieces from one HydroRIVERS feature when the line exits and re-enters the exclusion band;

  • simplifying clipped geometry and falling back to the unsimplified clipped piece if simplification would make it degenerate;

  • removing only degenerate pieces with fewer than two distinct points;

  • regenerating a diagnostic lat-lon mask product from the conditioned river geometry.

This step writes clipped_river_network.geojson and clipped_river_network.nc.

The min_segment_length_m helper argument and base_mesh_min_segment_length_km config option are retained for compatibility, but they no longer prune valid inland river geometry. This avoids artificial gaps far inland from the coastline.

viz.py

VizRiverStep is a pure diagnostic consumer of the shared source, coastline, lat-lon river, and clipped river products. It writes river_network_overlay.png, rasterized_river_network.png, and debug_summary.txt, and keeps visualization logic out of the numerical steps.

Configuration plumbing

All shared river steps use mesh-specific configs built through polaris.mesh.spherical.unified.get_unified_mesh_config(). That loader combines:

  • the generic unified_mesh.cfg defaults;

  • the shared river_network.cfg file; and

  • the selected named-mesh config file.

SimplifyRiverNetworkStep consumes [river_network] options and, when drainage_area_threshold or branch_distance_tolerance is set to the sentinel value -1, reads land_background_km and river_channel_km from [sizing_field] to derive the threshold values automatically.

RasterizeRiverLatLonStep consumes [river_rasterize] options and also reads the selected unified_mesh settings such as mesh_name and resolution_latlon. It reads antarctic_boundary_convention from [spherical_mesh] when selecting the coastline product.

ClipRiverNetworkStep consumes [river_network] and unified_mesh settings, and also reads antarctic_boundary_convention from [spherical_mesh] when selecting the coastline product for clipping.

VizRiverStep consumes [viz_river_network].dpi.

Extension points

Common extension paths for future development are:

  • adding a new named unified mesh by creating a new config file under polaris.mesh.spherical.unified; task registration discovers mesh names from those config files automatically;

  • extending source-level metadata or retention rules in simplify_river_network_feature_collection() and the associated GeoJSON property builders;

  • extending the target-grid output contract in build_river_network_dataset() and the visualization step; and

  • adjusting downstream coastline-aware conditioning in ClipRiverNetworkStep without creating a separate river-processing code path for base meshes.

The public API in this package is intentionally narrow: task wrappers, shared-step factories, step classes, and the two reusable dataset-building helpers. Most geometry and graph utilities remain private so they can evolve without breaking downstream callers.

Test coverage

Unit tests in tests/mesh/spherical/unified/test_river.py currently cover:

  • source-level terminal-root traversal, deep main-stem traversal, tributary retention, and cycle detection;

  • HydroRIVERS archive unpacking and shapefile-to-GeoJSON conversion helpers;

  • target-grid river-channel raster contracts;

  • coastline-aware base-mesh conditioning helpers, including local clipping, re-entry through the exclusion band, densification before signed-distance sampling, preservation of short inland pieces, and shared-step factories;

  • and task registration for all named unified meshes.

There is not yet a full task-level integration test that runs the end-to-end river workflow on real HydroRIVERS data inside the documentation build or unit test suite.