Skip to content

Commit a7cdaa8

Browse files
committed
Add docs, README entry, and user guide notebook for flow_length_mfd (#1009)
1 parent bcb9c17 commit a7cdaa8

3 files changed

Lines changed: 208 additions & 0 deletions

File tree

README.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,8 @@ In the GIS world, rasters are used for representing continuous phenomena (e.g. e
294294
| [Flow Direction (MFD)](xrspatial/flow_direction_mfd.py) | Partitions flow to all downslope neighbors with an adaptive exponent (Qin et al. 2007) | Qin et al. 2007 | ✅️ | ✅️ | ✅️ | ✅️ |
295295
| [Flow Accumulation (D8)](xrspatial/flow_accumulation.py) | Counts upstream cells draining through each cell in a D8 flow direction grid | Jenson & Domingue 1988 | ✅️ | ✅️ | ✅️ | ✅️ |
296296
| [Flow Accumulation (MFD)](xrspatial/flow_accumulation_mfd.py) | Accumulates upstream area through all MFD flow paths weighted by directional fractions | Qin et al. 2007 | ✅️ | ✅️ | ✅️ | 🔄 |
297+
| [Flow Length (D8)](xrspatial/flow_length.py) | Computes D8 flow path length from each cell to outlet (downstream) or from divide (upstream) | Standard (D8 tracing) | ✅️ | ✅️ | ✅️ | 🔄 |
298+
| [Flow Length (MFD)](xrspatial/flow_length_mfd.py) | Proportion-weighted flow path length using MFD fractions (downstream or upstream) | Qin et al. 2007 | ✅️ | ✅️ | ✅️ | 🔄 |
297299
| [Watershed](xrspatial/watershed.py) | Labels each cell with the pour point it drains to via D8 flow direction | Standard (D8 tracing) | ✅️ | ✅️ | ✅️ | ✅️ |
298300
| [Basins](xrspatial/watershed.py) | Delineates drainage basins by labeling each cell with its outlet ID | Standard (D8 tracing) | ✅️ | ✅️ | ✅️ | ✅️ |
299301
| [Stream Order](xrspatial/stream_order.py) | Assigns Strahler or Shreve stream order to cells in a drainage network | Strahler 1957, Shreve 1966 | ✅️ | ✅️ | ✅️ | ✅️ |

docs/source/reference/hydrology.rst

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ Flow Length
5353

5454
xrspatial.flow_length.flow_length
5555

56+
Flow Length (MFD)
57+
=================
58+
.. autosummary::
59+
:toctree: _autosummary
60+
61+
xrspatial.flow_length_mfd.flow_length_mfd
62+
5663
Flow Path
5764
=========
5865
.. autosummary::
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
{
2+
"cells": [
3+
{
4+
"cell_type": "markdown",
5+
"id": "a1b2c3d4",
6+
"metadata": {},
7+
"source": [
8+
"# Flow Length (MFD)\n",
9+
"\n",
10+
"Flow length measures the distance water travels along its flow path. The MFD (Multiple Flow Direction) variant computes proportion-weighted path lengths using MFD fraction grids, where each cell distributes flow to all downslope neighbors.\n",
11+
"\n",
12+
"Two modes are supported:\n",
13+
"- **Downstream**: Expected (weighted-average) distance from each cell to its outlet\n",
14+
"- **Upstream**: Longest flow path from any drainage divide to each cell"
15+
]
16+
},
17+
{
18+
"cell_type": "code",
19+
"execution_count": null,
20+
"id": "e5f6a7b8",
21+
"metadata": {},
22+
"outputs": [],
23+
"source": [
24+
"import numpy as np\n",
25+
"import matplotlib.pyplot as plt\n",
26+
"import xarray as xr\n",
27+
"\n",
28+
"from xrspatial import flow_direction_mfd, flow_length_mfd, flow_accumulation_mfd\n",
29+
"from xrspatial.terrain import generate_terrain"
30+
]
31+
},
32+
{
33+
"cell_type": "markdown",
34+
"id": "c9d0e1f2",
35+
"metadata": {},
36+
"source": [
37+
"## Generate terrain"
38+
]
39+
},
40+
{
41+
"cell_type": "code",
42+
"execution_count": null,
43+
"id": "a3b4c5d6",
44+
"metadata": {},
45+
"outputs": [],
46+
"source": [
47+
"W = 400\n",
48+
"H = 400\n",
49+
"terrain = generate_terrain(\n",
50+
" x_range=(-2, 2), y_range=(-2, 2), width=W, height=H, seed=42\n",
51+
")\n",
52+
"\n",
53+
"fig, ax = plt.subplots(figsize=(8, 6))\n",
54+
"im = ax.imshow(terrain.values, cmap='terrain', origin='lower')\n",
55+
"ax.set_title('Elevation')\n",
56+
"plt.colorbar(im, ax=ax, label='meters')\n",
57+
"plt.tight_layout()\n",
58+
"plt.show()"
59+
]
60+
},
61+
{
62+
"cell_type": "markdown",
63+
"id": "e7f8a9b0",
64+
"metadata": {},
65+
"source": [
66+
"## Compute MFD flow direction and accumulation"
67+
]
68+
},
69+
{
70+
"cell_type": "code",
71+
"execution_count": null,
72+
"id": "c1d2e3f4",
73+
"metadata": {},
74+
"outputs": [],
75+
"source": [
76+
"mfd_dir = flow_direction_mfd(terrain)\n",
77+
"mfd_acc = flow_accumulation_mfd(mfd_dir)\n",
78+
"\n",
79+
"fig, ax = plt.subplots(figsize=(8, 6))\n",
80+
"im = ax.imshow(np.log1p(mfd_acc.values), cmap='Blues', origin='lower')\n",
81+
"ax.set_title('MFD flow accumulation (log scale)')\n",
82+
"plt.colorbar(im, ax=ax, label='log(1 + accumulation)')\n",
83+
"plt.tight_layout()\n",
84+
"plt.show()"
85+
]
86+
},
87+
{
88+
"cell_type": "markdown",
89+
"id": "a5b6c7d8",
90+
"metadata": {},
91+
"source": [
92+
"## Downstream flow length\n",
93+
"\n",
94+
"Downstream flow length gives the proportion-weighted average distance from each cell to the outlet it drains to. Cells near outlets have short distances; cells far upstream have long distances."
95+
]
96+
},
97+
{
98+
"cell_type": "code",
99+
"execution_count": null,
100+
"id": "e9f0a1b2",
101+
"metadata": {},
102+
"outputs": [],
103+
"source": [
104+
"downstream = flow_length_mfd(mfd_dir, direction='downstream')\n",
105+
"\n",
106+
"fig, ax = plt.subplots(figsize=(8, 6))\n",
107+
"im = ax.imshow(downstream.values, cmap='viridis', origin='lower')\n",
108+
"ax.set_title('MFD downstream flow length')\n",
109+
"plt.colorbar(im, ax=ax, label='distance (coordinate units)')\n",
110+
"plt.tight_layout()\n",
111+
"plt.show()"
112+
]
113+
},
114+
{
115+
"cell_type": "markdown",
116+
"id": "c3d4e5f6",
117+
"metadata": {},
118+
"source": [
119+
"## Upstream flow length\n",
120+
"\n",
121+
"Upstream flow length gives the longest flow path distance from any drainage divide to each cell. Cells on divides have zero length; cells at outlets accumulate the full watershed length."
122+
]
123+
},
124+
{
125+
"cell_type": "code",
126+
"execution_count": null,
127+
"id": "a7b8c9d0",
128+
"metadata": {},
129+
"outputs": [],
130+
"source": [
131+
"upstream = flow_length_mfd(mfd_dir, direction='upstream')\n",
132+
"\n",
133+
"fig, ax = plt.subplots(figsize=(8, 6))\n",
134+
"im = ax.imshow(upstream.values, cmap='magma', origin='lower')\n",
135+
"ax.set_title('MFD upstream flow length')\n",
136+
"plt.colorbar(im, ax=ax, label='distance (coordinate units)')\n",
137+
"plt.tight_layout()\n",
138+
"plt.show()"
139+
]
140+
},
141+
{
142+
"cell_type": "markdown",
143+
"id": "e1f2a3b4",
144+
"metadata": {},
145+
"source": [
146+
"## Comparison: D8 vs MFD flow length\n",
147+
"\n",
148+
"MFD distributes flow across multiple neighbors, producing smoother flow length fields than D8 which routes everything through the single steepest neighbor."
149+
]
150+
},
151+
{
152+
"cell_type": "code",
153+
"execution_count": null,
154+
"id": "c5d6e7f8",
155+
"metadata": {},
156+
"outputs": [],
157+
"source": [
158+
"from xrspatial import flow_direction, flow_length\n",
159+
"\n",
160+
"d8_dir = flow_direction(terrain)\n",
161+
"d8_downstream = flow_length(d8_dir, direction='downstream')\n",
162+
"\n",
163+
"fig, axes = plt.subplots(1, 2, figsize=(14, 5))\n",
164+
"\n",
165+
"im0 = axes[0].imshow(d8_downstream.values, cmap='viridis', origin='lower')\n",
166+
"axes[0].set_title('D8 downstream flow length')\n",
167+
"plt.colorbar(im0, ax=axes[0], label='distance')\n",
168+
"\n",
169+
"im1 = axes[1].imshow(downstream.values, cmap='viridis', origin='lower')\n",
170+
"axes[1].set_title('MFD downstream flow length')\n",
171+
"plt.colorbar(im1, ax=axes[1], label='distance')\n",
172+
"\n",
173+
"plt.tight_layout()\n",
174+
"plt.show()"
175+
]
176+
}
177+
],
178+
"metadata": {
179+
"kernelspec": {
180+
"display_name": "Python 3 (ipykernel)",
181+
"language": "python",
182+
"name": "python3"
183+
},
184+
"language_info": {
185+
"codemirror_mode": {
186+
"name": "ipython",
187+
"version": 3
188+
},
189+
"file_extension": ".py",
190+
"mimetype": "text/x-python",
191+
"name": "python",
192+
"nbconvert_exporter": "python",
193+
"pygments_lexer": "ipython3",
194+
"version": "3.14.2"
195+
}
196+
},
197+
"nbformat": 4,
198+
"nbformat_minor": 5
199+
}

0 commit comments

Comments
 (0)