A collection of diagnostic and interpolation routines for use with output from the Weather Research and Forecasting (WRF-ARW) Model.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

402 lines
13 KiB

from math import floor, ceil
import numpy as n
import numpy.ma as ma
from wrf.var.extension import (interpz3d,interp2dxy,interp1d,
smooth2d,monotonic,vintrp)
from wrf.var.decorators import handle_left_iter, handle_casting
from wrf.var.util import extract_vars, is_staggered
from wrf.var.constants import Constants, ConversionFactors
from wrf.var.terrain import get_terrain
from wrf.var.geoht import get_height
from wrf.var.temp import get_theta, get_temp, get_eth
from wrf.var.pressure import get_pressure
__all__ = ["interplevel", "vertcross", "interpline", "vinterp"]
# Note: Extension decorator is good enough to handle left dims
def interplevel(data3d,zdata,desiredloc,missingval=Constants.DEFAULT_FILL):
"""Return the horizontally interpolated data at the provided level
data3d - the 3D field to interpolate
zdata - the vertical values (height or pressure)
desiredloc - the vertical level to interpolate at (must be same units as
zdata)
missingval - the missing data value (which will be masked on return)
"""
r1 = interpz3d(data3d, zdata, desiredloc, missingval)
masked_r1 = ma.masked_values (r1, missingval)
return masked_r1
def _to_positive_idxs(shape, coord):
if (coord[-2] >= 0 and coord[-1] >=0):
return coord
return [x if (x >= 0) else shape[i]+x for (i,x) in enumerate(coord) ]
def _get_xy(xdim, ydim, pivot_point=None, angle=None,
start_point=None, end_point=None):
"""Returns the x,y points for the horizontal cross section line.
xdim - maximum x-dimension
ydim - maximum y-dimension
pivot_point - a pivot point of (south_north, west_east) (must be used with angle)
angle - the angle through the pivot point in degrees
start_point - a start_point sequence of [south_north1, west_east1]
end_point - an end point sequence of [south_north2, west_east2]
"""
# Have a pivot point with an angle to find cross section
if pivot_point is not None and angle is not None:
xp = pivot_point[-1]
yp = pivot_point[-2]
if (angle > 315.0 or angle < 45.0
or ((angle > 135.0) and (angle < 225.0))):
#x = y*slope + intercept
slope = -(360.-angle)/45.
if( angle < 45. ):
slope = angle/45.
if( angle > 135.):
slope = (angle-180.)/45.
intercept = xp - yp*slope
# find intersections with domain boundaries
y0 = 0.
x0 = y0*slope + intercept
if( x0 < 0.): # intersect outside of left boundary
x0 = 0.
y0 = (x0 - intercept)/slope
if( x0 > xdim-1): #intersect outside of right boundary
x0 = xdim-1
y0 = (x0 - intercept)/slope
y1 = ydim-1. #need to make sure this will be a float?
x1 = y1*slope + intercept
if( x1 < 0.): # intersect outside of left boundary
x1 = 0.
y1 = (x1 - intercept)/slope
if( x1 > xdim-1): # intersect outside of right boundary
x1 = xdim-1
y1 = (x1 - intercept)/slope
else:
# y = x*slope + intercept
slope = (90.-angle)/45.
if( angle > 225. ):
slope = (270.-angle)/45.
intercept = yp - xp*slope
#find intersections with domain boundaries
x0 = 0.
y0 = x0*slope + intercept
if( y0 < 0.): # intersect outside of bottom boundary
y0 = 0.
x0 = (y0 - intercept)/slope
if( y0 > ydim-1): # intersect outside of top boundary
y0 = ydim-1
x0 = (y0 - intercept)/slope
x1 = xdim-1. # need to make sure this will be a float?
y1 = x1*slope + intercept
if( y1 < 0.): # intersect outside of bottom boundary
y1 = 0.
x1 = (y1 - intercept)/slope
if( y1 > ydim-1):# intersect outside of top boundary
y1 = ydim-1
x1 = (y1 - intercept)/slope
elif start_point is not None and end_point is not None:
x0 = start_point[-1]
y0 = start_point[-2]
x1 = end_point[-1]
y1 = end_point[-2]
if ( x1 > xdim-1 ):
x1 = xdim
if ( y1 > ydim-1):
y1 = ydim
else:
raise ValueError("invalid combination of None arguments")
dx = x1 - x0
dy = y1 - y0
distance = (dx*dx + dy*dy)**0.5
npts = int(distance)
dxy = distance/npts
xy = n.zeros((npts,2), "float")
dx = dx/npts
dy = dy/npts
for i in xrange(npts):
xy[i,0] = x0 + i*dx
xy[i,1] = y0 + i*dy
return xy
@handle_left_iter(3, 0, ignore_args=(2,3,4,5,6),
ignore_kargs=("missingval", "pivot_point", "angle",
"start_point", "end_point"))
@handle_casting(arg_idxs=(0,1))
def vertcross(data3d, z, missingval=Constants.DEFAULT_FILL,
pivot_point=None,angle=None,
start_point=None,end_point=None):
"""Return the vertical cross section for a 3D field, interpolated
to a verical plane defined by a horizontal line.
Arguments:
data3d - a 3D data field
z - 3D height field
pivot_point - a pivot point of (south_north,west_east) (must be used with angle)
angle - the angle through the pivot point in degrees
start_point - a start_point tuple of (south_north1,west_east1)
end_point - an end point tuple of (south_north2,west_east2)
"""
if pivot_point is not None:
pos_pivot = _to_positive_idxs(z.shape[-2:], pivot_point)
else:
pos_pivot = pivot_point
if start_point is not None:
pos_start = _to_positive_idxs(z.shape[-2:], start_point)
else:
pos_start = start_point
if end_point is not None:
pos_end = _to_positive_idxs(z.shape[-2:], end_point)
else:
pos_end = start_point
xdim = z.shape[-1]
ydim = z.shape[-2]
xy = _get_xy(xdim, ydim, pos_pivot, angle, pos_start, pos_end)
# Interp z
var2dz = interp2dxy(z, xy)
# interp to constant z grid
if(var2dz[0,0] > var2dz[-1,0]): # monotonically decreasing coordinate
z_max = floor(n.amax(z) / 10) * 10 # bottom value
z_min = ceil(n.amin(z) / 10) * 10 # top value
dz = 10
nlevels = int((z_max-z_min) / dz)
z_var2d = n.zeros((nlevels), dtype=z.dtype)
z_var2d[0] = z_max
dz = -dz
else:
z_max = n.amax(z)
z_min = 0.
dz = 0.01 * z_max
nlevels = int(z_max / dz)
z_var2d = n.zeros((nlevels), dtype=z.dtype)
z_var2d[0] = z_min
for i in xrange(1,nlevels):
z_var2d[i] = z_var2d[0] + i*dz
#interp the variable
var2d = n.zeros((nlevels, xy.shape[0]),dtype=var2dz.dtype)
var2dtmp = interp2dxy(data3d, xy)
for i in xrange(xy.shape[0]):
var2d[:,i] = interp1d(var2dtmp[:,i], var2dz[:,i], z_var2d, missingval)
return ma.masked_values(var2d, missingval)
@handle_left_iter(2, 0, ignore_args=(1,2,3,4),
ignore_kargs=("pivot_point", "angle",
"start_point", "end_point"))
@handle_casting(arg_idxs=(0,))
def interpline(data2d, pivot_point=None,
angle=None, start_point=None,
end_point=None):
"""Return the 2D field interpolated along a line.
Arguments:
var2d - a 2D data field
pivot_point - a pivot point of (south_north,west_east)
angle - the angle through the pivot point in degrees
start_point - a start_point tuple of (south_north1,west_east1)
end_point - an end point tuple of (south_north2,west_east2)
"""
if pivot_point is not None:
pos_pivot = _to_positive_idxs(data2d.shape[-2:], pivot_point)
else:
pos_pivot = pivot_point
if start_point is not None:
pos_start = _to_positive_idxs(data2d.shape[-2:], start_point)
else:
pos_start = start_point
if end_point is not None:
pos_end = _to_positive_idxs(data2d.shape[-2:], end_point)
else:
pos_end = start_point
tmp_shape = [1] + [x for x in data2d.shape]
var2dtmp = n.zeros(tmp_shape, data2d.dtype)
xdim = data2d.shape[-1]
ydim = data2d.shape[-2]
xy = _get_xy(xdim, ydim, pos_pivot, angle, pos_start, pos_end)
var2dtmp[0,:,:] = data2d[:,:]
var1dtmp = interp2dxy(var2dtmp, xy)
return var1dtmp[0,:]
def vinterp(wrfnc, field, vert_coord, interp_levels, extrapolate=False,
field_type=None, log_p=False):
# Remove case sensitivity
field_type = field_type.lower() if field_type is not None else "none"
vert_coord = vert_coord.lower() if vert_coord is not None else "none"
valid_coords = ("pressure", "pres", "p", "ght_msl",
"ght_agl", "theta", "th", "theta-e", "thetae", "eth")
valid_field_types = ("none", "pressure", "pres", "p", "z",
"tc", "tk", "theta", "th", "theta-e", "thetae",
"eth", "ght")
icase_lookup = {"none" : 0,
"p" : 1,
"pres" : 1,
"pressure" : 1,
"z" : 2,
"ght" : 2,
"tc" : 3,
"tk" : 4,
"theta" : 5,
"th" : 5,
"theta-e" : 6,
"thetae" : 6,
"eth" : 6}
# These constants match what's in the fortran code.
rgas = 287.04 #J/K/kg
ussalr = .0065 # deg C per m, avg lapse rate
sclht = rgas*256./9.81
# interp_levels might be a list or tuple, make a numpy array
if not isinstance(interp_levels, n.ndarray):
interp_levels = n.array(interp_levels, "float64")
# TODO: Check if field is staggered
if is_staggered(field, wrfnc):
raise RuntimeError("Please unstagger field in the vertical")
# Check for valid coord
if vert_coord not in valid_coords:
raise RuntimeError("'%s' is not a valid vertical "
"coordinate type" % vert_coord)
# Check for valid field type
if field_type not in valid_field_types:
raise RuntimeError("'%s' is not a valid field type" % field_type)
log_p_int = 1 if log_p else 0
icase = 0
extrap = 0
if extrapolate:
extrap = 1
icase = icase_lookup[field_type]
# Extract vriables
timeidx = -1 # Should this be an argument?
ncvars = extract_vars(wrfnc, timeidx, vars=("PSFC", "QVAPOR", "F"))
sfp = ncvars["PSFC"] * ConversionFactors.PA_TO_HPA
qv = ncvars["QVAPOR"]
coriolis = ncvars["F"]
terht = get_terrain(wrfnc, timeidx)
t = get_theta(wrfnc, timeidx)
tk = get_temp(wrfnc, timeidx, units="k")
p = get_pressure(wrfnc, timeidx, units="pa")
ght = get_height(wrfnc, timeidx, msl=True)
ht_agl = get_height(wrfnc, timeidx, msl=False)
smsfp = smooth2d(sfp, 3)
# Vertical coordinate type
vcor = 0
if vert_coord in ("pressure", "pres", "p"):
vcor = 1
vcord_array = p * ConversionFactors.PA_TO_HPA
elif vert_coord == "ght_msl":
vcor = 2
vcord_array = n.exp(-ght/sclht)
elif vert_coord == "ght_agl":
vcor = 3
vcord_array = n.exp(-ht_agl/sclht)
elif vert_coord in ("theta", "th"):
vcor = 4
idir = 1
icorsw = 0
delta = 0.01
p_hpa = p * ConversionFactors.PA_TO_HPA
vcord_array = monotonic(t,p_hpa,coriolis,idir,delta,icorsw)
# We only extrapolate temperature fields below ground
# if we are interpolating to pressure or height vertical surfaces.
icase = 0
elif vert_coord in ("theta-e", "thetae", "eth"):
vcor = 5
icorsw = 0
idir = 1
delta = 0.01
eth = get_eth(wrfnc, timeidx)
p_hpa = p * ConversionFactors.PA_TO_HPA
vcord_array = monotonic(eth,p_hpa,coriolis,idir,delta,icorsw)
# We only extrapolate temperature fields below ground if we are
# interpolating to pressure or height vertical surfaces
icase = 0
# Set the missing value
if isinstance(field, n.ma.MaskedArray):
missing = field.fill_value
else:
missing = Constants.DEFAULT_FILL
res = vintrp(field,p,tk,qv,ght,terht,sfp,smsfp,
vcord_array,interp_levels,
icase,extrap,vcor,log_p_int,missing)
return ma.masked_values(res,missing)