Short answer
The Terrain Ruggedness Index (TRI) is a per-cell measure of how much a point of terrain differs in elevation from its immediate neighbours. Riley's original 1999 formulation computes, for every cell, the square root of the summed squared elevation differences between that centre cell and its eight surrounding cells, returning a value in the same vertical units as the input DEM. Higher numbers mean rougher ground; flat plains return values near zero and steep, dissected terrain returns tens to hundreds of metres.
Because TRI is built directly from raw elevation differences, it is strongly correlated with slope and is sensitive to DEM resolution, vertical noise, and the size of the analysis window. If you understand those three controls, TRI becomes a fast, interpretable roughness layer for habitat modelling, route screening, geomorphic classification, and hazard work.
Riley's TRI: the exact formula
Riley, DeGloria and Elliot (1999) defined TRI over a 3x3 moving window. For a centre cell with elevation z0 and eight neighbours z1..z8:
TRI = sqrt( Σ (zi − z0)² ) for i = 1..8
The output is an absolute difference measure in DEM vertical units. A cell sitting in a uniform plane returns 0. A cell where every neighbour is 10 m higher returns sqrt(8 × 100) ≈ 28.3 m. Riley's paper also proposed seven descriptive classes (level, nearly level, slightly rugged, intermediately rugged, moderately rugged, highly rugged, extremely rugged) keyed to value ranges, but those thresholds were calibrated to a specific dataset and should be re-derived for your own terrain rather than copied blindly.
A common variant, attributed to Wilson et al. (2007) and used in marine/benthic work, replaces the root-sum-of-squares with the mean absolute difference of the eight neighbours. It is less heavily weighted by single large outliers and is what you get with gdaldem TRI -alg Wilson. Both variants answer the same question — local elevation contrast — but the Riley value will always be numerically larger and more outlier-sensitive than the Wilson value.
TRI is not the only roughness metric
Roughness is a family, not a single number, and choosing the wrong member of the family is the most common mistake teams make.
- Roughness (gdaldem roughness): the difference between the maximum and minimum elevation in the 3x3 window. It is the simplest and noisiest of the set, fully controlled by two cells.
- TRI (Riley/Wilson): uses all eight neighbours, so it is more stable than plain roughness but still correlated with slope.
- Vector Ruggedness Measure (VRM): developed by Sappington et al. (2007). It converts each cell's slope and aspect into a 3D unit vector, sums those vectors over the window, and measures how much they fail to align. VRM ranges from 0 (perfectly flat or perfectly planar slope) to 1 (maximally dispersed). Its key advantage is that it decorrelates ruggedness from slope — a steep but planar hillside scores low VRM, while a gully-cut slope of the same gradient scores high. If your downstream model already includes slope, prefer VRM so you are not feeding two collinear predictors.
The practical rule: if you want a quick relief-contrast layer in metres, use Riley TRI. If you are building a habitat or terrain-classification model alongside slope, use VRM.
Worked example with GDAL and QGIS
Start by making sure your DEM is in a projected CRS with metres for both horizontal and vertical units. TRI computed on a geographic DEM (EPSG:4326) is meaningless because the horizontal "distance" between cells is in degrees while the vertical is in metres. Reproject first:
gdalwarp -t_srs EPSG:32633 -tr 10 10 -r bilinear dem_4326.tif dem_utm.tif
Then compute the classic index:
gdaldem TRI dem_utm.tif tri_riley.tif -alg Riley -compute_edges
gdaldem TRI dem_utm.tif tri_wilson.tif -alg Wilson -compute_edges
-compute_edges generates values along the raster border instead of leaving a one-pixel NoData frame; without it the outer ring is dropped because a full 3x3 window is unavailable. In the QGIS Processing Toolbox, the same operation is Raster terrain analysis → Terrain Ruggedness Index, which wraps the GDAL algorithm. In ArcGIS Pro, there is no native TRI tool, but you can reproduce Riley's index with Focal Statistics (to get neighbour statistics) combined with Raster Calculator, or use VRM with the published Sappington toolbox.
To classify the result into interpretable bins, derive the breaks from your own histogram rather than Riley's defaults:
gdalinfo -stats tri_riley.tif # read min/max/mean/stddev
Then apply quantile or natural-breaks classes in the symbology so the legend reflects the actual distribution of your study area.
Choosing a window size
gdaldem TRI is hard-wired to a 3x3 window, which captures only the finest-scale roughness — at 1 m LiDAR resolution that is sub-3-metre texture, often dominated by vegetation, buildings, and DEM noise rather than landform. Ruggedness is scale-dependent: a 3x3 window on a 30 m SRTM tile describes ~90 m landform contrast, while the same window on a 1 m DTM describes surface micro-relief.
If your question is about landform-scale ruggedness, use a larger moving window. SAGA's terrain tools, GRASS r.tri-style approaches, or a focal RMS in a raster calculator let you set 9x9, 15x15, or larger neighbourhoods. A practical approach is to compute TRI at two or three window sizes (fine, medium, coarse) and inspect which one separates the landforms you actually care about. Always smooth or denoise the DEM (for example a light Gaussian or a gdalwarp resample to a coarser grid) before computing fine-window TRI on high-resolution LiDAR, or the index will largely map noise.
Common pitfalls and why they happen
- Mixed units. Computing TRI on an unprojected DEM mixes degrees and metres; the result is uninterpretable. This happens because GDAL does not warn you — it just reads cell values. Reproject to UTM or a national grid first.
- Treating TRI as slope-independent. Riley TRI rises with gradient, so on a smooth steep slope it reports "rugged" even though the surface is planar. Reach for VRM when you need to separate the two.
- Comparing TRI across resolutions. A TRI value from 1 m LiDAR is not comparable to one from 30 m SRTM, because the window covers a different ground footprint. Always state the source resolution and window size next to the values.
- NoData and voids. Sinks, water masks, and SRTM voids create artificial spikes at their edges. Fill or mask voids (for example
gdal_fillnodata.py) before computing the index. - Copying Riley's class thresholds. They were calibrated to one dataset and one resolution; re-derive breaks from your own histogram.
Validation and QA
Before a TRI layer goes into a model or report: confirm the CRS is projected and the histogram min is at or near zero (a non-zero minimum often signals a NoData or units problem). Overlay the TRI raster on a hillshade — high-TRI zones should coincide with visible gullies, scarps, ridges, and dissection, not with flat valley floors. Spot-check a handful of cells by reading the 3x3 neighbourhood and recomputing the formula by hand. Finally, if you computed both Riley and VRM, plot one against slope: TRI should correlate with slope, VRM should not, which confirms each is doing its job.
Bathyl perspective
We treat ruggedness as a deliberate modelling choice, not a default raster. The decision that matters is which roughness metric and what window match the geomorphic process under study, and we document both alongside the source DEM resolution so the layer can be reproduced and reused rather than guessed at later.
Related reading
- Topographic Position Index Explained
- DEM vs DTM vs DSM
- Why Your GIS Layers Do Not Line Up
- Terrain intelligence