How to Estimate Wind Speed at Hub Height in Python

On this page

Problem statement

Weather stations measure wind at 10 m. Modern turbines put their hubs at 100โ€“170 m, where the wind is faster and steadier, and the energy calculation depends on the cube of the speed at that height. Moving a speed from 10 m to the hub is therefore the most consequential single step in a wind resource estimate, and it is usually done with one number โ€” the shear exponent โ€” copied from a textbook.

When a dataset offers two heights, the exponent can be measured instead. ERA5 via Open-Meteo gives 10 m and 100 m for every hour at every grid cell, which is enough to measure shear per site, per hour of day and per season, and to test how wrong the textbook value is.

Quick answer

The power law moves a speed between heights:

v(h) = v(h_ref) ยท (h / h_ref) ** ฮฑ

Measure ฮฑ from two heights, then apply it from the higher one โ€” the shorter the extrapolation, the smaller the error:

import requests, numpy as np, pandas as pd

def wind(lat, lon):
    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": "ms",                 # the default is km/h
        "timezone": "UTC"}, timeout=120).json()
    d = pd.DataFrame(j["hourly"])
    d.index = pd.to_datetime(d.pop("time"))
    return d.rename(columns={"wind_speed_10m": "v10", "wind_speed_100m": "v100"})

d = wind(53.55, 9.99)                                     # Hamburg
alpha = np.log(d.v100.mean() / d.v10.mean()) / np.log(100 / 10)
hub = 117
d["v_hub"] = d.v100 * (hub / 100) ** alpha
print(f"mean speed: 10 m {d.v10.mean():.2f}, 100 m {d.v100.mean():.2f} m/s")
print(f"shear exponent from the two means: ฮฑ = {alpha:.3f}")
print(f"at {hub} m: {d.v_hub.mean():.2f} m/s")
mean speed: 10 m 3.94, 100 m 6.61 m/s
shear exponent from the two means: ฮฑ = 0.225
at 117 m: 6.85 m/s

The measured ฮฑ of 0.22 is 57% higher than the textbook 1/7, and that matters when extrapolating from 10 m โ€” much less when starting from 100 m.

Step-by-step solution

1. Request speeds in metres per second

Open-Meteo returns km/h unless told otherwise. Hamburg's 100 m mean arrives as 23.8, which would be an extraordinary site in m/s; it is 6.6 m/s.

2. Measure ฮฑ from two heights where you can

Compute it from the ratio of means for a site-level value, or hour by hour for a distribution. Drop hours where either speed is below about 1 m/s; the logarithm of a ratio of two small numbers is noise.

3. Extrapolate from the nearest height

From 100 m to 117 m, an error of 0.05 in ฮฑ changes the speed by less than 1%. From 10 m to 117 m, the same error changes it by 13%.

4. Let ฮฑ vary by hour if the hours matter

Shear is low on sunny afternoons and high on clear nights. If the analysis needs the diurnal pattern of output โ€” for matching demand, for example โ€” use an hourly ฮฑ, not an annual one.

5. Validate the method on the heights you have

Extrapolate 10 m to 100 m with the method you intend to use, and compare with the modelled 100 m speeds. The error there bounds the error you should expect at hub height.

Extrapolating to a 117 m hub from 10 m and from 100 m with the same uncertainty in the shear exponent. the same ยฑ0.05 in ฮฑ, two distancesfrom 10 mโœ•height ratio 11.7โœ•speed uncertainty ยฑ13%โœ•energy +45% / โˆ’31%โœ•the textbook ฮฑ dominatesfrom 100 mโœ“height ratio 1.17โœ“speed uncertainty ยฑ0.8%โœ“energy uncertainty ยฑ2.4%โœ“ฮฑ barely mattersStart from the highest height the data has: ERA5 gives 100 m everywhere spatialworkflow.io
The same uncertainty in ฮฑ, applied over a short and a long distance.

Code examples

Example 1 โ€” how wrong is 1/7?

sites = {"Ringkรธbing": (56.09, 8.24), "Hamburg": (53.55, 9.99), "Albacete": (38.99, -1.86)}
print(f"{'site':<12}{'ฮฑ':>7}{'v100 modelled':>15}{'10 m ร— 1/7':>12}{'error':>8}{'error in vยณ':>13}")
for name, (lat, lon) in sites.items():
    d = wind(lat, lon)
    a = np.log(d.v100.mean() / d.v10.mean()) / np.log(10)
    guess = d.v10 * 10 ** (1 / 7)
    err_v = guess.mean() / d.v100.mean() - 1
    err_p = (guess ** 3).mean() / (d.v100 ** 3).mean() - 1
    print(f"{name:<12}{a:>7.3f}{d.v100.mean():>15.2f}{guess.mean():>12.2f}"
          f"{err_v:>8.0%}{err_p:>13.0%}")
site              ฮฑ  v100 modelled  10 m ร— 1/7   error  error in vยณ
Ringkรธbing    0.183           8.37        7.62     -9%         -20%
Hamburg       0.225           6.61        5.48    -17%         -38%
Albacete      0.220           5.12        4.28    -16%         -36%

The textbook exponent underestimates the 100 m speed at every site, by 9โ€“17%, and the error in vยณ โ€” the energy in the wind โ€” is 2.2 times larger: 20โ€“38%.

Example 2 โ€” shear by hour of day and by season

d = wind(53.55, 9.99)
ok = (d.v10 > 1) & (d.v100 > 1)
a = np.log(d.v100[ok] / d.v10[ok]) / np.log(10)
table = a.groupby([a.index.month.map(lambda m: "summer" if m in (5, 6, 7, 8) else
                                      "winter" if m in (11, 12, 1, 2) else "other"),
                   a.index.hour]).median().unstack(0)
print(table.loc[[0, 3, 6, 9, 12, 15, 18, 21], ["winter", "summer"]].round(3).to_string())
time  winter  summer
time                
0      0.248   0.282
3      0.243   0.290
6      0.241   0.193
9      0.215   0.126
12     0.167   0.122
15     0.229   0.134
18     0.257   0.213
21     0.253   0.283

In summer the diurnal cycle is strong: ฮฑ falls from 0.29 at night to 0.12 at midday, when the sun mixes the lower atmosphere. In winter the sun is too weak to do much: the dip reaches only 0.17 around noon and shear is back near 0.25 by evening.

Example 3 โ€” the log law and the roughness length

# v(h) โˆ ln(h / z0): solve for z0 from the two heights, then extrapolate.
d = wind(53.55, 9.99)
r = d.v100.mean() / d.v10.mean()
z0 = np.exp((np.log(100) - r * np.log(10)) / (1 - r))
v_log = d.v100.mean() * np.log(hub / z0) / np.log(100 / z0)
v_pow = d.v100.mean() * (hub / 100) ** alpha
print(f"implied roughness length z0 = {z0:.2f} m")
print(f"at {hub} m: log law {v_log:.2f} m/s, power law {v_pow:.2f} m/s")
implied roughness length z0 = 0.33 m
at 117 m: log law 6.80 m/s, power law 6.85 m/s

The two laws agree closely over a short extrapolation. The implied roughness length is also a sanity check on the data: 0.1โ€“0.5 m is farmland with hedges and scattered buildings; 1 m or more is forest or town.

Median shear exponent between 10 m and 100 m at Hamburg by time of day in summer and winter. median ฮฑ by hour, Hamburg 2023summer 03:00 UTC0.290summer 12:00 UTC0.122 โ€” mixed by the sunsummer 21:00 UTC0.283winter 03:00 UTC0.243winter 12:00 UTC0.167 โ€” a shallow dipwinter 21:00 UTC0.25300.150.3textbook 1/7 = 0.143Annual median ฮฑ 0.223; ERA5 via Open-Meteo spatialworkflow.io
Afternoon sun mixes the air in summer; in winter the midday dip is shallow and short.

Explanation

Why shear exists

The ground slows the air next to it, and the effect weakens with height. Rough ground โ€” forest, towns, hedgerows โ€” slows it more and deeper, which is why ฮฑ is larger over land than over water. Stability adds a second effect: when the ground is colder than the air above it, vertical mixing stops and the layers decouple.

Why reanalysis heights are a gift

ERA5's 100 m wind is a model level, not a measurement, but it is a model that knows the stability and the surface roughness hour by hour. Starting from it removes most of the extrapolation. It does not remove the grid-cell smoothing: a ridge inside a 30 km cell is windier than the cell.

Why a mean ฮฑ is not the ฮฑ of the means

ฮฑ computed hour by hour and averaged is not the same as ฮฑ computed from the two mean speeds, because the relationship is non-linear. For energy work, apply ฮฑ to each hour, or use the one derived from the means as a single scalar โ€” but say which.

Measured shear exponent and the error of the 1/7 law in 100 m speed and in energy at three sites. how wrong is 1/7?siteฮฑ measuredv10010 m ร— 1/7error in vยณRingkรธbing0.1838.377.62โˆ’20%Hamburg0.2256.615.48โˆ’38%Albacete0.2205.124.28โˆ’36%speed errors of 9โ€“17% become energy errors 2.2 times largerThe textbook exponent underestimates shear over land at all three sites spatialworkflow.io
Over land, 1/7 is a lower bound on shear, not a default.

Edge cases or notes

  • Forest sites need a displacement height: the effective ground is two thirds of the canopy height up.
  • Complex terrain: speed-up over hills breaks the power law; use a flow model or a mast.
  • Low-level jets at night can make the wind at 150 m faster than a smooth profile predicts โ€” a benefit the power law misses.
  • Offshore: ฮฑ is typically 0.08โ€“0.12; 1/7 overestimates offshore shear.
  • Measurement masts with three or more heights allow a fitted profile rather than a two-point exponent.

FAQ

How do I convert wind speed from 10 m to hub height?

Use the power law with a shear exponent measured at the site, preferably starting from the nearest measured height.

What shear exponent should I use?

Measure it. Over land it is usually 0.15โ€“0.30; the textbook 1/7 (0.143) suits open, flat, neutral conditions.

Is the log law better than the power law?

It is more physical and needs a roughness length. Over short extrapolations the two agree closely.

Does Open-Meteo provide wind at 100 m?

Yes, in the historical archive (from ERA5). Request wind_speed_unit=ms or the values come in km/h.

Why does shear change during the day?

Sunshine heats the ground and mixes the air vertically in the afternoon; at night the ground cools and the layers decouple.

How far can I extrapolate?

As little as possible. The error grows with the logarithm of the height ratio times the error in ฮฑ.