Skip to article frontmatterSkip to article content

Heat Waves - AIFS


Run this tutorial via free cloud platforms: colab kaggle

In the end of April 2025, Western European countries experienced unseasonably warm weather. This example shows analysis of the selected parameters:

  • z geopotential at constant pressure level 500 hPa,

  • t temperature at 850 hPa, and

  • 2t 2 metre temperature of the AIFS-Single datasets on 30 April at 12 UTC in France (48.3° N, 4.1° 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

import os

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 = [20250429, 20250430, 20250501]
TIME = 0
STEPS = 12
STREAM = "oper"
TYPE = "fc"
MODEL = "aifs-single"
  • Parameters available on a single level:

PARAM_SFC = ["2t"]
LEVELTYPE = "sfc"

DATES = [20250429, 20250430, 20250501]
TIME = 0
STEPS = 12
STREAM = "oper"
TYPE = "fc"
MODEL = "aifs-single"

Data and plots directories

DATADIR = './data_dir/'
os.makedirs(DATADIR, exist_ok=True)

PLOTSDIR = './plots/'
os.makedirs(PLOTSDIR, exist_ok=True)

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"{DATADIR}{model}_{''.join(param)}_{''.join(map(str, levelist))}_{_date}.grib2" if levelist else f"{DATADIR}{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. Geopotential at 500 hPa and temperature at 850 hPa

When using the ls() method, a list of all the fields in the file we downloaded will be displayed.

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 29 April 2025
ds = ekd.from_source("file", list_of_files[0])
ds.ls()
Loading...

Only the parameters and their levels which we specified above will be returned when we apply the sel() function.

t850 = ds.sel({"level": 850, "shortName": "t"})
t850.ls()
Loading...

In the cell below, the geopotential on 500 hPa will be selected and a new fieldlist with a single field containing the new values and metadata will be created.

z500 = ds.sel({"level": 500, "shortName": "z"})[0]
z500.ls()
Loading...

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.

ds_gh500 = z500.values / (9.80665 * 10)

To change metadata values, put key value pairs to the override() function.

md_gh500 = z500.metadata().override(shortName="gh")
md_gh500["shortName"], md_gh500["level"]
('gh', 500)

When using the head() method, a selected number of rows n and information about the fields in the file we downloaded will be displayed.

gh500 = ekd.FieldList.from_array(ds_gh500, md_gh500)
gh500.head()
Loading...

3. 2-metre temperature

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=[])

# Select data from 30 April 2025
ds_2t = ekd.from_source("file", list_of_files[1])
ds_2t.ls()
Loading...

There is no need to convert temperature from Kelvin to Celsius, because in the earthkit-plots library we can set units of it.

t2m = ds_2t.sel(shortName="2t")
t2m.ls()
Loading...

4. Data visualisation

The plot below shows analysis of T850 and z500 on 29 April 2025.

chart = ekp.Map(domain="Europe")

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

chart.contourf(t850, style=t850_shade)

chart.contour(gh500,
              levels={"step": 10, "reference": 550},
              linecolors="black",
              linewidths=[0.5, 1, 0.5, 0.5],
              labels = True,
              legend_style = None,
              transform_first=True)

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

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

chart.title(
    "AIFS-Single: 500 hPa geopotential height and 850 hPa temperature\n"
    "{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)\n",
    fontsize=14, horizontalalignment="center",
)
chart.save(f"{PLOTSDIR}{''.join(PARAM_PL)}_{MODEL}_{DATES[0]}{TIME}-{STEPS}h.png")
chart.show()
<Figure size 800x700 with 2 Axes>

The plot below shows analysis of 2 metre temperature on 30 April at 00 UTC.

chart = ekp.Map(domain=[-7, 15, 43, 53])

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

chart.grid_cells(t2m, style=t2m_shade)

chart.title(
    "AIFS-Single: {variable_name} over Western Europe\n"
    "{base_time:%Y-%m-%d %H} UTC (+{lead_time}h)\n",
    fontsize=14, horizontalalignment="center",
)

chart.coastlines(resolution="low")
chart.gridlines()
chart.cities(adjust_labels=True)

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

chart.save(f"{PLOTSDIR}{''.join(PARAM_SFC)}_{MODEL}_{DATES[1]}{TIME}-{STEPS}h.png")
chart.show()
<Figure size 800x700 with 2 Axes>