Skip to content

Delineation

Three backends

bk.Basin.from_point(lat, lon, backend="auto")   # default
backend mechanism good for floor first-run cost
hydrobasins walks the NEXT_DOWN graph over level-12 units any size ~130 km² one 80 MB regional file, cached forever
dem D8 routing with pyflwdir on a Copernicus DEM window headwaters one 30 m pixel tiles for the window
api Global Watersheds web service a first look ~90 m nothing

Why graph traversal is the default

HydroBASINS ships every sub-basin with a pointer to the unit it drains into. Finding everything upstream is then a breadth-first walk over a few hundred thousand nodes, not a fill over hundreds of millions of raster cells. The Koshi basin (54,000 km², 423 level-12 units) delineates in about five seconds, and a basin ten times larger takes about the same, because the cost is in the graph, not the area.

The price is a resolution floor. A level-12 unit has a median area near 130 km², so a 20 km² headwater catchment cannot be resolved: you get the whole unit. backend="auto" detects that case and re-runs on the DEM.

Two failure modes, handled explicitly

The outlet is not on the channel. A coordinate read off a map, or taken from a gauge record, is often a pixel or two from the modelled stream. Routing from a hillslope cell returns a few hectares instead of a few hundred km², and it returns it silently, which is what makes it dangerous. The DEM backend snaps to the local maximum of upstream area, refuses to snap onto anything draining less than min_uparea_km2, and reports how far it moved:

basin.provenance["snap_distance_px"]           # 3
basin.provenance["flow_accum_at_outlet_km2"]   # 1752.4

The basin is larger than the DEM window. If the delineated basin touches the window edge, part of it is off-map and the answer is wrong. The window doubles and routing re-runs, up to max_window_deg, then raises rather than quietly returning a truncated basin.

A subtlety worth knowing

basinkit builds the flow network with outlets="edge", not outlets="min". With outlets="min", pyflwdir routes the entire window toward its single lowest cell, which on a cropped DEM pulls the network away from the real channels. During development a river with 2,500 km² of upstream area came back with 0.2 km² of accumulation at exactly the same coordinate, purely because the window had been shifted. edge lets flow leave wherever it reaches the boundary, which is what a window cut out of a larger landscape actually does.

Provenance

Every polygon records where it came from, and that dict is written into every export:

{'backend': 'hydrobasins',
 'source_dataset': 'HydroBASINS v1c level 12 (Central and Southeast Asia)',
 'n_units': 423,
 'outlet_hybas_id': 4120874300,
 'reported_up_area_km2': 54581.3,
 'area_km2': 54497.1,
 'license': 'CC BY 4.0'}

The gap between area_km2 (computed here, by equal-area projection) and reported_up_area_km2 (HydroBASINS' own bookkeeping) is an internal consistency check: it says the upstream traversal and the area computation agree with the dataset about which units are in the basin. For the Koshi it is 0.15%.

It is not an accuracy figure and must never be quoted as one. Both numbers come from the same polygons, so the comparison is close to circular: it can only catch a bug in the traversal, never an error in HydroBASINS itself. The accuracy figure is the comparison against the operating agency's published area, which for the Koshi is 54,100 km² and 0.73%. Quoting the 0.15% instead makes the package look roughly four times better than it is.