🌎 Simple and fast watershed delineation in python
View the Project on GitHub mdbartos/pysheds
Grid
methods operate on Raster
objects. You can think of a Raster
as a numpy array with additional attributes that specify the location, resolution and coordinate reference system of the data.
When a dataset is read from a file, it will automatically be saved as a Raster
object.
from pysheds.grid import Grid
grid = Grid.from_raster('./data/dem.tif')
dem = grid.read_raster('./data/dem.tif')
Here, grid
is the Grid
instance, and dem
is a Raster
object. If we call the Raster
object, we will see that it looks much like a numpy array.
dem
Raster([[214, 212, 210, ..., 177, 177, 175], [214, 210, 207, ..., 176, 176, 174], [211, 209, 204, ..., 174, 174, 174], ..., [263, 262, 263, ..., 217, 217, 216], [266, 265, 265, ..., 217, 217, 217], [268, 267, 266, ..., 216, 217, 216]], dtype=int16)
Hydrologic functions (such as flow direction determination and catchment delineation) accept and return Raster objects
:
inflated_dem = grid.resolve_flats(dem)
fdir = grid.flowdir(inflated_dem)
fdir
Raster([[ 0, 0, 0, ..., 0, 0, 0], [ 0, 2, 2, ..., 4, 1, 0], [ 0, 1, 2, ..., 4, 2, 0], ..., [ 0, 64, 32, ..., 8, 1, 0], [ 0, 64, 32, ..., 16, 128, 0], [ 0, 0, 0, ..., 0, 0, 0]])
The viewfinder attribute contains all the information needed to specify the Raster’s spatial reference system. It can be accessed using the viewfinder
attribute.
dem.viewfinder
'affine' : Affine(0.0008333333333333, 0.0, -97.4849999999961, 0.0, -0.0008333333333333, 32.82166666666536) 'shape' : (359, 367) 'nodata' : -32768 'crs' : Proj('+proj=longlat +datum=WGS84 +no_defs', preserve_units=True) 'mask' : array([[ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], ..., [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True]])
The viewfinder contains five necessary elements that completely define the spatial reference system.
affine
: An affine transformation matrix.shape
: The desired shape (rows, columns).crs
: The coordinate reference system.mask
: A boolean array indicating which cells are masked.nodata
: A sentinel value indicating ‘no data’.An affine transform uniquely specifies the spatial location of each cell in a gridded dataset. In a Raster
, the affine transform is given by the affine
attribute.
dem.affine
Affine(0.0008333333333333, 0.0, -100.0, 0.0, -0.0008333333333333, 34.9999999999998)
The elements of the affine transform (a, b, c, d, e, f)
are:
The affine transform uses the affine module.
The shape is equal to the shape of the underlying array (i.e. number of rows, number of columns).
dem.shape
(359, 367)
The coordinate reference system (CRS) defines a map projection for the gridded
dataset. The crs
attribute is a pyproj.Proj
object. For datasets read from a
raster file, the CRS will be detected and populated automaticaally.
dem.crs
Proj('+proj=longlat +datum=WGS84 +no_defs', preserve_units=True)
This example dataset has a geographic projection (meaning that coordinates are defined in terms of latitudes and longitudes).
The coordinate reference system uses the pyproj module.
The mask is a boolean array indicating which cells in the dataset should be masked in the output view.
dem.mask
array([[ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], ..., [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True]])
The nodata
attribute specifies the value that indicates missing or invalid data.
dem.nodata
-32768
Other attributes are derived from these primary attributes:
dem.bbox
(-97.4849999999961, 32.52166666666537, -97.17833333332945, 32.82166666666536)
dem.extent
(-97.4849999999961, -97.17833333332945, 32.52166666666537, 32.82166666666536)
dem.coords
array([[ 32.82166667, -97.485 ], [ 32.82166667, -97.48416667], [ 32.82166667, -97.48333333], ..., [ 32.52333333, -97.18166667], [ 32.52333333, -97.18083333], [ 32.52333333, -97.18 ]])
Rasters can be instantiated directly using the pysheds.Raster
class. Both an array-like object and a ViewFinder
must be provided.
from pysheds.view import Raster, ViewFinder
array = np.random.randn(*grid.shape)
raster = Raster(array, viewfinder=grid.viewfinder)
raster Raster([[-0.71876505, -0.35747123, -0.3296262 , ..., -0.07522118, -0.86431367, -0.45065405], [-1.12477409, 2.28759514, 0.5855458 , ..., -0.43795955, 0.42813309, 0.03900371], [-1.33345727, 1.03254272, 0.0904066 , ..., 0.06465593, -1.09938815, 1.1821455 ], ..., [ 0.67330805, 0.37022934, 0.13783694, ..., -1.59943506, 0.65154575, -0.58218991], [ 0.67738517, 0.43696016, 1.09402764, ..., -1.63815592, 1.67867785, 0.16609381], [ 1.17302635, 0.31176851, 1.79257942, ..., -0.48385788, 1.38478075, -0.76431488]])
We can also instantiate the raster using our own custom ViewFinder
.
raster = Raster(array, viewfinder=ViewFinder(shape=array.shape))
Note that the affine
transformation defaults to the identity matrix, the nodata
value defaults to zero, the crs
defaults to geographic coordinates, and the mask
defaults to a boolean array of ones. If a shape
is not provided, the shape of the viewfinder defaults to (1, 1)
. However, when instantiating a Raster
, the shape of the viewfinder and the shape of the array-like object must be identical.
raster.viewfinder
'affine' : Affine(1.0, 0.0, 0.0, 0.0, 1.0, 0.0) 'shape' : (359, 367) 'nodata' : 0 'crs' : Proj('+proj=longlat +datum=WGS84 +no_defs', preserve_units=True) 'mask' : array([[ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], ..., [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True], [ True, True, True, ..., True, True, True]])
The Raster can be transformed to a new coordinate reference system using the to_crs
method:
import pyproj
import numpy as np
# Initialize new CRS
new_crs = pyproj.Proj('epsg:3083')
# Convert CRS of dataset and set nodata value for better plotting
dem.nodata = np.nan
proj_dem = dem.to_crs(new_crs)
import matplotlib.pyplot as plt import seaborn as sns fig, ax = plt.subplots(1, 2, figsize=(12,8)) fig.patch.set_alpha(0) ax[0].imshow(dem, cmap='terrain', zorder=1) ax[1].imshow(proj_dem, cmap='terrain', zorder=1) ax[0].set_title('DEM', size=14) ax[1].set_title('Projected DEM', size=14) plt.tight_layout()
Note that the projected Raster appears slightly rotated to the counterclockwise direction.