Copernicus Sentinel-5P TROPOMI - Carbon Monoxide - Level 2

The Copernicus Sentinel-5 Ultraviolet Visible Near-Infrared Shortwave (UVNS) spectrometer enables the measurement of trace gases which will improve air quality forecasts produced by the Copernicus Atmosphere Monitoring service.

This notebook provides you an introduction to data from Sentinel-5P, the precursor instrument and proxy for data from Sentinel-5.

The data product of interest is the total column of carbon monoxide sensed by Sentinel-5P TROPOMI. Carbon monoxide is a good trace gas for monitoring and tracking smoke from wildfires.

The events featured in this notebook are the wildfires in Italy and Greece in summer 2021.

Basic Facts

Spatial resolution: Up to 5.5* km x 3.5 km (5.5 km in the satellite flight direction and 3.5 km in the perpendicular direction at nadir)
Spatial coverage: Global
Revisit time: less than one day
Data availability: since April 2018

How to access the data

Sentinel-5P Pre-Ops data are disseminated in the netCDF format and can be downloaded via the Sentinel-5P Pre-Operations Data Hub. You can login with the following credentials:

  • Username: s5pguest

  • Password: s5pguest


Load required libraries

import os
import xarray as xr
import numpy as np
import netCDF4 as nc

# Python libraries for visualization
%matplotlib inline
import matplotlib.pyplot as plt
from matplotlib.axes import Axes
import cartopy.crs as ccrs
from cartopy.mpl.gridliner import LONGITUDE_FORMATTER, LATITUDE_FORMATTER
import cartopy.feature as cfeature
from cartopy.mpl.geoaxes import GeoAxes
GeoAxes._pcolormesh_patched = Axes.pcolormesh

import warnings
import logging
warnings.filterwarnings('ignore')
warnings.simplefilter(action = "ignore", category = RuntimeWarning)
logging.basicConfig(level=logging.ERROR)

Load helper functions

%run ../functions.ipynb

Load and browse Sentinel-5P TROPOMI Carbon Monoxide Level 2 data

A Sentinel-5P TROPOMI Carbon Monoxide Level 2 file is organised in two groups: PRODUCT and METADATA. The PRODUCT group stores the main data fields of the product, including latitude, longitude and the variable itself. The METADATA group provides additional metadata items.

Sentinel-5P TROPOMI variables have the following dimensions:

  • scanline: the number of measurements in the granule / along-track dimension index

  • ground_pixel: the number of spectra in a measurement / across-track dimension index

  • time: time reference for the data

  • corner: pixel corner index

  • layer: this dimension indicates the vertical grid of profile variables

Sentinel-5P TROPOMI data is disseminated in netCDF. You can load a netCDF file with the open_dataset() function of the xarray library. In order to load the variable as part of a Sentinel-5P data files, you have to specify the following keyword arguments:

  • group='PRODUCT': to load the PRODUCT group.

Let us load a Sentinel-5P TROPOMI data file as xarray.Dataset from 05 August 2021 and inspect the data structure:

s5p = xr.open_dataset('../data/sentinel-5p/co/2021/08/05/S5P_OFFL_L2__CO_____20210805T110012_20210805T124141_19750_02_020200_20210807T004645.nc', group='PRODUCT')
s5p
<xarray.Dataset>
Dimensions:                                (scanline: 4172, ground_pixel: 215, time: 1, corner: 4, layer: 50)
Coordinates:
  * scanline                               (scanline) float64 0.0 ... 4.171e+03
  * ground_pixel                           (ground_pixel) float64 0.0 ... 214.0
  * time                                   (time) datetime64[ns] 2021-08-05
  * corner                                 (corner) float64 0.0 1.0 2.0 3.0
  * layer                                  (layer) float32 4.95e+04 ... 500.0
    latitude                               (time, scanline, ground_pixel) float32 ...
    longitude                              (time, scanline, ground_pixel) float32 ...
Data variables:
    delta_time                             (time, scanline) datetime64[ns] 20...
    time_utc                               (time, scanline) object '2021-08-0...
    qa_value                               (time, scanline, ground_pixel) float32 ...
    carbonmonoxide_total_column            (time, scanline, ground_pixel) float32 ...
    carbonmonoxide_total_column_precision  (time, scanline, ground_pixel) float32 ...
    carbonmonoxide_total_column_corrected  (time, scanline, ground_pixel) float32 ...

You see that the loaded data object contains of five dimensions and five data variables:

  • Dimensions:

    • scanline

    • ground_pixel

    • time

    • corner

    • layer

  • Data variables:

    • delta_time: the offset of individual measurements within the granule, given in milliseconds

    • time_utc: valid time stamp of the data

    • ga_value: quality descriptor, varying between 0 (nodata) and 1 (full quality data).

    • carbonmonoxide_total_column: Vertically integrated CO column density

    • carbonmonoxide_total_column_precision: Standard error of the vertically integrate CO column

You can specify one variable of interest by putting the name of the variable into square brackets [] and get more detailed information about the variable. E.g. carbonmonoxide_total_column is the atmosphere mole content of carbon monoxide, has the unit mol per m-2, and has three dimensions, time, scanline and groundpixel respectively.

s5p_co = s5p['carbonmonoxide_total_column']
s5p_co
<xarray.DataArray 'carbonmonoxide_total_column' (time: 1, scanline: 4172, ground_pixel: 215)>
[896980 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 0.0 1.0 2.0 ... 4.17e+03 4.171e+03
  * ground_pixel  (ground_pixel) float64 0.0 1.0 2.0 3.0 ... 212.0 213.0 214.0
  * time          (time) datetime64[ns] 2021-08-05
    latitude      (time, scanline, ground_pixel) float32 ...
    longitude     (time, scanline, ground_pixel) float32 ...
Attributes:
    units:                                                 mol m-2
    standard_name:                                         atmosphere_mole_co...
    long_name:                                             Vertically integra...
    ancillary_variables:                                   carbonmonoxide_tot...
    multiplication_factor_to_convert_to_molecules_percm2:  6.022141e+19

You can do this for the available variables, but also for the dimensions latitude and longitude.

print('Latitude')
print(s5p_co.latitude)

print('Longitude')
print(s5p_co.longitude)
Latitude
<xarray.DataArray 'latitude' (time: 1, scanline: 4172, ground_pixel: 215)>
[896980 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 0.0 1.0 2.0 ... 4.17e+03 4.171e+03
  * ground_pixel  (ground_pixel) float64 0.0 1.0 2.0 3.0 ... 212.0 213.0 214.0
  * time          (time) datetime64[ns] 2021-08-05
    latitude      (time, scanline, ground_pixel) float32 ...
    longitude     (time, scanline, ground_pixel) float32 ...
Attributes:
    long_name:      pixel center latitude
    units:          degrees_north
    standard_name:  latitude
    valid_min:      -90.0
    valid_max:      90.0
    bounds:         /PRODUCT/SUPPORT_DATA/GEOLOCATIONS/latitude_bounds
Longitude
<xarray.DataArray 'longitude' (time: 1, scanline: 4172, ground_pixel: 215)>
[896980 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 0.0 1.0 2.0 ... 4.17e+03 4.171e+03
  * ground_pixel  (ground_pixel) float64 0.0 1.0 2.0 3.0 ... 212.0 213.0 214.0
  * time          (time) datetime64[ns] 2021-08-05
    latitude      (time, scanline, ground_pixel) float32 ...
    longitude     (time, scanline, ground_pixel) float32 ...
Attributes:
    long_name:      pixel center longitude
    units:          degrees_east
    standard_name:  longitude
    valid_min:      -180.0
    valid_max:      180.0
    bounds:         /PRODUCT/SUPPORT_DATA/GEOLOCATIONS/longitude_bounds

You can retrieve the array values of the variable with squared brackets: [:,:,:]. One single time step can be selected by specifying one value of the time dimension, e.g. [0,:,:].

s5p_co_0708 = s5p_co[0,:,:]
s5p_co_0708
<xarray.DataArray 'carbonmonoxide_total_column' (scanline: 4172, ground_pixel: 215)>
[896980 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 0.0 1.0 2.0 ... 4.17e+03 4.171e+03
  * ground_pixel  (ground_pixel) float64 0.0 1.0 2.0 3.0 ... 212.0 213.0 214.0
    time          datetime64[ns] 2021-08-05
    latitude      (scanline, ground_pixel) float32 ...
    longitude     (scanline, ground_pixel) float32 ...
Attributes:
    units:                                                 mol m-2
    standard_name:                                         atmosphere_mole_co...
    long_name:                                             Vertically integra...
    ancillary_variables:                                   carbonmonoxide_tot...
    multiplication_factor_to_convert_to_molecules_percm2:  6.022141e+19

The attributes of the xarray.DataArray hold the entry multiplication_factor_to_convert_to_molecules_percm2, which is a conversion factor that has to be applied to convert the data from mol per m2 to molecules per cm2.

conversion_factor = s5p_co.multiplication_factor_to_convert_to_molecules_percm2
conversion_factor
6.022141e+19

Additionally, you can save the attribute longname, which you can make use of when visualizing the data.

longname = s5p_co.long_name
longname
'Vertically integrated CO column'

Create a geographical subset around Italy and Greece

You can zoom into a region by specifying a bounding box of interest. Let us set the extent to Italy and Greece with the following bounding box information:

lonmin=10
lonmax=30
latmin=35
latmax=45

You can use the function generate_geographical_subsetto subset an xarray.DataArray based on a given bounding box.

s5p_co_subset = generate_geographical_subset(xarray=s5p_co_0708, 
                                             latmin=latmin, 
                                             latmax=latmax, 
                                             lonmin=lonmin, 
                                             lonmax=lonmax)
s5p_co_subset
<xarray.DataArray 'carbonmonoxide_total_column' (scanline: 241, ground_pixel: 179)>
array([[nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       ...,
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan]], dtype=float32)
Coordinates:
  * scanline      (scanline) float64 2.365e+03 2.366e+03 ... 2.604e+03 2.605e+03
  * ground_pixel  (ground_pixel) float64 33.0 34.0 35.0 ... 209.0 210.0 211.0
    time          datetime64[ns] 2021-08-05
    latitude      (scanline, ground_pixel) float32 32.2 32.24 ... 46.55 46.52
    longitude     (scanline, ground_pixel) float32 11.21 11.34 ... 29.3 29.69
Attributes:
    units:                                                 mol m-2
    standard_name:                                         atmosphere_mole_co...
    long_name:                                             Vertically integra...
    ancillary_variables:                                   carbonmonoxide_tot...
    multiplication_factor_to_convert_to_molecules_percm2:  6.022141e+19

Visualise Sentinel-5P TROPOMI Carbon Monoxide Level 2 data

You can plot data arrays of type numpy with matplotlib’s pcolormesh function. In combination with the library cartopy, you can produce high-quality maps.

In order to make it easier to visualize the Carbon Monoxide values, we apply the conversion factor to the xarray.DataArray. This converts the Carbon Monoxide values from mol per m2 to molecules per cm2.

s5p_co_converted = s5p_co_subset*conversion_factor
s5p_co_converted
<xarray.DataArray 'carbonmonoxide_total_column' (scanline: 241, ground_pixel: 179)>
array([[nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       ...,
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan],
       [nan, nan, nan, ..., nan, nan, nan]], dtype=float32)
Coordinates:
  * scanline      (scanline) float64 2.365e+03 2.366e+03 ... 2.604e+03 2.605e+03
  * ground_pixel  (ground_pixel) float64 33.0 34.0 35.0 ... 209.0 210.0 211.0
    time          datetime64[ns] 2021-08-05
    latitude      (scanline, ground_pixel) float32 32.2 32.24 ... 46.55 46.52
    longitude     (scanline, ground_pixel) float32 11.21 11.34 ... 29.3 29.69

For visualization, you can use the function visualize_pcolormesh to visualize the data. The following keyword arguments have to be defined:

  • data_array

  • longitude

  • latitude

  • projection

  • color palette

  • unit

  • long_name

  • vmin,

  • vmax

  • extent (lonmin, lonmax, latmin, latmax)

  • set_global

Now, let us apply the visualize_pcolormesh function and visualize the vertically integrated carbon monoxide column recorded the Sentinel-5P satellite on 05 August 2021.

Note: Multiplying the DataArray values with 1e-18 improves the readibility of the map legend.

visualize_pcolormesh(data_array=s5p_co_converted*1e-18, 
                     longitude=s5p_co_converted.longitude, 
                     latitude=s5p_co_converted.latitude, 
                     projection=ccrs.PlateCarree(), 
                     color_scale='viridis', 
                     unit='*1e-18 molecules per cm2', 
                     long_name=longname + ' ' + str(s5p_co_converted.time.data)[0:10], 
                     vmin=0, 
                     vmax=8, 
                     lonmin=lonmin, 
                     lonmax=lonmax, 
                     latmin=latmin, 
                     latmax=latmax, 
                     set_global=False)
(<Figure size 1440x720 with 2 Axes>,
 <GeoAxesSubplot:title={'center':'Vertically integrated CO column 2021-08-05'}>)
../_images/figure5_Sentinel-5P_TROPOMI_CO_38_1.png

References

Return to the case study

Monitoring active fires with next-generation satellites: Mediterranean Fires Case Study
Carbon monoxide total column