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.

Metres per second, a measured exponent, the nearest height

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.

How wrong the one-seventh rule is in Hamburg

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.

Why shear exists, and averaging exponents

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.

Forests, complex terrain and low-level jets

  • 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 α.