Google Contrails Forecasts

The Google Contrails API provides a global contrail forecast free of charge.

To access the Contrails API, use an API key from a GCP project with the Contrails API enabled. Then, set the GOOGLE_API_KEY environment variable to the API key or provide it in the constructor GoogleForecast(key='YOUR_API_KEY').

Alternatively, you can use any google-auth credentials, e.g. run gcloud auth application-default login to authenticate on your local machine.

[1]:
import cartopy.crs as ccrs
import matplotlib.pyplot as plt
import pandas as pd

from pycontrails.core.flight import Flight
from pycontrails.datalib.google_forecast import (
    ExpectedEffectiveEnergyForcing,
    GoogleForecast,
    Severity,
)

Download the Google Contrails forecast for 24h into the future

Request the Severity variable for a simple scale from 0 (no warming) to 4 (severe warming). Alternatively, request ExpectedEffectiveEnergyForcing for the expected, effective energy forcing in J/m. See the Google Contrails documentation for more details.

[2]:
gf = GoogleForecast(
    # The API provides hourly forecasts, up to +48h into the future.
    time=pd.Timestamp("2025-01-04T20:00:00Z"),
    variables=Severity,  # Also supports ExpectedEffectiveEnergyForcing.
    # key="YOUR_API_KEY",  # Optional if GOOGLE_API_KEY env var is set
)

met = gf.open_metdataset()
met
[2]:
MetDataset with data:

<xarray.Dataset> Size: 75MB
Dimensions:                  (longitude: 1441, latitude: 721, level: 18, time: 1)
Coordinates:
  * longitude                (longitude) float64 12kB -180.0 -179.8 ... 180.0
  * latitude                 (latitude) float64 6kB -90.0 -89.75 ... 89.75 90.0
  * level                    (level) float64 144B 154.7 162.4 ... 329.3 344.3
    flight_level             (level) int16 36B 440 430 420 410 ... 290 280 270
    air_pressure             (level) float32 72B 1.547e+04 ... 3.443e+04
    altitude                 (level) float32 72B 1.341e+04 ... 8.23e+03
  * time                     (time) datetime64[ns] 8B 2025-01-04T20:00:00
    forecast_reference_time  (time) datetime64[ns] 8B 2026-02-05T08:00:00
Data variables:
    contrails                (longitude, latitude, level, time) float32 75MB ...
Attributes:
    inference_pipeline_version:  contrails.forecast-pipeline_20260129.02_p0
    api_version:                 contrails.api-service_20260217.02_p1
    aircraft_class:              default
    provider:                    Google
    dataset:                     Contrails Forecast
    product:                     forecast

Plot the forecasted contrail impact

[3]:
severity_da = met.data["contrails"].isel(time=0).max(dim="level")
p = severity_da.plot(
    x="longitude",
    y="latitude",
    cmap="Reds",
    subplot_kws=dict(projection=ccrs.PlateCarree(), transform=ccrs.PlateCarree()),
    figsize=(10, 4),
)
p.axes.coastlines()
plt.title("Maximum contrail severity over all flight levels");
../_images/integrations_GoogleForecast_5_0.png

Check the forecast for a specific flight trajectory

[4]:
# Load a sample flight.
df = pd.read_csv("../notebooks/data/flight-ap.csv", parse_dates=["time"])

# Hack: Because the sample flight is too far in the past, we "move" it to a more recent date.
# This is for demo purposes only and doesn't need to be done with any real flight plan!
df["time"] += pd.Timedelta(days=500)

fl = Flight(df)
fl
[4]:
Flight [4 keys x 130 length, 0 attributes]

Attributes
time[2025-01-04 20:07:00, 2025-01-04 22:16:00]
longitude[-97.01, -83.352]
latitude[32.903, 42.229]
altitude[190.5, 11582.4]
longitude latitude altitude time
0 -83.351920 42.228520 194.1576 2025-01-04 20:07:00
1 -83.366890 42.208270 234.0864 2025-01-04 20:08:00
2 -83.387894 42.181263 647.7000 2025-01-04 20:09:00
3 -83.438850 42.129593 1089.6600 2025-01-04 20:10:00
4 -83.499092 42.074844 1645.9200 2025-01-04 20:11:00
... ... ... ... ...
125 -97.008675 33.096130 1211.5800 2025-01-04 22:12:00
126 -97.008942 33.043941 944.8800 2025-01-04 22:13:00
127 -97.009109 32.992447 662.9400 2025-01-04 22:14:00
128 -97.009560 32.946274 419.1000 2025-01-04 22:15:00
129 -97.009727 32.903263 190.5000 2025-01-04 22:16:00

130 rows × 4 columns

Load the forecast for the entire flight duration

[5]:
# The forecast is provided hourly, so we round down the departure time, round up the arrival time, and load all hours inbetween.
global_mds = GoogleForecast(
    time=(fl.time_start.floor("h"), fl.time_end.ceil("h")),
    variables=ExpectedEffectiveEnergyForcing,
).open_metdataset()

global_mds
[5]:
MetDataset with data:

<xarray.Dataset> Size: 299MB
Dimensions:                            (longitude: 1441, latitude: 721,
                                        level: 18, time: 4)
Coordinates:
  * longitude                          (longitude) float64 12kB -180.0 ... 180.0
  * latitude                           (latitude) float64 6kB -90.0 ... 90.0
  * level                              (level) float64 144B 154.7 ... 344.3
    flight_level                       (level) int16 36B 440 430 420 ... 280 270
    air_pressure                       (level) float32 72B 1.547e+04 ... 3.44...
    altitude                           (level) float32 72B 1.341e+04 ... 8.23...
  * time                               (time) datetime64[ns] 32B 2025-01-04T2...
    forecast_reference_time            (time) datetime64[ns] 32B 2026-02-05T0...
Data variables:
    expected_effective_energy_forcing  (longitude, latitude, level, time) float32 299MB ...
Attributes:
    inference_pipeline_version:  contrails.forecast-pipeline_20260129.02_p0
    api_version:                 contrails.api-service_20260217.02_p1
    aircraft_class:              default
    provider:                    Google
    dataset:                     Contrails Forecast
    product:                     forecast

Intersect the forecast with the flight trajectory

[6]:
fl = fl.resample_and_fill()
fl["eeef_per_m"] = fl.intersect_met(
    global_mds["expected_effective_energy_forcing"],
    method="linear",
    fill_value=0.0,
)
fl.dataframe[["eeef_per_m"]].describe()
[6]:
eeef_per_m
count 1.300000e+02
mean 4.886437e+05
std 8.505285e+05
min 0.000000e+00
25% 0.000000e+00
50% 5.245293e+04
75% 5.177957e+05
max 4.006021e+06

Plot the expected effective energy forcing for the flight’s vertical profile

[7]:
fl.plot_profile(kind="scatter", c="eeef_per_m", cmap="Reds", rot=-45);
../_images/integrations_GoogleForecast_13_0.png