Access real-time NCEP HRRR model output from Unidata’s THREDDS server¶
Parse the current time and choose a recent hour when we might expect the model run to be complete. There is normally a ~90 minute lag, but we’ll give it a bit more than that.
# The previous hour's data is typically available shortly after the half-hourif(minute<45):hrDelta=2else:hrDelta=1runTime=now-timedelta(hours=hrDelta)runTimeStr=runTime.strftime('%Y%m%d %H00 UTC')modelDate=runTime.strftime('%Y%m%d')modelHour=runTime.strftime('%H')modelMin=runTime.strftime('%M')modelDay=runTime.strftime('%D')titleTime=modelDay+' '+modelHour+'00 UTC'print(modelDay)print(modelHour)print(runTimeStr)print(titleTime)
Hourly Maximum of Upward Vertical Velocity in the lowest 400hPa (Mixed_intervals Maximum) @ Level at specified pressure difference from ground to level layer
units :
m.s-1
abbreviation :
MAXUVV
grid_mapping :
LambertConformal_Projection
Grib_Statistical_Interval_Type :
Maximum
Grib_Variable_Id :
VAR_0-2-220_L108_layer_Imixed_S2
Grib2_Parameter :
[ 0 2 220]
Grib2_Parameter_Discipline :
Meteorological products
Grib2_Parameter_Category :
Momentum
Grib2_Parameter_Name :
Hourly Maximum of Upward Vertical Velocity in the lowest 400hPa
Grib2_Level_Type :
108
Grib2_Level_Desc :
Level at specified pressure difference from ground to level
Hourly Maximum of Downward Vertical Velocity in the lowest 400hPa (Mixed_intervals Maximum) @ Level at specified pressure difference from ground to level layer
units :
m.s-1
abbreviation :
MAXDVV
grid_mapping :
LambertConformal_Projection
Grib_Statistical_Interval_Type :
Maximum
Grib_Variable_Id :
VAR_0-2-221_L108_layer_Imixed_S2
Grib2_Parameter :
[ 0 2 221]
Grib2_Parameter_Discipline :
Meteorological products
Grib2_Parameter_Category :
Momentum
Grib2_Parameter_Name :
Hourly Maximum of Downward Vertical Velocity in the lowest 400hPa
Grib2_Level_Type :
108
Grib2_Level_Desc :
Level at specified pressure difference from ground to level
Hourly Maximum of Upward Vertical Velocity in the lowest 400hPa (Mixed_intervals Maximum) @ Level at specified pressure difference from ground to level layer
units :
m.s-1
abbreviation :
MAXUVV
grid_mapping :
LambertConformal_Projection
Grib_Statistical_Interval_Type :
Maximum
Grib_Variable_Id :
VAR_0-2-220_L108_layer_Imixed_S2
Grib2_Parameter :
[ 0 2 220]
Grib2_Parameter_Discipline :
Meteorological products
Grib2_Parameter_Category :
Momentum
Grib2_Parameter_Name :
Hourly Maximum of Upward Vertical Velocity in the lowest 400hPa
Grib2_Level_Type :
108
Grib2_Level_Desc :
Level at specified pressure difference from ground to level
Hourly Maximum of Downward Vertical Velocity in the lowest 400hPa (Mixed_intervals Maximum) @ Level at specified pressure difference from ground to level layer
units :
m.s-1
abbreviation :
MAXDVV
grid_mapping :
LambertConformal_Projection
Grib_Statistical_Interval_Type :
Maximum
Grib_Variable_Id :
VAR_0-2-221_L108_layer_Imixed_S2
Grib2_Parameter :
[ 0 2 221]
Grib2_Parameter_Discipline :
Meteorological products
Grib2_Parameter_Category :
Momentum
Grib2_Parameter_Name :
Hourly Maximum of Downward Vertical Velocity in the lowest 400hPa
Grib2_Level_Type :
108
Grib2_Level_Desc :
Level at specified pressure difference from ground to level
GRIB files that are served by THREDDS are automatically converted into NetCDF. A quirk in this translation can result in certain dimensions (typically those that are time or vertically-based) varying from run-to-run. For example, the time coordinate dimension for a given variable might be time in one run, but time1 (or time2, time3, etc.) in another run. This poses a challenge any time we want to subset a DataArray. For example, if a particular dataset had vertical levels in units of hPa, the dimension name for the geopotential height grid might be isobaric. If we wanted to select only the 500 and 700 hPa levels, we might write the following line of code:
Z=ds.sel(isobaric=[500,700])
This would fail if we pointed to model output from a different time, and the vertical coordinate name for geopotential height was instead isobaric1.
Via strategic use of Python dictionaries, we can programmatically deal with this!
First, let’s use some MetPy accessors that determine the dimension names for times and vertical levels:
We next construct dictionaries corresponding to each desired subset of the relevant dimensions. Here, we specify the first time, and the one and only vertical level:
idxTime=0# First timeidxVert=0# First (and in this case, only) vertical leveltimeDict={timeDim:idxTime}vertDict={vertDim:idxVert}timeDict,vertDict
({'time2': 0}, {'height_above_ground3': 0})
Produce a quick visualization at the given time and level. Pass the two dictionaries into Xarray’s isel function.
grid=var.isel(timeDict).isel(vertDict)grid.plot()
<matplotlib.collections.QuadMesh at 0x14b79b674250>
Get the Dataset’s map projection.
proj_data=var.metpy.cartopy_crsproj_data;
Plot the data over the full domain
Create a title string
validTime=var.isel(timeDict)[timeDim].values# Returns validTime as a NumPy Datetime 64 objectvalidTime=pd.Timestamp(validTime)# Converts validTime to a Pandas Timestamp objecttimeStr=validTime.strftime("%Y-%m-%d %H%M UTC")# We can now use Datetime's strftime method to create a time string.tl1="HRRR 2m temperature ($^\circ$C)"tl2=str('Valid at: '+timeStr)title_line=(tl1+'\n'+tl2+'\n')
Convert grid to desired units
grid=grid.metpy.convert_units('degC')
Create DataArray objects for the horizontal coordinate dimensions. In this case, the model is output on a Lambert Conic Conformal projection, which means the horizontal dimensions are named x and y and represent physical distance in meters from the projection’s central point.
x,y=ds.x,ds.y
res='50m'fig=plt.figure(figsize=(18,12))ax=plt.subplot(1,1,1,projection=proj_data)ax.set_facecolor(cfeature.COLORS['water'])ax.add_feature(cfeature.LAND.with_scale(res))ax.add_feature(cfeature.COASTLINE.with_scale(res))ax.add_feature(cfeature.LAKES.with_scale(res),alpha=0.5,zorder=4)ax.add_feature(cfeature.STATES.with_scale(res))# Usually avoid plotting counties once the regional extent is large#ax.add_feature(USCOUNTIES,edgecolor='grey', linewidth=1 );CF=ax.contourf(x,y,grid,cmap='coolwarm',alpha=0.99,transform=proj_data)cbar=plt.colorbar(CF,fraction=0.046,pad=0.03,shrink=0.5)cbar.ax.tick_params(labelsize=10)cbar.ax.set_ylabel(f'Temperature ($^\circ$C)',fontsize=10);title=ax.set_title(title_line,fontsize=16)
Find the x- and y-coordinate indicies that are closest to the values of the four corners.
# Lats/Lons of corner pointsSWLon,SWLat=(mapExtent[0]),(mapExtent[2])NWLon,NWLat=(mapExtent[0],mapExtent[3])SELon,SELat=(mapExtent[1],mapExtent[2])NELon,NELat=(mapExtent[1],mapExtent[3])# Corresponding corner points' coordinates in the dataset's native CRSxSW,ySW=pFull(SWLon,SWLat)xNW,yNW=pFull(NWLon,NWLat)xSE,ySE=pFull(SELon,SELat)xNE,yNE=pFull(NELon,NELat)# Find the indicies that correspond to these corner points coordinatesxSWds,ySWds=find_closest(x,xSW),find_closest(y,ySW)xNWds,yNWds=find_closest(x,xNW),find_closest(y,yNW)xSEds,ySEds=find_closest(x,xSE),find_closest(y,ySE)xNEds,yNEds=find_closest(x,xNE),find_closest(y,yNE)# Get the minimum and maximum values of the x- and y- coordinatesxMin,xMax=np.min([xNWds,xSWds]),np.max([xNEds,xSEds])+1yMin,yMax=np.min([ySEds,ySWds]),np.max([yNEds,yNWds])+1
In order for the data region to fill the plotted region, pad the x and y ranges (trial and error, depends on the subregion)
Plot the regional map. Omit plotting the land/water features since they tend to obscure the nuances of the temperature field, and also slows down the creation of the figure.
res='10m'fig=plt.figure(figsize=(18,12))ax=plt.subplot(1,1,1,projection=proj_map)ax.set_extent(mapExtent,crs=ccrs.PlateCarree())#ax.set_facecolor(cfeature.COLORS['water'])#ax.add_feature (cfeature.LAND.with_scale(res))ax.add_feature(cfeature.COASTLINE.with_scale(res))#ax.add_feature (cfeature.LAKES.with_scale(res), alpha = 0.5)ax.add_feature(cfeature.STATES.with_scale(res))# Usually avoid plotting counties once the regional extent is large#ax.add_feature(USCOUNTIES,edgecolor='grey', linewidth=1 );CF=ax.contourf(xSub,ySub,gridSub,cmap='coolwarm',alpha=0.5,transform=proj_data)cbar=plt.colorbar(CF,fraction=0.046,pad=0.03,shrink=0.5)cbar.ax.tick_params(labelsize=10)cbar.ax.set_ylabel(f'Temperature ($^\circ$C)',fontsize=10);title=ax.set_title(title_line,fontsize=16)
Create a time series at the gridpoint closest to a given location.¶
fig=plt.figure(figsize=(12,10))fig.suptitle(f'HRRR {validTime} Initialization',fontweight='bold',fontsize=15)ax=fig.add_subplot(111)ax.set_xlabel('Date/Time')ax.set_ylabel('2 m Temperature ($^\circ$C)')ax.set_title(f'Time series at gridpoint closest to {siteName}: Latitude {siteLat} , Longitude {siteLon}')# Improve on the default tickingax.plot(times,timeSer,linewidth=0.8,color='tab:blue',marker='o',linestyle='--')ax.xaxis.set_major_locator(HourLocator(interval=1))hoursFmt=DateFormatter('%d/%H')ax.xaxis.set_major_formatter(hoursFmt)ax.tick_params(axis='x',rotation=45,labelsize=8)ax.set_xlim(times.values[0],times.values[-1]);