The 10-Meter Standard and Why It Immediately Causes Problems
Every wind field in WeatherAPI — wind_kph, wind_mph, gust_kph — is referenced to 10 meters above ground level. That’s not arbitrary. It’s the WMO standard for surface synoptic observations, what METAR reports target, what GFS and NAM model output is normalized to, and what anemometers at official stations are supposed to be mounted at. Consistent reference height is what makes station data comparable across a global network.
Your application almost certainly does not care about wind at 10m. It cares about wind at whatever height is actually relevant — a rooftop solar installation at 4m, a construction crane at 30m, a wind turbine hub at 80m, a cyclist at roughly 1.5m above road surface. The API gives you 10m. The real-world use case is somewhere else. That gap matters more than most developers notice until something breaks in production.
How Big Is the Error, Actually?
Wind speed increases with height in the lower atmosphere because surface friction slows the air closest to the ground — the boundary layer effect. The standard empirical model for this is the power law profile:
V2 = V1 × (H2 / H1)^α
V1 is the known wind speed at height H1, V2 is what you want at height H2, and α (alpha) is a surface roughness exponent. For open flat terrain — airports, plains — alpha runs around 0.14. Suburban terrain with trees and buildings pushes it to 0.25–0.30. Dense urban cores can reach 0.40.
Run the numbers on a practical case: the API returns 20 kph at 10m and you’re building a logistics app that flags wind risk for delivery drones at 50m. With α = 0.14 (open terrain):
V_50m = 20 × (50 / 10)^0.14
= 20 × 5^0.14
≈ 20 × 1.237
≈ 24.7 kph
That’s nearly a 25% underestimate if you just pass the raw API value through. Now flip it — you’re flagging wind for workers on a platform at 2m, pouring concrete or handling sheet material. Same 20 kph reading:
V_2m = 20 × (2 / 10)^0.14
= 20 × 0.2^0.14
≈ 20 × 0.808
≈ 16.2 kph
The naive approach overestimates by almost 4 kph at near-ground level. If your alert threshold is 15 kph for scaffold workers, that difference changes whether the alert fires at all.
Choosing Alpha Is Where It Gets Honest
The roughness exponent is the awkward part. You don’t get it from the API — you have to assign it based on what you know about the location, and that knowledge is often imprecise.
General guidance consistent with Davenport’s roughness classification, which underpins most wind engineering standards:
- 0.10–0.14: Open water, flat coastal areas, airports — very low surface roughness
- 0.16–0.20: Open farmland, grassland, light scattered obstacles
- 0.25–0.30: Suburban residential, mixed low buildings, tree lines
- 0.35–0.40: Dense urban with tall buildings, heavily forested terrain
If your app has no land-use context at all, 0.20 is a reasonable default. Wrong for extremes, but not catastrophically wrong for most populated areas. If you have access to land-use data — OpenStreetMap land cover, for instance — you can do better by selecting alpha based on the dominant land class around the point.
My honest take: resist the urge to over-engineer this. For most applications, picking a single reasonable alpha for the terrain type and documenting the assumption is the right call. Dynamically estimating roughness per location sounds appealing but adds significant complexity for modest accuracy gains unless you’re doing serious wind resource assessment.
A Practical Implementation
Here’s a minimal Python function that takes a WeatherAPI response and corrects wind speed for a target height:
def correct_wind_height(wind_kph_10m, target_height_m, alpha=0.20):
"""
Adjust wind speed from 10m reference to target height.
alpha: surface roughness exponent (0.14 open, 0.20 suburban, 0.30+ urban)
"""
if target_height_m <= 0:
raise ValueError("Target height must be positive")
return wind_kph_10m * ((target_height_m / 10.0) ** alpha)
# Example: API returns 18 kph, you need speed at 25m in suburban terrain
api_wind = 18 # kph at 10m
height = 25 # meters
alpha = 0.25 # suburban
adjusted = correct_wind_height(api_wind, height, alpha)
print(f"{adjusted:.1f} kph at {height}m") # → 21.8 kph
You can apply the same function to gust_kph, but treat those results carefully. Gusts are short-duration events and the power law is a time-averaged profile, so a corrected gust figure is a rougher approximation than a corrected mean wind speed.
Where Our Own Pipeline Hits This Limit
Our GRIB2 ingestion pipeline processes HRRR, NAM, and GFS model output. HRRR's 3km grid gives us genuinely useful surface wind resolution across most of CONUS — but the model output we ingest is normalized to 10m AGL before it reaches the API response. ECMWF follows the same convention. The models themselves do compute wind at multiple pressure levels and sometimes at specific fixed heights (HRRR outputs an 80m wind layer for wind energy applications), but surfacing that cleanly through an API endpoint adds schema complexity we haven't tackled yet.
If you specifically need hub-height wind for turbine monitoring at 60m, 80m, or 100m — the correction above gives you a working approximation, but dedicated wind resource APIs that ingest the 80m HRRR layer directly will be more accurate. For most other use cases, the power law correction from our 10m values is the practical path forward.
Direction Doesn't Scale the Same Way
wind_degree doesn't change with height the way speed does. In the boundary layer, wind direction does veer — rotating clockwise in the northern hemisphere — with increasing height, due to the Coriolis effect and decreasing friction. This is the Ekman spiral. But the magnitude of that veering is typically 10–30 degrees across the first 100m, and it depends heavily on atmospheric stability conditions you don't have access to from a standard API response. For most application logic, treating wind direction as constant across heights within 100m is a defensible simplification. Just don't build a precise directional alert system assuming the 10m direction is exact at 80m.
The Edge Case That Actually Trips People Up
Low-wind conditions. The power law breaks down below roughly 1–2 m/s. When wind_kph returns 2 or 3, the math still runs fine, but the underlying assumption — a stable logarithmic boundary layer — stops holding. Near-calm winds don't follow a predictable height profile; thermal convection, local terrain features, and building wake effects take over instead. If your use case involves detecting near-calm conditions — drone launch decisions, outdoor event planning around very light breeze thresholds — skip the correction entirely below about 5 kph. Use the raw API value and accept that actual wind at your target height is somewhere in that neighborhood.
If you're currently passing wind_kph directly into any height-sensitive decision — safety flagging, energy estimation, drone ops — drop the correction function in, pick an alpha that fits your terrain, and rerun your threshold logic against a sample of historical cases. The numbers move more than you'd expect, and the cases where they move most tend to be exactly the ones where getting it wrong matters.
