The short answer
Sink filling (or breaching) is the step that makes a DEM hydrologically conditioned: it removes pits — cells or groups of cells with no lower neighbour — so that water can always route downhill to the edge of the grid. You do it before flow direction, flow accumulation, stream extraction and watershed delineation because every standard flow-routing algorithm assumes a monotonically descending surface; an unfilled pit traps flow, truncates streams and fragments catchments. The crucial nuance is that not every pit is an error, so you condition a copy of the DEM for hydrology while keeping the original for geomorphic interpretation.
What a sink is and why it breaks flow routing
In a single-flow-direction model such as D8, each cell's flow goes to the one of its eight neighbours with the steepest downhill drop. A sink (also called a pit or depression) is a cell, or a set of cells bounded by higher ground, that has no lower neighbour. When the algorithm reaches it, flow has nowhere to go: accumulation stops, the contributing area downstream of the sink is undercounted, and the extracted stream network develops gaps. A single one-cell pit on a main valley floor can sever an entire downstream river.
Sinks come from several sources:
- Interpolation and resampling artefacts — the most common, especially in older or coarsely interpolated DEMs.
- Sensor noise and voids in lidar or radar (SRTM) data.
- Real topography that blocks surface flow in the model — bridges and culverts read as solid dams, so a road embankment crossing a stream creates an artificial pit upstream.
- Genuinely closed depressions — karst sinkholes (dolines), glacial kettle holes, playas, volcanic craters, quarries and tailings dams.
The first three you want to eliminate. The last are real, which is why blindly filling can erase meaningful landforms.
Filling versus breaching versus hybrids
There are two ways to make flow continuous:
- Filling raises pit cells to the elevation of the lowest point on their surrounding rim (the spill point) so water can flow out. The classic algorithm is Jenson and Domingue (1988); the efficient, widely used implementation is Wang and Liu (2006), which also imposes a tiny minimum slope so flow has a defined direction across the filled flat. Filling is robust but can create large flat areas and slightly inflate elevations.
- Breaching does the opposite: it lowers a narrow channel through the rim that impounds the pit, carving an outlet rather than flooding the depression. Where the obstruction is an artefact like a bridge, breaching reconstructs the true drainage far better than filling. WhiteboxTools'
BreachDepressionsLeastCostis a common implementation. - Hybrid methods breach where it costs little elevation change and fill the remainder, minimising the total modification to the surface. This is generally the best default for natural terrain with man-made crossings.
A second, related step is stream burning: forcing the DEM down along a known stream vector before conditioning, to make extracted drainage honour mapped rivers. Use it cautiously, only with an accurate stream layer, because it overrides the elevation data.
A worked QGIS / WhiteboxTools workflow
Starting from a reprojected, void-checked DEM in a metric CRS (e.g. UTM, EPSG:326xx) with consistent vertical units:
1. Confirm the DEM is real elevation, not a styled image, and that NoData is set correctly. A NoData value misread as -9999 elevation creates enormous false pits.
2. Inspect the raw pits first so you know what you are about to change. In QGIS, the GRASS r.fill.dir or SAGA tools report or output the modifications.
3. Condition the surface. With WhiteboxTools (available in QGIS Processing), hybrid breach-then-fill is a good default:
whitebox_tools --run=BreachDepressionsLeastCost \
--dem=dem.tif --output=dem_breached.tif --dist=100
whitebox_tools --run=FillDepressionsWangAndLiu \
--dem=dem_breached.tif --output=dem_cond.tif
Or, purely in QGIS Processing, run SAGA "Fill Sinks (Wang & Liu)" with a minimum slope of about 0.01 degrees on dem.tif.
4. Audit the change. Subtract the original from the conditioned DEM and map where, and by how much, elevations moved:
gdal_calc.py -A dem_cond.tif -B dem.tif --outfile fill_diff.tif \
--calc="A-B" --type=Float32
Large positive blobs in fill_diff.tif are filled depressions. Check whether any coincide with real features (a known sinkhole, a quarry); if so, you may want to exclude them or accept that this DEM is now for hydrology only.
5. Route flow on the conditioned DEM:
gdaldem ... # (or)
# QGIS: r.watershed / r.flow / Strahler order on dem_cond.tif
Compute flow direction, then flow accumulation, threshold accumulation to extract the stream network, and delineate watersheds from pour points.
Common pitfalls and why they happen
- Filling real depressions silently. Aggressive filling drowns karst dolines and playas, which then vanish from your maps. Reason: the algorithm cannot tell a real closed basin from an artefact. Keep an unconditioned DEM and review the fill-difference raster.
- Bridges and culverts as dams. Surface DEMs treat road decks as solid, damming the valley. Reason: the elevation model has no concept of a void beneath a structure. Breach these crossings or burn the stream.
- NoData treated as elevation. A void coded as 0 or -9999 but not flagged as NoData becomes a giant artificial pit or cliff. Always verify the NoData value with
gdalinfo. - Over-filling from coarse resampling. Resampling a DEM to a coarser cell size before conditioning creates new flats and pits. Condition at the native resolution and resample afterward if needed.
- Filling then computing slope/aspect from the same raster. The filled surface has artificial flats; derive slope and aspect from the original DEM, and use the conditioned one only for flow routing.
Validation and QA
After conditioning, overlay the extracted stream network on a hillshade and on a reference hydrography layer (national datasets or OpenStreetMap waterways) and check that mainstems are continuous and follow the valleys. Confirm there are no remaining interior pits by re-running a pit count. Inspect fill_diff.tif to ensure modifications are modest and concentrated where you expect (valley crossings, voids), not smeared across whole hillsides — broad changes usually signal a units or NoData problem. Record the method, parameters and the maximum elevation change so the conditioning is reproducible and auditable.
Bathyl perspective
We condition a dedicated copy of the DEM for hydrology and always keep the audit raster that shows exactly what changed, so a reviewer can see whether a "filled sink" was an artefact or a real basin. Drainage products are delivered with their conditioning method and the reference hydrography they were checked against.
Related reading
- Flow Direction and Flow Accumulation Basics
- Watershed Delineation From DEM Data
- Slope, Aspect, and Hillshade From DEM Data
- Why Your GIS Layers Do Not Line Up
- Terrain intelligence