Estimating Time of Concentration with the NRCS Velocity Method

Time of concentration, Tc, is the time it takes a drop of water to travel from the hydraulically most distant point in a watershed to the outlet. It sets the timing of the runoff response, and in the NRCS framework it is computed not from a single regression formula but by summing the travel time of water through each flow regime it passes on the way down. This guide implements that segment-by-segment velocity method in Python, walking a flow path through sheet flow, shallow concentrated flow, and open channel, and then deriving the basin lag. It is a sibling guide to computing composite curve numbers from land-use rasters in Python under the SCS curve number runoff estimation topic, within the Rainfall-Runoff Modeling & Hydrologic Simulation domain. Together the curve number and the time of concentration supply the two parameters a curve-number hydrograph model needs: how much runs off, and when it arrives.

The velocity method is more work than a lumped empirical formula, but it is defensible because every term is traceable to a physical reach with a measured length, slope, and surface type. Where a regression equation hides its assumptions, the velocity method exposes them one segment at a time.


The Three Flow Regimes

Water leaving the divide starts as a thin sheet, concentrates into rills and swales, and finally enters a defined channel. Each regime moves water at a different speed, and the NRCS method computes a travel time Tt for each, then sums them:

text
Tc = Tt(sheet) + Tt(shallow concentrated) + Tt(channel)

The convention below uses United States customary units because the sheet-flow equation is defined in them: lengths in feet, slopes in feet per foot, velocities in feet per second, the two-year twenty-four-hour rainfall in inches, and travel times in hours.

NRCS Velocity Method Flow Path Three boxes left to right connected by arrows: sheet flow governed by the TR-55 kinematic equation, shallow concentrated flow governed by velocity equals coefficient times square root of slope, and channel flow governed by Manning's equation. Their travel times sum to Tc, and basin lag equals 0.6 times Tc. Sheet flow TR-55 kinematic ≤ 30 m reach Shallow concentrated V = k · √s rills and swales Channel flow Manning's equation defined channel Tc = Tt(sheet) + Tt(shallow) + Tt(channel) basin lag = 0.6 · Tc

Sheet flow

Sheet flow is shallow overland flow over a plane surface, the slowest regime. The TR-55 kinematic-wave solution gives its travel time:

text
Tt = 0.007 * (n * L)^0.8 / (P2^0.5 * s^0.4)

where n is the sheet-flow Manning roughness (a special overland-flow value, not a channel value), L is the reach length in feet, P2 is the two-year twenty-four-hour rainfall depth in inches, and s is the land slope in feet per foot. Tt comes out in hours. Sheet flow persists only until the flow gathers into rills — current NRCS guidance limits the reach to roughly 30 metres (about 100 feet), beyond which the reach must be reclassified as shallow concentrated flow.

Shallow concentrated flow

Once flow concentrates into rills and swales, hydraulic radius becomes impractical to define, so the method estimates velocity directly from slope:

text
V = k * sqrt(s)

with V in feet per second and s the slope in feet per foot. The coefficient k is 16.1345 for unpaved surfaces and 20.3282 for paved surfaces; these values already fold in a representative roughness and flow depth. The travel time is then Tt = L / (3600 * V) in hours, dividing by 3600 to convert seconds to hours.

Channel flow

In a defined channel with a measurable cross section, Manning’s equation gives the velocity:

text
V = (1.49 / n) * R^(2/3) * sqrt(s)

where R is the hydraulic radius (flow area divided by wetted perimeter) in feet, n is the channel Manning roughness, s is the channel slope, and the 1.49 factor carries the customary-unit conversion. As before, Tt = L / (3600 * V).


Parameter Reference

The velocity method’s accuracy rests almost entirely on choosing defensible roughness and coefficient values for each reach.

Segment Governing input Typical values
Sheet flow Overland Manning n Smooth asphalt 0.011; fallow soil 0.05; cultivated, > 20% residue 0.17; short-grass prairie 0.15; dense grass 0.24; woods, light underbrush 0.40
Shallow concentrated Coefficient k Unpaved 16.1345; paved 20.3282 (ft/s per √(ft/ft))
Channel Manning n Concrete-lined 0.013; clean natural stream 0.030; winding stream with pools 0.040; heavy brush channel 0.075

The sheet-flow n is a special overland-flow roughness and is much larger than the channel n for the same nominal cover, because it represents raindrop impact and micro-topography over a plane, not established open-channel resistance. Mixing the two tables is the single most common parameter error in the method.


Python Implementation

The function below accepts a list of flow-path segments, each a dictionary describing its type, length, slope, and roughness, and returns the total time of concentration and the derived lag. Each regime is handled by its own helper so the equations stay readable and independently testable.

python
import logging
import math
from dataclasses import dataclass

logging.basicConfig(level=logging.INFO, format="%(levelname)s: %(message)s")
logger = logging.getLogger(__name__)

# Shallow concentrated flow velocity coefficients (ft/s per sqrt(ft/ft))
K_UNPAVED = 16.1345
K_PAVED = 20.3282
SHEET_FLOW_MAX_FT = 100.0  # ~30 m; beyond this, reclassify as shallow concentrated


@dataclass
class Segment:
    """One reach of the hydraulic flow path."""
    kind: str          # 'sheet', 'shallow', or 'channel'
    length_ft: float   # reach length along the flow path
    slope: float       # slope in ft/ft (rise over run)
    n: float = 0.0     # Manning roughness (sheet or channel)
    paved: bool = False           # shallow concentrated only
    hydraulic_radius_ft: float = 0.0  # channel only
    p2_in: float = 0.0            # sheet only: 2-yr 24-hr rainfall, inches


def sheet_flow_time(seg: Segment) -> float:
    """TR-55 kinematic sheet-flow travel time in hours."""
    if seg.length_ft > SHEET_FLOW_MAX_FT:
        logger.warning(
            "Sheet-flow reach %.0f ft exceeds the %.0f ft limit; "
            "reclassify the excess as shallow concentrated flow.",
            seg.length_ft, SHEET_FLOW_MAX_FT,
        )
    tt = 0.007 * (seg.n * seg.length_ft) ** 0.8 / (
        seg.p2_in ** 0.5 * seg.slope ** 0.4
    )
    logger.info("Sheet flow: L=%.0f ft, n=%.3f -> Tt=%.3f h", seg.length_ft, seg.n, tt)
    return tt


def shallow_flow_time(seg: Segment) -> float:
    """Shallow concentrated flow travel time in hours from V = k*sqrt(s)."""
    k = K_PAVED if seg.paved else K_UNPAVED
    velocity = k * math.sqrt(seg.slope)          # ft/s
    tt = seg.length_ft / (3600.0 * velocity)     # seconds -> hours
    logger.info("Shallow flow: V=%.2f ft/s, L=%.0f ft -> Tt=%.3f h",
                velocity, seg.length_ft, tt)
    return tt


def channel_flow_time(seg: Segment) -> float:
    """Open-channel travel time in hours from Manning's equation."""
    velocity = (1.49 / seg.n) * seg.hydraulic_radius_ft ** (2.0 / 3.0) * math.sqrt(seg.slope)
    tt = seg.length_ft / (3600.0 * velocity)
    logger.info("Channel flow: V=%.2f ft/s, R=%.2f ft, L=%.0f ft -> Tt=%.3f h",
                velocity, seg.hydraulic_radius_ft, seg.length_ft, tt)
    return tt


def time_of_concentration(segments: list[Segment]) -> dict:
    """Sum segment travel times into Tc (hours) and derive SCS lag = 0.6*Tc."""
    dispatch = {
        "sheet": sheet_flow_time,
        "shallow": shallow_flow_time,
        "channel": channel_flow_time,
    }
    total = 0.0
    for seg in segments:
        if seg.kind not in dispatch:
            raise ValueError(f"Unknown segment kind {seg.kind!r}")
        if seg.slope <= 0:
            raise ValueError(f"Segment slope must be positive; got {seg.slope} for {seg.kind}")
        total += dispatch[seg.kind](seg)

    lag = 0.6 * total
    logger.info("Time of concentration Tc = %.2f h; basin lag = %.2f h", total, lag)
    return {"tc_hours": total, "lag_hours": lag}


if __name__ == "__main__":
    flow_path = [
        Segment(kind="sheet", length_ft=100.0, slope=0.010, n=0.24, p2_in=3.5),
        Segment(kind="shallow", length_ft=1400.0, slope=0.020, paved=False),
        Segment(kind="channel", length_ft=7500.0, slope=0.005, n=0.040,
                hydraulic_radius_ft=2.2),
    ]
    result = time_of_concentration(flow_path)
    logger.info("Result: %s", result)

The time_of_concentration function validates that every slope is positive before applying an equation — a zero or negative slope produces a division by zero in the sheet-flow term and an imaginary velocity everywhere else. The dispatch table keeps the summation loop agnostic to the number and order of segments, so a flow path with two channel reaches or no sheet-flow reach works without special cases.


Worked Example: Reading the Segment Contributions

Running the demo flow path makes the method’s structure concrete. The three reaches produce very different travel times despite the channel being by far the longest:

Reach Length Slope Velocity or basis Travel time
Sheet flow 100 ft 0.010 TR-55 kinematic, n = 0.24 0.30 h
Shallow concentrated 1400 ft 0.020 V = 2.28 ft/s (unpaved) 0.17 h
Channel 7500 ft 0.005 V = 4.46 ft/s (Manning) 0.47 h

The 100 ft sheet-flow reach contributes 0.30 hours — nearly a third of the total Tc of 0.94 hours — despite being less than 2 percent of the flow path length. That is the signature of the velocity method: the slow overland regime dominates the timing far out of proportion to its distance, which is exactly why the sheet-flow length cap matters so much. Double the sheet reach to 200 ft (in violation of the cap) and its travel time would jump to roughly 0.52 hours, inflating the whole Tc by more than 20 percent and pushing the derived lag from 0.56 to nearly 0.70 hours.

The channel reach, though 75 times longer than the sheet reach, adds only 0.47 hours because water moves an order of magnitude faster once it is confined. This pattern — a short, decisive overland head and a long, fast channel tail — holds for most natural watersheds and is worth checking as a sanity test: if the channel reach dominates the travel time in a basin with any appreciable overland length, the sheet or shallow roughness values are probably too low.


Deriving the Flow Path from a DEM

In an automated pipeline the segments are rarely typed in by hand; they are extracted from terrain. The hydraulically longest flow path is traced by following the flow-direction grid upstream from the outlet to the cell with the greatest travel-weighted distance, a computation that builds directly on the flow-routing products described in the broader hydrology data preparation work. Once the polyline path exists, it is split into reaches by regime: the uppermost portion, above the point where contributing area first exceeds a small threshold, is sheet flow; the mid-path, below that threshold but above channel initiation, is shallow concentrated flow; and everything below the mapped channel head is channel flow.

Slope for each reach comes from sampling DEM elevations at the reach endpoints and dividing the drop by the along-path length rather than the straight-line distance, which keeps the grade consistent with how the travel-time equations define it. Roughness and, for channels, hydraulic radius are attached from land-cover and channel-geometry lookups keyed to each reach. The result is a list of Segment objects identical in structure to the hand-built demo, which the same time_of_concentration function then reduces to Tc and lag. Automating the split points is where most of the engineering judgment lives, because the transition from sheet to shallow flow and the location of channel initiation both depend on thresholds that should be documented and, ideally, calibrated against surveyed flow paths.


Gotchas and Edge Cases

  • Sheet-flow length cap. Sheet flow is the slowest regime, so an over-long sheet-flow reach inflates Tc dramatically. Enforce the 30 m (100 ft) limit and push the remainder into shallow concentrated flow. The implementation logs a warning rather than silently accepting a physically implausible reach.

  • Roughness table confusion. The overland-flow Manning n used in the sheet-flow equation is far larger than the channel n used in Manning’s equation. Using a channel roughness of 0.03 in the sheet-flow term, or a sheet value of 0.24 in the channel term, produces travel times off by an order of magnitude.

  • Flat or reversed slopes. DEM-derived slopes can come out as zero or slightly negative in flat reaches and depressions. Guard against non-positive slopes explicitly and substitute a defensible minimum grade rather than letting the arithmetic blow up.

  • Not tracing the hydraulically longest path. Tc must follow the flow path with the greatest travel time, which is not always the longest straight-line distance. A short, steep channel can carry water faster than a long, flat overland reach; select the path by travel time, not geometry alone.

  • Unit slips. The customary-unit equations assume feet, feet per second, and inches. Feeding metres into the sheet-flow length or the channel hydraulic radius, or millimetres into P2, yields a plausible-looking but wrong Tc. Convert all inputs to the expected units before calling the functions, and keep the conversion at the boundary of the code.

  • Ignoring the lag propagation. Because basin lag is fixed at 0.6 times Tc, a 20 percent error in Tc becomes a 20 percent error in the modeled hydrograph peak timing. Sensitivity-test the segments that dominate the sum before trusting a single Tc value.


Frequently Asked Questions

How long can the sheet-flow segment be in the NRCS velocity method?

The TR-55 sheet-flow equation is only valid for short overland distances before flow concentrates into rills. Current NRCS guidance caps sheet flow at roughly 30 metres (about 100 feet). Beyond that length, reclassify the reach as shallow concentrated flow, or the equation will greatly overestimate travel time because sheet flow is the slowest regime and its travel time grows nonlinearly with length.

What is the relationship between time of concentration and lag?

In the NRCS method, basin lag is 0.6 times the time of concentration. Lag is the time from the centroid of excess rainfall to the peak of the runoff hydrograph, and it is the parameter that positions the SCS dimensionless unit hydrograph in time. Because lag is a fixed fraction of Tc, any error in Tc propagates directly into the modeled peak timing.

Why does shallow concentrated flow use a coefficient instead of Manning’s equation?

Shallow concentrated flow occurs in poorly defined rills and swales where the hydraulic radius is impractical to measure. The NRCS collapses Manning’s equation into a simple velocity-slope relationship, V = k * sqrt(s), with separate paved and unpaved coefficients that already embed a representative roughness and flow depth. This avoids forcing the analyst to estimate a cross section for a flow regime that has none.