Sentinel-5P TROPOMI UVAI

The following example introduces you to the Aerosol Index (AI) product from the Sentinel-5P TROPOMI instrument. The Aerosol Index (AI) is a qualitative index indicating the presence of elevated layers of aerosols with significant absorption. The main aerosol types that cause signals detected in the AI are desert dust, biomass burning and volcanic ash plumes. An advantage of the AI is that it can be derived for clear as well as (partly) cloudy ground pixels.

The Copernicus Sentinel-5 Precursor mission is the first Copernicus mission dedicated to atmospheric monitoring. The main objective of the Sentinel-5P mission is to perform atmospheric measurements with high spatio-temporal resolution, to be used for air quality, ozone & UV radiation, and climate monitoring and forecasting.

Sentinel-5P carries the TROPOMI instrument, which is a spectrometer in the UV-VIS-NIR-SWIR spectral range. TROPOMI provides measurements on:

  • Ozone

  • NO2

  • SO2

  • Formaldehyde

  • Aerosol

  • Carbonmonoxide

  • Methane

  • Clouds

Read more information about Sentinel-5P here.

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 xarray as xr

import matplotlib.pyplot as plt
import matplotlib.colors
from matplotlib.cm import get_cmap
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
warnings.simplefilter(action = "ignore", category = RuntimeWarning)
warnings.simplefilter(action = "ignore", category = UserWarning)

Load helper functions

%run ../../functions.ipynb

Load and browse Sentinel-5P TROPOMI Aerosol Index Level 2 data

A Sentinel-5P TROPOMI Aerosol Index 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

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 22 February 2021 and inspect the data structure:

file = xr.open_dataset('../../eodata/1_satellite/sentinel5p/S5P_OFFL_L2__AER_AI_20210206T120713_20210206T134843_17197_01_010400_20210208T015719.nc', group='PRODUCT')
file
<xarray.Dataset>
Dimensions:                          (scanline: 4172, ground_pixel: 450, time: 1, corner: 4)
Coordinates:
  * scanline                         (scanline) float64 0.0 1.0 ... 4.171e+03
  * ground_pixel                     (ground_pixel) float64 0.0 1.0 ... 449.0
  * time                             (time) datetime64[ns] 2021-02-06
  * corner                           (corner) float64 0.0 1.0 2.0 3.0
    latitude                         (time, scanline, ground_pixel) float32 ...
    longitude                        (time, scanline, ground_pixel) float32 ...
Data variables:
    delta_time                       (time, scanline) datetime64[ns] 2021-02-...
    time_utc                         (time, scanline) object '2021-02-06T12:2...
    qa_value                         (time, scanline, ground_pixel) float32 ...
    aerosol_index_354_388            (time, scanline, ground_pixel) float32 ...
    aerosol_index_340_380            (time, scanline, ground_pixel) float32 ...
    aerosol_index_354_388_precision  (time, scanline, ground_pixel) float32 ...
    aerosol_index_340_380_precision  (time, scanline, ground_pixel) float32 ...

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

  • Dimensions:

    • scanline

    • ground_pixel

    • time

    • corner

  • Data variables:

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

    • time_utc: valid time stamp of the data

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

    • aerosol_index_354_388: Aerosol index from 354 and 388 nm

    • aerosol_index_340_380: Aerosol index from 340 and 380 nm

    • aerosol_index_354_388_precision: Precision of aerosol index from 354 and 388 nm

    • aerosol_index_340_380_precision: Precision of aerosol index from 340 and 380 nm


Retrieve the variable aerosol index from 354 and 388 nm as xarray.DataArray

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. aerosol_index_354_388 is the ‘Aerosol index from 354 and 388 nm’ and has three dimensions, time, scanline and ground_pixel respectively.

ai = file['aerosol_index_354_388']
ai
<xarray.DataArray 'aerosol_index_354_388' (time: 1, scanline: 4172, ground_pixel: 450)>
[1877400 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 ... 447.0 448.0 449.0
  * time          (time) datetime64[ns] 2021-02-06
    latitude      (time, scanline, ground_pixel) float32 ...
    longitude     (time, scanline, ground_pixel) float32 ...
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 388 and 354 nm
    long_name:               Aerosol index from 388 and 354 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precision

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,:,:].

ai_0602 = ai[0,:,:]
ai_0602
<xarray.DataArray 'aerosol_index_354_388' (scanline: 4172, ground_pixel: 450)>
[1877400 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 ... 447.0 448.0 449.0
    time          datetime64[ns] 2021-02-06
    latitude      (scanline, ground_pixel) float32 ...
    longitude     (scanline, ground_pixel) float32 ...
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 388 and 354 nm
    long_name:               Aerosol index from 388 and 354 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precision

Additionally, you can save the attributes units and longname, which you can make use of when visualizing the data.

longname = ai_0602.long_name
units = ai_0602.units

longname, units
('Aerosol index from 388 and 354 nm', '1')

Visualize Sentinel-5P TROPOMI aerosol index from 388 and 354 nm

The next step is to visualize the dataset. You can use the function visualize_pcolormesh, which makes use of matploblib’s function pcolormesh and the Cartopy library.

With ?visualize_pcolormesh you can open the function’s docstring to see what keyword arguments are needed to prepare your plot.

?visualize_pcolormesh

You can make use of the variables we have defined above:

  • units

  • long_name

  • latitude

  • longitude

Additionally, you can specify the color scale and minimum and maxium data values.

visualize_pcolormesh(data_array=ai_0602,
                     longitude=ai_0602.longitude,
                     latitude=ai_0602.latitude,
                     projection=ccrs.PlateCarree(),
                     color_scale='hot_r',
                     unit=units,
                     long_name=longname + ' ' + str(ai_0602.time.data),
                     vmin=0, 
                     vmax=1.5)
(<Figure size 1440x720 with 2 Axes>,
 <GeoAxesSubplot:title={'center':'Aerosol index from 388 and 354 nm 2021-02-06T00:00:00.000000000'}>)
../_images/sentinel5p_ai_27_1.png

Create a geographical subset for Europe

The map above shows the aerosol index of one footprint along the entire latitude range. Let us create a geographical subset for Europe, in order to better analyse the Saharan dust event which occured in February 2021 over Europe.

For geographical subsetting, you can use the function generate_geographical_subset. You can use ?generate_geographical_subset to open the docstring in order to see the function’s keyword arguments.

?generate_geographical_subset

Define the bounding box information for Europe

latmin = 28.
latmax = 71.
lonmin = -22.
lonmax = 43

Now, let us apply the function generate_geographical_subset to subset the ai_0602 xarray.DataArray. Let us call the new xarray.DataArray ai_0602_subset.

ai_0602_subset = generate_geographical_subset(xarray=ai_0602, 
                                             latmin=latmin, 
                                             latmax=latmax, 
                                             lonmin=lonmin, 
                                             lonmax=lonmax)
ai_0602_subset
<xarray.DataArray 'aerosol_index_354_388' (scanline: 911, ground_pixel: 450)>
array([[        nan,         nan,         nan, ..., -0.18838878,
        -0.550947  , -0.5468501 ],
       [        nan,         nan,         nan, ..., -0.20505159,
        -0.361677  , -0.2746366 ],
       [        nan,         nan,         nan, ..., -0.30932063,
        -0.32334223, -0.20750041],
       ...,
       [        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.903e+03 2.904e+03 ... 3.812e+03 3.813e+03
  * ground_pixel  (ground_pixel) float64 0.0 1.0 2.0 3.0 ... 447.0 448.0 449.0
    time          datetime64[ns] 2021-02-06
    latitude      (scanline, ground_pixel) float32 22.49 22.53 ... 71.66 71.63
    longitude     (scanline, ground_pixel) float32 -11.97 -11.89 ... 13.92 14.17
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 388 and 354 nm
    long_name:               Aerosol index from 388 and 354 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precision

Let us visualize the subsetted xarray.DataArray again. This time, you set the set_global kwarg to False and you specify the longitude and latitude bounds specified above.

Additionally, in order to have the time information as part of the title, we add the string of the datetime information to the longname variable: longname + ' ' + str(ai_0602.time.data).

visualize_pcolormesh(data_array=ai_0602_subset,
                     longitude=ai_0602_subset.longitude,
                     latitude=ai_0602_subset.latitude,
                     projection=ccrs.PlateCarree(),
                     color_scale='hot_r',
                     unit=units,
                     long_name=longname + ' ' + str(ai_0602_subset.time.data),
                     vmin=0, 
                     vmax=3,
                     lonmin=lonmin,
                     lonmax=lonmax,
                     latmin=latmin,
                     latmax=latmax,
                     set_global=False)
(<Figure size 1440x720 with 2 Axes>,
 <GeoAxesSubplot:title={'center':'Aerosol index from 388 and 354 nm 2021-02-06T00:00:00.000000000'}>)
../_images/sentinel5p_ai_39_1.png