4.6 KiB
4.6 KiB
swot river discharge pipeline
how it works (short version)
- SWOT L2 HR RiverSP version D (
SWOT_L2_HR_RiverSP_D), node + reach shapefiles from nasa earthdata - ALOS World 3D 30m DSM via GEE (or a local geotiff if you dont have GEE)
- gauge WSE + discharge from india-WRIS
- match each gauge to the nearest SWOT node (haversine), match times within ±3h
- depth = WSE − bed, A = width × depth, R = A/P, then Q = (1/n) · A · R^(2/3) · S^(1/2)
- compare against gauge discharge, get R²/RMSE/NSE/bias + plots
setup
# Install UV
# Windows:
powershell -ExecutionPolicy ByPass -c "irm https://astral.sh/uv/install.ps1 | iex"
#linux/macOS:
curl -LsSf https://astral.sh/uv/install.sh | sh
nasa earthdata - first run will ask for username/password once and save them. also approve the "PO.DAAC" app on the earthdata site if downloads fail.
google earth engine - register at https://code.earthengine.google.com/register, then uv run earthengine authenticate and put your project id in config.yaml under gee.project.
gauge data - put the WRIS xlsx exports here:
data/gauge/wse/ River Water Level_<Station>.xlsx
data/gauge/discharge/ River Water Discharge_<Station>.xlsx
and add each station's lat/lon to stations.csv. station name has to match the filename part after the underscore ("(seasonal)" is ignored). get the exact coordinates from the station page on WRIS - if they're wrong the pipeline matches the wrong river and you get garbage (ask me how i know).
running it
uv run python run_pipeline.py # all 8 steps
uv run python run_pipeline.py --steps 3-8 # skip the downloads
uv run python run_pipeline.py --steps 6,7,8 # just recompute discharge + export
| # | name | what it does | output |
|---|---|---|---|
| 1 | download | SWOT granules from earthdata (~50gb for 2 yrs, skips already-downloaded files) | data/swot/ |
| 2 | process | merge shapefiles, clip to basin, clean (slow, rerun only if AOI/dates change) | data/processed/nodes_clean.csv etc |
| 3 | gauge | parse the WRIS xlsx files (picks the MSL series, converts IST→UTC) | gauge_wse.csv, gauge_discharge.csv |
| 4 | wse | node matching + WSE validation | matched_wse.csv, outputs/wse_metrics.csv |
| 5 | dem | bed elevation (min DSM within 300m buffer) | node_bed_elevation.csv |
| 6 | discharge | manning's equation | swot_discharge.csv |
| 7 | validate | SWOT vs gauge discharge | outputs/discharge_metrics.csv, plots |
| 8 | export | everything into one excel workbook for analysis | outputs/<project>_swot_analysis.xlsx |
tuning knobs (config.yaml)
hydraulics.mannings_n- global roughness, default 0.035hydraulics.mannings_n_per_station- override n per station, this is the main thing to play with. if a station is consistently overestimating by ~4x, try ~4x the n (it scales Q linearly). the n used shows up in the outputs so you always know which run used whathydraulics.baseline_depth_min_m/baseline_depth_max_m- the DEM is unreliable at the channel (bank pixels, voids) so the implied low-water depth gets clamped into this band, default [0.5, 2.0]quality.max_node_q,quality.swot_wse_mad_k,matching.wse_outlier_mad_k- outlier filtering. SWOT throws the occasional ±15m garbage observation, these catch itmatching.*- distance limit + time windowshydraulics.scope: all- compute discharge at every node in the basin, not just gauge-matched ones (step 5 gets slower)
things to know before running the pipeline
- the "bed elevation" from the DEM is really the water surface the satellite saw at acquisition time, so depths are relative, not true bathymetry. thats why I created a clamp
- SWOT WSE is EGM2008, gauges are local MSL/datum - expect a constant offset (plots auto-shift when its > 1m, metrics report the raw bias)
- dry season: SWOT sees pooled water behind weirs and manning's cant tell a standing pool from flowing water, so theres a discharge floor where the gauge says zero. like estimating traffic from how full the parking lot is - utter garbage data