Canadian Fires - Carbon Monoxide (CO) - CrIS JPSS-1#

Total Column - (14km x 14km) gridded plots#

This code plots the Carbon Monoxide (CO) total column for each day from 01-Jun-2023 to 08-Jun-2023.

import datetime
import contextlib

from pathlib import Path

import pandas as pd

import numpy as np
from netCDF4 import Dataset

import matplotlib.pyplot as plt

from mpl_toolkits.basemap import Basemap

from scipy.interpolate import griddata
from scipy.spatial import cKDTree

import imageio.v2 as imageio
# Specify date range
start_date = datetime.date(2023, 6, 1)
end_date = datetime.date(2023, 6, 8)

# Loop through the date range and plot Carbon Monoxide for each day
for current_date in pd.date_range(start_date, end_date):
    # Open the netCDF file
    file_date = current_date.strftime("%Y%m%d")
    dataset = Dataset(f'./data/TROPESS_CrIS-JPSS1_L2_Summary_CO_{file_date}_MUSES_R1p20_FS_F0p6.nc', 'r')

    # Read the data from your variables
    latitude = dataset.variables['latitude'][:]
    longitude = dataset.variables['longitude'][:]
    x_col = dataset.variables['x_col'][:]

    dataset.close()

    # Filter the data for North America only
    latitude_max = 80.0
    latitude_min = 10.0
    longitude_max = -40.0
    longitude_min = -170.0

    north_america_only = np.where(
        (latitude > latitude_min) & (latitude < latitude_max) & 
        (longitude > longitude_min) &(longitude < longitude_max)
    )[0]

    latitude = latitude[north_america_only]
    longitude = longitude[north_america_only]
    x_col = x_col[north_america_only]

    # Calculate width and height
    aspect_ratio = (longitude_max - longitude_min) / (latitude_max - latitude_min)
    w = 3600; h = w / aspect_ratio

    # Specify figure size (in inches)
    dpi = 300; 
    plt.figure(figsize=(w / dpi, h / dpi))  

    # Create a basemap instance
    m = Basemap(projection='cyl', resolution='l',
                llcrnrlat=10, urcrnrlat=80,  # set latitude limits to 10 and 80
                llcrnrlon=-170, urcrnrlon=-40)  # set longitude limits to -170 and -40

    m.drawmapboundary(fill_color='black')
    m.drawcoastlines(linewidth=0.3, color='white')
    m.drawcountries(linewidth=0.2, color='white')
    m.drawstates(linewidth=0.2, color='white')


    # Draw parallels (latitude lines) and meridians (longitude lines)
    parallels = np.arange(-90., 91., 10.)
    m.drawparallels(parallels, labels=[True,False,False,False], linewidth=0.3)

    meridians = np.arange(-180., 181., 10.)
    m.drawmeridians(meridians, labels=[False,False,False,True], linewidth=0.3)

    # Get CrIS pixel size in degrees, pixel is 14 x 14 km, 1 degree is roughly 111.1 km
    cris_pixel_size_deg = 14.0 / 111.1 

    # Get the grid for the interpolated values
    grid_lat, grid_lon = np.mgrid[10:81:cris_pixel_size_deg, -170:-39:cris_pixel_size_deg]

    # Interpolate the data using griddata
    grid_x_col = griddata((latitude, longitude), x_col, (grid_lat, grid_lon), method='linear', rescale=True)

    # Find the distance to the nearest original point for each point in the interpolated grid
    tree = cKDTree(np.vstack((latitude, longitude)).T)
    dist, _ = tree.query(np.vstack((grid_lat.ravel(), grid_lon.ravel())).T)

    # Reshape the distance array to have the same shape as the x_col grid
    dist_grid = dist.reshape(grid_x_col.shape)

    # Mask the interpolated values that are too far from any original point
    max_distance_degrees = 3.0
    grid_x_col[dist_grid > max_distance_degrees] = np.nan

    # Plot the interpolated data using pcolormesh instead of scatter
    sc = m.pcolormesh(grid_lon, grid_lat, grid_x_col, latlon=True, cmap='jet', alpha=0.7, vmin=60.0, vmax=200.0)

    # Add a colorbar
    cbar = m.colorbar(sc, location='bottom', pad="10%")
    cbar.set_label('Column-averaged dry-air mixing ratio of carbon monoxide from the surface to Top of Atmosphere (TOA), ppbv')

    # set plot title
    title_date = current_date.strftime("%d-%b-%Y")
    title_min = np.min(x_col)
    title_max = np.max(x_col)
    plt.title(f'TROPESS - CrIS JPSS-1 - Carbon Monoxide Column (XCO) - {title_date}\nMin: {title_min:.01f}, Max: {title_max:.01f} ppbv')

    # Save figure to PNG file
    plt.savefig(f'./images/TROPESS_Canadian-Fires_CrIS-JPSS-1_XCO_{title_date}.png', dpi=dpi, bbox_inches='tight')

    plt.close()

Now generate an animated GIF from the saved figures.

timelapse_start_date = start_date.strftime("%d-%b-%Y")
timelapse_end_date = end_date.strftime("%d-%b-%Y")
timelapse_file = f'./images/TROPESS_Canadian-Fires_CrIS-JPSS-1_XCO_Timelapse_{timelapse_start_date}_{timelapse_end_date}.gif'

# Path to the directory where the images were saved in the previous step
img_dir = Path('./images')

# Get all file names in the directory
files = sorted([img for img in img_dir.glob("TROPESS_Canadian-Fires_CrIS-JPSS-1_XCO_*.png")])

# Read the images and add to a list
with contextlib.redirect_stdout(None):
    images = []
    for filename in files:
        images.append(imageio.imread(filename))

    # Save the images as an animated gif
    # duration is the time spent on each image (in milliseconds)
    imageio.mimsave(timelapse_file, images, duration=1000.0, loop=0)  

First day#

first-day

Timelapse#

timelapse