使用 Metpy 将 GFS 风从等压线插值到高度坐标

Interpolating GFS winds from isobaric to height coordinates using Metpy

我的任务是在大气层的各个层面绘制风图以支持航空。虽然我已经能够使用 GFS 模型数据绘制一些漂亮的图(请参见下面的代码),但我真的不得不使用 GFS 提供的压力坐标来粗略估计高度。我正在使用 300 hPA、700 hPA 和 925 hPA 的风来估算 30,000 英尺、9000 英尺和 3000 英尺的风速。我的问题真的是针对那些外行的人的,他们是 metpy gurus ......在那里我可以将这些风插值到高度表面的方法?在这些高度获得实际风肯定会很好!感谢任何人都可以分享关于这个主题的任何信息!

import cartopy.crs as ccrs
import cartopy.feature as cfeature
import matplotlib.pyplot as plt
from matplotlib.colors import ListedColormap
import numpy as np
from netCDF4 import num2date
from datetime import datetime, timedelta
from siphon.catalog import TDSCatalog
from siphon.ncss import NCSS
from PIL import Image
from matplotlib import cm


# For the vertical levels we want to grab with our queries
# Levels need to be in Pa not hPa
Levels = [30000,70000,92500]
# Time deltas for days
Deltas = [1,2,3]
#Deltas = [1]
# Levels in hPa for the file names
LevelDict = {30000:'300', 70000:'700', 92500:'925'}
# The path to where our banners are stored 
impath = 'C:\Users\shell\Documents\Python Scripts\Banners\'
# Final images saved here
imoutpath = 'C:\Users\shell\Documents\Python Scripts\TVImages\'

# Quick function for finding out which variable is the time variable in the
# netCDF files
def find_time_var(var, time_basename='time'):
    for coord_name in var.coordinates.split():
        if coord_name.startswith(time_basename):
            return coord_name
    raise ValueError('No time variable found for ' + var.name)

# Function to grab data at different levels from Siphon 
def grabData(level):

    query.var = set()
    query.variables('u-component_of_wind_isobaric', 'v-component_of_wind_isobaric')
    query.vertical_level(level)
    data = ncss.get_data(query)
    u_wind_var = data.variables['u-component_of_wind_isobaric']
    v_wind_var = data.variables['v-component_of_wind_isobaric']
    time_var = data.variables[find_time_var(u_wind_var)]
    lat_var = data.variables['lat']
    lon_var = data.variables['lon']

    return u_wind_var, v_wind_var, time_var, lat_var, lon_var

# Construct a TDSCatalog instance pointing to the gfs dataset
best_gfs = TDSCatalog('http://thredds-jetstream.unidata.ucar.edu/thredds/catalog/grib/'
                      'NCEP/GFS/Global_0p5deg/catalog.xml')

# Pull out the dataset you want to use and look at the access URLs
best_ds = list(best_gfs.datasets.values())[1]
#print(best_ds.access_urls)

# Create NCSS object to access the NetcdfSubset
ncss = NCSS(best_ds.access_urls['NetcdfSubset'])
print(best_ds.access_urls['NetcdfSubset'])

# Looping through the forecast times

for delta in Deltas:
    # Create lat/lon box and the time(s) for location you want to get data for
    now = datetime.utcnow()
    fcst = now + timedelta(days = delta)
    timestamp = datetime.strftime(fcst, '%A')
    query = ncss.query()
    query.lonlat_box(north=78, south=45, east=-90, west=-220).time(fcst)
    query.accept('netcdf4')


    # Now looping through the levels to create our plots

    for level in Levels:
        u_wind_var, v_wind_var, time_var, lat_var, lon_var = grabData(level)
        # Get actual data values and remove any size 1 dimensions
        lat = lat_var[:].squeeze()
        lon = lon_var[:].squeeze()
        u_wind = u_wind_var[:].squeeze()
        v_wind = v_wind_var[:].squeeze()
        #converting to knots
        u_windkt= u_wind*1.94384
        v_windkt= v_wind*1.94384
        wspd = np.sqrt(np.power(u_windkt,2)+np.power(v_windkt,2))

        # Convert number of hours since the reference time into an actual date
        time = num2date(time_var[:].squeeze(), time_var.units)
        print (time)
        # Combine 1D latitude and longitudes into a 2D grid of locations
        lon_2d, lat_2d = np.meshgrid(lon, lat)

        # Create new figure
        #fig = plt.figure(figsize = (18,12))
        fig = plt.figure()
        fig.set_size_inches(26.67,15)
        datacrs = ccrs.PlateCarree()
        plotcrs = ccrs.LambertConformal(central_longitude=-150,
                                       central_latitude=55,
                                       standard_parallels=(30, 60))

        # Add the map and set the extent
        ax = plt.axes(projection=plotcrs)
        ext = ax.set_extent([-195., -115., 50., 72.],datacrs)
        ext2 = ax.set_aspect('auto')
        ax.background_patch.set_fill(False)

        # Add state boundaries to plot
        ax.add_feature(cfeature.STATES, edgecolor='black', linewidth=2)

        # Add geopolitical boundaries for map reference
        ax.add_feature(cfeature.COASTLINE.with_scale('50m'))
        ax.add_feature(cfeature.OCEAN.with_scale('50m'))
        ax.add_feature(cfeature.LAND.with_scale('50m'),facecolor = '#cc9666', linewidth = 4)

        if level == 30000:
            spdrng_sped = np.arange(30, 190, 2)
            windlvl = 'Jet_Stream'
        elif level == 70000:
            spdrng_sped = np.arange(20, 100, 1)
            windlvl = '9000_Winds_Aloft'
        elif level == 92500:
            spdrng_sped = np.arange(20, 80, 1)
            windlvl = '3000_Winds_Aloft'
        else:
            pass


        top = cm.get_cmap('Greens')
        middle = cm.get_cmap('YlOrRd')
        bottom = cm.get_cmap('BuPu_r')
        newcolors = np.vstack((top(np.linspace(0, 1, 128)),
                       middle(np.linspace(0, 1, 128))))
        newcolors2 = np.vstack((newcolors,bottom(np.linspace(0,1,128))))

        cmap = ListedColormap(newcolors2)
        cf = ax.contourf(lon_2d, lat_2d, wspd, spdrng_sped, cmap=cmap,
                         transform=datacrs, extend = 'max', alpha=0.75)

        cbar = plt.colorbar(cf, orientation='horizontal', pad=0, aspect=50,
                            drawedges = 'true')
        cbar.ax.tick_params(labelsize=16)
        wslice = slice(1, None, 4)

        ax.quiver(lon_2d[wslice, wslice], lat_2d[wslice, wslice],
                       u_windkt[wslice, wslice], v_windkt[wslice, wslice], width=0.0015,
                       headlength=4, headwidth=3, angles='xy', color='black', transform = datacrs)

        plt.savefig(imoutpath+'TV_UpperAir'+LevelDict[level]+'_'+timestamp+'.png',bbox_inches= 'tight')

        # Now we use Pillow to overlay the banner with the appropriate day
        background = Image.open(imoutpath+'TV_UpperAir'+LevelDict[level]+'_'+timestamp+'.png')
        im = Image.open(impath+'Banner_'+windlvl+'_'+timestamp+'.png')

        # resize the image
        size = background.size
        im = im.resize(size,Image.ANTIALIAS)

        background.paste(im, (17, 8), im)
        background.save(imoutpath+'TV_UpperAir'+LevelDict[level]+'_'+timestamp+'.png','PNG')

我不确定这是否是您要查找的内容(我对 Metpy 很陌生),但我一直在使用 metpy height_to_pressure_std(altitude) 函数。它以 hPa 为单位,然后我将其转换为帕斯卡,然后转换为无单位值以用于虹吸管 vertical_level(float) 函数。

感谢提问!我在这里的方法是为每个单独的列将 GFS 输出位势高度的压力坐标插入到您提供的高度上,以估计每个列的每个高度级别的压力。然后我可以使用该压力将 GFS 输出 u、v 插入。 GFS 输出的 GPH 和风的压力坐标略有不同,这就是我插值两次的原因。我使用 MetPy 的 interpolate.log_interpolate_1d 执行插值,它对输入的日志执行线性插值。这是我使用的代码!

from datetime import datetime
import numpy as np
import metpy.calc as mpcalc
from metpy.units import units
from metpy.interpolate import log_interpolate_1d
from siphon.catalog import TDSCatalog


gfs_url = 'https://tds.scigw.unidata.ucar.edu/thredds/catalog/grib/NCEP/GFS/Global_0p5deg/catalog.xml'
cat = TDSCatalog(gfs_url)

now = datetime.utcnow()

# A shortcut to NCSS
ncss = cat.datasets['Best GFS Half Degree Forecast Time Series'].subset()

query = ncss.query()
query.var = set()
query.variables('u-component_of_wind_isobaric', 'v-component_of_wind_isobaric', 'Geopotential_height_isobaric')
query.lonlat_box(north=78, south=45, east=-90, west=-220)
query.time(now)
query.accept('netcdf4')

data = ncss.get_data(query)


# Reading in the u(isobaric), v(isobaric), isobaric vars and the GPH(isobaric6) and isobaric6 vars
# These are two slightly different vertical pressure coordinates.
# We will also assign units here, and this can allow us to go ahead and convert to knots
lat = units.Quantity(data.variables['lat'][:].squeeze(), units('degrees'))
lon = units.Quantity(data.variables['lon'][:].squeeze(), units('degrees'))
iso_wind = units.Quantity(data.variables['isobaric'][:].squeeze(), units('Pa'))
iso_gph = units.Quantity(data.variables['isobaric6'][:].squeeze(), units('Pa'))
u = units.Quantity(data.variables['u-component_of_wind_isobaric'][:].squeeze(), units('m/s')).to(units('knots'))
v = units.Quantity(data.variables['v-component_of_wind_isobaric'][:].squeeze(), units('m/s')).to(units('knots'))
gph = units.Quantity(data.variables['Geopotential_height_isobaric'][:].squeeze(), units('gpm'))


# Here we will select our altitudes to interpolate onto and convert them to geopotential meters 
altitudes = ([30000., 9000., 3000.] * units('ft')).to(units('gpm'))

# Now we will interpolate the pressure coordinate for model output geopotential height to 
# estimate the pressure level for our given altitudes at each grid point
pressures_of_alts = np.zeros((len(altitudes), len(lat), len(lon)))

for ilat in range(len(lat)):
    for ilon in range(len(lon)):
        pressures_of_alts[:, ilat, ilon] = log_interpolate_1d(altitudes,
                                                              gph[:, ilat, ilon],
                                                              iso_gph)

pressures_of_alts = pressures_of_alts * units('Pa')


# Similarly, we will use our interpolated pressures to interpolate
# our u and v winds across their given pressure coordinates.
# This will provide u, v at each of our interpolated pressure
# levels corresponding to our provided initial altitudes
u_at_levs = np.zeros((len(altitudes), len(lat), len(lon)))
v_at_levs = np.zeros((len(altitudes), len(lat), len(lon)))

for ilat in range(len(lat)):
    for ilon in range(len(lon)):
        u_at_levs[:, ilat, ilon], v_at_levs[:, ilat, ilon] = log_interpolate_1d(pressures_of_alts[:, ilat, ilon],
                                                                                iso_wind,
                                                                                u[:, ilat, ilon],
                                                                                v[:, ilat, ilon])

u_at_levs = u_at_levs * units('knots')
v_at_levs = v_at_levs * units('knots')


# We can use mpcalc to calculate a wind speed array from these
wspd = mpcalc.wind_speed(u_at_levs, v_at_levs)

我能够从中获取我的输出并将其强制转换为您的绘图代码(带有一些单元剥离。)

Your 300-hPa GFS winds
My "30000-ft" GFS winds

Here 是我在每个估计高度级别的插值压力场的样子。

希望对您有所帮助!

我认为您不能使用 metpy 函数将高度转换为压力,反之亦然。使用标准大气将压力转换为英尺时也存在错误。