Watershed and Stream Network Delimitation with Python and Pysheds - Tutorial


What would happen if we shift our GIS geoprocessing to Python? What would happen if we treat our raster and vector spatial data as objects and variables on a Python 3 script? Then we can ask ourselves if it is neccessary to reinvent the wheel, it is necessary to change a workflow that already work on a GIS software.

There is a simple answer to this dilemma: More control

Working with Python give us more control on the geoprocessing itself since we leave the Graphical User Interface (GUI) with its icons, buttons and dialog boxes. With Python running on a Jupyter Notebook, we can link with specific files, define geoprocess and it options, make plots of draft and final data, and export results to vector/raster SIG formats. There are other advantages of spatial analysis in Python which are the reproducibility and the processing speed.

Pysheds is a Python 3 package designed for watershed delimitation and stream network extraction. This library requires a set of advanced data processing and spatial analysis libraries as Numpy, Pandas, Scipy, Scikit-Image, Rasterio and others. This tutorial show the complete procedure on a Jupyter Notebook with Python and Pysheds to :

  • Import a digital elevation model ( without sinks)

  • Determination of flow direction raster

  • Watershed delimitation

  • Analysis of flow accumulation raster

  • Extraction of stream network

  • Basin vector/raster generation

Since most users works on Windows, we have done a tutorial for the installation of Pysheds and required libraries in Windows with Anaconda.

Useful links and commands

For anaconda installation please visit:


Pysheds homepage:


For the installation of the library mplleaflet, please type on Anaconda Prompt:

pip install mplleaflet


Rasterio, Geopandas, GDAL and Pysheds install for Anaconda in Windows

Watershed and stream network delimitation with Python and Pyshed

Python code

This is the python code used for the geoprocessing:

Import required libraries

import numpy as np
import matplotlib.pyplot as plt
import matplotlib.colors as colors
import geopandas as gpd
from pysheds.grid import Grid
import mplleaflet
%matplotlib inline

Open DEM file

grid = Grid.from_raster('../Rst/LocalDem.tif', data_name='dem')
def plotFigure(data, label, cmap='Blues'):
    plt.imshow(data, extent=grid.extent, cmap=cmap)
plotFigure(grid.dem, 'Elevation (m)')

Define direction map

#N    NE    E    SE    S    SW    W    NW
dirmap = (64,  128,  1,   2,    4,   8,    16,  32)
grid.flowdir(data='dem', out_name='dir', dirmap=dirmap)
plotFigure(grid.dir,'Flow Directiom','viridis')

Define catchment

# Specify pour point
x, y = 285612.017,2936416.682

# Delineate the catchment
grid.catchment(data='dir', x=x, y=y, dirmap=dirmap, out_name='catch',
               recursionlimit=15000, xytype='label', nodata_out=0)
# Clip the bounding box to the catchment
# Get a view of the catchment
demView = grid.view('dem', nodata=np.nan)
#export selected raster
grid.to_raster(demView, '../Output/clippedElevations.tif')

Define accumulation grid and stream network

grid.accumulation(data='catch', dirmap=dirmap, pad_inplace=False, out_name='acc')
accView = grid.view('acc', nodata=np.nan)
plotFigure(accView,"Cell Number",'PuRd')
streams = grid.extract_river_network('catch', 'acc', threshold=200, dirmap=dirmap)
def saveDict(dic,file):
    f = open(file,'w')
#save geojson as separate file

Plot DEM and stream network

streamNet = gpd.read_file('../Output/streams.geojson')
streamNet.crs = {'init' :'epsg:32613'}
# The polygonize argument defaults to the grid mask when no arguments are supplied
shapes = grid.polygonize()

# Plot catchment boundaries
fig, ax = plt.subplots(figsize=(6.5, 6.5))

for shape in shapes:
    coords = np.asarray(shape[0]['coordinates'][0])
    ax.plot(coords[:,0], coords[:,1], color='cyan')

ax.set_xlim(grid.bbox[0], grid.bbox[2])
ax.set_ylim(grid.bbox[1], grid.bbox[3])
ax.set_title('Catchment boundary (vector)')
gpd.plotting.plot_dataframe(streamNet, None, cmap='Blues', ax=ax)
#ax = streamNet.plot()
mplleaflet.display(fig=ax.figure, crs=streamNet.crs, tiles='esri_aerial')

Input data

You can download the input data for this tutorial on this link.

Saul Montoya

Saul Montoya es Ingeniero Civil graduado de la Pontificia Universidad Católica del Perú en Lima con estudios de postgrado en Manejo e Ingeniería de Recursos Hídricos (Programa WAREM) de la Universidad de Stuttgart con mención en Ingeniería de Aguas Subterráneas y Hidroinformática.

Smiley face

Subscribe to our free e-newsletter for tutorials, articles, webminars, courses and more.