Plot an 500-hPa map with calculating vorticity advection using MetPy calculations.
Beyond just plotting 500-hPa level data, this uses calculations from metpy.calc to find
the vorticity and vorticity advection. Currently, this needs an extra helper function to
calculate the distance between lat/lon grid points.
Imports
from datetime import datetime
import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
import metpy.calc as mpcalc
from metpy.units import units
import numpy as np
from siphon.catalog import TDSCatalog
from xarray.backends import NetCDF4DataStore
import xarray as xrFunction to Compute Earth-Relative Winds¶
This function takes a data array with relevant information about the projection of the wind component data, along with the grid-relative components of the wind. It outputs the earth-relative components of the wind.
def earth_relative_wind_components(ugrd, vgrd):
"""Calculate the north-relative components of the
wind from the grid-relative components using Cartopy
transform_vectors.
Parameters
----------
ugrd : Xarray DataArray (M, N)
grid relative u-component of the wind
vgrd : Xarray DataArray (M, N)
grid relative v-component of the wind
Returns
-------
unr, vnr : tuple of array-like Quantity
The north-relative wind components in the X (East-West)
and Y (North-South) directions, respectively.
"""
if 'metpy_crs' not in ugrd.coords:
raise ValueError('No CRS in coordinate, be sure to use'
'the MetPy accessor parse_cf()')
data_crs = ugrd.metpy.cartopy_crs
x = ugrd.x.values
y = ugrd.y.values
xx, yy = np.meshgrid(x, y)
ut, vt = ccrs.PlateCarree().transform_vectors(data_crs, xx, yy,
ugrd.values, vgrd.values)
# Make a copy of u and v component DataArrays
uer = ugrd.copy()
ver = vgrd.copy()
# Update values with transformed winds
uer.values = ut
ver.values = vt
return uer, ver
Data Aquisition¶
dt = datetime(2016, 4, 16, 18)
# Assemble our URL to the THREDDS Data Server catalog,
# and access our desired dataset within via NCSS
base_url = 'https://www.ncei.noaa.gov/thredds/catalog/model-namanl-old/'
cat = TDSCatalog(f'{base_url}{dt:%Y%m}/{dt:%Y%m%d}/catalog.xml')
ncss = cat.datasets[f'namanl_218_{dt:%Y%m%d}_{dt:%H}00_000.grb'].subset()
# Query for Latest GFS Run
query = ncss.query()
query.time(dt)
query.accept('netcdf')
query.variables('Geopotential_height_isobaric',
'u-component_of_wind_isobaric',
'v-component_of_wind_isobaric')
# Obtain our queried data
data = ncss.get_data(query)
ds_data = xr.open_dataset(NetCDF4DataStore(data)).metpy.parse_cf()
ds = ds_data.metpy.assign_latitude_longitude()
times = ds.Geopotential_height_isobaric.metpy.time
vtime = times.values.squeeze().astype('datetime64[ms]').astype('O')
lev_500 = 500 * units.hPa
hght_500 = ds.Geopotential_height_isobaric.metpy.sel(
vertical=lev_500).squeeze()
hght_500 = mpcalc.smooth_gaussian(hght_500, 4)
uwnd_500 = ds['u-component_of_wind_isobaric'].metpy.sel(
vertical=lev_500).squeeze()
vwnd_500 = ds['v-component_of_wind_isobaric'].metpy.sel(
vertical=lev_500).squeeze()
# Compute north-relative wind components for calculation purposes
uwnd_500er, vwnd_500er = earth_relative_wind_components(uwnd_500, vwnd_500)---------------------------------------------------------------------------
HTTPError Traceback (most recent call last)
Cell In[3], line 7
5 base_url = 'https://www.ncei.noaa.gov/thredds/catalog/model-namanl-old/'
6 cat = TDSCatalog(f'{base_url}{dt:%Y%m}/{dt:%Y%m%d}/catalog.xml')
----> 7 ncss = cat.datasets[f'namanl_218_{dt:%Y%m%d}_{dt:%H}00_000.grb'].subset()
9 # Query for Latest GFS Run
10 query = ncss.query()
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/catalog.py:707, in Dataset.subset(self, service)
703 elif service not in self.ncss_service_names:
704 raise ValueError(service + ' is not a valid service for subset. Options are: '
705 + ', '.join(self.ncss_service_names))
--> 707 return self.access_with_service(service)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/catalog.py:763, in Dataset.access_with_service(self, service, use_xarray)
760 raise ValueError(service + ' is not an access method supported by Siphon')
762 try:
--> 763 return provider(self.access_urls[service])
764 except KeyError:
765 raise ValueError(service + ' is not available for this dataset') from None
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/http_util.py:383, in HTTPEndPoint.__init__(self, url)
381 self._base = url
382 self._session = session_manager.create_session()
--> 383 self._get_metadata()
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/ncss.py:57, in NCSS._get_metadata(self)
55 def _get_metadata(self):
56 # Need to use .content here to avoid decode problems
---> 57 meta_xml = self.get_path('dataset.xml').content
58 root = ET.fromstring(meta_xml)
59 self.metadata = NCSSDataset(root)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/http_util.py:453, in HTTPEndPoint.get_path(self, path, query)
430 def get_path(self, path, query=None):
431 """Make a GET request, optionally including a query, to a relative path.
432
433 The path of the request includes a path on top of the base URL
(...) 451
452 """
--> 453 return self.get(self.url_path(path), query)
File ~/micromamba/envs/metpy-cookbook/lib/python3.14/site-packages/siphon/http_util.py:488, in HTTPEndPoint.get(self, path, params)
486 else:
487 text = resp.text
--> 488 raise requests.HTTPError(f'Error accessing {resp.request.url}\n'
489 f'Server Error ({resp.status_code: d}: {text})')
490 return resp
HTTPError: Error accessing https://www.ncei.noaa.gov/thredds/ncss/grid/model-namanl-old/201604/20160416/namanl_218_20160416_1800_000.grb/dataset.xml
Server Error ( 404: FileNotFound: No such file or directory)Begin Data Calculations¶
avor = mpcalc.vorticity(uwnd_500er, vwnd_500er)
avor = mpcalc.smooth_n_point(avor, 9, 10) * 1e5
vort_adv = mpcalc.advection(avor, uwnd_500er, vwnd_500er) * 1e4Map Creation¶
# Set up Coordinate System for Plot and Transforms
datacrs = ds.Geopotential_height_isobaric.metpy.cartopy_crs
plotcrs = ccrs.LambertConformal(central_latitude=45., central_longitude=-100.,
standard_parallels=[30, 60])
fig = plt.figure(1, figsize=(12., 14.))
ax = plt.subplot(111, projection=plotcrs)
# Plot Titles
plt.title(r'500-hPa Heights (m), AVOR$*10^5$ ($s^{-1}$),'
'AVOR Adv$*10^9$ ($s^{-2}$)', loc='left')
plt.title(f'VALID: {vtime}', loc='right')
# Plot Background
ax.set_extent([235., 290., 20., 58.], ccrs.PlateCarree())
ax.coastlines('50m', edgecolor='black', linewidth=0.75)
ax.add_feature(cfeature.STATES, linewidth=.5)
# Plot Height Contours
clev500 = np.arange(5100, 6061, 60)
cs = ax.contour(hght_500.longitude, hght_500.latitude,
hght_500, clev500,
colors='black', linewidths=1.0,
linestyles='solid', transform=ccrs.PlateCarree())
plt.clabel(cs, fontsize=10, inline=1, inline_spacing=10, fmt='%i',
rightside_up=True, use_clabeltext=True)
# Plot Absolute Vorticity Contours
clevvort500 = np.arange(-9, 50, 5)
cs2 = ax.contour(avor.longitude, avor.latitude,
avor, clevvort500,
colors='grey', linewidths=1.25, linestyles='dashed',
transform=ccrs.PlateCarree())
plt.clabel(cs2, fontsize=10, inline=1, inline_spacing=10, fmt='%i',
rightside_up=True, use_clabeltext=True)
# Plot Colorfill of Vorticity Advection
clev_avoradv = np.arange(-30, 31, 5)
cf = ax.contourf(vort_adv.longitude, vort_adv.latitude, vort_adv,
clev_avoradv[clev_avoradv != 0], extend='both',
cmap='bwr', transform=ccrs.PlateCarree())
cb = plt.colorbar(cf, orientation='horizontal', pad=0, aspect=50,
extendrect='True', ticks=clev_avoradv)
cb.set_label(r'$1/s^2$', size='large')
# Plot Wind Barbs
# Transform Vectors and plot wind barbs.
wind_slice = (slice(None, None, 20), slice(None, None, 20))
xx, yy = np.meshgrid(uwnd_500.x.values[wind_slice[0]],
uwnd_500.y.values[wind_slice[0]])
ax.barbs(xx, yy, uwnd_500.values[wind_slice], vwnd_500.values[wind_slice],
length=6, pivot='middle', transform=datacrs)