Basics
Overview
river-route routes catchment-scale runoff through a vector river network. All routing runs through the
Router class, and the kind of routing it performs is chosen with the forcing config selector:
forcing: channel: pure channel routing with no runoff entering the rivers. Routes an existing discharge state forward in time using only Muskingum channel equations. Requires an explicit initial state.forcing: catchment,grid, orecmwf_grib: routes the runoff volumes or depths offorcing_files, in that form, directly into river channel inlets at each timestep. This is the most common starting point.
This tutorial routes catchment runoff (forcing: catchment).
Vocabulary
- Region: a named group of catchments and channels forming a complete routing domain.
- Catchment: a subunit of a watershed. Water enters at one upstream location and exits at exactly one outlet.
- Depth first search (DFS) order: rivers sorted so that each river comes after every river upstream of it and the rivers upstream of a river are the rows immediately before it, so every river's upstream watershed is one contiguous range of rows ending at that river. The network file must be in DFS order.
Required Files
Three files are needed for a routing run:
- Network file (
network.parquet) — river network topology and Muskingum parameters. - Catchment runoff (
catchment_runoff.nc) — per-catchment runoff time series. - Routed discharge (
discharge.zarr) — output path where results will be written.
See the File Schemas reference for field names and formats.
Network File
The network file must contain at minimum these columns:
| Column | Description |
|---|---|
riverId |
Unique integer ID for each river segment |
nextRiverId |
ID of the downstream segment (-1 at outlets) |
muskingumK |
Muskingum K — travel time (seconds); typically channel length / wave speed |
muskingumX |
Muskingum X — attenuation factor (0 ≤ x ≤ 0.5) |
riverIndex |
Position of the river in DFS order |
upstreamCount |
Number of rivers upstream of the river, not counting itself |
Rows must be in DFS order, which riverIndex numbers: a river's upstream watershed is the rows from its
riverIndex minus its upstreamCount to its riverIndex. Every file with one entry per river, such as the catchment
runoff and channel state files, lists the rivers in this same order, and routing refuses one that does not. See the
File Schemas reference for how to check a table.
Config File
Config values are held by a frozen Configs object. Build it from keyword arguments or read it from a JSON
file with Configs.from_json, then pass it to Router. A Router takes its options from a Configs and
nowhere else, and a Configs is set once when it is built, so an option is changed by building the Configs you
want.
{
"network_file": "/path/to/network.parquet",
"forcing": "catchment",
"forcing_files": "/path/to/catchment_runoff.nc",
"discharge_dir": "/path/to/output/"
}
First Routing Run
import river_route as rr
configs = rr.Configs.from_json('config.json')
rr.Router(configs).route()
Or build the configs directly without a config file:
import river_route as rr
configs = rr.Configs(
network_file='network.parquet',
forcing_files=['catchment_runoff.nc', ],
discharge_dir='./output/',
forcing='catchment',
)
rr.Router(configs).route()
Or route a config file from the command line with rr route config.json. --threads 4 routes on 4 threads, and
rr route - reads the config as JSON from stdin. rr app opens a window that sets up, saves, and runs configs.
A Configs is set once, when it is built, and is frozen afterward. There is no method to copy one with an
option changed: build the Configs you want. Use configs.to_json(path) to write the
options to a file that Configs.from_json reads back, e.g. to prepare many jobs for a scheduler.
Warm-Starting Channel State
By default, the channel starts at zero discharge. Provide a state file to initialize from a previous run:
{
"network_file": "network.parquet",
"forcing": "catchment",
"forcing_files": "catchment_runoff.nc",
"discharge_dir": "output/",
"channel_state_init_file": "state.parquet",
"channel_state_final_file": "new_state.parquet"
}
The state file is a parquet with the columns riverId and Q, listing the rivers of the network file in the same
order. It has one row per sub-reach: one per river, except that each river use_subreaches splits into sub-reaches at
the routing step has its riverId repeated for every one of its sub-reaches. A final state file is written this way,
so it can start the next run routed at the same step. A state file with one row per river starts a run only when no
river is split at its step.
Reading the Output
The routed discharge output is a zarr store with dimensions riverId and time, named for its forcing file:
import xarray as xr
river_of_interest = 123456789
ds = xr.open_zarr('output/discharge_catchment_runoff.zarr')
series = ds['Q'].sel(riverId=river_of_interest).to_pandas()
# Save to CSV
series.to_csv('hydrograph.csv')
# Plot
series.plot()