Hillslope-reach mapping
The hillslope-reach mapping module connects hillslope grid cells to river reaches, enabling lateral flow routing from the distributed grid to the channel network.
Overview
This module performs two key operations:
- Rasterization: Identifies which grid cells are occupied by river reaches
- Flow routing: Traces flow paths from hillslope cells to their corresponding reaches
These functions are used for distributing lateral inflow from the hillslope water balance to the appropriate river reaches in the channel routing module.
Functions
Compute hillslope cells
Compute hillslope cells for each reach in the river network.
This function rasterizes each reach geometry onto the grid to identify which grid cells are directly occupied by the channel. These cells are stored as linear indices in the ‘hillslope_cells’ column.
The densification uses iterative midpoint subdivision (matching MATLAB’s densify.m) to ensure exact compatibility with the original implementation.
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
GeoDataFrame
|
GeoDataFrame with processed river network (must contain geometry column) |
required |
grid_path
|
str | Path | None
|
Path to reference grid raster (used to get grid parameters). Required if grid_array is not provided. |
None
|
densify_step
|
float
|
Maximum distance [m] between points when densifying reach geometries (default: 10.0) |
10.0
|
grid_array
|
ndarray | None
|
Optional numpy array with grid data. If provided, xllcorner, yllcorner, and cellsize must also be provided. |
None
|
xllcorner
|
float | None
|
X coordinate of lower-left corner [m]. Required if grid_array is provided. |
None
|
yllcorner
|
float | None
|
Y coordinate of lower-left corner [m]. Required if grid_array is provided. |
None
|
cellsize
|
float | None
|
Grid cell size [m]. Required if grid_array is provided. |
None
|
Returns:
| Type | Description |
|---|---|
GeoDataFrame
|
GeoDataFrame with added ‘hillslope_cells’ column containing list of linear indices |
Examples:
>>> from mobidic import process_river_network
>>> network = process_river_network("river_network.shp")
>>>
>>> # Option 1: Use file path
>>> flowdir_path = "flowdir.tif"
>>> network = compute_hillslope_cells(network, grid_path=flowdir_path)
>>>
>>> # Option 2: Use array with metadata
>>> network = compute_hillslope_cells(
... network,
... grid_array=flowdir_array,
... xllcorner=xll,
... yllcorner=yll,
... cellsize=cs
... )
Source code in mobidic/preprocessing/hillslope_reach_mapping.py
17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 | |
Map hillslope to reach
Map each hillslope cell to its downstream river reach.
For each grid cell, this function follows the flow direction path until it reaches a cell that belongs to a river reach (hillslope cell), then assigns the reach’s mobidic_id to the original cell.
This function translates MATLAB’s hill2chan.m algorithm: - Builds a lookup table of reach cells from network.hillslope_cells - For each valid cell, follows flow direction until reaching a reach cell - Assigns the reach’s mobidic_id to all cells along the path - Handles edge cases: out-of-bounds, invalid directions, loops
Parameters:
| Name | Type | Description | Default |
|---|---|---|---|
network
|
GeoDataFrame
|
GeoDataFrame with river network (must have ‘hillslope_cells’ and ‘mobidic_id’ columns) |
required |
flowdir_path
|
str | Path | None
|
Path to flow direction raster file. Required if flowdir_array is not provided. |
None
|
flow_dir_type
|
str
|
Flow direction notation (‘Grass’ for 1-8 or ‘Arc’ for power-of-2). Only used if flowdir_path is provided. |
'Grass'
|
flowdir_array
|
ndarray | None
|
Optional numpy array with flow direction data in MOBIDIC notation (1-8). If provided, no conversion is applied. |
None
|
Returns:
| Type | Description |
|---|---|
ndarray
|
2D array with reach assignment for each cell (mobidic_id or -9999 for unassigned) |
Raises:
| Type | Description |
|---|---|
ValueError
|
If network doesn’t have required columns or flow_dir_type is invalid |
Examples:
>>> from mobidic import process_river_network, compute_hillslope_cells
>>> network = process_river_network("river_network.shp")
>>> network = compute_hillslope_cells(network, "flow_dir.tif")
>>>
>>> # Option 1: Use file path
>>> reach_map = map_hillslope_to_reach(network, flowdir_path="flow_dir.tif")
>>>
>>> # Option 2: Use array (already in MOBIDIC notation)
>>> reach_map = map_hillslope_to_reach(network, flowdir_array=flowdir_mobidic)
Source code in mobidic/preprocessing/hillslope_reach_mapping.py
141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | |
Workflow
The typical workflow for hillslope-reach mapping is:
from mobidic import (
process_river_network,
compute_hillslope_cells,
map_hillslope_to_reach,
)
# Step 1: Process river network
network = process_river_network(
shapefile_path="data/river_network.shp",
join_single_tributaries=True,
)
# Step 2: Rasterize reaches onto grid
network = compute_hillslope_cells(
network=network,
grid_path="data/flow_direction.tif",
)
# Step 3: Map hillslope cells to reaches
reach_map = map_hillslope_to_reach(
network=network,
flowdir_path="data/flow_direction.tif",
flow_dir_type="Grass", # or "Arc"
)
# Result: 2D array with reach ID for each cell
print(f"Grid shape: {reach_map.shape}")
print(f"Reaches identified: {len(set(reach_map[reach_map >= 0]))}")
Algorithm details
Rasterization algorithm
compute_hillslope_cells() performs the following steps:
- Reads the reference grid to obtain spatial reference (transform, CRS, dimensions)
- For each reach in the network:
- Converts the reach geometry to a list of (x, y) coordinates
- Transforms geographic coordinates to pixel indices using the grid’s affine transform
- Records all cells intersected by the reach geometry
- Stores the list of cell indices (as linear indices) in a
hillslope_cellscolumn
Flow path
map_hillslope_to_reach() implements a flow path following algorithm:
- For each grid cell:
- If the cell belongs to a reach (has hillslope_cells), assign that reach ID
- Otherwise, follow the flow direction downstream until reaching a reach
- Assign the reached reach ID to the original cell
- Cells that don’t reach any reach (e.g., flowing out of basin) are assigned -9999
Flow direction support: Both Grass (1-8 notation) and Arc (power-of-2 notation) formats are supported.
Return values
compute_hillslope_cells()
Returns a modified GeoDataFrame with an additional column:
hillslope_cells(list of int): Linear indices of cells occupied by each reach
map_hillslope_to_reach()
Returns a 2D numpy array with:
- Values ≥ 0:
mobidic_idof the reach draining this cell - Value = -9999: Cell does not drain to any reach (e.g., basin outlet)
- NaN: Nodata cells outside the valid domain
Example output
After processing, the network GeoDataFrame contains:
network[['mobidic_id', 'length_m', 'hillslope_cells']].head()
# mobidic_id length_m hillslope_cells
# 0 0 5432.1 [12045, 12046, 12147, ...]
# 1 1 3210.8 [15234, 15235, ...]
# 2 2 8765.4 [18902, 18903, 19004, ...]
And the reach_map can be visualized: