Short answer

Terrain classification turns a continuous elevation grid into discrete landform classes — ridge, valley, flat, hillslope, depression — by computing DEM derivatives and thresholding them. The workhorses are slope, curvature (profile and plan), the Topographic Position Index (TPI), and the Terrain Ruggedness Index (TRI). The single most important idea is that classification is scale-dependent: a cell can be a local ridge and a regional valley at the same time, so the neighbourhood size you choose defines the landforms you get. Build the derivatives deliberately, threshold them with rules you can justify, and validate against known geomorphology.

The derivatives and what each one captures

Each derivative answers a different question about a cell's neighbourhood:

  • Slope — the steepest gradient (Horn's method over the 3×3 window). Separates flat ground from hillslopes. Distinguishes plains from valley walls where position alone is ambiguous.
  • Aspect — downslope direction. Useful for insolation, exposure, and structural orientation, less so for pure landform classes.
  • Profile curvature — curvature in the direction of slope; controls flow acceleration/deceleration. Convex (positive) sheds, concave (negative) accumulates.
  • Plan curvature — curvature across the slope; controls flow convergence/divergence. Concave converges (channels), convex diverges (spurs).
  • TPI — a cell's elevation minus the mean elevation of its neighbourhood. The direct measure of topographic position: ridge, slope, valley.
  • TRI — the mean absolute difference between a cell and its neighbours; a measure of local roughness.

For landform classification, slope + TPI does most of the work, with curvature refining convex/concave forms.

TPI is the backbone of landform classification

TPI (Weiss, 2001) is conceptually simple and very effective. For each cell:

TPI = elevation(cell) - mean(elevation in neighbourhood)
  • TPI > threshold: the cell sits above its surroundings → ridge, crest, upper slope.
  • TPI < -threshold: the cell sits below its surroundings → valley, channel, depression.
  • TPI ≈ 0: the cell matches its surroundings. This is ambiguous, so slope splits it: low slope → flat plain; higher slope → mid-slope / planar hillside.

Because TPI depends entirely on the neighbourhood radius, a small radius (a few cells) detects local micro-relief while a large radius detects regional position. The standard Weiss landform classification combines a small-scale and a large-scale TPI to capture both, yielding classes such as canyons, midslope drainages, upland flats, open slopes, local ridges, and high mountain ridges. This two-scale approach is why a single TPI surface is rarely enough.

Worked workflow: a six-class landform map

Starting from a bare-earth DEM in a projected, metric CRS (terrain derivatives are meaningless in degrees — reproject first if needed):

  1. Sanity-check the DEM. gdalinfo -stats dem.tif — confirm a projected CRS, square cells, sensible elevation min/max, and the NoData value. Fix striping or pits if present (Wang & Liu fill in QGIS) before deriving anything, because curvature and TPI amplify noise.

  2. Slope (degrees):

    gdaldem slope dem.tif slope.tif -compute_edges
    

    -compute_edges avoids a one-cell NoData border.

  3. TPI at two scales. GDAL's gdaldem TPI uses a fixed 3×3 window, which is too small for landforms. For a chosen radius use QGIS TPI Based Landform Classification (in the SAGA / processing providers) or compute TPI as elevation minus a focal mean. A focal-mean approach with GDAL/Python or r.neighbors (GRASS) at, say, a 150 m and a 1500 m radius gives the small- and large-scale TPI for a Weiss classification.

  4. Curvature to refine forms — QGIS/SAGA "Slope, Aspect, Curvature" or GRASS r.slope.aspect outputs profile and plan curvature.

  5. Classify with raster algebra. With slope S (degrees) and a single-scale TPI standardised to its standard deviation (tpi_sd), a defensible six-class rule using the QGIS Raster Calculator is:

    • Valley / channel: tpi_sd <= -1
    • Lower slope: tpi_sd > -1 AND tpi_sd <= -0.5
    • Flat plain: tpi_sd > -0.5 AND tpi_sd < 0.5 AND S <= 5
    • Mid-slope: tpi_sd > -0.5 AND tpi_sd < 0.5 AND S > 5
    • Upper slope: tpi_sd >= 0.5 AND tpi_sd < 1
    • Ridge / crest: tpi_sd >= 1

    The ±0.5 / ±1 standard-deviation breaks are the conventional Weiss thresholds; the 5° slope split between flat and mid-slope is a typical, adjustable boundary. Standardising TPI by its standard deviation makes the thresholds transferable between areas with different relief.

  6. Combine two TPI scales for the full Weiss classification if you need the richer ten-class scheme rather than six classes — the small-scale TPI captures local form, the large-scale TPI captures regional position.

Why thresholds and windows are not universal

Two things make a classification non-portable, and both are physical, not arbitrary:

  • Cell size sets the smallest landform that exists in the data and rescales slope and curvature (a coarse DEM has gentler slopes and weaker curvature). Thresholds tuned on 1 m LiDAR will misclassify 30 m SRTM.
  • Neighbourhood radius sets the scale of landform you are classifying. A 90 m TPI radius finds gullies and small ridges; a 2 km radius finds mountain ranges and basins. The same cell legitimately belongs to different classes at different scales.

This is why a classification must always be reported with its cell size, TPI radius (or radii), and thresholds. Without them the class map cannot be reproduced or compared.

Validation and QA

  • Overlay on a hillshade. gdaldem hillshade dem.tif hs.tif and drape the classification at ~50% opacity. Ridges should follow visual crests, valleys should follow visible drainage lines. Obvious mismatches mean the radius or thresholds are off.
  • Check against drainage. Burn a known stream network over the map; valley/channel classes should coincide with the streams.
  • Class balance. A classification that is 90% one class usually has thresholds that do not fit the relief; inspect the TPI histogram and re-centre breaks.
  • Edge inspection. Confirm -compute_edges was used and that NoData around the DEM border is excluded, not classified as flat.
  • Sensitivity test. Re-run at a second TPI radius; classes that flip wildly indicate you are at the wrong scale for the landforms of interest.

Common mistakes and why they happen

  • Deriving in a geographic CRS. Degree cells are anisotropic, so slope and TPI are latitude-biased. Fix: reproject to a metric CRS first.
  • Using GDAL's 3×3 TPI for landforms. A 3×3 window only detects pixel-scale bumps, not landforms. Fix: use a focal-mean TPI at a radius matched to the landform scale, or the SAGA/GRASS landform tools.
  • Hard-coded absolute TPI thresholds. They do not transfer between flat and mountainous areas. Fix: standardise TPI by its standard deviation.
  • Classifying a DSM. You classify rooftops and canopy, not terrain. Fix: use a bare-earth DTM.
  • Ignoring noise. Curvature and TPI explode on striped or pitted DEMs. Fix: fill pits and check for artefacts before deriving.

Bathyl perspective

We treat a landform classification as an interpretation with a stated recipe, not a finished fact: the DEM source and cell size, the TPI radius, the thresholds, and the validation against hillshade and drainage all travel with the layer. That lets a geologist judge whether the "ridges" are real structural crests or artefacts of the chosen scale, and lets a reviewer reproduce the map exactly rather than take the colours on trust.

Related reading

Sources