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.

Wind capacity factors that are too low or suspiciously plausible, with cause and fix. check the input, not the outputsymptomcause → fixCF 5–25%, farms nearby 30%+10 m speed at the hub →extrapolateCF low even with shearα = 1/7 over land → measure αmean "speed" 20+km/h or knots → wind_speed_unit=msCF right but speeds hugecut-out cancelling a unit error →guard the inputPlausible gross onshore CF: 25–55% for low-specific-power turbines spatialworkflow.io
A capacity factor far outside 25–55% is usually a height or a unit, not a site.

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.

Gross capacity factor at Hamburg from 10 m wind, 10 m with the 1/7 law, 10 m with measured shear, 100 m wind and km/h mistaken for m/s. Hamburg 2023, V126 at 117 m10 m straight in10.3%10 m × 11.7^(1/7)26.6%10 m × 11.7^0.22539.2%100 m × 1.17^0.22539.8% — referencekm/h read as m/s40.9% — right for the wrong reason020%40%The last bar is 53% of hours past cut-out offsetting the rest spatialworkflow.io
The same year of wind, four heights of truth.

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 11.7α11.7^\alpha: 1.42 for α = 1/7, 1.74 for α = 0.225. The power curve between cut-in and rated is roughly cubic, so a 40–70% speed difference becomes a 2.2-fold (Ringkøbing) to 4.5-fold (Albacete) difference in output. The ratio is larger still at weak sites where much of the 10 m distribution sits below cut-in.

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.

Gross capacity factor at three sites from 10 m wind, the 1/7 law, measured shear and the 100 m reference. three sites, four heights of truth10 m raw1/7 lawα measured100 m ref.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 weaker the site, the larger the relative error spatialworkflow.io
Measured shear recovers almost everything; the 1/7 law recovers about half the gap.

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.

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.