Meteorology in Python
North American Regional Reanalysis (NARR) 500-mb Vorticity Advection Map
# Name: narr_500mb_vor_adv_map.py
#
# Purpose: Extracts numerical weather
# prediction model output from
# North American Regional Reanalysis
# (NARR). Calculates several key
# atmospheric properties from
# output. Plots contours,
# filled-contours, and barbs from output.
# Here I focus on creating a synoptic scale, 500-mb map.
#
# Caveats: Run code with Python 3.6
# or higher. Code requires making
# modification to one line of MetPy's
# cartopy_utils.py code to function
# with later releases of Cartopy
# (see footnote [1] below).
#
# Authors: Source code originally
# developed by University
# Corporation for Atmospheric Research.
# Code modified by Lawrence Burkett
# (2020) for academic use.
# Import the following modules:
import matplotlib
matplotlib.use('Agg')
from matplotlib import pyplot as plt
from datetime import datetime
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.gridspec as gridspec
import matplotlib.pyplot as plt
import metpy.calc as mpcalc
import numpy as np
from metpy.plots import StationPlot
from metpy.units import units
from netCDF4 import Dataset, num2date
from scipy.ndimage import gaussian_filter
from siphon.catalog import TDSCatalog
# Year, month, day, and hour of interest.
year = 2004
month = 5
day = 29
hour = 12
dt = datetime(year, month, day, hour)
# Pressure level of interest in mb.
p0 = 500
# Read NARR data from THREDDS
# server.
base_url = 'https://www.ncei.noaa.gov/thredds/catalog/model-narr-a-files/'
# Generate URL to year, month,
# day, and hour of interest.
cat = TDSCatalog(f'{base_url}{dt:%Y%m}/{dt:%Y%m%d}/catalog.xml')
# Have Siphon find appropriate dataset.
ds = cat.datasets.filter_time_nearest(dt)
# Interface with data through NetCDF
# Subset Service (NCSS).
ncss = ds.subset()
# Create NCSS query with desired
# specifications.
query = ncss.query()
query.lonlat_box(north = 60, south = 18, east = 300, west = 225)
query.all_times()
query.add_lonlat()
query.accept('netcdf')
query.variables('Geopotential_height_isobaric', 'u-component_of_wind_isobaric', 'v-component_of_wind_isobaric')
# Use the query to obtain NetCDF data.
data = ncss.get_data(query)
# Extract data and assign units
# Geopotential height in m AMSL.
h = gaussian_filter(data.variables['Geopotential_height_isobaric'][0], sigma = 1.0) * units.meter
# West-to-east (zonal) wind in m/s.
u = gaussian_filter(data.variables['u-component_of_wind_isobaric'][0], sigma = 1.0) * units('m/s')
# South-to-north (meridional) wind in m/s.
v = gaussian_filter(data.variables['v-component_of_wind_isobaric'][0], sigma = 1.0) * units('m/s')
# Extract coordinate data.
lat = data.variables['lat'][:]
lon = data.variables['lon'][:]
p = data.variables['isobaric1'][:]
# Extract time. Check whether
# time2, time1, or time by printing data:
time = data.variables['time2']
vtime = num2date(time[0], units = time.units)
# Calcualte dx and dy for calculations.
dx, dy = mpcalc.lat_lon_grid_deltas(lon, lat)
# Model pressure level k in (i, j, k)
# grid index coordinate system.
k = np.where(p == p0)[0][0]
# Geopotential height at k-th
# index in m.
h = h[k]
# West-to-east (zonal) wind
# at k-th index in m/s.
u = u[k]
# South-to-north (meridional)
# wind at k-th index in m/s.
v = v[k]
# Relative vorticity (curl) in hertz / 1/s.
vor = mpcalc.vorticity(u, v, dx, dy, dim_order = 'yx')
# Planetary vorticity (curl) in hertz / 1/s.
f = mpcalc.coriolis_parameter(np.deg2rad(lat)).to('1/s')
# Absolute vorticity (curl) in hertz / 1/s.
avor = vor + f
# Absolute vorticity advection in hertz / 1/s.
absvor_adv = mpcalc.advection(avor, [u, v], (dx, dy), dim_order = 'yx') * 1e9
# Data projection.
dataproj = ccrs.PlateCarree()
# Plot projection.
plotproj = ccrs.LambertConformal(central_longitude = -100., central_latitude = 40., standard_parallels = [30, 60])
def create_map_background():
fig=plt.figure(figsize = (14, 12))
ax=plt.subplot(111, projection = plotproj)
ax.set_extent([-125, -73, 25, 50],ccrs.PlateCarree())
ax.coastlines("50m", linewidth = 0.75)
ax.add_feature(cfeature.STATES, linewidth = 0.5)
return fig, ax
# Cell content replaced by load magic replacement.
fig, ax = create_map_background()
# First layer are geopotential
# height contours in m AMSL.
geo_range = np.arange(5100, 6061, 60)
ly1 = ax.contour(lon, lat, h, geo_range, colors = 'k', linewidths = 1.0, linestyles = 'solid', transform = dataproj)
plt.clabel(ly1, fontsize = 10, inline = 1, inline_spacing = 10, fmt = '%i', rightside_up = True, use_clabeltext = True)
# Second layer are absolute
# vorticity contours in 10^5
# Hz / 1/s.
avor_range = np.arange(-9, 50, 5)
ly2 = ax.contour(lon, lat, avor*10**5, avor_range, colors = 'grey', linewidths = 1.25, linestyles = 'dashed', transform = dataproj)
plt.clabel(ly2, fontsize = 10, inline = 1, inline_spacing = 10, fmt = '%i', rightside_up = True, use_clabeltext = True)
# Third layer are colorfill
# vorticity advections in 10^8
# Hz^2 / 1/s^2.
avoradv_range = np.arange(-30, 31, 2)
ly3 = ax.contourf(lon, lat, absvor_adv, avoradv_range[avoradv_range != 0], extend = 'both', cmap = 'bwr', transform = dataproj)
plt.colorbar(ly3, orientation = 'horizontal', pad = 0, aspect = 50, extendrect = True, ticks = avoradv_range)
# Fourth layer are wind barbs
# in kt.
ax.barbs(lon, lat, u.to('kts'), v.to('kts'), regrid_shape = 15, transform = dataproj)
# Titles
plt.title('NARR 500-mb geo heights (m), avor$*10^5$ ($s^{-1}$), avor adv$*10^8$ ($s^{-2}$), and wind barbs (kt)', loc = 'left')
plt.title(f'valid: {vtime} utc', loc = 'right')
# Save figure.
plt.savefig('narr-5-29-2004-12utc-500mb-vor-adv-map.png')
Figure 1. NARR 500-mb output map that indicate geopotential height (m), absolute vorticity * 105 (s-1), absolute vorticity advection * 108 (s-2), and wind barbs (kt) for 12 utc on May 29, 2004.
- The "ValueError: 20m is not a valid Natural Earth scale" problem with cartopy_utils.py: here.