Short answer
The Topographic Position Index (TPI) measures whether a location sits higher or lower than its surroundings. For every cell it subtracts the mean elevation of a surrounding neighbourhood from the cell's own elevation. Positive TPI means the cell is elevated relative to its neighbours — a ridge, crest, or convex break. Negative TPI means it sits below them — a valley bottom, channel, or hollow. Values near zero mean either flat ground or a constant, planar slope.
TPI was popularised by Andrew Weiss in 2001 as the basis for an automated landform classification, and it remains one of the most useful single terrain derivatives because, unlike slope or aspect, it directly encodes relative position. Its behaviour is dominated by one parameter: the size of the neighbourhood you average over.
The formula and why the sign matters
For a cell with elevation z0 and a neighbourhood N:
TPI = z0 − mean(z in N)
The neighbourhood is usually an annulus (a ring) or a disc defined by an inner and outer radius. Because the index is a simple difference, its output is in DEM vertical units (metres) but is signed — and that sign is the whole point. The Terrain Ruggedness Index throws the sign away by squaring differences, so it can tell you a place is rugged but not whether you are standing on a peak or in a pit. TPI keeps the sign, so a ridge crest (+12 m above its surroundings) and a channel floor (−12 m below them) are clearly distinguished.
To make TPI comparable between areas with different relief, standardise it to z-scores using the mean and standard deviation of the TPI raster:
TPI_std = (TPI − mean_TPI) / stddev_TPI
Weiss's classification uses thresholds of roughly +1 and −1 standard deviation to separate ridges, valleys, and the middle ground.
Scale is everything
TPI has no single correct value because it is meaningless without a stated radius. The same cell can be a local ridge (positive TPI at a 90 m radius, sitting on a small terrace edge) and a regional valley (negative TPI at a 2 km radius, lying inside a large basin). This is a feature, not a flaw: by choosing the radius you tune which scale of landform the index responds to.
A practical recipe:
- Small neighbourhood (a few cells, e.g. 3–10 cell radius): detects fine features — gully shoulders, terrace risers, levees, individual outcrops. On 1 m LiDAR this is the scale of microtopography.
- Large neighbourhood (tens to hundreds of cells): detects broad ridges, major drainage valleys, and basin position.
Weiss's landform classification deliberately combines two TPI grids — one small, one large — precisely because no single scale captures hierarchy. A point can be a small valley nested inside a large ridge (a saddle), and only a two-scale comparison reveals that.
Worked example: a landform classification
Assume a projected DEM in EPSG:32633 at 10 m resolution. SAGA GIS, available inside QGIS Processing, provides the cleanest path.
-
Reproject and clean if needed:
gdalwarp -t_srs EPSG:32633 -tr 10 10 -r bilinear dem_4326.tif dem_utm.tif -
Compute a fine-scale TPI with the SAGA Topographic Position Index tool, annulus radius set so the outer ring is ~150 m (15 cells). This becomes the small grid.
-
Compute a broad-scale TPI with an outer radius of ~2,000 m (200 cells). This becomes the large grid.
-
Run the SAGA TPI Based Landform Classification tool, which takes both radii directly and applies Weiss's decision table. The output is the classic ten-class raster: deeply incised streams, midslope drainages, upland drainages, U-shaped valleys, plains, open slopes, upper slopes/mesas, local ridges, midslope ridges, and high ridges/mountain tops.
If you prefer to build it by hand, standardise each TPI to z-scores and apply the rules in a raster calculator: for example, large_TPI > 1 and small_TPI > 1 → high ridge; both < −1 → canyon/deep valley; both near zero with slope < 5° → plain; both near zero with slope ≥ 5° → open slope. The slope grid (from gdaldem slope -compute_edges) breaks the tie between flat plains and planar hillsides, which otherwise both return near-zero TPI.
Computing TPI without SAGA
A plain GDAL approach uses focal mean via a moving-window resample, then subtraction. The most direct route in QGIS is the SAGA TPI tool, but you can also approximate it with Raster calculator after building a focal-mean raster (for example with gdal_calc.py on a smoothed copy, or GRASS r.neighbors method=average):
r.neighbors input=dem output=dem_mean method=average size=31
r.mapcalc "tpi = dem - dem_mean"
Here size=31 is a 31x31 window (radius 15 cells), the GRASS equivalent of the fine-scale grid above. Note that GRASS r.neighbors uses a square window rather than a true annulus, which slightly biases the result toward the corners of the window; for rigorous work, the SAGA annulus is preferable.
Common pitfalls and why they happen
- Reporting TPI without a radius. A TPI value is uninterpretable without its neighbourhood size, because the index is intrinsically scale-relative. Always record the inner/outer radius in cells and metres.
- Confusing flat with planar. Both a flat plain and a uniform straight slope return TPI near zero, because the cell matches its neighbourhood mean in both cases. You must bring slope in to separate them — this is exactly why Weiss's table includes a slope threshold.
- Square windows on dissected terrain. A square neighbourhood weights diagonal neighbours over a longer distance than orthogonal ones, subtly distorting TPI in steep, linear valleys. Use an annulus where the tooling allows it.
- Unprojected DEMs. As with all neighbourhood terrain operations, run TPI on a projected grid in metres, not on EPSG:4326.
- Vegetation and DSM noise. Fine-radius TPI on a DSM will flag tree crowns and rooflines as ridges. Use a DTM, or denoise first.
Validation and QA
Overlay the TPI raster on a hillshade: positive zones must align with visible crests and ridgelines, negative zones with channels and hollows. Profile a transect across a known valley — TPI should swing negative at the thalweg and positive at both shoulders. For a landform classification, confirm that "plains" fall on genuinely flat valley floors and not on smooth steep slopes (the classic slope-threshold failure). Check the standardised TPI histogram is roughly centred on zero; a strong offset usually signals a NoData or edge-effect problem.
Bathyl perspective
We use TPI as a structural backbone for terrain interpretation because its signed, scale-tunable output maps cleanly onto how geologists actually describe a landscape — crests, slopes, and valley bottoms at nested scales. The discipline that makes it reliable is stating the radii explicitly and pairing it with slope, so the classification can be reproduced rather than re-tuned by eye.
Related reading
- Terrain Ruggedness Index Explained
- DEM vs DTM vs DSM
- LiDAR-Derived DEMs for Terrain Work
- Terrain intelligence