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:
OzoneNO2SO2FormaldehydeAerosolCarbonmonoxideMethaneClouds
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 data are disseminated in the netCDF format and can be downloaded as a zipped archive via the Copernicus Data Space:
Step 1: Register and create an account.
Step 2: Go to the Sentinel-5P data collection. 
Step 3: Go to SEARCH and search for Sentinel-5P Level-2 AER-AI data and select a date range (e.g 8 April 2024) and region of interest (e.g. bounding box covering the Mediterranean sea). 
Step 4: Select a file and download it.
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 indexground_pixel: the number of spectra in a measurement / across-track dimension indextime: time reference for the datacorner: 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 thePRODUCTgroup
Let us load a Sentinel-5P TROPOMI data file as xarray.Dataset from 8 April 2024 at 12:20 UTC and inspect the data structure:
file = xr.open_dataset('../../eodata/1_satellite/sentinel5p/S5P_OFFL_L2__AER_AI_20240408T122051_20240408T140222_33612_03_020600_20240410T021230.nc', group='PRODUCT')
file
<xarray.Dataset> Size: 68MB
Dimensions:                          (scanline: 4173, ground_pixel: 450,
                                      time: 1, corner: 4)
Coordinates:
  * scanline                         (scanline) float64 33kB 0.0 ... 4.172e+03
  * ground_pixel                     (ground_pixel) float64 4kB 0.0 ... 449.0
  * time                             (time) datetime64[ns] 8B 2024-04-08
  * corner                           (corner) float64 32B 0.0 1.0 2.0 3.0
    latitude                         (time, scanline, ground_pixel) float32 8MB ...
    longitude                        (time, scanline, ground_pixel) float32 8MB ...
Data variables:
    delta_time                       (time, scanline) datetime64[ns] 33kB ...
    time_utc                         (time, scanline) object 33kB ...
    qa_value                         (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_354_388            (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_340_380            (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_335_367            (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_354_388_precision  (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_340_380_precision  (time, scanline, ground_pixel) float32 8MB ...
    aerosol_index_335_367_precision  (time, scanline, ground_pixel) float32 8MB ...You see that the loaded data object contains of four dimensions and seven data variables:
Dimensions:
scanlineground_pixeltimecorner
Data variables:
delta_time: the offset of individual measurements within the granule, given in millisecondstime_utc: valid time stamp of the dataqa_value: quality descriptor, varying between 0 (nodata) and 1 (full quality data).aerosol_index_354_388: Aerosol index from 354 and 388 nmaerosol_index_340_380: Aerosol index from 340 and 380 nmaerosol_index_354_388_precision: Precision of aerosol index from 354 and 388 nmaerosol_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: 4173,
                                           ground_pixel: 450)> Size: 8MB
[1877850 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 33kB 0.0 1.0 2.0 ... 4.171e+03 4.172e+03
  * ground_pixel  (ground_pixel) float64 4kB 0.0 1.0 2.0 ... 447.0 448.0 449.0
  * time          (time) datetime64[ns] 8B 2024-04-08
    latitude      (time, scanline, ground_pixel) float32 8MB ...
    longitude     (time, scanline, ground_pixel) float32 8MB ...
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 354 and 388 nm
    long_name:               Aerosol index from 354 and 388 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precisionYou 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_0804 = ai[0,:,:]
ai_0804
<xarray.DataArray 'aerosol_index_354_388' (scanline: 4173, ground_pixel: 450)> Size: 8MB
[1877850 values with dtype=float32]
Coordinates:
  * scanline      (scanline) float64 33kB 0.0 1.0 2.0 ... 4.171e+03 4.172e+03
  * ground_pixel  (ground_pixel) float64 4kB 0.0 1.0 2.0 ... 447.0 448.0 449.0
    time          datetime64[ns] 8B 2024-04-08
    latitude      (scanline, ground_pixel) float32 8MB ...
    longitude     (scanline, ground_pixel) float32 8MB ...
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 354 and 388 nm
    long_name:               Aerosol index from 354 and 388 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precisionAdditionally, you can save the attributes units and longname, which you can make use of when visualizing the data.
longname = ai_0804.long_name
units = ai_0804.units
longname, units
('Aerosol index from 354 and 388 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:
unitslong_namelatitudelongitude
Additionally, you can specify the color scale and minimum and maxium data values.
visualize_pcolormesh(data_array=ai_0804,
                     longitude=ai_0804.longitude,
                     latitude=ai_0804.latitude,
                     projection=ccrs.PlateCarree(),
                     color_scale='hot_r',
                     unit=units,
                     long_name=longname + ' ' + str(ai_0804.time.data),
                     vmin=0, 
                     vmax=3)
(<Figure size 2000x1000 with 2 Axes>,
 <GeoAxes: title={'center': 'Aerosol index from 354 and 388 nm 2024-04-08T00:00:00.000000000'}>)
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 April 2024 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_0804 xarray.DataArray. Let us call the new xarray.DataArray ai_0804_subset.
ai_0804_subset = generate_geographical_subset(xarray=ai_0804, 
                                             latmin=latmin, 
                                             latmax=latmax, 
                                             lonmin=lonmin, 
                                             lonmax=lonmax)
ai_0804_subset
<xarray.DataArray 'aerosol_index_354_388' (scanline: 908, ground_pixel: 450)> Size: 2MB
array([[       nan,        nan,        nan, ...,        nan,        nan,
               nan],
       [       nan,        nan,        nan, ..., 0.74332285, 0.8022466 ,
        0.7808357 ],
       [       nan,        nan,        nan, ..., 0.73813915, 0.8050948 ,
        0.8071973 ],
       ...,
       [       nan,        nan,        nan, ...,        nan,        nan,
               nan],
       [       nan,        nan,        nan, ...,        nan,        nan,
               nan],
       [       nan,        nan,        nan, ...,        nan,        nan,
               nan]], dtype=float32)
Coordinates:
  * scanline      (scanline) float64 7kB 2.405e+03 2.406e+03 ... 3.312e+03
  * ground_pixel  (ground_pixel) float64 4kB 0.0 1.0 2.0 ... 447.0 448.0 449.0
    time          datetime64[ns] 8B 2024-04-08
    latitude      (scanline, ground_pixel) float32 2MB 22.48 22.52 ... 71.48
    longitude     (scanline, ground_pixel) float32 2MB -13.48 -13.4 ... 12.63
Attributes:
    units:                   1
    proposed_standard_name:  ultraviolet_aerosol_index
    comment:                 Aerosol index from 354 and 388 nm
    long_name:               Aerosol index from 354 and 388 nm
    radiation_wavelength:    [354. 388.]
    ancillary_variables:     aerosol_index_354_388_precisionLet 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_0804.time.data).
visualize_pcolormesh(data_array=ai_0804_subset,
                     longitude=ai_0804_subset.longitude,
                     latitude=ai_0804_subset.latitude,
                     projection=ccrs.PlateCarree(),
                     color_scale='hot_r',
                     unit=units,
                     long_name=longname + ' ' + str(ai_0804_subset.time.data),
                     vmin=0, 
                     vmax=3,
                     lonmin=lonmin,
                     lonmax=lonmax,
                     latmin=latmin,
                     latmax=latmax,
                     set_global=False)
(<Figure size 2000x1000 with 2 Axes>,
 <GeoAxes: title={'center': 'Aerosol index from 354 and 388 nm 2024-04-08T00:00:00.000000000'}>)