3 min read

Scoring Trail Difficulty from Elevation Data

Using the Shenandoah formula to automatically compute trail difficulty ratings from elevation gain and distance — and when to override it.

Trail apps love difficulty ratings. But most of them assign ratings by hand, inconsistently, or not at all. For a trail discovery project I’ve been building, I wanted something automatic and reproducible — compute a difficulty score from the data rather than guess at it.

The Shenandoah Difficulty Rating is a simple formula that’s been used by the US National Park Service for decades:

difficulty = √(2 × gain_ft × dist_mi)

It captures the two things that actually make a trail hard: how far you go and how much you climb. A long flat walk and a short steep scramble can have the same difficulty score — which roughly matches real-world experience.

Bucketing Scores into Labels

The raw score is a continuous number. To assign the familiar easy/moderate/hard/expert labels, you need cutoffs. After some calibration against trails I know personally, I landed on:

LabelScore range
Easy< 50
Moderate50–100
Hard100–200
Expert> 200

A few reference points to sanity-check these thresholds: a flat 5-mile trail with 200 ft of gain scores ~45 (easy). Half Dome via the standard route — 17 miles, ~5,000 ft gain — scores ~412 (expert). That lines up with reality.

Implementation

The pipeline already computed distance with Haversine and elevation gain from SRTM data, so plugging in the formula was straightforward:

import math

def shenandoah_difficulty(gain_m: float, dist_m: float) -> float:
    gain_ft = gain_m * 3.28084
    dist_mi = dist_m / 1609.34
    return math.sqrt(2 * gain_ft * dist_mi)

def classify_difficulty(score: float) -> str:
    if score < 50:
        return "easy"
    elif score < 100:
        return "moderate"
    elif score < 200:
        return "hard"
    else:
        return "expert"

Overrides for Edge Cases

Automated scoring works well for most trails, but it breaks down at the extremes. A 0.3-mile summit scramble with fixed ropes will score “easy” because the distance is tiny, even though it requires technical skill. A paved multi-use path scores identically to a brushy off-trail slog of the same distance and gain.

The solution is a simple override system: the metadata layer can specify difficulty: hard for a trail, and the pipeline will use that instead of the computed value. Overrides are explicit and version-controlled, so you can see when and why a trail was manually classified.

Most trails — about 95% in my dataset — use the computed score. Overrides are reserved for genuinely weird cases.

Limitations

The formula treats elevation gain as the only measure of “hard.” It ignores trail surface, exposure, technical terrain, and route-finding difficulty. A granite slab climb and a well-maintained switchback trail can have the same difficulty score under this model.

For a general trail discovery app, that’s an acceptable tradeoff. Difficulty ratings are meant to help someone decide whether to attempt a trail, not precisely quantify the experience. The formula is transparent, reproducible, and consistent — which is more than most hand-assigned ratings manage.