Plot PPI data for BAMS Paper#

Plot maps for the SAIL Bulletin of the American Meteorological Society (BAMS) submission.

Imports#

import glob
import os
import datetime
import warnings
import numpy as np
import matplotlib.pyplot as plt
import pyart
import xarray as xr
import cartopy.crs as ccrs
import cartopy.feature as cfeature
from matplotlib.dates import DateFormatter
import pandas as pd
import geopandas as gpd
from metpy.plots import USCOUNTIES
from matplotlib.colors import ListedColormap
from datetime import datetime
import act
import fiona
from podpac.datalib.terraintiles import TerrainTiles
from podpac import Coordinates, clinspace
import hvplot as hv
import hvplot.xarray
import numpy as np
from math import atan2 as atan2
from matplotlib import colors
from matplotlib import ticker, cm
fiona.drvsupport.supported_drivers['lib?kml'] = 'rw' # enable KML support which is disabled by default
fiona.drvsupport.supported_drivers['LIBKML'] = 'rw' # enable KML support which is disabled by default
warnings.filterwarnings('ignore')

Read the Watershed and Instrumentation Locations#

east_river = gpd.read_file('data/site-locations/East_River.kml')
splash_locations = gpd.read_file('data/site-locations/SPLASH_Instruments.kml')
amf_sensor_locations = gpd.read_file('data/site-locations/SAIL_Instruments.kml')

Retrieve the DEM Data#

min_lat = 38.8
max_lat = 39.1
min_lon = -107.2
max_lon = -106.7

# create terrain tiles node
node = TerrainTiles(tile_format='geotiff', zoom=10)

# create coordinates to get tiles
c = Coordinates([clinspace(min_lat, max_lat, 1000), clinspace(min_lon, max_lon, 1000)], dims=['lat', 'lon'])

# evaluate node
terrain = node.eval(c)

Setup Helper Functions#

We need these helper functions to create the scale bar within our figure

def gc_latlon_bear_dist(lat1, lon1, bear, dist):
    """
    Input lat1/lon1 as decimal degrees, as well as bearing and distance from
    the coordinate. Returns lat2/lon2 of final destination. Cannot be
    vectorized due to atan2.
    """
    re = 6371.1  # km
    lat1r = np.deg2rad(lat1)
    lon1r = np.deg2rad(lon1)
    bearr = np.deg2rad(bear)
    lat2r = np.arcsin((np.sin(lat1r) * np.cos(dist/re)) +
                      (np.cos(lat1r) * np.sin(dist/re) * np.cos(bearr)))
    lon2r = lon1r + atan2(np.sin(bearr) * np.sin(dist/re) *
                          np.cos(lat1r), np.cos(dist/re) - np.sin(lat1r) *
                          np.sin(lat2r))
    return np.rad2deg(lat2r), np.rad2deg(lon2r)
 
    
def add_scale_line(scale, ax, projection, color='k',
                  linewidth=None, fontsize=None, fontweight=None):
    """
    Adds a line that shows the map scale in km. The line will automatically
    scale based on zoom level of the map. Works with cartopy.
 
    Parameters
    ----------
    scale : scalar
        Length of line to draw, in km.
    ax : matplotlib.pyplot.Axes instance
        Axes instance to draw line on. It is assumed that this was created
        with a map projection.
    projection : cartopy.crs projection
        Cartopy projection being used in the plot.
 
    Other Parameters
    ----------------
    color : str
        Color of line and text to draw. Default is black.
    """
    frac_lat = 0.1  # distance fraction from bottom of plot
    frac_lon = 0.5  # distance fraction from left of plot
    e1 = ax.get_extent()
    center_lon = e1[0] + frac_lon * (e1[1] - e1[0])
    center_lat = e1[2] + frac_lat * (e1[3] - e1[2])
    # Main line
    lat1, lon1 = gc_latlon_bear_dist(
        center_lat, center_lon, -90, scale / 2.0)  # left point
    lat2, lon2 = gc_latlon_bear_dist(
        center_lat, center_lon, 90, scale / 2.0)  # right point
    if lon1 <= e1[0] or lon2 >= e1[1]:
        warnings.warn('Scale line longer than extent of plot! ' +
                      'Try shortening for best effect.')
    ax.plot([lon1, lon2], [lat1, lat2], linestyle='-',
            color=color, transform=projection, 
            linewidth=linewidth)
    # Draw a vertical hash on the left edge
    lat1a, lon1a = gc_latlon_bear_dist(
        lat1, lon1, 180, frac_lon * scale / 20.0)  # bottom left hash
    lat1b, lon1b = gc_latlon_bear_dist(
        lat1, lon1, 0, frac_lon * scale / 20.0)  # top left hash
    ax.plot([lon1a, lon1b], [lat1a, lat1b], linestyle='-',
            color=color, transform=projection, linewidth=linewidth)
    # Draw a vertical hash on the right edge
    lat2a, lon2a = gc_latlon_bear_dist(
        lat2, lon2, 180, frac_lon * scale / 20.0)  # bottom right hash
    lat2b, lon2b = gc_latlon_bear_dist(
        lat2, lon2, 0, frac_lon * scale / 20.0)  # top right hash
    ax.plot([lon2a, lon2b], [lat2a, lat2b], linestyle='-',
            color=color, transform=projection, linewidth=linewidth)
    # Draw scale label
    ax.text(center_lon, center_lat - frac_lat * (e1[3] - e1[2]) / 4.0,
            str(int(scale)) + ' km', horizontalalignment='center',
            verticalalignment='center', color=color, fontweight=fontweight,
            fontsize=fontsize)

Plot the Reflectivity with the Pluvio Sensor and Watershed Locations#

def plot_reflectivity_sail(file, out_directory='.'):
    """
    Reads and plots data from the SAIL field campaign
    """
    
    radar = pyart.io.read(file)

    elevation_contours = np.arange(2_700, 4_200, 250)

    fig = plt.figure(figsize=(12,8))

    ax = plt.subplot(111,
                     projection=ccrs.PlateCarree())

    #Setup a RadarMapDisplay, which gives our axes in lat/lon
    display = pyart.graph.RadarMapDisplay(radar)

    # Add our reflectivity (DBZ) field to the plot, including our gatefilter
    radar_plot = display.plot_ppi_map('DBZ',
                                      3,
                                      ax=ax,
                                      vmin=-20,
                                      min_lat=min_lat,
                                      max_lat=max_lat,
                                      min_lon=min_lon,
                                      max_lon=max_lon,
                                      embelish=False,
                                      colorbar_label="Horizontal Reflectivity Factor ($Z_{H}$) \n (dBZ)",
                                      vmax=40.,)

    cbar = display.cbs[0]
    cbar.set_ticklabels(np.arange(-20, 50, 10), fontsize=14)
    cbar.set_label(label='Horizontal Reflectivity Factor ($Z_{H}$) (dBZ)', fontsize=16)

    display.cbs = [cbar]


    east_river.plot(ax=ax,
                    linewidth=3,
                    edgecolor='k',
                    facecolor="None",
                    linestyle='--',)

    ax.plot(0,
            0,
            color='k',
            linestyle=':',
            linewidth=3,
            label='East River Basin')

    amf_sensor_locations.loc[amf_sensor_locations.Name == 'PLUVIO'].plot(ax=ax,
                                                                         color='k',
                                                                         label='ARM AMF Site')

    plt.legend(loc='upper right',
               fontsize=14)

    plt.xlim(min_lon, max_lon)
    plt.ylim(min_lat, max_lat)

    # Add our scale bar
    add_scale_line(10.0, ax, projection=ccrs.PlateCarree(), 
                   color='black', linewidth=3,
                   fontsize=20,
                   fontweight='bold')

    # Add gridlines
    gl = ax.gridlines(crs=ccrs.PlateCarree(),
                       draw_labels=True,
                       linewidth=1, 
                       color='gray', 
                       alpha=0.3,
                       linestyle='--')

    plt.gca().xaxis.set_major_locator(plt.NullLocator())

    # Make sure labels are only plotted on the left and bottom
    gl.xlabels_top = False
    gl.ylabels_right = False

    gl.xlabel_style = {'size': 14}
    gl.ylabel_style = {'size': 14}

    third_sweep = radar.extract_sweeps([2])
    time = pd.to_datetime(third_sweep.time["data"], unit='s').mean()
    time_in_label = time.strftime("%H%M UTC %d %b %Y")
    time_in_file = time.strftime("%B_%d_%H%M").lower()

    contours = terrain.to_dataset(name='DEM').DEM.plot.contour(levels=elevation_contours, cmap='Greys', alpha=0.6)
    ax.clabel(contours, elevation_contours[::2], fontsize=10, inline=1, fmt='%i m', rightside_up=True)
    #cbar_elevation.set_ticklabels(np.arange(2_000, 5_000, 750), fontsize=14)
    #cbar_elevation.set_label(label='Elevation (meters)', fontsize=16)

    plt.title(f"Horizontal Reflectivity at 6° Elevation \n {time_in_label}", fontsize=20)

    plt.savefig(f"{out_directory}/ppi_4deg_{time_in_file}.png", dpi=300, bbox_inches="tight")

    plt.show()
hour = '12'
file_list = sorted(glob.glob(f"/gpfs/wolf/atm124/proj-shared/gucxprecipradarS2.00/glue_files/202204_glued/xprecipradar_guc_volume_20220412-*"))

for file in file_list[::30]:
    plot_reflectivity_sail(file, out_directory='ppi_animation_april14')
../_images/7826a318be2a5734f0b881c0535010bbde1443be9a4adce33c439d607a65716e.png ../_images/aa02e5b2f7780297a7d95d2f2b69bac1730d3a92222583e0ec51b6ec9c6e53ad.png ../_images/a76b882913dc2f911804380f8f361556f9bbc64c022345e97cb6ec5b0b7d00f7.png ../_images/029109c19e40eaeddbc712e996dcf587afe63ff8456a048f0b8e50098387782e.png ../_images/503b24652177bbc307d34f989d479de427326f84dcbc92e4ac4fa5f4dbbb46cb.png ../_images/cb34408716fb8f5766bbc8f5616ec8a23b05b754f83d83f5e904a6f62e3b3c45.png ../_images/753173cc095a8d1d4f9fa633e06d3114b512ddb35a4d0a5887ed915a684a87c7.png ../_images/83708e95fd992ab63f27d3a75523dc8e6d7b1b2d584c214972d598ca7630ab66.png ../_images/b2e8f9a22427cc6d06e395c4d957b39f028e4fe252f268289af289ab1db58371.png

import glob
ppi_images = sorted(glob.glob("ppi_animation_april14/*"))
import imageio.v2 as imageio
with imageio.get_writer('april-ref-ppi-animation-6deg.gif', mode='I') as writer:
    for filename in ppi_images:
        image = imageio.imread(filename)
        writer.append_data(image)