Skip to article frontmatterSkip to article content

High Winds - AIFS


Winter storms had a tremendous impact on Sweden during the first week of January. A low-pressure system named Floriane impacted various parts of north-western Europe.

This example shows analysis of the selected parameters:

  • z geopotential at constant pressure level 500 hPa,
  • t temperature at 850 hPa,
  • msl mean sea level pressure, and
  • tp total precipitation of the AIFS datasets on 7 January 2025 in Sweden (58.58° N, 11.07° E).

1. Set Up Your Environment and Find ECMWF Open Data

Open data will be downloaded from a publicly available Amazon S3 Bucket. First, the following Python libraries need to be installed in the current Jupyter kernel:

  • ecmwf-opendata to download data and
  • earthkit to analyse and plot the data.

If the packages are not installed yet, uncomment the code below and run it.

# !pip3 install earthkit ecmwf-opendata
from ecmwf.opendata import Client
import earthkit.data as ekd
import earthkit.plots as ekp
import earthkit

List of parameters to retrieve from open datasets

The selected values below can be modified.

  • Parameters available on pressure levels:
PARAM_PL = "z" # "t"
LEVELS = [500, 850]
LEVELTYPE = "pl"

DATES = [20250104, 20250105, 20250106, 20250107, 20250108]
TIME = 0
STEPS = 0
STREAM = "oper"
TYPE = "fc"
MODEL = "aifs"
  • Parameters available on a single level:
PARAM_SFC = "tp" # "msl"
LEVELTYPE = "sfc"

DATES = [20250104, 20250105, 20250106, 20250107, 20250108]
TIME = 0
STEPS = 12
STREAM = "oper"
TYPE = "fc"
MODEL = "aifs"

Get the data using the ECMWF Open Data API

def get_open_data(date, time, step, stream, _type, model, param, leveltype, levelist=[]):
    client = Client(source="aws")
    list_of_files = []
    # Get the data for all dates
    for _date in DATES:
        filename = f"{model}_{''.join(param)}_{''.join(map(str, levelist))}_{_date}.grib2" if levelist else f"{model}_{''.join(param)}_{leveltype}_{_date}.grib2"
        data = client.retrieve(
            date=_date,
            time=time,
            step=step,
            stream=stream,
            type=_type,
            levtype=leveltype,
            levelist=levelist,
            param=param,
            model=model,
            target=filename
            )
        list_of_files.append(filename)
    return data, list_of_files

2. Temperature at 850 hPa

data, list_of_files = get_open_data(date=DATES,
                                    time=TIME,
                                    step=STEPS,
                                    stream=STREAM,
                                    _type=TYPE,
                                    model=MODEL,
                                    param=PARAM_PL,
                                    leveltype=LEVELTYPE,
                                    levelist=LEVELS)
                                                                                
# Select AIFS model data from 4 to 8 January 2025
t = ekd.SimpleFieldList()
for _file in list_of_files:
    ds = ekd.from_source("file", _file)
    md = ds.metadata()
    v = ds.to_array()
    for f in range(len(md)):
        t.append(ekd.ArrayField(v[f], md[f]))
t.ls()
Loading...
t850 = t.sel({"level": 850, "shortName": "t"})
t850.head()
Loading...

3. Geopotential at 500 hPa

The input values can be set here.

data, list_of_files = get_open_data(date=DATES,
                                    time=TIME,
                                    step=STEPS,
                                    stream=STREAM,
                                    _type=TYPE,
                                    model=MODEL,
                                    param=PARAM_PL,
                                    leveltype=LEVELTYPE,
                                    levelist=LEVELS)
                                                                                

Geopotential height is calculated by dividing the geopotential by the Earth’s mean gravitational acceleration, g (=9.80665 m s-2). In the ECMWF Open Charts, it is plotted in geopotential decameters. Therefore, our result also need to be divided by 10.

# Select AIFS model data from 4 to 8 January 2025
z = ekd.SimpleFieldList()
for _file in list_of_files:
    ds = ekd.from_source("file", _file)
    md = ds.metadata()
    v = ds.to_array() / (9.80665 * 10)
    for f in range(len(md)):
        z.append(ekd.ArrayField(v[f], md[f].override(shortName="gh")))
z.ls()
Loading...
gh500 = z.sel({"level": 500, "shortName": "gh"})
gh500.describe("gh")
Loading...

3. Total precipitation

The input values can be set here.

data, list_of_files = get_open_data(date=DATES,
                                    time=TIME,
                                    step=STEPS,
                                    stream=STREAM,
                                    _type=TYPE,
                                    model=MODEL,
                                    param=PARAM_SFC,
                                    leveltype=LEVELTYPE,
                                    levelist=[])
                                                                                

A unit of total precipitation is kg/m2^2. 1 kg of rainwater fills an area of 1 m2^2 with the water of height 1 mm.
In the ECMWF Open Charts, total precipitation is also plotted in millimetres.

# Select AIFS model data from 4 to 8 January 2025
tp = ekd.SimpleFieldList()
for _file in list_of_files:
    ds = ekd.from_source("file", _file)
    md = ds.metadata()
    v = ds.to_array()
    for f in range(len(md)):
        tp.append(ekd.ArrayField(v[f], md[f]))
tp.ls()
Loading...

4. Mean sea level pressure

The input values can be set here.

data, list_of_files = get_open_data(date=DATES,
                                    time=TIME,
                                    step=STEPS,
                                    stream=STREAM,
                                    _type=TYPE,
                                    model=MODEL,
                                    param=PARAM_SFC,
                                    leveltype=LEVELTYPE,
                                    levelist=[])
                                                                                

We will plot mean sea level pressure data in hPa, therefore we need to divide them by 100.

# Select AIFS model data from 4 to 8 January 2025
msl = ekd.SimpleFieldList()
for _file in list_of_files:
    ds = ekd.from_source("file", _file)
    md = ds.metadata()
    v = ds.to_array() / 100
    for f in range(len(md)):
        msl.append(ekd.ArrayField(v[f], md[f]))
msl.ls()
Loading...

5. Data visualisation

The plots below show analysis of T850 and z500 from 4 to 8 January 2025.

figure = ekp.Figure(domain="Europe", size=(9, 8), rows=3, columns=4)

t850_shade = ekp.styles.Style(
    colors="Spectral_r",
    levels=range(-40, 50, 2),
    units="celsius",
)

for i in range(5):
    figure.add_map(1+i//4, i%4)
figure.contourf(t850, style=t850_shade)

figure.coastlines(resolution="low")
figure.gridlines()

figure.legend(location="bottom", label="{variable_name} ({units})")

figure.subplot_titles("{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)")
figure.title(
    "AIFS: Temperature at 850 hPa over {domain}\n",
    fontsize=14, horizontalalignment="center",
)
figure.save(fname=f"./plots/{PARAM_PL}_{MODEL}_{DATES[-1]}{TIME}-{STEPS}h.png")
figure.show()
<Figure size 900x800 with 6 Axes>
figure = ekp.Figure(domain="Europe", size=(9, 8), rows=3, columns=4)

gh500_shade = ekp.styles.Style(
    colors="Spectral_r",
    levels=range(490, 600, 10),
)

for i in range(5):
    figure.add_map(1+i//4, i%4)
figure.contourf(gh500, style=gh500_shade)

figure.coastlines(resolution="low")
figure.gridlines()

figure.legend(location="bottom", label="{variable_name} (gpd)")

figure.subplot_titles("{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)")
figure.title(
    "AIFS: Geopotential height at 500 hPa over {domain}\n",
    fontsize=14, horizontalalignment="center",
)
figure.save(fname=f"./plots/{PARAM_PL}_{MODEL}_{DATES[-1]}{TIME}-{STEPS}h.png")
figure.show()
<Figure size 900x800 with 6 Axes>

The plots below show analysis of total precipitation and mean sea level pressure from 4 to 8 January 2025.

figure = ekp.Figure(domain="Europe", size=(9, 8), rows=3, columns=4)

tp_shade = ekp.styles.Style(
    colors="Spectral_r",
    levels=range(1, 250, 5),
    units="mm",
)

for i in range(5):
    figure.add_map(1+i//4, i%4)
figure.contourf(tp, style=tp_shade)

figure.coastlines(resolution="low")
figure.gridlines()

figure.legend(location="bottom", label="{variable_name} ({units})")

figure.subplot_titles("{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)")
figure.title(
    "AIFS: {variable_name} over {domain}\n",
    fontsize=14, horizontalalignment="center",
)
figure.save(fname=f"./plots/{PARAM_SFC}_{MODEL}_{DATES[-1]}{TIME}-{STEPS}h.png")
figure.show()
<Figure size 900x800 with 6 Axes>
figure = ekp.Figure(domain="Europe", size=(9, 8), rows=3, columns=4)

msl_shade = ekp.styles.Style(
    colors="Spectral_r",
    levels=range(950, 1050, 10),
)

for i in range(5):
    figure.add_map(1+i//4, i%4)
figure.contourf(msl, style=msl_shade)

figure.coastlines(resolution="low")
figure.gridlines()

figure.legend(location="bottom", label="{variable_name} (hPa)")

figure.subplot_titles("{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)")
figure.title(
    "AIFS: {variable_name} over {domain}\n",
    fontsize=14, horizontalalignment="center",
)
figure.save(fname=f"./plots/{PARAM_SFC}_{MODEL}_{DATES[-1]}{TIME}-{STEPS}h.png")
figure.show()
<Figure size 900x800 with 6 Axes>