Digital elevation models (DEM) are representation of surface topography measured as elevation. There are processed a 3D raster representaion of a land surface, typically build as a 2D array, each cell having an associated elevation. A DEM allows to derive quantities such as slope,aspect and hillshade, which we will explore in this chapter.
# First, let's import the needed libraries.
import numpy as np
import matplotlib.pyplot as plt
import pandas as pd
import geopandas as gpd
import rasterio
from rasterio.plot import plotting_extent
import earthpy as et
import earthpy.plot as ep
import earthpy.spatial as es
import elevation
import richdem as rd
Here we will work with a DEM covering Germany. The SRTM digital elevation model (DEM) for Germany was retrieved from OPENDEM on June 25, 2017. Please note that the zipped file is about 86.4 MB in size and can be accessed here.
import requests, zipfile, io, os
url = "http://www.userpage.fu-berlin.de/~soga/300/30100_data_sets/spatial/srtm_germany_dtm.zip"
r = requests.get(url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall("../data")
You may want to check for the file size prior to loading the file. Processing large raster images can result in very long computing times.
filepath = '../data/srtm_germany_dtm.tif'
print(f"The file size is {os.path.getsize(filepath)/1e6} MB")
The file size is 285.254776 MB
richDEM
provides the LoadGDAL
function for loading DEM data.
srtm_germ = rd.LoadGDAL(filepath, no_data=np.nan)
plt.imshow(srtm_germ, interpolation='none', cmap ="terrain", vmin = 0, vmax = 2000)
plt.colorbar(label = "reflectance *1000")
plt.show()
Check meta data of srtm_germ
, since it is slightly different compared to loading it with rasterio
.
srtm_germ.projection
'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
srtm_germ.geotransform
(4.999583333333334, 0.0008333333333333301, 0.0, 56.000416666666666, 0.0, -0.0008333333333333301)
Note: We are also able to directly download elevation data from from the NASA SRTM mission through the
elevation
package. You can checkout this tutorial from Oakley & Joseph 2020 if you want to learn how.
Crop the SRTM of Germany
As we saw in the previous section, we are able to crop the SRTM raster data with the help of the EarthPy
library. Consequently, we will crop the rasterdata set prior to working with it as follows.
First, we will load the required shapefile data, convert to desired crs
("epsg:32633"
).
# Retrieve Federal States
import zipfile, requests, io
url = "https://daten.gdz.bkg.bund.de/produkte/vg/vg5000_0101/aktuell/vg5000_01-01.utm32s.shape.ebenen.zip"
r = requests.get(url)
z = zipfile.ZipFile(io.BytesIO(r.content))
z.extractall(path="../data")
germany_admin = gpd.read_file(
"../data/vg5000_01-01.utm32s.shape.ebenen/vg5000_ebenen_0101/VG5000_LAN.shp"
)
srtm_germ.projection
'GEOGCS["WGS 84",DATUM["WGS_1984",SPHEROID["WGS 84",6378137,298.257223563,AUTHORITY["EPSG","7030"]],AUTHORITY["EPSG","6326"]],PRIMEM["Greenwich",0,AUTHORITY["EPSG","8901"]],UNIT["degree",0.0174532925199433,AUTHORITY["EPSG","9122"]],AXIS["Latitude",NORTH],AXIS["Longitude",EAST],AUTHORITY["EPSG","4326"]]'
# load shapefile of Berlin
berlin = germany_admin[germany_admin["GEN"] == "Berlin"]
berlin = berlin.to_crs("epsg:4326")
Now, we are ready to crop the srtm to the size of berlin.
with rasterio.open(filepath) as src:
srtm_berlin, srtm_berlin_meta = es.crop_image(src, berlin)
extent = plotting_extent(srtm_berlin[0], srtm_berlin_meta["transform"])
# mask the nodata and plot the newly cropped raster layer
srtm_berlin_ma = np.ma.masked_equal(srtm_berlin[0], -9999.0)
### plot ###
fig, ax = plt.subplots(figsize=(8, 8))
berlin.boundary.plot(ax=ax, color="red", zorder=10)
ep.plot_bands(srtm_berlin_ma, title = "DEM SRTM Berlin",
cmap='terrain', vmin = 0, vmax = 120,
cbar=True, extent = extent,
ax = ax,)
plt.show()
The rd.TerrainAttribute
function is very easy to use, when computing the slope and aspect for each pixel. The function provides the atrrib
argument which can be set to 'slope_riserun'
and 'aspect'
, respectively. To visualization use the rd.rdShow
function.
Note: The
richDEM
documentation is a helpful ressource for various DEM derivates and calculations.
Slope
Slope is the steepness or the degree of incline of a surface and an be expressed in degrees or as a percentage. The slope of a cell is calculated by using a central difference estimation of a surface fitted to the cell and its neighbours. The slope chosen is the maximum of this surface (Horn 1981).
slope = rd.TerrainAttribute(srtm_germ, attrib='slope_riserun')
rd.rdShow(slope, axes=False, cmap='RdYlGn_r', figsize=(8, 5.5))
plt.show()
Get the just the slope for the SRTM of Berlin. Therefore, we will transform the data loaded with rasterio
into a format richDEM
can process. We will have to set the right projection and geotransform of the srtm_berlin
prior to calculating the slope.
rd_srtm_berlin = rd.rdarray(srtm_berlin[0], no_data=-9999)
rd_srtm_berlin.projection = srtm_germ.projection
rd_srtm_berlin.geotransform = srtm_germ.geotransform
# calculate the slope
slope = rd.TerrainAttribute(rd_srtm_berlin, attrib='slope_riserun')
# plot
rd.rdShow(slope, axes=False, cmap='RdYlGn_r', figsize=(8, 5.5))
plt.show()
Aspect
The Aspect is the orientation of slope, measured clockwise in degrees from 0 to 360, where 0 is north-facing, 90 is east-facing, and so on. The aspect is calculated as the direction of the maximum slope of the cell (Horn 1981).
## aspect
aspect = rd.TerrainAttribute(rd_srtm_berlin, attrib='aspect')
rd.rdShow(aspect, axes=False, cmap='gnuplot', figsize=(8, 5.5))
plt.show()
A hillshade is a 3D visualization of a surface and is usually rendered in greyscale. Hillshades are used to make a map look more appealing. For computing the hillshade, we will use the EarthPy
library which provides the hillshade()
function within the earthpy.spatial
module.
The scope of this function is explained very nicely in this tutorial.
hillshade = es.hillshade(srtm_berlin_ma)
ep.plot_bands(
hillshade,
cbar=True,
title="Hillshade made from DEM Berlin",
figsize=(10, 6),
)
plt.show()
We can also overlay the hillshade on top of the DEM for better visualization.
fig, ax = plt.subplots(figsize=(10, 6))
ep.plot_bands(
srtm_berlin_ma,
ax=ax,
cmap="terrain",
title="SRTM DEM Berlin\n overlayed with hillshade",
)
ax.imshow(hillshade, cmap="Greys", alpha=0.5)
plt.show()
Each cell in a DEM can be modeled as generating a certain amount of flow. When computing the flow accumulation, we cumulatively count the number of pixels that naturally drain into outlets. Flow accumulation can be used to find the drainage pattern of a terrain.
Flow accumulation calculations are always based on a so-called flow metric. A flow metric is a rule that divides the flow passing through a cell among one or more of its neighboring cells. There are several ways to do this. The methods vary in accuracy, complexity and thus computation time. If you need more metrics and methods than provided here, check out the documentation of richDEM
, since it gives a nice overview about all implemented flow metrics.
Here, we will only concetrate on the D8 metric after O'Callaghan & Mark 1984, which is sufficient for the scope of this introduction. The D8 metric assigns the flow from a target cell to one of its eight neighboring cells. The chosen neighbour is the one with the steepest slope. If there is no steepest neighbour, no flow direction is assigned (O'Callaghan & Mark 1984).
#Get flow accumulation with no explicit weighting, default = 1
accum_d8 = rd.FlowAccumulation(rd_srtm_berlin, method='D8')
rd.rdShow(accum_d8, axes=False, cmap='jet', figsize=(8, 5.5))
plt.show()
If you want to see the flow pattern for a specific area, please note, that we would have to use a smaller area of interest. Since it is very difficult to see specific flow patterns for larger areas. Please note that there are also other flow metrics, such as the $D_{4}$ (O'Callaghan & Mark 1984) or $D_{\infty}$ (Tarboton 1997). If you want to learn more about flow accumulation and metrics in Python, check out the documentation of richDEM
.
Citation
The E-Learning project SOGA-Py was developed at the Department of Earth Sciences by Annette Rudolph, Joachim Krois and Kai Hartmann. You can reach us via mail by soga[at]zedat.fu-berlin.de.
Please cite as follow: Rudolph, A., Krois, J., Hartmann, K. (2023): Statistics and Geodata Analysis using Python (SOGA-Py). Department of Earth Sciences, Freie Universitaet Berlin.