Wind Speed at 10 m Used as Hub Height
On this page
Problem statement
The wind energy estimate for an inland site comes out at a capacity factor of 10%, where operating farms nearby report 25–30%. Or it comes out exactly right, for a reason that has nothing to do with the wind. Both usually trace back to the first line of the calculation: the wind speed fed into the power curve was not the speed at the hub.
The standard 10 m wind — the height of weather station anemometers and the default in most datasets — sits in the layer where the ground slows the air most. A turbine hub at 117 m sees wind 40–70% faster, and the power curve turns that into a two- to four-and-a-half-fold difference in energy. The opposite error, feeding km/h into a curve that expects m/s, multiplies every speed by 3.6 — and can produce a plausible answer.
Quick answer
Use a speed at or near the hub, extrapolated with a shear exponent measured at the site, in m/s:
import requests, numpy as np, pandas as pd
from windpowerlib import WindTurbine
turbine = WindTurbine(turbine_type="V126/3450", hub_height=117)
cv, cp = turbine.power_curve.wind_speed, turbine.power_curve.value / 3.45e6
def cf(v):
return np.interp(v, cv, cp, right=0).mean()
def wind(lat, lon, unit="ms"):
j = requests.get("https://archive-api.open-meteo.com/v1/archive", params={
"latitude": lat, "longitude": lon,
"start_date": "2023-01-01", "end_date": "2023-12-31",
"hourly": "wind_speed_10m,wind_speed_100m", "wind_speed_unit": unit,
"timezone": "UTC"}, timeout=120).json()
return pd.DataFrame(j["hourly"]).drop(columns="time"), j["hourly_units"]["wind_speed_10m"]
d, unit = wind(53.55, 9.99) # Hamburg, inland
alpha = np.log(d.wind_speed_100m.mean() / d.wind_speed_10m.mean()) / np.log(10)
right = d.wind_speed_100m * (117 / 100) ** alpha
print(f"unit: {unit}; measured α = {alpha:.3f}")
print(f"10 m speed straight into the curve: CF {cf(d.wind_speed_10m):.1%}")
print(f"10 m × (117/10)^(1/7): CF {cf(d.wind_speed_10m * 11.7 ** (1 / 7)):.1%}")
print(f"10 m × (117/10)^α measured: CF {cf(d.wind_speed_10m * 11.7 ** alpha):.1%}")
print(f"100 m × (117/100)^α (reference): CF {cf(right):.1%}")
unit: m/s; measured α = 0.225
10 m speed straight into the curve: CF 10.3%
10 m × (117/10)^(1/7): CF 26.6%
10 m × (117/10)^α measured: CF 39.2%
100 m × (117/100)^α (reference): CF 39.8%
The 10 m speed alone cuts the gross capacity factor to a quarter of the reference. The textbook 1/7 exponent recovers part of the gap; a measured exponent recovers nearly all of it; starting from 100 m makes the exponent almost irrelevant.
Check every height and unit, start high, measure shear
1. Check the height of every speed you use
Dataset variables are usually named by height (wind_speed_10m, ws100, WS10m). If the name has no height, the documentation does; station data is 10 m unless stated.
2. Check the unit
Open-Meteo returns km/h by default; METAR and many aviation sources use knots; power curves are always m/s. A mean "speed" above 15 at 100 m over land is almost certainly not in m/s.
3. Start from the highest available height
ERA5 (via Open-Meteo), and many wind atlases, provide 100 m speeds. Extrapolating 17 m is far safer than extrapolating 107 m.
4. Measure α if you must extrapolate from 10 m
With two heights, compute α from their mean speeds. With only one, use a value appropriate to the terrain — 0.2–0.25 for farmland with hedges and scattered buildings, more over forest — and treat 1/7 as a lower bound over land.
5. Sanity-check the capacity factor
Onshore gross capacity factors for modern low-specific-power turbines are roughly 25–55%. Anything below 15% or above 60% needs an explanation before it is used.
The error at three sites, and the km/h default
Example 1 — the error at three sites
sites = {"Ringkøbing": (56.09, 8.24), "Hamburg": (53.55, 9.99), "Albacete": (38.99, -1.86)}
print(f"{'site':<12}{'10 m raw':>10}{'1/7':>8}{'α meas.':>9}{'reference':>11}")
for name, (lat, lon) in sites.items():
d, _ = wind(lat, lon)
a = np.log(d.wind_speed_100m.mean() / d.wind_speed_10m.mean()) / np.log(10)
ref = cf(d.wind_speed_100m * (117 / 100) ** a)
print(f"{name:<12}{cf(d.wind_speed_10m):>10.1%}"
f"{cf(d.wind_speed_10m * 11.7 ** (1 / 7)):>8.1%}"
f"{cf(d.wind_speed_10m * 11.7 ** a):>9.1%}{ref:>11.1%}")
site 10 m raw 1/7 α meas. reference
Ringkøbing 25.3% 47.6% 53.9% 55.1%
Hamburg 10.3% 26.6% 39.2% 39.8%
Albacete 5.5% 15.5% 24.2% 24.7%
The relative error is largest where the wind is weakest, because low speeds sit on the steep lower part of the power curve — which is exactly where siting decisions between marginal sites are made.
Example 2 — the km/h default
d_kmh, unit = wind(53.55, 9.99, unit="kmh")
print(f"unit returned: {unit}; mean 100 m value {d_kmh.wind_speed_100m.mean():.1f}")
print(f"fed to the curve as if m/s: CF {cf(d_kmh.wind_speed_100m):.1%}")
print(f"hours past the curve's cut-out ({cv.max()} m/s): "
f"{(d_kmh.wind_speed_100m > cv.max()).mean():.0%}")
unit returned: km/h; mean 100 m value 23.8
fed to the curve as if m/s: CF 40.9%
hours past the curve's cut-out (22.5 m/s): 53%
Speeds 3.6 times too high push most hours past rated power and 53% of them past the 22.5 m/s cut-out, where the curve returns zero. The two effects cancel almost exactly: 40.9% against the correct 39.8%. A unit error that produces a plausible number is the worst kind, and only a check on the speeds themselves catches it.
Example 3 — a guard for both errors
def check_hub_wind(v, height_m, unit):
assert unit in ("m/s", "ms"), f"wind speed must be m/s, got {unit}"
assert height_m >= 60, f"speed is for {height_m} m; extrapolate to hub height first"
mean = float(np.mean(v))
assert 3 <= mean <= 12, f"mean hub-height speed {mean:.1f} m/s is implausible"
return mean
d, unit = wind(53.55, 9.99)
print(check_hub_wind(d.wind_speed_100m, 100, unit))
for args in ((d.wind_speed_10m, 10, unit), (d_kmh.wind_speed_100m, 100, "m/s")):
try:
check_hub_wind(*args)
except AssertionError as e:
print("caught:", e)
6.614501141552512
caught: speed is for 10 m; extrapolate to hub height first
caught: mean hub-height speed 23.8 m/s is implausible
The second case lies about its unit on purpose: the plausibility range on the mean catches it anyway.
Why the error is so large
Why the error is so large
Between 10 m and 117 m the wind speed rises by a factor of
Why 1/7 underestimates over land
The one-seventh law comes from neutral flow over smooth, open terrain. Over farmland with hedges, villages and woods, and in the stable nights that dominate winter, shear is higher. Using 1/7 over land is systematically optimistic about the ground and pessimistic about the wind.
Why the unit error is not obviously absurd
Cut-out caps the damage: hours pushed into the storm range produce nothing, offsetting the hours pushed to rated power. At Hamburg the offset is nearly perfect and the wrong calculation lands within 1.1 points of the right one. At a windier site more hours cross cut-out and the answer comes out too low; at a calmer one, too high. Nothing about the output reveals the error — which is why the guard checks the input.
Station data, knots and gusts
- Station 10 m data is often at airports, which are flat and open — lower shear than the surrounding country.
- Hub heights differ by model and site: 84–166 m for current onshore turbines; use the one you are evaluating.
- Knots: 1 kn = 0.514 m/s; METAR and marine data use them.
- ERA5 at 100 m underestimates speed over complex terrain; the height fix does not fix the grid-cell smoothing.
- Gust fields (
wind_gusts_10m) are peaks, not means, and never belong in a power curve.
Internal links
- How to estimate wind speed at hub height — measuring and applying α
- Wind resource explained — why the cube makes every speed error bigger
- Wind energy from the mean speed is wrong — the other common shortcut
- How to estimate wind energy with a Weibull distribution — the full calculation
- Capacity factor explained — what a plausible value looks like
- PV yield off by a factor — the solar equivalent of a unit error
FAQ
Why is my wind capacity factor so low?
You probably used 10 m wind speeds without extrapolating to hub height. The energy can be two to four and a half times too low.
Is the 1/7 power law good enough?
As a lower bound over flat, open land. Measured shear over most land is higher.
Why is my capacity factor unrealistically high?
Check the unit: km/h or knots fed to a power curve in m/s inflate every speed. Beware that cut-out can hide this and return a plausible figure.
What unit does Open-Meteo use for wind?
km/h by default. Add wind_speed_unit=ms to the request.
Which height should I start from?
The highest available — 100 m from ERA5 — to minimise extrapolation.
What is a plausible onshore capacity factor?
Roughly 25–55% gross for modern low-specific-power turbines; net values are 10–20% lower.