riverarchitect.riverbuilder

River Builder: synthetic river valleys from a handful of parameters.

The open-source replacement for the River Builder half of the ArcGIS ModifyTerrain module. The other half - threshold-based grading and widening of an existing DEM - is riverarchitect.terraforming; this one starts from nothing and invents a valley.

Why invent a valley

A restoration design needs a target, and a reach that has been channelised for a century no longer contains one. River Builder generates a reach with the geometry a natural one would have - a meandering centreline, a thalweg that rises and falls, bankfull width that varies along it, floodplain and terrace benches - from parameters an engineer can defend: valley slope, bankfull width and depth, median grain size, and a critical Shields stress. The result is a DEM you can run a 2D model over and compare against the existing one.

The construction

  1. A centreline along the valley, displaced sideways by the meandering function. Its arc length s and the sinuosity follow from that displacement.

  2. Bankfull width at each station, from the width function about the base width, floored at the minimum width.

  3. Curvature, from the curvature function - and, for an asymmetric cross-section, the analytical curvature of the sine that defines it.

  4. Channel slope by least squares of curvature against arc length, plus valley slope over sinuosity. When the bankfull depth is not given it follows from it: H = 165 * D50 * tau_cr / S - a regime relation, so depth is not a free parameter.

  5. Thalweg and bank tops at each station, and a cross-section between them: a symmetric U, an asymmetric U leaning into the bend, or a trapezoid.

  6. Floodplain and terrace benches either side, offset by their own functions.

  7. The points are triangulated onto a regular grid, clipped to the valley boundary, and written as a GeoTIFF.

Relation to the original

The original called an R script (riverbuilder.r, 1189 lines) through rpy2, wrote a point cloud to CSV, and rebuilt it as a TIN and a raster with arcpy 3D Analyst - so it needed R, rpy2, ArcGIS and a 3D Analyst licence. The geometry is reproduced here in numpy and the interpolation is scipy.interpolate.LinearNDInterpolator, which is what a TIN is. Nothing else is required.

The parameter file format is unchanged, so an existing RiverBuilder input runs as it is.

class riverarchitect.riverbuilder.RiverBuilder(parameters, unit='si', seed=0)[source]

Bases: object

Build a synthetic river valley from a RiverBuilderInput.

Parameters:
  • parameters (RiverBuilderInput or str) – the parameters, or a file to read them from.

  • unit (str) – "us" or "si". The parameter file is metric by convention; this only labels the result.

  • seed (int) – seed for the PERL noise, so a run is reproducible.

Variables:

error (bool) – True when a step could not be completed.

build()[source]

Compute the whole geometry.

Returns:

the station arrays (x, centreline, arc_length, bankfull_width, curvature, thalweg, top_of_bank) and the cross-section grids (xs_x, xs_y, xs_z).

Return type:

dict

point_cloud()[source]

Every generated point as (x, y, z) columns.

to_raster(path=None, cell_size=1.0, crs=None)[source]

Triangulate the point cloud onto a regular grid and write a GeoTIFF.

The original built an ArcGIS TIN and sampled it, which needed a 3D Analyst licence. A Delaunay triangulation with linear interpolation is the same surface; scipy.interpolate.LinearNDInterpolator returns NaN outside the convex hull, which is exactly the clip to the valley boundary that the original did with a soft-clip polygon.

Parameters:
  • path (str) – where to write. Optional; without it only the array is returned.

  • cell_size (float) – output resolution in the parameters’ length unit.

  • crs (str) – coordinate reference system to tag the raster with.

Returns:

(dem, profile).

Return type:

tuple

run(output_dir=None, cell_size=1.0, crs=None, write_points=True)[source]

Build the valley and write the DEM, a hillshade and the point cloud.

Returns:

the geometry summary and the paths written.

Return type:

dict

class riverarchitect.riverbuilder.RiverBuilderInput(**overrides)[source]

Bases: object

The parameters of a synthetic valley, and the file they are read from and written to.

Every attribute has a default, so a partially specified input still builds. Sub-reach variability parameters hold a list of UserFunction, which are summed.

Parameters:

**overrides – any attribute in DEFAULTS, or a variability parameter as a list of UserFunction.

VARIABILITY = ('meander', 'curvature', 'width_function', 'thalweg', 'left_floodplain', 'right_floodplain')

The variability parameters, and the independent variable each is evaluated against.

classmethod read(path)[source]

Read a RiverBuilder input file in the original format.

User functions are defined by name and then referenced:

SIN1=SIN(0, 1, 0)
Meandering Centerline Function=SIN1

A parameter may reference several, separated by commas or spaces; they are summed.

write(path)[source]

Write the parameters back out in the original format.

class riverarchitect.riverbuilder.UserFunction(kind, arguments)[source]

Bases: object

One term of a sub-reach variability parameter.

Parameters:
  • kind (str) – one of FUNCTION_TYPES.

  • arguments (list) – the numbers in the call, in the order the format writes them.

SIN, COS and SIN_SQ take amplitude, frequency and phase shift and are evaluated against the radian station; LINE takes a slope and an intercept and is evaluated against the station in length units; PERL takes an amplitude and a wavelength and produces smooth pseudo-random noise against the station index.

classmethod parse(text)[source]

Read SIN(0, 1, 0) into a UserFunction.

riverarchitect.riverbuilder.PARAMETERS = {'Bankfull Depth (Hbf, A)': 'bankfull_depth', 'Bankfull Width (Wbf)': 'bankfull_width', 'Bankfull Width Function': 'width_function', 'Bankfull Width Minimum': 'bankfull_width_min', 'Boundary Width': 'boundary_width', 'Centerline Curvature Function': 'curvature', 'Channel XS Points': 'xs_points', 'Critical Shields Stress (t*50)': 'tau_cr', 'Cross-Sectional Shape': 'xs_shape', 'Datum': 'datum', 'Floodplain Width': 'floodplain_width', 'Left Floodplain Function': 'left_floodplain', 'Length': 'length', 'Meandering Centerline Function': 'meander', 'Median Sediment Size (D50)': 'd50', 'Outer Floodplain Edge Height': 'floodplain_outer_height', 'Outer Terrace Edge Height': 'terrace_outer_height', 'Right Floodplain Function': 'right_floodplain', 'Terrace Width': 'terrace_width', 'Thalweg Elevation Function': 'thalweg', 'Trapezoidal Base': 'trapezoid_base', 'Valley Slope (Sv)': 'valley_slope', 'X Resolution': 'x_resolution'}

Parameter name in the input file -> attribute name. The file format is the original’s, so an existing RiverBuilder input runs unchanged.

riverarchitect.riverbuilder.CROSS_SECTIONS = ('SU', 'AU', 'TZ')

symmetric U, asymmetric U, trapezoid.

Type:

Cross-sectional shapes

riverarchitect.riverbuilder.FUNCTION_TYPES = ('SIN_SQ', 'SIN', 'COS', 'LINE', 'PERL')

The function forms a sub-reach variability parameter may take, longest name first so that SIN_SQ is not matched as SIN.

riverarchitect.riverbuilder.DEFAULTS = {'bankfull_depth': 0.0, 'bankfull_width': 10.0, 'bankfull_width_min': 5.0, 'boundary_width': 0.0, 'd50': 0.01, 'datum': 1.0, 'floodplain_outer_height': 0.0, 'floodplain_width': 0.0, 'length': 100.0, 'tau_cr': 0.047, 'terrace_outer_height': 0.0, 'terrace_width': 0.0, 'trapezoid_base': 0, 'valley_slope': 0.001, 'x_resolution': 100, 'xs_points': 23, 'xs_shape': 'SU'}

Defaults, reproducing cRiverBuilderConstruct.InputFile.get_par_defaults.