basinkit quickstartFrom an outlet coordinate to a fully characterised river basin, with no accountanywhere in the chain.We'll use the Sapta Koshi at Chatara (26.87 N, 87.15 E) -- a Himalayan basinthat drains Everest and empties onto the Gangetic plain. It is a good test casebecause its area is independently published, so we can check the answer.¶
In [ ]:
Copied!
%pip install -q "basinkit[all]"
%pip install -q "basinkit[all]"
In [ ]:
Copied!
import basinkit as bkimport numpy as npbk.__version__
import basinkit as bkimport numpy as npbk.__version__
1. DelineateOne call. The default backend walks the HydroBASINS upstream graph, so this isfast at any basin size.¶
In [ ]:
Copied!
basin = bk.Basin.from_point(26.87, 87.15)basin
basin = bk.Basin.from_point(26.87, 87.15)basin
Check the answerarea_km2 is computed here by reprojecting to an equal-area projection centredon the basin. reported_up_area_km2 is HydroBASINS' own figure, derivedcompletely separately. They should agree.¶
In [ ]:
Copied!
print(f"computed {basin.area_km2:,.0f} km2")print(f"reported {basin.provenance['reported_up_area_km2']:,.0f} km2")print(f"published literature value at Chatara: ~54,100 km2")
print(f"computed {basin.area_km2:,.0f} km2")print(f"reported {basin.provenance['reported_up_area_km2']:,.0f} km2")print(f"published literature value at Chatara: ~54,100 km2")
Why the polygon mattersbbox_efficiency is the share of the bounding box the basin actually occupies.Everything below is clipped to the polygon, not the box.¶
In [ ]:
Copied!
print(f"bbox efficiency: {basin.bbox_efficiency:.0%}")print(f"a bbox download would waste {1 - basin.bbox_efficiency:.0%} of every layer")
print(f"bbox efficiency: {basin.bbox_efficiency:.0%}")print(f"a bbox download would waste {1 - basin.bbox_efficiency:.0%} of every layer")
2. TerrainThe DEM comes from Copernicus GLO-30 on anonymous S3, falling back per tile toGLO-90 where the public bucket has holes.¶
In [ ]:
Copied!
basin.terrain_stats()
basin.terrain_stats()
Elevation should span the Gangetic plain to Everest -- roughly 75 m to8,800 m. Note basinkit_coarsen_factor in the attributes: for a basin thislarge, basinkit automatically coarsens rather than trying to hold 1.3 billionpixels in memory.
In [ ]:
Copied!
dem = basin.dem()dem.attrs
dem = basin.dem()dem.attrs
3. Land cover¶
In [ ]:
Copied!
from basinkit.sources.landcover import class_fractionslc = basin.landcover()class_fractions(lc)
from basinkit.sources.landcover import class_fractionslc = basin.landcover()class_fractions(lc)
4. Soil hydrologyPlant-available water capacity is field capacity minus wilting point. SoilGridspublishes both but not the difference, and its native CRS is Interrupted GoodeHomolosine -- basinkit handles the reprojection.¶
In [ ]:
Copied!
awc = basin.available_water_capacity()float(awc.mean()), awc.attrs["units"]
awc = basin.available_water_capacity()float(awc.mean()), awc.attrs["units"]
5. RainfallCHIRPS v3.0, read straight out of the remote COGs over HTTP range requests, soonly the basin window travels.¶
In [ ]:
Copied!
rain = basin.precipitation(2010, 2023)print(f"mean annual: {float(rain.mean()) * 12:,.0f} mm")rain.plot()
rain = basin.precipitation(2010, 2023)print(f"mean annual: {float(rain.mean()) * 12:,.0f} mm")rain.plot()
6. Water balanceTerraClimate gives precipitation, actual and potential evapotranspiration,runoff and soil moisture. The residual term is P - AET - Q: storage changeplus model error. It is worth looking at before trusting any single component.¶
In [ ]:
Copied!
wb = basin.water_balance(2015, 2020)wb[["ppt", "aet", "q"]].to_dataframe().describe()
wb = basin.water_balance(2015, 2020)wb[["ppt", "aet", "q"]].to_dataframe().describe()
7. Surface waterJRC Global Surface Water is a pre-reduced 37-year Landsat stack -- what mostpeople spend a week of cloud compute recreating.¶
In [ ]:
Copied!
occ = basin.surface_water("occurrence")occ.plot(cmap="Blues", vmin=0, vmax=100)
occ = basin.surface_water("occurrence")occ.plot(cmap="Blues", vmin=0, vmax=100)
8. Satellite imagerySentinel-2 via Earth Search: plain COGs, no token layer at all. The median overtime suppresses cloud without needing a cloud mask.¶
In [ ]:
Copied!
s2 = basin.sentinel2("2023-10-01", "2023-12-31", cloud_cover=15)s2
s2 = basin.sentinel2("2023-10-01", "2023-12-31", cloud_cover=15)s2
9. Everything at once¶
In [ ]:
Copied!
manifest = basin.download_all("koshi/")manifest["layers"]
manifest = basin.download_all("koshi/")manifest["layers"]
10. What you are allowed to do with itPrint this into your methods section.¶
In [ ]:
Copied!
print(basin.license_report(("hydrobasins", "cop30", "worldcover", "chirps")))
print(basin.license_report(("hydrobasins", "cop30", "worldcover", "chirps")))