Forcings
The river_route.forcings package holds the RunoffForcing classes, which aggregate runoff to catchments. There is one for each
forcing that routes runoff, and RUNOFF_CLASS_FOR_FORCING[configs.forcing] is the class the forcing of a Configs names:
forcing |
Class | Aggregates |
|---|---|---|
catchment |
CatchmentRunoffForcing |
nothing: its files are already aggregated to catchments, as volumes or depths |
grid |
GridRunoff |
grids with x and y dimensions, with a weight table |
ecmwf_grib |
ECMWFGribReducedGrid |
ECMWF GRIB files on a reduced gaussian grid, read with eccodes, with a weight table |
Gridded runoff is aggregated with a grid weight table built with the functions in river_route.forcings.weights. Build a GridRunoff with
GridRunoff(grid_weights_file, ...), or with GridRunoff.from_configs(configs) to read those same values off a Configs, which is how a Router
builds one. Only from_configs validates, since a directly built GridRunoff has no Configs to check.
Every class subclasses the abstract RunoffForcing, whose to_netcdf writes a catchment runoff array in the format CatchmentRunoffForcing reads.
The grid classes precompute that file from their grids with aggregate_to_file, so it can be routed later with forcing catchment. Routing the grids
directly is faster, because the aggregation then happens inside the routing kernel, except for cumulative or clipped runoff, which is aggregated
before it is routed.
Each class has a generator method that yields one (dates, runoff, source_file) tuple per input, with the runoff in the form routing reads:
CatchmentRunoffVolumes, or a GridCellRunoff whose grid cells routing reads as it routes each river. It can be used on its own, and a Router
builds the one its forcing names. See Customizing Runoff Inputs.
import river_route as rr
configs = rr.Configs.from_json('config.json')
runoff = rr.GridRunoff.from_configs(configs)
for dates, catchment_runoff, forcing_file in runoff.catchment_reader(configs.forcing_files):
... # each file's catchment runoff volumes, a (river, time) array overwritten by the next file
river_route.forcings.RunoffForcing
Bases: ABC
Base class for the sources of catchment runoff, the runoff volume each catchment adds to its river in each step.
Each subclass is built from a Configs with from_configs and provides a
generator that yields its runoff in the form routing reads, and every subclass writes catchment runoff to disk
with to_netcdf in the one format CatchmentRunoffForcing reads.
from_configs(configs)
abstractmethod
classmethod
Build this RunoffForcing from the options on a Configs, as a Router does.
generator(forcing_files)
abstractmethod
Yield one (dates, runoff, source_file) tuple per input, where runoff is what routing reads: its
CatchmentRunoffVolumes, or a GridCellRunoff whose grid cells routing reads as it routes. Each checks its arrays
with check.
distribute(network)
Match the runoff to a stabilized network, whose synthetic sub-reaches each need a share of their parent river's runoff. Classes that can split their runoff override this; the others raise when the network has rows they have no runoff for.
to_netcdf(path, dates, catchment_runoff, river_ids, catchment_area, *, as_volumes)
staticmethod
Write a catchment runoff array to a netCDF file that CatchmentRunoffForcing reads, routed as
forcing_files with forcing catchment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
path
|
PathInput
|
netCDF file to write |
required |
dates
|
DatetimeArray
|
(time,) datetime64 values of the steps |
required |
catchment_runoff
|
FloatArray
|
(n_rivers, time) depths (m) or volumes (m³), rows ordered like |
required |
river_ids
|
IntArray
|
(n_rivers,) river id of each row |
required |
catchment_area
|
FloatArray
|
(n_rivers,) area of each catchment in m², the factor between depths and volumes |
required |
as_volumes
|
bool
|
whether catchment_runoff holds volumes (m³) rather than depths (m), which sets the units
written. It is given with the array rather than read from a RunoffForcing's options, since
|
required |
river_route.forcings.BaseGridRunoffForcing
dataclass
Bases: RunoffForcing
Prepares catchment runoff for routing from gridded runoff depths and a grid weight table. Each subclass names the
weight table columns that locate a grid cell and the runoff dimension each one indexes, with cell_dimensions.
Only grid_weights_file is required. Every option is named the same here as it is on Configs, so
from_configs reads each one off a Configs by its own name, which is how a Router builds one.
Reading and indexing the weight table is the same work for every forcing file, so it is done once when the
runoff is created and reused for all of them. Each forcing file is read at the grid cells the table touches.
Routing reads each river's cells directly as it routes the river, and aggregate sums them onto rivers as area
weighted depths or volumes one river at a time, each river's series written straight into its row of a C-order
(river, time) array.
cell_dimensions
abstractmethod
property
Each weight table column that locates a grid cell, and the runoff dimension it indexes.
cumulative
abstractmethod
property
Whether the runoff values are totals accumulated from the start of each file, differenced into the runoff of each step, rather than the runoff of each step.
from_configs(configs)
classmethod
Build this grid runoff from the runoff options on a Configs. The configs are validated for runoff
before the weight table is read. Each option is read off the Configs by the field's own name, so a field with
no matching option raises AttributeError rather than silently keeping its default.
distribute(network)
Match the weight table to a stabilized network: each river's runoff volume is split between every row with it as parentRiverId, which is the river and the synthetic sub-reaches injected upstream of it, in proportion to their travel times, the share of the river each one is. Does nothing when the table already has a row per network row.
catchment_runoff(runoff_data, thread_pool=None, threads=1, *, as_volumes=None)
Read and aggregate runoff into one reused output buffer, so repeated calls allocate no new output. Reading the runoff still allocates its arrays on every call.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff_data
|
PathInput | list[PathInput]
|
path(s) to gridded forcing files |
required |
thread_pool
|
Executor | None
|
optional thread pool to aggregate on, used as given and never shut down here |
None
|
threads
|
int
|
number of river ranges to aggregate concurrently when |
1
|
as_volumes
|
bool | None
|
volumes (m³) rather than depths (m); None follows the |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[FloatArray, DatetimeArray]
|
(catchment runoff as a C-order (n_rivers, time) array, its time values). The array is a view of the reused buffer and is overwritten by the next call. |
catchment_reader(forcing_files, thread_pool=None, threads=1)
Aggregate gridded forcing files into catchment runoff volumes, one file at a time, with the weight table this
instance already holds. Routing does not use this: generator hands routing the grid cells to aggregate as
it routes.
Every file is aggregated into one reused C-order buffer: no per-file allocation of the output or copy of it. The yielded array is overwritten by the next file, so a consumer that needs to keep it must copy it.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forcing_files
|
PathList
|
gridded forcing files to aggregate |
required |
thread_pool
|
Executor | None
|
optional thread pool to aggregate on, used as given and never shut down here |
None
|
threads
|
int
|
number of river ranges to aggregate concurrently when |
1
|
Yields:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[DatetimeArray, FloatArray, PathInput]
|
(dates, catchment_runoff, source_file) per input file |
generator(forcing_files)
Read gridded forcing files for routing, which reads each river's grid cells as it routes the river, so no catchment runoff array is built. A file whose catchment runoff must be de-accumulated or clipped at zero needs each river's whole catchment runoff series first, so it is aggregated here and yielded as catchment runoff instead.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forcing_files
|
PathList
|
gridded forcing files to read |
required |
Yields:
| Name | Type | Description |
|---|---|---|
tuple |
RunoffGenerator
|
(dates, runoff, source_file) per input file, where runoff is a GridCellRunoff, or CatchmentRunoffVolumes when the file was aggregated here |
to_dataset(runoff_data, thread_pool=None, threads=1)
Read and aggregate runoff into a catchment runoff dataset that can be saved and routed as forcing_files
with forcing catchment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff_data
|
PathInput | list[PathInput]
|
path(s) to gridded forcing files |
required |
thread_pool
|
Executor | None
|
optional thread pool to aggregate on, used as given and never shut down here |
None
|
threads
|
int
|
number of river ranges to aggregate concurrently when |
1
|
Returns:
| Type | Description |
|---|---|
Dataset
|
xr.Dataset: catchment runoff with dimensions ( |
aggregate_to_file(runoff_data, path, thread_pool=None, threads=1)
Precompute the catchment runoff of gridded runoff and write it to a netCDF file that is routed as
forcing_files with forcing catchment. Routing the grids directly aggregates inside the routing
kernel instead, which is faster than routing a precomputed file.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff_data
|
PathInput | list[PathInput]
|
path(s) to gridded forcing files |
required |
path
|
PathInput
|
netCDF file to write |
required |
thread_pool
|
Executor | None
|
optional thread pool to aggregate on, used as given and never shut down here |
None
|
threads
|
int
|
number of river ranges to aggregate concurrently when |
1
|
read_runoff(runoff_data)
Read runoff at the grid cells the weight table touches.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff_data
|
PathInput | list[PathInput]
|
path(s) to gridded forcing files |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[FloatArray, DatetimeArray, float]
|
(runoff as a C-order (time, n_cells) float32 array, time values, factor converting the depth unit to meters) |
aggregate(runoff, conversion_factor=1, out=None, thread_pool=None, threads=1, *, as_volumes=None)
Aggregate gridded runoff depths onto rivers as area weighted depths or volumes, one river at a time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff
|
FloatArray
|
(time, n_cells) runoff depths at the weight table's cells, as returned by |
required |
conversion_factor
|
float
|
multiplier converting the runoff depth unit to meters |
1
|
out
|
FloatArray | None
|
optional flat buffer of at least n_rivers * time values, used as the output |
None
|
thread_pool
|
Executor | None
|
optional thread pool, used as given and never shut down here. The rivers are split into
|
None
|
threads
|
int
|
number of river ranges to aggregate concurrently when |
1
|
as_volumes
|
bool | None
|
volumes (m³) rather than depths (m); None follows the |
None
|
Returns:
| Name | Type | Description |
|---|---|---|
FloatArray |
FloatArray
|
catchment runoff as a C-order (n_rivers, time) array, a view of the start of |
GridRunoff
dataclass
Bases: BaseGridRunoffForcing
Runoff on a grid with x and y dimensions, such as a regular latitude and longitude grid, whose weight table
locates each cell with x_index and y_index.
cell_dimensions
property
The weight table columns x_index and y_index, and the runoff dimensions each indexes.
cumulative
property
Whether the runoff values are totals accumulated from the start of each file, as accumulation_type says.
CatchmentRunoffForcing
Bases: RunoffForcing
Reads runoff already aggregated to catchments and yields each as CatchmentRunoffVolumes (bases.py),
whose rows routing reads in place, a cell per river. It defines no form of runoff of its own.
A degenerate Runoff aggregator class which only allows for iterating over files already converted to
catchment level volumes or depths.
generator(forcing_files)
Read catchment runoff straight from netCDF files, one at a time.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
forcing_files
|
PathList
|
netCDF files each holding |
required |
Yields:
| Name | Type | Description |
|---|---|---|
tuple |
RunoffGenerator
|
(dates, catchment_runoff, source_file) per input file, the runoff as CatchmentRunoffVolumes: C-order (n_rivers, time) volumes (m³) with NaN replaced by zero |
from_configs(configs)
classmethod
Build a CatchmentRunoffForcing. The catchment runoff file schema is fixed, so no option of the Configs is
read.
ECMWFGribReducedGrid
dataclass
Bases: BaseGridRunoffForcing
ECMWFGribReducedGrid reads runoff depths from ECMWF GRIB files on a reduced gaussian grid with eccodes, the
forcing ecmwf_grib. It is a BaseGridRunoffForcing (bases.py) that overrides only read_runoff, so it yields
the same GridCellRunoff, or CatchmentRunoffVolumes when a file must be aggregated first, routed with the
overloads registered in bases.py. It defines no form of runoff or overload of its own. ReducedGaussianGrid is
the grid of those files, read from their metadata, on which reduced_grid_weights (weights.py) builds a weight
table.
Runoff from ECMWF GRIB files on a reduced gaussian grid, such as the octahedral O1280 grid of the IFS, with a
weight table made by reduced_grid_weights that locates each cell by its cell_index, its position in the
values of a GRIB message. This reader is specialized to that format; runoff in any other form must be prepared as
catchment or grid forcing.
GRIB files are read with eccodes one message at a time: each message is decoded whole and only the weight table's cells are kept. At the 18,721 cells of the Columbia River basin in a 145 step O1280 forecast that took 0.72 s with memory for one message, where xarray and cfgrib took 5.8 s and built the whole (step, cell) array in 4 GB.
Every message whose shortName is var_runoff is read as one time step, in the order of the files and of
their messages, at its validity date and time. Choosing files whose grid, ensemble member, and steps suit the
weight table and the routing is left to the caller. A GRIB message has no named dimensions, so var_t is not
read: each cell is taken by its position in the message's values. IFS forecast runoff accumulates from the start of
the forecast, so it is always differenced into the runoff of each step and has no accumulation_type option.
cumulative
property
Always: IFS forecast runoff is the total accumulated from the start of the forecast.
cell_dimensions
property
The weight table column cell_index, the position of each cell in the values of a GRIB message. A message has
no named dimensions, so the column names itself: read_runoff takes each cell by its position.
read_runoff(runoff_data)
Read runoff at the grid cells the weight table touches from GRIB files, in the order of their messages.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
runoff_data
|
PathInput | list[PathInput]
|
path(s) to GRIB files |
required |
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[FloatArray, DatetimeArray, float]
|
(runoff as a C-order (time, n_cells) array, validity times, factor converting the depth unit to meters) |
ReducedGaussianGrid
Bases: NamedTuple
A global reduced gaussian grid: 2N rows of cells at the gaussian latitudes, north to south, each row's cells evenly spaced in longitude eastward from 0 degrees. Cells are numbered row after row, the order of the values in a GRIB message.
n_cells
property
The number of cells of the whole grid, the number of values in a GRIB message on it.
spacing_km
property
Side of a square with the mean cell area, the resolution ECMWF quotes, e.g. 9 km for O1280.
from_grib(grib_path)
classmethod
Read the grid from the metadata of the first message in a GRIB file.
cell_polygons(bounds=None)
The area each cell represents, as a box of longitude and latitude in EPSG:4326 with longitudes from -180 to 180: halfway in longitude to the cells beside it, and in latitude between the edges of its row. The edges split the sphere into bands whose areas are the gaussian quadrature weights of the rows, so each cell covers the share of the sphere the grid's own quadrature weights its value by.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
bounds
|
tuple[float, float, float, float] | None
|
optional (min x, min y, max x, max y) in degrees; only the cells overlapping it are made. Without it every cell of the world is made. |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
gpd.GeoDataFrame: one row per cell, in cell order: cell_index (position in a GRIB message's values), x and y (the cell center longitude and latitude), and geometry |
river_route.forcings.CatchmentRunoffVolumes
Bases: NamedTuple
Each river's catchment runoff volume (m³) in every runoff step, as a C-order (river, time) array whose rows routing reads in place.
check(river_ids, n_steps)
Raise ValueError unless it holds n_steps steps for river_ids in that order, each river's row
contiguous.
cells_and_weights()
The runoff as the routing kernels read it: a cell per river, its own volumes, each weighted by one.
river_route.forcings.GridCellRunoff
Bases: NamedTuple
One file of gridded runoff depths read at the weight table's grid cells, with the weights that turn them into each river's catchment runoff volume as routing routes it.
check(river_ids, n_steps)
Raise ValueError unless the weight table describes river_ids in that order and the runoff has n_steps
steps.
cells_and_weights()
The runoff as the routing kernels read it: the depths of the cells, and each river's weights of them.
river_route.forcings.weights
cell_xy_from_regular_grid(dataset, var_x='lon', var_y='lat')
The cell center x and y coordinates of a regular grid.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
dataset
|
PathInput
|
netCDF, or any file xarray opens, holding the grid's 1D x and y coordinate variables |
required |
var_x
|
str
|
name of the x coordinate variable |
'lon'
|
var_y
|
str
|
name of the y coordinate variable |
'lat'
|
Returns:
| Name | Type | Description |
|---|---|---|
tuple |
tuple[ndarray, ndarray]
|
(x, y), the 1D coordinate arrays |
Raises:
| Type | Description |
|---|---|
KeyError
|
if either variable is missing |
ValueError
|
if either coordinate is not 1D |
voronoi_diagram_from_regular_xy(x, y, crs=4326)
The Voronoi polygon around the center of each cell of a regular grid, with the cell's center and its position in the x and y coordinates.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
x
|
ndarray
|
(n_x,) cell center x coordinates |
required |
y
|
ndarray
|
(n_y,) cell center y coordinates |
required |
crs
|
int
|
EPSG code of the coordinates |
4326
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
gpd.GeoDataFrame: one polygon per cell with columns x, y, x_index, and y_index, sorted by x and y |
Raises:
| Type | Description |
|---|---|
ValueError
|
if x or y is not a non-empty 1D array |
compute_voronoi_catchment_intersects(voronoi_gdf, catchments_gdf, save_path=None, attributes=None)
The weight table of the intersections between grid cell Voronoi polygons and catchments: the area of each catchment inside each cell and that area's proportion of the catchment.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
voronoi_gdf
|
GeoDataFrame
|
cell polygons with columns x_index, y_index, x, and y, as from voronoi_diagram_from_regular_xy |
required |
catchments_gdf
|
GeoDataFrame
|
catchment polygons with a riverId column |
required |
save_path
|
PathInput | None
|
optional netCDF file to save the weight table to |
None
|
attributes
|
dict | None
|
optional attributes added to the saved file |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: columns riverId, x_index, y_index, x, y, area_sqm, area_sqm_total, and proportion |
Raises:
| Type | Description |
|---|---|
KeyError
|
if a required column is missing |
grid_weights(grid_path, catchments_path, *, var_x='lon', var_y='lat', crs=4326, save_voronoi_path=None, save_weights_path=None, network_path=None)
Compute the grid weights for a given grid and catchments.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grid_path
|
PathInput
|
path to a NetCDF file containing the grid's 1D x and y coordinate variables |
required |
catchments_path
|
PathInput
|
path to a GeoParquet file containing the catchment geometries and a riverId column |
required |
var_x
|
str
|
x-coordinate variable name in the grid file |
'lon'
|
var_y
|
str
|
y-coordinate variable name in the grid file |
'lat'
|
crs
|
int
|
EPSG code for the grid coordinate reference system (default: 4326) |
4326
|
save_voronoi_path
|
PathInput | None
|
optional path to save the Voronoi polygons as a GeoParquet file |
None
|
save_weights_path
|
PathInput | None
|
optional path to save the grid weights as a NetCDF file |
None
|
network_path
|
PathInput | None
|
optional path to a network file whose riverId column order is used to topologically sort the weight table rows. When omitted the row order is spatial (not topological). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: a DataFrame containing the grid weights with columns ['riverId', 'x_index', 'y_index', 'x', 'y', 'area_sqm', 'area_sqm_total', 'proportion']. The saved file omits area_sqm_total. |
reduced_grid_weights(grib_path, catchments_path, *, save_weights_path=None, network_path=None)
Compute the grid weights of the cells of a reduced gaussian grid GRIB file, read by ECMWFGribReducedGrid: the
area of each catchment inside each cell polygon of ReducedGaussianGrid.cell_polygons that overlaps it.
Every part of a cell polygon is a rectangle of longitude and latitude, and stays a rectangle in the cylindrical equal-area projection, so the catchments are projected to it once and cut to each cell with the rectangle clipping of GEOS, whose pieces are measured where they are. A general polygon overlay followed by projecting every piece took 40 s for the 58,358 catchments of the Columbia (39.9 million vertices) on O1280, and this takes 6 s.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
grib_path
|
PathInput
|
GRIB file whose first message's grid is used |
required |
catchments_path
|
PathInput
|
GeoParquet file of the catchment polygons with a riverId column, in any CRS |
required |
save_weights_path
|
PathInput | None
|
optional path to save the grid weights as a netCDF file |
None
|
network_path
|
PathInput | None
|
optional path to a network file whose riverId column order is used to topologically sort the weight table rows. When omitted the row order is spatial (not topological). |
None
|
Returns:
| Type | Description |
|---|---|
DataFrame
|
pd.DataFrame: the grid weights with columns [riverId, cell_index, x, y, area_sqm, area_sqm_total, proportion] |