Blame | Last modification | View Log | Download | RSS feed
# -*- coding:utf-8 -*-
import cartopy
import copy
import math
import numpy as np
from pyproj import Geod
from datetime import timedelta
from dypy.intergrid import Intergrid
from dypy.constants import rdv, L, R, cp, t_zero
__all__ = ['CrossSection', 'interpolate', 'rotate_points', 'interval',
'dewpoint', 'esat', 'moist_lapse']
class CrossSection(object):
"""
Create a cross-section
return the distance, the pressure levels,
and the variables.
"""
def __init__(self, variables, coo, pressure,
int2p=False, pollon=-170, pollat=43, nbre=1000, order=1):
"""
variables: dictionary of variables {'name': np.array},
need to contain rlat, rlon
coo: list of coordinates [(startlon, startlat), (endlon, endlat)]
pressure: pressure coordinate 1d np.array
int2p: True if variables need to be interpolated,
require P in the variables
pollon: pole_longitude of the rotated grid
pollat: pole_latitude of the rotated grid
nre: nbre of points along the cross-section
order: order of interpolation see for details
"""
variables = copy.deepcopy(variables)
# test the input
required = ['rlon', 'rlat']
if int2p:
required.append('p')
for var in required:
if var not in variables.keys():
raise Exception('{} not in variables dictionary'.format(var))
rlon = variables.pop('rlon')
rlat = variables.pop('rlat')
self.coo = coo
self.nbre = nbre
self.pressure = pressure
self.nz = pressure.size
if int2p:
p = variables['p']
# find the coordinate of the cross-section
self.lon, self.lat, crlon, crlat, self.distance = find_cross_points(
coo, nbre, pollon, pollat)
self.distances = np.linspace(0, self.distance, nbre)
# get the information for intergrid
self._lo = np.array([rlat.min(), rlon.min()])
self._hi = np.array([rlat.max(), rlon.max()])
self._query_points = [[lat, lon] for lat, lon in zip(crlat, crlon)]
if int2p:
for var, data in variables.items():
if data.ndim > 2:
dataint = interpolate(data, p, pressure)
variables[var] = dataint
variables = self._int2cross(variables)
self.__dict__.update(variables)
def _int2cross(self, variables):
for var, data in variables.items():
if data.squeeze().ndim > 2:
datacross = np.zeros((self.nz, self.nbre))
for i, datah in enumerate(data.squeeze()):
intfunc = Intergrid(datah, lo=self._lo, hi=self._hi,
verbose=False, order=1)
datacross[i, :] = intfunc.at(self._query_points)
else:
datacross = np.zeros((self.nbre, ))
intfunc = Intergrid(data.squeeze(), lo=self._lo, hi=self._hi,
verbose=False, order=1)
datacross[:] = intfunc.at(self._query_points)
variables[var] = datacross
return variables
def find_cross_points(coo, nbre, pole_longitude, pole_latitude):
""" give nbre points along a great circle between coo[0] and coo[1]
iand rotated the points
"""
g = Geod(ellps='WGS84')
cross_points = g.npts(coo[0][0], coo[0][1], coo[1][0], coo[1][1], nbre)
lat = np.array([point[1] for point in cross_points])
lon = np.array([point[0] for point in cross_points])
rlon, rlat = rotate_points(
pole_longitude, pole_latitude, lon, lat)
distance = great_circle_distance(coo[0][0], coo[0][1],
coo[1][0], coo[1][1])
return lon, lat, rlon, rlat, distance
def great_circle_distance(lon1, lat1, lon2, lat2):
"""
return the distance (km) between points following a great circle
based on : https://gist.github.com/gabesmed/1826175
>>> great_circle_distance(0, 55, 8, 45.5)
1199.13065955064
"""
EARTH_CIRCUMFERENCE = 6378137 # earth circumference in meters
dLat = math.radians(lat2 - lat1)
dLon = math.radians(lon2 - lon1)
a = (math.sin(dLat / 2) * math.sin(dLat / 2) +
math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
math.sin(dLon / 2) * math.sin(dLon / 2))
c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
distance = EARTH_CIRCUMFERENCE * c
return distance/1000
def interpolate(data, grid, interplevels):
"""
intrerpolate data to grid at interplevels
data and grid are numpy array in the form (z,lat,lon)
"""
data = data.squeeze()
grid = grid.squeeze()
shape = list(data.shape)
if (data.ndim > 3) | (grid.ndim > 3):
message = "data and grid need to be 3d array"
raise IndexError(message)
try:
nintlev = len(interplevels)
except:
interplevels = [interplevels]
nintlev = len(interplevels)
shape[-3] = nintlev
outdata = np.ones(shape)*np.nan
if nintlev > 20:
for idx, _ in np.ndenumerate(data[0]):
column = grid[:, idx[0], idx[1]]
column_GRID = data[:, idx[0], idx[1]]
value = np.interp(
interplevels,
column,
column_GRID,
left=np.nan,
right=np.nan)
outdata[:, idx[0], idx[1]] = value[:]
else:
for j, intlevel in enumerate(interplevels):
for lev in range(grid.shape[0]):
cond1 = grid[lev, :, :] > intlevel
cond2 = grid[lev-1, :, :] < intlevel
right = np.where(cond1 & cond2)
if right[0].size > 0:
sabove = grid[lev, right[0], right[1]]
sbelow = grid[lev-1, right[0], right[1]]
dabove = data[lev, right[0], right[1]]
dbelow = data[lev-1, right[0], right[1]]
result = (intlevel-sbelow)/(sabove-sbelow) * \
(dabove-dbelow)+dbelow
outdata[j, right[0], right[1]] = result
return outdata
def rotate_points(pole_longitude, pole_latitude, lon, lat, direction='n2r'):
"""
rotate points from non-rotated system to rotated system
n2r : non-rotated to rotated (default)
r2n : rotated to non-rotated
return rlon,rlat,
"""
lon = np.array(lon)
lat = np.array(lat)
rotatedgrid = cartopy.crs.RotatedPole(
pole_longitude=pole_longitude,
pole_latitude=pole_latitude
)
standard_grid = cartopy.crs.Geodetic()
if direction == 'n2r':
rotated_points = rotatedgrid.transform_points(standard_grid, lon, lat)
elif direction == 'r2n':
rotated_points = standard_grid.transform_points(rotatedgrid, lon, lat)
rlon, rlat, _ = rotated_points.T
return rlon, rlat
def interval(starting_date, ending_date, delta=timedelta(hours=1)):
curr = starting_date
while curr < ending_date:
yield curr
curr += delta
def dewpoint(p, qv):
"""
Calculate the dew point temperature based on p and qv following (eq.8):
Lawrence, M.G., 2005. The Relationship between Relative Humidity
and the Dewpoint Temperature in Moist Air:
A Simple Conversion and Applications.
Bulletin of the American Meteorological Society 86,
225–233. doi:10.1175/BAMS-86-2-225
"""
B1 = 243.04 # °C
C1 = 610.94 # Pa
A1 = 17.625
e = p*qv/(rdv+qv)
td = (B1*np.log(e/C1))/(A1-np.log(e/C1))
return td
def esat(t):
"""
Calculate the saturation vapor pressure for t in °C
Following eq. 6 of
Lawrence, M.G., 2005. The Relationship between Relative Humidity
and the Dewpoint Temperature in Moist Air:
A Simple Conversion and Applications.
Bulletin of the American Meteorological Society 86,
225–233. doi:10.1175/BAMS-86-2-225
"""
C1 = 610.94 # Pa
A1 = 17.625 #
B1 = 243.03 # °C
return C1*np.exp((A1 * t) / (B1 + t))
def moist_lapse(t, p):
""" Calculates moist adiabatic lapse rate for T (Celsius) and p (Pa)
Note: We calculate dT/dp, not dT/dz
See formula 3.16 in Rogers&Yau for dT/dz, but this must be combined with
the dry adiabatic lapse rate (gamma = g/cp) and the
inverse of the hydrostatic equation (dz/dp = -RT/pg)"""
a = 2. / 7.
b = rdv * L * L / (R * cp)
c = a * L / R
e = esat(t)
wsat = rdv * e / (p - e) # Rogers&Yau 2.18
numer = a * (t + t_zero) + c*wsat
denom = p * (1 + b * wsat / ((t + t_zero)**2))
return numer / denom