Short answer

Slope thresholds turn a continuous slope raster into a small set of decision bands — for example "developable", "constrained", "excluded" — so a site or corridor can be filtered before detailed design. The thresholds are only meaningful if you pin down three things first: whether slope is in degrees or percent, the DEM resolution the slope was computed from, and the purpose of the cut (foundations, roads, agriculture, pipeline trenching each have different limits). Get those wrong and a clean-looking classified map will be confidently incorrect.

Degrees versus percent: get this right first

Slope can be expressed two ways and they are routinely confused:

  • Degrees: angle = arctan(rise / run). Range 0–90°.
  • Percent rise: (rise / run) × 100. Range 0 to infinity.

The trap is that they coincide at low angles and diverge fast. A 10% slope is 5.7°; a 100% slope is 45°; a 173% slope is 60°. So a threshold of "25" means radically different terrain depending on units — 25° is a steep, generally unbuildable slope, while 25% is a moderate 14° slope you can usually grade. Always label the layer and the threshold with its unit, and check what your tool emitted. gdaldem slope returns degrees by default and percent with -p; ArcGIS Slope lets you choose; QGIS Slope returns degrees.

DEM resolution changes the numbers

Slope is computed from a 3×3 neighbourhood, so the cell size is the measurement baseline. A 30 m DEM measures the average gradient over 30 m; a 1 m lidar DEM measures it over 1 m. The consequences:

  • Coarse DEMs (SRTM/Copernicus 30 m) smooth terrain. Short steep road cuts, river bluffs and small scars are flattened, so maximum slope is under-reported.
  • Fine DEMs (1–2 m lidar) resolve real micro-relief but also surface noise, vegetation returns and building edges if the DEM is a DSM rather than a bare-earth DTM.

A practical illustration: the same hillside can read 22° on a 30 m DEM and 38° on a 1 m DEM, because the fine grid catches a near-vertical bench the coarse grid averages away. Neither is "wrong" — they answer different spatial questions. The rule: state the DEM source and resolution next to every slope threshold, and choose a resolution matched to the feature size you are screening (corridors: 5–10 m; building pads: 1–2 m).

Also confirm you are using a DTM (bare earth), not a DSM. A DSM includes tree canopy and structures, which inject false steep edges everywhere there is vegetation.

Defensible thresholds by purpose

These are widely used screening bands, not regulations — local codes and geotechnical conditions override them.

General building / development (degrees):

BandSlopeScreening read
Easy0–5°Minimal grading
Workable5–15°Standard grading, drainage attention
Constrained15–25°Engineered cut/fill, cost escalation
Excluded (screen out)> 25°Retaining/geotechnical design required

Roads and rail: ruling gradients are usually expressed in percent; mainline rail wants under ~2.5%, low-volume rural roads can tolerate 8–12% on grade, so the relevant slope here is along the alignment, not terrain slope per se.

Agriculture / machinery: combine harvesting becomes difficult above ~10–15%; tractor rollover risk rises sharply above ~20°.

The honest practice is to treat any borrowed table as a hypothesis and adjust it to the project's engineering criteria and to the DEM you actually have.

Worked example: classify a slope raster in QGIS and GDAL

Goal: a three-class developability screen on a 5 m DTM in EPSG:32633.

  1. Confirm the DTM is projected in metres (UTM here) so horizontal and vertical units match. If it were in EPSG:4326, slope would need a scale factor and you should reproject first with gdalwarp -t_srs EPSG:32633 -tr 5 5 -r bilinear dtm.tif dtm_utm.tif.

  2. Compute slope in degrees:

gdaldem slope dtm_utm.tif slope_deg.tif -compute_edges

-compute_edges avoids a NoData fringe one cell wide around the raster and along internal voids.

  1. Reclassify into 1 = developable (0–15°), 2 = constrained (15–25°), 3 = excluded (>25°). With gdal_calc.py:
gdal_calc.py -A slope_deg.tif --outfile=screen.tif \
  --calc="1*(A<15) + 2*((A>=15)*(A<25)) + 3*(A>=25)" --NoData=0

Or in QGIS use Reclassify by table (Raster analysis) with the band breaks, which is easier to audit and document.

  1. Convert excluded zones to polygons for area statistics with Polygonize (raster to vector) or gdal_polygonize.py, then dissolve to report the share of the site screened out.

Common pitfalls and why they happen

  • Unit mismatch. Someone applies a 25 threshold to a percent raster (or vice versa). It happens because the unit is rarely written in the layer name. Bake the unit into the filename: slope_deg_5m.tif.
  • DSM instead of DTM. Tree-line and building edges create false "cliffs". Verify the elevation product before computing slope.
  • CRS in degrees. Computing slope on an EPSG:4326 DEM without a scale factor yields meaningless values because one degree of latitude is ~111 km but elevation is in metres.
  • Hard breaks on a single DEM. A cell at 24.9° and one at 25.1° land in different classes from sub-pixel noise. For high-stakes boundaries, smooth slightly (e.g. a 3×3 mean) or present a transition band.
  • Comparing two sites on different DEMs. A 10 m site will look "smoother" than a 1 m site purely from resolution. Match resolution before comparing.

QA and validation checklist

  • DEM is bare-earth DTM, projected in metres, resolution recorded.
  • Slope unit (degrees/percent) explicit in the layer name and legend.
  • Class breaks justified against the engineering or regulatory question.
  • -compute_edges used; NoData propagated correctly through the reclass.
  • Spot-check a few cells against contour spacing or a field/measured gradient.

Bathyl perspective

We treat a slope-class map as a filter that removes the obviously unviable terrain so engineering attention goes to the marginal middle band. We always ship it with the DEM source, resolution, slope unit and class breaks written into the legend, so the next reviewer can see exactly what "excluded" was measuring.

Related reading

Sources