Source code for vacumm.misc.grid.misc

# -*- coding: utf8 -*-
"""
Grid utilities.

It deals with bounds, areas, interpolations...

See: :ref:`user.tut.misc.grid`.
"""
# Copyright or © or Copr. Actimar/IFREMER (2010-2018)
#
# This software is a computer program whose purpose is to provide
# utilities for handling oceanographic and atmospheric data,
# with the ultimate goal of validating the MARS model from IFREMER.
#
# This software is governed by the CeCILL license under French law and
# abiding by the rules of distribution of free software.  You can  use,
# modify and/ or redistribute the software under the terms of the CeCILL
# license as circulated by CEA, CNRS and INRIA at the following URL
# "http://www.cecill.info".
#
# As a counterpart to the access to the source code and  rights to copy,
# modify and redistribute granted by the license, users are provided only
# with a limited warranty  and the software's author,  the holder of the
# economic rights,  and the successive licensors  have only  limited
# liability.
#
# In this respect, the user's attention is drawn to the risks associated
# with loading,  using,  modifying and/or developing or reproducing the
# software by the user in light of its specific status of free software,
# that may mean  that it is complicated to manipulate,  and  that  also
# therefore means  that it is reserved for developers  and  experienced
# professionals having in-depth computer knowledge. Users are therefore
# encouraged to load and test the software's suitability as regards their
# requirements in conditions enabling the security of their systems and/or
# data to be ensured and,  more generally, to use and operate it in the
# same conditions as regards security.
#
# The fact that you are presently reading this means that you have had
# knowledge of the CeCILL license and that you accept its terms.
#


import operator
from gc import collect

import numpy as N, MV2 ,cdms2
from numpy import ma as MA
from cdms2.coord import TransientAxis2D, TransientVirtualAxis, FileAxis2D
from cdms2.auxcoord import TransientAuxAxis1D
from cdms2.axis import TransientAxis
from cdms2.hgrid import AbstractCurveGrid, TransientCurveGrid
from cdms2.grid import AbstractGrid, AbstractRectGrid
from cdms2.gengrid import AbstractGenericGrid, TransientGenericGrid
from genutil import minmax
cdms = cdms2
MV = MV2

from mpl_toolkits.basemap import Basemap
from _geoslib import Point

isseq = operator.isSequenceType
isnum = operator.isNumberType
isdic= ismap = operator.isMappingType

__all__ = ['isoslice','isgrid', 'get_resolution', 'get_distances', 'get_closest', 'get_closest_depth', 'get_geo_area',
    'bounds2d', 'bounds1d', 'get_axis', 'get_grid', 'get_grid_axes', "gridsel",
    'grid2d', 'num2axes2d', 'var2d', 'axis1d_from_bounds', 'get_xy',
    'deg2xy', 'set_grid', 'check_xy_shape', 'axes2d', 'meshgrid','meshbounds', 'meshweights',
    't2uvgrids', 'meshcells', 'cells2grid', 'curv_grid', 'bounds2mesh', 'resol',
    'create_grid', 'rotate_grid', 'isregular', 'monotonic', 'transect_specs',
    'depth2dz', 'isdepthup', 'makedepthup', 'makealtitudeup', 'dz2depth', 'get_axis_slices',
    'xextend', 'xshift', 'curv2rect',  'isrect', 'create_grid2d', 'create_var2d', 'create_axes2d',
    'merge_axis_slice', 'merge_axis_slices', 'get_zdim', 'coord2slice', 'mask2ind',
    'create_aux_axes', 'create_ugrid', 'istri', 'get_tri',
    'varsel', 'haversine', 'create_curv_grid',
    'issgrid', 'isugrid', 'get_grid_type',
    'get_unstruct_indices', 'GriddedSelector']
__all__.sort()



[docs]def isoslice(var,prop,isoval=0,axis=0,masking=True): """ result = isoslice(variable,property[,isoval=0]) result is a a projection of variable at property == isoval in the first nonsingleton dimension. In the case when there is more than one zero crossing, the results are averaged. EXAMPLE: Assume two three dimensional variable, s (salt), z (depth), and u (velicity), all on the same 3D grid. x and y are the horizontal positions, with the same horizontal dimensions as z (the 3D depth field). Here, assume the vertical dimension, that will be projected, is the first. s_at_m5 = isoslice(s,z,-5); # s at z == -5 h_at_s30 = isoslice(z,s,30); # z at s == 30 u_at_s30 = isoslice(u,s,30); # u at s == 30 Get from OCTANT library : https://github.com/hetland/octant """ if (len(var.squeeze().shape)<=2): raise ValueError, 'variable must have at least two dimensions' if not prop.shape == var.shape: raise ValueError, 'dimension of var and prop must be identical' var = var.swapaxes(0, axis) prop = prop.swapaxes(0, axis) prop=prop-isoval sz = N.shape(var) var = var.reshape(sz[0],-1) prop = prop.reshape(sz[0],-1) #find zero-crossings (zc == 1) zc = N.where( (prop[:-1,:]*prop[1:,:])<0.0 ,1.0, 0.0) varl = var[:-1,:]*zc varh = var[1:,:]*zc propl = prop[:-1,:]*zc proph = prop[1:,:]*zc result = varl - propl*(varh-varl)/(proph-propl) result = N.where(zc==1., result, 0.) szc = zc.sum(axis=0) szc = N.where(szc==0., 1, szc) result = result.sum(axis=0)/szc if masking: result = N.ma.masked_where(zc.sum(axis=0)==0, result) if all(result.mask): raise Warning, 'property==%f out of range (%f, %f)' % \ (isoval, (prop+isoval).min(), (prop+isoval).max()) result = result.reshape(sz[1:]) return(result)
[docs]def get_grid_type(grid): """Get the grid type as one of "rect", "curv", "unstruct" or None""" if isinstance(grid, AbstractRectGrid): return "rect" if isinstance(grid, AbstractCurveGrid): return "curv" if isinstance(grid, AbstractGenericGrid): return "unstruct"
[docs]def isgrid(grid, gtype=None, curv=None): """Check if gg is a grid :Params: - **gg**: A cdms grid. - **gtype**, optional, strings, None: Grid type as one of "rect", "curv", "unstruct". Prepend a "-" to inverse the test. You can also provide a list of choices. - **curv**, optional: If True, restrict to curvilinear grids. DEPRECATED :Example: >>> isgrid(grid) # is it a grid? >>> isgrid(grid, "curv") # is a curved grid? >>> isgrid(grid, "-curv") # is a grid but not a curved grid? >>> isgrid(grid, ["rect", "unstruct"]) # is a rectangular or unstructured grid? """ if curv: gtype = 'curv' if not gtype: return cdms2.isGrid(grid) grid_type = get_grid_type(grid) if grid_type is None: return False return check_case(gtype, grid_type)
[docs]def issgrid(grid): """Check if a grid in structured""" return isgrid(grid, ['rect', 'curv'])
[docs]def isugrid(grid): """Check if a grid in unstructured""" return isgrid(grid, 'unstruct')
[docs]def get_resolution(mygrid, lon_range=None,lat_range=None): """Get the mean resolution of a grid .. warning:: Deprecated: please use :meth:`resol` instead. """ # Get data if isinstance(mygrid,tuple): if len(mygrid) == 2: lon,lat = mygrid mask = None else: lon,lat,mask = mygrid else: lon = mygrid.getLongitude().getValue() lat = mygrid.getLatitude().getValue() mask = mygrid.getMask() # Work on 2D arrays if lon.shape != lat.shape: lon,lat = N.meshgrid(lon,lat) # Initial mask if mask is MA.nomask: lon[:] = N.where(mask,999.,lon) lat[:] = N.where(mask,999.,lat) # Geographic selection mask if lon_range is not None: mask_lon = N.logical_and(N.greater_equal(lon,lon_range[0]),N.less_equal(lon,lon_range[1])) if mask is MA.nomask: mask = mask_lon else: mask = N.logical_and(mask,mask_lon) if lat_range is not None: mask_lat = N.logical_and(N.greater_equal(lat,lat_range[0]),N.less_equal(lat,lat_range[1])) if mask is MA.nomask: mask = mask_lat else: mask = N.logical_and(mask,mask_lon) lon[:] = N.where(mask,999.,lon) lat[:] = N.where(mask,999.,lat) # Select valid data if mask is MA.nomask: lon = N.compress((1-mask).ravel(),lon.ravel()) lat = N.compress((1-mask).ravel(),lat.ravel()) else: lon = lon.ravel() lat = lat.ravel() # Compute areas xx = units.deg2m(lon,lat=lat) yy = units.deg2m(lat) dx = xx[1:]-xx[:-1] dy = yy[1:]-yy[:-1] ds = dx*dy del xx,yy,dx,dy collect() # Return mean resolution return N.sqrt(ds).mean()
[docs]def get_distances(xxa, yya, xxb=None, yyb=None, mode=None, pairwise=False, geo=False): """Find the distances (in m) between a series of points :Params: - **xxa**: X coordinate of the first series - **yya**: Y // - **xxb**: X coordinate of the second series - **yyb**: Y // - **mode**, optional: distance computation mode - ``None``: use ``"harversine"`` if longitude and latitude axes else ``"direct"`` - ``"simple"`` or ``"euclidian"`` or ``"meters"``: simple euclidian distance with no coordinate tranformation - ``"harversine"`` or ``"sphere"`` or ``"degrees"``: great circle distance in meters from coordinates in degrees - ``"deg2m"``: euclidian distance with coordinates converted from degrees to meters using :func:`~vacumm.misc.phys.units.deg2m`. - A callable object like a function that directly compute distance from coordinates:: mode(xxa, yya, xxb, yyb) - **pairwise**, optional: The distances between A and B points are computed pairwise. There must have the same number of A and B points. :Return: Distances as an ``(nb,na)`` array if pairwise if false, else a ``(na,)`` array. """ # Mode if not callable(mode): if mode is None: mode = "haversine" if (islon(xxa) or islon(xxb) or islat(yya) or islat(yyb)) else "simple" if geo: # backward compat mode = 'haversine' mode = str(mode).lower() valid_modes = 'simple', 'haversine', 'sphere', 'deg2m', 'degrees', 'euclidian', 'meters' if mode not in valid_modes: raise VACUMMError('Wrong mode ({}). Please choose one of: {}'.format( mode, ', '.join(valid_modes))) if mode in ['sphere', "degrees"]: mode = 'harversine' elif mode in ["meters", "euclidian"]: mode = 'simple' # With what if xxb is None: xxb = xxa if yyb is None: yyb = yya # Numerical types Nma = numod(xxa, yya) Nmb = numod(xxb, xxb) nma = None if N.isscalar(xxa) and N.isscalar(yya) else Nma nmb = None if N.isscalar(xxb) and N.isscalar(yyb) else Nmb if MV2 is Nma or MV2 is Nmb: Nm = MV2 elif N.ma is Nma or N.ma is Nmb: Nm = N.ma else: Nm = N # Make sur to have arrays oldshapea = N.shape(xxa) if N.ndim(xxa)>=N.ndim(yya) else N.shape(yya) oldshapeb = N.shape(xxb) if N.ndim(xxb)>=N.ndim(yyb) else N.shape(yyb) if cdms2.isVariable(xxa): xxa = xxa.asma() yya = yya.asma() else: xxa = N.atleast_1d(xxa) yya = N.atleast_1d(yya) if cdms2.isVariable(xxb): xxb = xxb.asma() yyb = yyb.asma() else: xxb = N.atleast_1d(xxb) yyb = N.atleast_1d(yyb) # Reshape them xxa = xxa.ravel().astype('d') yya = yya.ravel().astype('d') xxb = xxb.ravel().astype('d') yyb = yyb.ravel().astype('d') if not pairwise: xxa, xxb = N.meshgrid(xxa, xxb) yya, yyb = N.meshgrid(yya, yyb) # Compute it # print 'coords', xxa, yya, xxb, yyb if callable(mode): dist = mode(xxa, yya, xxb, yyb) else: if mode=='deg2m': xxa = units.deg2m(xxa,lat=yya) yya = units.deg2m(yya) xxb = units.deg2m(xxb,lat=yyb) yyb = units.deg2m(yyb) if mode=='haversine': dist = haversine(xxa, yya, xxb, yyb, degrees=True) else: dist = Nm.sqrt((xxa-xxb)**2+(yya-yyb)**2) # Reform if nma or nmb: if pairwise: dist = dist.reshape(oldshapea or oldshapeb) else: dist = dist.reshape(oldshapeb + oldshapea) else: dist = float(dist) return dist
[docs]def haversine(xa, ya, xb, yb, radius=None, degrees=True): """Compute the haversine distance for a known radius""" if radius: radius = constants.EARTH_RADIUS if degrees: xa *= N.pi/180 ya *= N.pi/180 xb *= N.pi/180 yb *= N.pi/180 Nm = numod(xa, ya, xb, yb) a = Nm.sin((ya-yb)/2)**2 + Nm.cos(ya) * Nm.cos(yb) * Nm.sin((xa-xb)/2)**2 return constants.EARTH_RADIUS * 2 * Nm.arcsin(Nm.sqrt(a))
[docs]def get_closest(xx, yy, xp, yp, proj=True, mask=None, gridded=True, **kwargs): """Find the closest unmasked point on a grid and return indices :Params: - **xx**: 1D or 2D X axis, or random positions. - **yy**: 1D or 2D Y axis, or random positions. - **xp**: X position of the point (float) - **yp**: Y // - **geo**, optional: If True, force to treat the grid as geographical, thus convert coordinates to meters using :func:`~vacumm.misc.phys.units.deg2m`. - **gridded**, optional: Treat input as gridded points, otherwise treat them as random points (flatten ``xx`` and ``yy``). :Returns: - ``(i,j)`` 2-element tuple of indices along y and x for gridded data, - OR ``i`` if not gridded. """ # Longitude/Latitude grid? proj = kwargs.pop('geo', proj) try: if xx.isLongitude() and yy.isLatitude(): proj = True except: pass # Gridded xx = N.asarray(xx[:]) yy = N.asarray(yy[:]) if gridded: xx, yy = meshgrid(xx,yy, copy=0) else: xx = xx.ravel() yy = yy.ravel() # Geo grid distances if proj: if not callable(proj): proj = get_proj((xx,yy)) xx,yy = proj(xx,yy) xp,yp = proj(xp,yp) # Compute the distance to all grid points if mask is not None: xx = MA.array(xx, mask=mask, copy=False) yy = MA.array(yy, mask=mask, copy=False) dd = (xx - xp)**2 + (yy - yp)**2 # Find the smallest distance imin = N.ma.argmin(dd) if gridded: return N.unravel_index(imin, dd.shape)[::-1] return imin
[docs]def get_closest_depth(zz, zp, mask=None, **kwargs): """Find the closest unmasked point on a depth vector and return indices :Params: - **zz**: 1D Z axis, or random positions. - **zp**: Z position of the point (float) :Returns: - ``(i,)`` 1-element tuple of indices along z for gridded data, - OR ``i`` if not gridded. """ # Gridded zz = N.asarray(zz[:]) # Compute the distance to all grid points if mask is not None: zz = MA.array(zz, mask=mask, copy=False) dd = N.abs(zz - zp) # Find the smallest distance imin = N.ma.argmin(dd) return imin
[docs]def get_geo_area(grid,mask=None): """Compute cell areas on the a regular geographical grid. :Params: - **grid** The grid - *mask*: Force the use of this mask :Returns: 2D array of areas in m^2 .. TODO:: get_geo_area: treat 2D grid + use standard projection """ if mask is None and grid.mask is not False: mask = grid.getMask() latBounds, lonBounds = grid.getBounds() lat = grid.getLatitude() latWeights = units.deg2m(latBounds[:,1] - latBounds[:,0],lat=lat) lonWeights = units.deg2m(lonBounds[:,1] - lonBounds[:,0]) area = N.outerproduct(latWeights,lonWeights) if mask.any(): area = MA.array(area, mask = mask) return area
[docs]def isregular(axis, tol=.05, iaxis=None, dx=None): """Check is an 1S or 2D axis is regular :Params: - **axis**: A :mod:`cdms2` axis or :mod:`numpy` array - **tol**, optional: Relative tolerance - **iaxis**, optional: On which direction to operate for 2D axis :Example: >>> isregular(lon1d, dx=2., tol=.01) >>> isregular(lon2d, iaxis=1) """ cdaxis = isaxis(axis) if cdaxis: xx = axis.getValue() else: xx = axis[:] if iaxis is None: if xx.ndim == 1 or not cdaxis: iaxis = 0 else: iaxis = int(islon(axis)) thisdx = N.diff(xx, axis=iaxis) dxx = N.abs(N.diff(thisdx, axis=iaxis)) if dx is None: dx = N.median(thisdx) return not ((dxx/dx)%1.>tol).any()
[docs]def bounds1d(xx, cellwidth=None): """Compute bounds on a linear sequence or axis. It is based on :func:`cdms2.genGenericBounds` of CDAT. :Example: >>> bounds1d(xx).shape 360, 2 :Returns: xx(nx,2) """ try: xx = xx.getValues().astype('d') except: if not isinstance(xx, N.ndarray): xx = N.asarray(xx[:],'d') M = numod(xx) if len(xx)>1: leftPoint = M.array([1.5*xx[0]-0.5*xx[1]]) midArray = (xx[0:-1]+xx[1:])/2.0 rightPoint = M.array([1.5*xx[-1]-0.5*xx[-2]]) bnds = N.concatenate((leftPoint,midArray,rightPoint)) else: assert cellwidth is not None delta = cellwidth/2.0 bnds = M.array([xx[0]-delta, xx[0]+delta]) # Transform to (n,2) array retbnds = M.zeros((len(xx),2),'d') retbnds[:,0] = bnds[:-1] retbnds[:,1] = bnds[1:] return retbnds
[docs]def bounds2d(*xyaxes): """Compute bounds on a rectangular grid. :Params: - **xyaxes**: 2D arrays(ny,nx) (or 2x1D arrays) :Example: >>> xx_bounds,yy_bounds = bounds2d(xx,yy) :Returns: ``xx(ny,nx,4),...`` """ results = [] half = N.array(0.5,'d') quart = N.array(0.25,'d') if len(xyaxes) == 2: xyaxes = meshgrid(*xyaxes) for xy in xyaxes: if hasattr(xy,'getValue'): xy = xy.getValue() assert xy.ndim == 2, 'You must pass 2d arrays as argument' ny,nx = xy.shape M = numod(xy) bounds = M.zeros((ny,nx,4),'d') inside = quart * (xy[0:-1,0:-1] + xy[0:-1,1:] + xy[1: ,0:-1] + xy[1: ,1:]) left = half * (xy[0:-1, 0] - half * (xy[0:-1, 1] - xy[0:-1, 0]) + \ xy[1: , 0] - half * (xy[1: , 1] - xy[1: , 0])) right = half * (xy[0:-1,-1] + half * (xy[0:-1,-1] - xy[0:-1,-2]) + \ xy[1: ,-1] + half * (xy[1: ,-1] - xy[1: ,-2])) bot = half * (xy[ 0,0:-1] - half * (xy[ 1,0:-1] - xy[ 0,0:-1]) + \ xy[ 0,1: ] - half * (xy[ 1,1: ] - xy[ 0,1: ])) top = half * (xy[-1,0:-1] + half * (xy[-1,0:-1] - xy[-2,0:-1]) + \ xy[-1,1: ] + half * (xy[-1,1: ] - xy[-2,1: ])) left_bot = xy[ 0, 0] + half * (xy[ 0, 0] - xy[ 1, 1]) right_bot = xy[ 0,-1] + half * (xy[ 0,-1] - xy[ 1,-2]) right_top = xy[-1,-1] + half * (xy[-1,-1] - xy[-2,-2]) left_top = xy[-1, 0] + half * (xy[-1, 0] - xy[-2, 1]) bounds[1: ,1: ,0] = inside bounds[1: ,0:-1,1] = inside bounds[0:-1,0:-1,2] = inside bounds[0:-1,1: ,3] = inside bounds[1: , 0,0] = left bounds[0:-1, 0,3] = left bounds[1: ,-1,1] = right bounds[0:-1,-1,2] = right bounds[ 0,1: ,0] = bot bounds[ 0,0:-1,1] = bot bounds[-1,0:-1,2] = top bounds[-1,1: ,3] = top bounds[ 0, 0,0] = left_bot bounds[ 0,-1,1] = right_bot bounds[-1,-1,2] = right_top bounds[-1, 0,3] = left_top results.append(bounds) if len(results) == 1: return results[0] else: return results
[docs]def meshgrid(x,y,copy=1): """Convert pure numeric x/y axes to 2d arrays of same shape. It works like :func:`numpy.meshgrid` but is more flexible. :Params: - **x**: 1D or 2D array - **x**: 2D or 2D array :Return: ``xx(ny,nx),yy(ny,nx)`` """ # Be sure to have numpy arrays and make a copy x = _cdat2num_(x) Mx = numod(x) My = numod(y) if N.ma.isMA(x): if copy: x = x.copy() xmask = N.ma.getmaskarray(x) else: x = N.array(x[:],copy=copy) xmask = None y = _cdat2num_(y) if N.ma.isMA(y): if copy: y = y.copy() ymask = N.ma.getmaskarray(y) else: y = N.array(y[:],copy=copy) ymask = None # All is ok if x.ndim == 2 and x.shape == y.shape: return x,y # From regular grid if x.ndim == 1 and y.ndim == 1: x, y = N.meshgrid(x,y) if xmask is not None or ymask is not None: if xmask is None: xmask = N.zeros(len(x), '?') elif ymask is None: ymask = N.zeros(len(y), '?') xmask, ymask = N.meshgrid(xmask, ymask) x = N.ma.masked_where(xmask, x, copy=False) y = N.ma.masked_where(ymask, y, copy=False) return x, y # From x 1d if x.ndim == 1 and y.ndim == 2 and y.shape[1] == x.shape[0]: xx = Mx.resize(x,y.shape) return xx,y # From y 1d if y.ndim == 1 and x.ndim == 2 and x.shape[0] == y.shape[0]: yy = My.resize(y,x.shape[::-1]).transpose() return x,yy raise ValueError, "Unable to safely convert to 2D axes"
[docs]def bounds2mesh(xb, yb=None): """Convert 2D 4-corners cell bounds arrays (results from :func:`bounds2d`) to 2D arrays :Params: - **xb**: array(ny,nx,4) or array(nx,2) - **yb**: None or array(ny,nx,4) or array(ny,2) :Return: ``xxb(ny+1,nx+1),yyb(ny+1,nx+1)`` """ res = () for xyb in xb, yb: if not isinstance(xyb, N.ndarray): xyb = N.asarray(xyb) M = numod(xyb) # 1D if xyb.ndim==2: xy = M.zeros((len(xyb)+1, ),dtype=xb.dtype) xy[:-1] = xyb[:, 0] xy[-1] = xyb[-1, 1] # 1D else: ny,nx,nb = xyb.shape # - center + lower + left xy = M.zeros((ny+1,nx+1),dtype=xyb.dtype) xy[:-1,:-1] = xyb[:,:,0] # - right xy[:-1,-1] = xyb[:,-1,1] # - top xy[-1,:-1] = xyb[-1,:,3] # - top-left xy[-1,-1] = xyb[-1,-1,2] if yb is None: return xy res += xy, return res
[docs]def meshcells(x, y=None, xwidth=None, ywidth=None): """Return a 1D or 2D array corresponding the cell corners :params: - **x**: 1D or 2D array - **y**: 1D or 2D array :Returns: ``xxb(nx+1)``x OR ``xxb(ny+1,nx+1),yyb(ny+1,nx+1)`` TODO: Add full support to meshcells for xwidth and ywidth params """ if not isinstance(x, N.ndarray): x = N.asarray(x) if y is None and len(x.shape)==1: return bounds2mesh(bounds1d(x, xwidth)) if y is not None: if not isinstance(y, N.ndarray): y = N.asarray(y) xy = meshgrid(x,y) return bounds2mesh(*bounds2d(*xy)) else: return bounds2mesh(bounds2d(x))
[docs]def meshweights(x, y=None, proj=None, axis=-1): """Return a 1D or 2D array corresponding the cell weights :Params: - **x**: 1D or nD array - **y**: 1D or 2D array - **proj**, optional: Geographic projection before computing weights (for 2D case only with both x and y) - ``True``: use default mercator projection (see :func:`~vacumm.misc.grid.basemap.merc`), - else, directly apply projection. :Returns: ``ww(nx)`` OR ``ww(ny,nx)`` """ if not isinstance(x, N.ndarray): x = N.asarray(x) # Without y if y is None: if axis<0: axis += x.ndim dx = x.copy() M = numod(x) dd = M.diff(x, axis=axis) sl = [slice(None)]*x.ndim def ss(*sss): s = list(sl) s[axis] = slice(*sss) if len(sss)!=1 else sss[0] return tuple(s) dx[ss(1, -1)] = .5*(dd[ss(None, -1)]+dd[ss(1, None)]) dx[ss(0)] = dd[ss(0)] dx[ss(-1)] = dd[ss(-1)] del dd return dx # # 2D # if x.ndim==2: # if axis<0: axis = 2-axis # dx = x.copy() # dd = N.diff(x, axis=axis) # if axis==0: # dx[1:-1] = .5*(dd[:-1]+dd[1:]) # dx[0] = dd[0] # dx[-1] = dd[-1] # else: # dx[:, 1:-1] = .5*(dd[:, :-1]+dd[:, 1:]) # dx[:, 0] = dd[:, 0] # dx[:, -1] = dd[:, -1] # del dd # return dx # # # 1D # return N.diff(meshcells(x)) # With y if not isinstance(y, N.ndarray): y = N.asarray(y) # - 2D if x.ndim==2: if proj is True: proj = get_proj((x, y)) xxb, yyb = meshcells(x, y) if proj: xxb, yyb = proj(xxb, yyb) M = numod(x) dxx = M.diff(xxb, axis=1) dxy = M.diff(xxb, axis=0) dyx = M.diff(yyb, axis=1) dyy = M.diff(yyb, axis=0) dx = M.sqrt(dxx**2+dyx**2) del dxx, dyx dx = 0.5*(dx[:-1]+dx[1:]) dy = M.sqrt(dxy**2+dyy**2) del dxy, dyy dy = 0.5*(dy[:, :-1]+dy[:, 1:]) return dx*dy # - 1D return meshweights(x, axis=axis), meshweights(y, axis=axis)
# return N.diff(meshcells(x)), N.diff(meshcells(y))
[docs]def cells2grid(xxb,yyb): """Convert a grid of cells (grid of corners, like results from :func:`meshcells`) to a grid (grid of centers) :Params: - **xxb**: X of corners (ny+1,nx+1) - **yyb**: Y of corners (ny+1,nx+1) :Returns: ``xx(ny,nx),yy(ny,nx)`` """ M = numod(xxb) xxyy = [M.zeros((nx+1,ny+1),dtype=xxb.dtype), M.zeros((nx+1,ny+1), dtype=yyb.dtype)] bb = (xxb,yyb) ij2ic = M.reshape([0,1,3,2],(2,2)) for ib in 0,1: # Center xxyy[ib][1:-1,1:-1] = 0.25 * (bb[ib][1:,1:,0]+bb[ib][:-1,1:,1]+bb[ib][:-1,:-1,2]+bb[ib][1:,:-1,3]) # Sides for i in 0,-1: xxyy[ib][i,1:-1] = 0.5 * (bb[ib][i,1:,ij2ic[i,0]]+bb[ib][i,:-1,ij2ic[i,1]]) # Bottom/top xxyy[ib][1:-1,i] = 0.5 * (bb[ib][1:,i,ij2ic[0,i]]+bb[ib][:-1,i,ij2ic[1,i]]) # Left/right # Corners for j in 0,-1: for i in 0,-1: xxyy[ib][j,i] = bb[ib][j,i,ij2ic[j,i]] return xxyy[0],xxyy[1]
[docs]def meshbounds(*args, **kwargs): """A shortcut to :func:`meshcells`""" return meshcells(*args, **kwargs)
[docs]def coord2slice(gg, lon=None, lat=None, mode='slice', mask=None, assubmask=True, squeeze=False, **kwargs): """Convert from coordinates to indices on grid or axes :Params: - **gg**: A grid, a cmds variable with a grid, a tuple of axes or an axis. - **lon/lat**: Interval of coordinates or slice. If single coordinates, the interval becomes for instance ``(lon,lon,'ccb')``. - **mode**, optional: Output mode. You can pass only the first letter. - ``"slice"``: Return a :class:`slice` object. - ``"indices"``: Return a 3-element tuple as in a :class:`slice` context ``(start,stop,step)``. - ``"array"``: Return an array of valid indices of shape ``(2,nvalid)``, where ``out[0]`` gives the X indices and ``out[1]`` gives the Y indices. - **mask**, optional: Also use this mask to get indices (for 2D axes only). - **squeeze**, optional: If ``asslice`` is False ad - **asslice**, optional: DEPRECATED. Use ``mode='slice'`` instead. :Return: In array mode, it always returns an array of indices of shape ``(2,nvalid)``, possibly empty if not intersection if found. Here ``i/jslice`` is a slice or a tuple of indices, depending on asslice. It can also be ``None`` if not intersection are found. If ``gg`` is grid, a tuple of axes or a 2D axis: ``islice, jslice, mask``. In case of a tuple of 1D axes, ``mask`` is ``None`` because not relevant. If ``gg`` is a 1D axis: ``ijslice``, just as the :meth:`mapIntervaExt`` method of 1D axes. :Examples: >>> ijk = coord2slice(lon1d, lon=(lon0,lon1), mode='i') >>> xijk, yijk, mask = coord2slice((lon1d,lat1d), lon=(lon0,lon1), lat=slice(0,3), mode='i') >>> islice, jslice, mask = coord2slice(gridcurv, lat=(lat0,lat0,'ccb'), mode='s') >>> xijk, yijk, mask = coord2slice(lon2d, lon=(lon0,lon1), mode='i') >>> ij = coord2slice(grid, lon, lat, mode='a') """ # Output mode if 'asslice' in kwargs: mode = 'slice' if kwargs['asslice'] else 'indices' if mode is None: mode = 'slice' else: mode = str(mode).lower() if mode[:1] not in 'ias': raise VACUMMError('Wrong mode: %s. Choose one of: slice, indices, array'%mode) if mode[0]=='a': assubmask = True # Format selector specs if lon==':' or lon==slice(None): lon = None if lon==':' or lon==slice(None): lon = None elif isinstance(lon, tuple): if len(lon)==2: lon += 'cc', if len(lon)==3: lon += None, elif isinstance(lon, (int, float)): lon = (lon, lon, 'cob') if lat==':' or lat==slice(None): lat = None elif isinstance(lat, tuple): if len(lat)==2: lat += 'cc', if len(lat)==3: lat += None, elif isinstance(lat, (int, float)): lat = (lat, lat, 'cob') # CDMS variable if cdms2.isVariable(gg) and not isaxis(gg): gg = get_grid(gg) # Unstructured grid if isgrid(gg, 'unstruct'): raise VACUMMError('Not available for unstructured grids') # Strucutured grid if isgrid(gg): # Get axes xx, yy = get_xy(gg) # 2D if len(xx.shape)==2: # As axes if not isaxis(xx) or not isaxis(yy): xx, yy = create_axes2d(xx, yy) # Get indices xii, xjj, mask = coord2slice(xx, lon=lon, mode='indices', mask=mask, assubmask=False) if xii is None: return None, None, mask yii, yjj, mask = coord2slice(yy, lat=lat, mode='indices', mask=mask, assubmask=False) if yii is None: return None, None, mask # Merge imin = max(xii[0], yii[0]) imax = min(xii[1], yii[1]) jmin = max(xjj[0], yjj[0]) jmax = min(xjj[1], yjj[1]) xijk = (imin, imax, 1) # max(xii[2],yii[2]) instead of 1? yijk = (jmin, jmax, 1) sxijk = slice(*xijk) syijk = slice(*yijk) # Submask and slices if mode[0]=='s': xijk = sxijk yijk = syijk if assubmask: mask = mask[syijk, sxijk] if mode[0]=='a': # array of valid indices iijj = N.indices(xx.shape)[:, syijk, sxijk] iijj.shape = 2, -1 return iijj[:, ~mask.ravel()] return xijk, yijk, mask # 1D # - as axes if not isaxis(xx): xx = create_lon(xx) if not isaxis(yy): yy = create_lon(yy) # - indices cmode = mode if mode[0]!='a' else 'slice' xijk = coord2slice(xx, lon=lon, mode=cmode) if xijk is None: if mode[0]=='a': return N.zeros((2, 0)) return None, None, None yijk = coord2slice(yy, lat=lat, mode=cmode) if yijk is None: if mode[0]=='a': return N.zeros((2, 0)) return None, None, None # - mask mask = None # mask = N.ones((len(yy), len(xx))) # mask[slice(*yijk), slice(*xijk)] = False # - return if mode=='a': iijj = N.indices((len(yy), len(xx)))[:, yijk, xijk] iijj.shape = 2, -1 return iijj return xijk, yijk, mask # Single Axis # - selector if not isaxis(gg): raise VACUMMError("If gg is not a grid or an tuple, it must be an axis") axis = int(gg.isLongitude()) sel, osel = (lat, lon)[::1-2*axis] if axis: minval, maxval = -720., 720 modeval = 'mask' else: minval, maxval = -90, 90 modeval = 'clip' # - 2D if len(gg.shape)==2: # Selection with provided coordinates if isinstance(sel, slice): ijk = sel.indices(gg.shape[axis]) oijk = (0, gg.shape[1-axis], 1) mask = N.ones(gg.shape, '?') # mask[(ijk, None)[::1-2*axis]] = False mask[(sel, slice(None))[::1-2*axis]] = False else: xijk, yijk, mask = _coord2ind2d_(gg, sel, mask=mask, minval=minval, maxval=maxval, modeval=modeval) if xijk is None: return None, None, mask ijk, oijk = (yijk, xijk)[::1-2*axis] # Selection along other coordinates with slice if isinstance(osel, slice): i, j, k = osel.indices(gg.shape[1-axis]) oijk = (max(i, oijk[0]), min(j, oijk[1]), 1) bad = N.ones(gg.shape, '?') bad[(slice(None), slice(*oijk))[::1-2*axis]] = False mask |= bad del bad # Submask and slices xijk, yijk = (oijk, ijk)[::1-2*axis] if mode[0]=='s': xijk = slice(*xijk) yijk = slice(*yijk) if assubmask: mask = mask[yijk, xijk] elif assubmask: mask = mask[slice(*yijk), slice(*xijk)] if mode[0]=='a': # array of valid indices iijj = N.indices(xx.shape)[:, yijk, xijk] iijj.shape = 2, -1 return iijj[:, ~mask.ravel()] return xijk, yijk, mask # - 1D if isinstance(sel, slice): if mode[0]=='s': return sel ijk = sel.indices(len(gg)) if mode[0]=='i': return ijk else: ijk = gg.mapIntervalExt(sel) if ijk is None: if mode[0]=='a': return N.array((0,)) return if mode[0]=='s': ijk = slice(*ijk) elif mode[0]=='a': return N.arange(len(gg))[ijk] return ijk
[docs]def mask2ind(mask, extended=False): """Convert a mask to min and max valid indices :Params: - **mask**: N-dimensional array of logical where valid points are at False. - **extended**, optional: Extend indices. Accepts ``True``, ``False``, ``int``, ``(intmin, intmax)``. :Return: array(ndim,2) of (min,max) for each dimension """ inds = N.indices(mask.shape) ndim = inds.shape[0] inds.shape = ndim, -1 mask = mask.ravel() inds = inds[:, ~mask] mm = N.empty((ndim, 2), 'l') if extended==0: extended = False if extended is True: extended = 1 elif extended is False: extended = 0 if isinstance(extended, int): extended = (extended,extended) mm[:, 0] = inds.min(axis=1) - extended[0] mm[:, 1] = inds.max(axis=1) + extended[1] del inds mm[mm<0] = 0 return mm
def _mask4tomask_(mask4): """Convert mask at corners to mask at center of cell using the AND operator""" mask = mask4[:-1,:-1].copy() mask &= mask4[:-1,1:] mask &= mask4[1:,:-1] mask &= mask4[1:,1:] return mask def _coord2ind2d_(cc2d, sel, mask=None, maskonly=False, minval=None, maxval=None, modeval='mask'): """Convert lon/lat 2d coord to index respectively with lon/lat selection :Params: - **cc2d**(ny,nx): Coordinates. - **sel**: Coordinates interval. :Returns: ``None, None, mask`` or ``(imin,imax,istep),(jmin,jmax,jstep),mask(ny,nx)`` """ # No selection if sel is None: nj, ni = cc2d.shape if mask is None: return [(0, nj, 1), (0, ni, 1), N.zeros((nj, ni), '?')] [(jmin, jmax), (imin, imax)] = mask2ind(mask) return [(imin, imax+1, 1), (jmin, jmax+1, 1), mask] # Selection specs if len(sel)==2: sel = sel+('cc', ) b = sel[2] e = b[2:] if isinstance(cc2d, FileAxis2D): cc2d = cc2d.clone() if cdms2.isVariable(cc2d): cc2d = cc2d.asma() # Limits if minval is not None or maxval is not None: if minval is None: minval = -N.inf if maxval is None: maxval = N.inf if modeval=='mask': cc2d = N.ma.masked_outside(cc2d, minval, maxval, False) else: cc2d = N.ma.clip(cc2d, minval, maxval) # According to bounds of cells if 'b' in e: # Bounds become coordinates cc2db = meshcells(cc2d) # Remove 'b' from interval specifications selb = sel[:2]+(b[:2],) # no 'b' # Select min first selmin = (sel[0], N.inf)+(b[:2],) maskmin = _coord2ind2d_(cc2db, selmin, mask=None, maskonly=True) if maskmin is None: return mask if maskonly else (None,None,mask) xymask = _mask4tomask_(maskmin) # del maskmin # Select max selmax = (-N.inf, sel[1])+(b[:2],) maskmax = _coord2ind2d_(cc2db, selmax, mask=None, maskonly=True) if maskmax is None: return mask if maskonly else (None,None,mask) xymask |= _mask4tomask_(maskmax) # del maskmax # Merge if mask is not None: xymask |= mask if maskonly or xymask.all(): return xymask if maskonly else (None,None,xymask) [(jmin, jmax), (imin, imax)] = mask2ind(xymask, 'n' in e) return [(imin, imax+1, 1), (jmin, jmax+1, 1), xymask] # Operators Less = N.ma.less if b[0]=='c' else N.ma.less_equal Greater = N.ma.greater if b[1]=='c' else N.ma.greater_equal # Mask mask = False if mask is None else mask.copy() mask |= Less(cc2d, sel[0]).filled(True) mask |= Greater(cc2d, sel[1]).filled(True) if maskonly or mask.all(): return mask if maskonly else (None, None, mask) # Indices [(jmin, jmax), (imin, imax)] = mask2ind(mask, 'e' in e) return [(imin, imax+1, 1), (jmin, jmax+1, 1), mask]
[docs]def create_axes2d(x=None, y=None, bounds=False, numeric=False, lonid=None, latid=None, iid='ni', jid='nj', xatts=None, yatts=None, xbounds2d=None, ybounds2d=None, nobounds=False): """Create 2D numerical of complete axes :Example: >>> lon2d, lat2d = create_axes2d(x2d, y2d) :Params: - **xaxis**, optional: 1D or 2D X axis - **xaxis**, optional: 1D or 2D Y axis - *xatts*, optional: Attributes for output 2D X axis - *yatts*, optional: Attributes for output 2D Y axis - **xbounds2d**, optional: 2D bounds of input xaxis - **ybounds2d**, optional: 2D bounds of input yaxis - **nobounds**, optional: create (True) or not (False - default) bounds of axis - **numeric**, optional: Only return numerical values instead of complete axes - **bounds**, optional: Return extended axes positioned on bounds (useful for pcolor). Deprecated: use :func:`meshbounds` instead. - **lonid**, optional: Id of longitude axis [defaut='lon']. - **latid**, optional: Id of latitude axis [defaut='lat']. - **iid**, optional: Id of i axis [defaut='iid']. - **jid**, optional: Id of j axis [defaut='jid']. :Return: ``xaxis2d,yaxis2d`` """ if x is None: hasx = 0 elif isinstance(x, TransientAxis2D): hasx = 2 else: if isinstance(x, tuple): x = N.arange(*x) else: x = N.ma.asarray(x) hasx = 1 if y is None: hasy = 0 elif isinstance(y, TransientAxis2D): hasy = 2 else: if isinstance(y, tuple): y = N.arange(*y) else: y = N.ma.asarray(y) hasy = 1 # - ids xaxis1d = yaxis1d = None if hasx: lonid = getattr(x, 'id', lonid) if x[:].ndim==2 and hasattr(x, 'getAxis'): xaxis1d = x.getAxis(-1) yaxis1d = x.getAxis(-2) if hasy: latid = getattr(y, 'id', latid) if y[:].ndim==2 and hasattr(x, 'getAxis') and xaxis1d is None: xaxis1d = y.getAxis(-1) yaxis1d = y.getAxis(-2) # Numeric part if hasx: xn = N.asarray(x[:]) if xn.ndim==1 and y is None: raise VACUMMError("Can't create 2D from a single 1D X axis") if hasy: yn = N.asarray(y[:]) if yn.ndim==1 and x is None: raise VACUMMError("Can't create 2D from a single 1D Y axis") if hasx and hasy: xx, yy = meshgrid(xn, yn) else: xx = xn if hasx else None yy = yn if hasy else None if hasx: del xn if hasy: del yn # if hasx and hasy and xx.shape != yy.shape: # raise VACUMMError('Incomptible shape between 2D X and Y coordinates: %s != %s'%(xx.shape, yy.shape)) # if bounds: # if hasx: xx = meshbounds(xx) # if hasy: yy = meshbounds(yy) if numeric: return xx, yy # 2D axes if hasx == 2: xaxis2d = x elif hasx: xaxis2d = TransientAxis2D(xx) if hasy == 2: yaxis2d = y else: yaxis2d = TransientAxis2D(yy) # 2D bounds if not nobounds: if hasx: if xbounds2d is not None: xaxis2d.setBounds(xbounds2d) elif xaxis2d.getBounds() is None: xaxis2d.setBounds(bounds2d(xx)) if hasy: if ybounds2d is not None: yaxis2d.setBounds(ybounds2d) elif yaxis2d.getBounds() is None: yaxis2d.setBounds(bounds2d(yy)) # 1D axes for axis2d in ([xaxis2d] if hasx else [])+([yaxis2d] if hasy else []): if xaxis1d is not None: axis2d.setAxis(-1, xaxis1d) elif iid is not None: axis2d.getAxis(-1).id = iid if yaxis1d is not None: axis2d.setAxis(-2, yaxis1d) elif jid is not None: axis2d.getAxis(-2).id = jid # Format if hasx: xaxis2d.id = lonid or 'lon' # xaxis2d = create_lon(xaxis2d, id=lonid or 'lon') xaxis2d.getAxis(1).designateLongitude() xaxis2d.getAxis(0).designateLatitude() if hasattr(xaxis2d, 'axis'): del xaxis2d.axis if xatts is not None: xa = get_atts(x) xa.update(xatts) set_atts(xaxis2d, xatts) if hasy: yaxis2d.id = latid or 'lat' # yaxis2d = create_lat(yaxis2d, id=latid or 'lat') yaxis2d.getAxis(1).designateLongitude() yaxis2d.getAxis(0).designateLatitude() if hasattr(yaxis2d, 'axis'): del yaxis2d.axis if yatts is not None: ya = get_atts(y) ya.update(yatts) set_atts(yaxis2d, yatts) # Output if not hasx and not hasy: return if not hasx: return yaxis2d if not hasy: return xaxis2d return xaxis2d, yaxis2d
[docs]def axes2d(*args, **kwargs): """Alias for :func:`create_axes2d`""" return create_axes2d(*args, **kwargs)
[docs]def create_aux_axes(x=None, y=None, bounds=False, numeric=False, lonid=None, latid=None, iid='points', xatts=None, yatts=None, xbounds=None, ybounds=None, nobounds=False): """Create auxilary 1d axes :Example: >>> lon1d, lat1d = create_aux_axes(x1d, y1d) :Params: - **xaxis**, optional: 1D axis or array - **xaxis**, optional: 1D axis or array - *xatts*, optional: Attributes for output auxilary X axis - *yatts*, optional: Attributes for output auxilary Y axis - **lonid**, optional: Id of longitude axis [defaut='lon']. - **latid**, optional: Id of latitude axis [defaut='lat']. - **iid**, optional: Id of i axis [defaut='iid']. - **jid**, optional: Id of j axis [defaut='jid']. - **xbounds2d**, optional: 2D bounds of input xaxis - **ybounds2d**, optional: 2D bounds of input yaxis - **nobounds**, optional: create (True) or not (False - default) bounds of axis :Return: ``xaxis2d,yaxis2d`` """ if x is None: hasx = 0 elif isinstance(x, TransientAuxAxis1D): hasx = 2 else: hasx = 1 if y is None: hasy = 0 elif isinstance(y, TransientAuxAxis1D): hasy = 2 else: hasy = 1 # - ids if hasx: lonid = getattr(x, 'id', lonid) if hasy: latid = getattr(y, 'id', latid) # Numeric part if hasx: xn = N.ma.asarray(x[:]).ravel() if hasy: yn = N.ma.asarray(y[:]).ravel() xx = xn if hasx else None yy = yn if hasy else None if hasx: del xn if hasy: del yn if hasx and hasy: assert xx.size == yy.size, 'Incompatible sizes for auxiliary axes' # Auxilary cdms axes if hasx == 2: xaxis = x elif hasx: xaxis = TransientAuxAxis1D(xx) if hasy == 2: yaxis = y else: yaxis = TransientAuxAxis1D(yy) # bounds if not nobounds: if hasx and xbounds is not None: xaxis.setBounds(xbounds) if hasy and ybounds is not None: yaxis.setBounds(ybounds) # Format if hasx: xaxis.id = lonid or 'lon' xaxis.designateLongitude() if xatts is not None: xa = get_atts(x) xa.update(xatts) set_atts(xaxis, xatts) if hasy: yaxis.id = latid or 'lat' yaxis.designateLatitude() if yatts is not None: ya = get_atts(y) ya.update(yatts) set_atts(yaxis, yatts) if hasx and hasy and xaxis.getAxis(0) is not yaxis.getAxis(0): yaxis.setAxis(0, xaxis.getAxis(0)) if hasx and xaxis.getAxis(0).id.startswith('axis'): xaxis.getAxis(0).id = iid if hasy and yaxis.getAxis(0).id.startswith('axis'): yaxis.getAxis(0).id = iid # Output if not hasx and not hasy: return if not hasx: return yaxis if not hasy: return xaxis return xaxis, yaxis
[docs]def get_axis(gg, iaxis=0, strict=True): """A robust way to get an axis from a variable or a grid This is a generic way to get a 1D and 2D axes: the only way to get longitude and latitude axes of a curvilinear grid are the :meth:`getLongitude` and :meth:`getLatitude` methods (you can't use :meth:`getAxis` with such grid). :Examples: >>> lon = get_axis(grid, -1) >>> lat = get_axis(var, 0) :Params: - **gg**: The variable or a grid (see :func:`get_grid`) - **iaxis**, optional: The axis number [default: 0] :Returns: The requested axis """ grid = get_grid(gg, intercept=False, strict=strict) if iaxis >= 0: iaxis -= len(gg.shape) if iaxis >= -2 and isgrid(grid, ['curv', 'unstruct']): if iaxis == -1: return grid.getLongitude() else: return grid.getLatitude() return gg.getAxis(iaxis)
[docs]def get_grid(gg, intercept=False, strict=False, gtype=None): """Get a cdms grid from gg :Examples: >>> grid = get_grid((lon, lat)) >>> grid = get_grid(var) >>> grid = get_grid(grid) # does nothing :Params: - **g**: A cdms variable with a grid OR cdms grid OR a tuple like (xx,yy) where xx and yy are numpy arrays or cdms axes - **intercept**, optional: Raise an error in case of problem """ # From triangulation if istri(gg) and not strict: return create_ugrid(gg.x, gg.y, tempmask=gg.mask) # From a grid if isgrid(gg): if len(gg.shape)==2: gg.getAxis(-1).designateLongitude() gg.getAxis(-2).designateLatitude() return gg # From a variable if cdms.isVariable(gg): if gg.getGrid() is not None or strict: return gg.getGrid() if len(gg.shape) < 2: return xx = gg.getLongitude() yy = gg.getLatitude() if xx is None: xx = gg.getAxis(-1) if yy is None: yy = gg.getAxis(-2) gg = (xx, yy) elif strict: return # From a tuple of coordinates if isinstance(gg, tuple): xx, yy = gg # Check axis is compatible if isaxis(xx): if getattr(xx, 'axis', 'x').upper()!='X': if not intercept: return raise VACUMMError("Can't make a grid with %s axis as longitude"%xx.axis) xx.designateLongitude() if isaxis(yy): if getattr(yy, 'axis', 'y').upper()!='Y': if not intercept: return raise VACUMMError("Can't make a grid with %s axis as latitude"%yy.axis) yy.designateLatitude() # Create grid return create_grid(xx, yy, gtype=gtype) if intercept: raise VACUMMError('No way to guess the grid')
[docs]def set_grid(var, gg, intercept=False): """Set a grid to a variable :Params: - **var**: A cdms variable with at least 2 dimensions - **gg**: A cdms grid or tuple or axes (see :func:`get_grid`) :Return: ``var`` """ if not cdms2.isVariable(var): var = MV.asarray(var) gg = get_grid(gg, intercept=intercept) if gg is not None: ndim = len(gg.shape) for i in xrange(-ndim, 0): axis = gg.getAxis(i) if ndim==2: setattr(axis, 'axis', ['Y', 'X'][i]) var.setAxis(i, axis) var.setGrid(gg) return var
[docs]def get_grid_axes(gg, raw=False): """Get the (lat,lon) axes from a grid :Params: - **gg**: A cdms grid or a tuple or axes (see :func:`get_grid`) - **raw**, optional: If True, return raw axes which are different from real axes with curvilinear grids :Return: ``(lon, lat)`` """ gg = get_grid(gg) if raw: return tuple(gg.getAxisList()) return gg.getLatitude(), gg.getLongitude()
[docs]def create_curv_grid(xaxis, yaxis, xatts=None, yatts=None, id=None, mask=None, **kwargs): """Create a curvilinear 2D grid from 1D or 2D axes :Params: - **xaxis**: Numeric or formatted X axis - **yaxis**: Numeric or formatted Y axis - **xatts**, optional: Attributes of X axis - **yatts**, optional: Attributes of Y axis - **id**, optional: Id of the grid - Other keywords are passed to :func:`create_axes2d` :Example: >>> curvgrid = curv_grid(lon2d, lat2d) :See also: :func:`create_grid2d` """ xaxis2d,yaxis2d = create_axes2d(xaxis, yaxis, xatts=xatts, yatts=yatts, numeric=False, **kwargs) if id is None: id = 'grid2d' return TransientCurveGrid(yaxis2d, xaxis2d, id=id, tempmask=mask)
[docs]def curv_grid(*args, **kwargs): """Alias for :func:`create_curv_grid`""" return create_curv_grid(*args, **kwargs)
[docs]def create_grid2d(*args, **kwargs): """Alias for :func:`curv_grid`""" return create_curv_grid(*args, **kwargs)
[docs]def grid2d(*args, **kwargs): """Alias for :func:`curv_grid`""" return create_curv_grids(*args, **kwargs)
[docs]def create_var2d(var, xaxis=None, yaxis=None, xatts=None, yatts=None, gid=None, copy=1, lonid=None, latid=None, iid=None, jid=None, **kwargs): """Create 2D cdms variable with on a proper 2d curvilinear grid You should generally call this routine when you want to attach 2D axes to a variable. This may happen with netcdf that doesn't follow CF conventions, as in the example bellow. :Example: >>> f = cdms2.open('myfile.nc') >>> lon2d = f('X') >>> lat2d = f('Y') >>> sst = f('sst') >>> f.close() >>> sstc = create_var2d(sst, lon2d, lat2d) :Params: - **var**: Numeric or formatted X axis - **xaxis**, optional: Numeric or formatted X axis. Mandatory if var is not a cdms variable! - **yaxis**, optional: Numeric or formatted Y axis. Mandatory if var is not a cdms variable! - **atts**, optional: Attributes of the variable . - **xatts**, optional: Attributes of X axis. - **yatts**, optional: Attributes of Y axis. - **gid**, optional: Id of the grid. - All other keywords are passed to :func:`cdms.createVariable` :Return: A :mod:`MV2` array on a curvilinear grid. :See also: :func:`curv_grid` :func:`get_axis` """ assert len(var.shape) >= 2, 'Input variable must have a at least 2d shape' if cdms.isVariable(var): if yaxis is None: yaxis = get_axis(var,0) if xaxis is None: xaxis = get_axis(var,1) var = MV2.array(var, copy=copy, **kwargs) mygrid2d = curv_grid(xaxis, yaxis, xatts=xatts, yatts=yatts, id=gid, iid=iid, jid=jid, lonid=lonid)#,mask=MV.getmask(var)) set_grid(var, mygrid2d) return var
[docs]def var2d(*args, **kwargs): """Alias for :func:`create_var2d`""" return create_var2d(*args, **kwargs)
[docs]def num2axes2d(*args, **kwargs): """Alias for :func:`axes2d`""" return axes2d(*args, **kwargs)
[docs]def create_ugrid(xaxis, yaxis, **kwargs): """Create a unstructured grid from axes""" xykw = kwfilter(kwargs, 'x', keep=True, short=True) xykw.update(kwfilter(kwargs, 'y', keep=True, short=True)) assert xaxis is not None and yaxis is not None xaxis, yaxis = create_aux_axes(xaxis, yaxis, **xykw) return TransientGenericGrid(yaxis, xaxis, **kwargs)
[docs]def create_grid(lon, lat, gtype=None, mask=None, lonatts={}, latatts={}, **kwargs): """Create a cdms rectangular or curvilinear grid from axes :Params: - **lon**: Array or axis of longitudes or any argument passed to :func:`~vacumm.misc.axes.create_lon`. - **lat**: Array or axis of latitudes or any argument passed to :func:`~vacumm.misc.axes.create_lat`. - **mask**, optional: Grid mask. - **(lon/lat)atts**, optional: Attributes to set for axes. - **gtype**, optional, string: grid type as one of None, 'rect', 'curv', 'unstruct' :Return: A :mod:`cdms2` grid object. :Example: >>> create_grid([1.,3.,5.,7], numpy.arange(45., 60., .5)) >>> create_grid((.1, 8., 1.), (45., 60., .5)) :See also: :func:`~vacumm.misc.axes.create_lon` :func:`~vacumm.misc.axes.create_lat` :func:`get_grid` :func:`set_grid` """ if kwargs.get('curv', None): gtype = 'curv' assert gtype in [None, 'rect', 'curv', 'unstruct'] # Arrays if isinstance(lon, list): lon = N.asarray(lon) if isinstance(lat, list): lat = N.asarray(lat) # Curvilinear 2D axes? if (gtype is None and not isinstance(lon, tuple) and not isinstance(lat, tuple) and (lon[:].ndim != 1 or lat[:].ndim != 1)): gtype = 'curv' if gtype != 'curv': # 1D axes: rect or unstruct # Guess if auxilary if gtype is None: if (isinstance(lon, tuple) or isinstance(lat, tuple) or lon[:].size != lat[:].size): gtype = 'rect' elif (cdms2.isVariable(lon) and cdms2.isVariable(lat) and lon.getAxis(0).id == lat.getAxis(0).id): gtype = 'rect' else: dxp = N.diff(lon[:])>0 if dxp.any() and ~dxp.any(): gtype = 'unstruct' if not gtype: dyp = N.diff(lat[:])>0 if dyp.any() and ~dyp.any(): gtype = 'unstruct' if gtype is None: vcwarn("Can't guess the type of the grid to create." " Falling back to rect.") # Unstructured if gtype == 'unstruct': return create_ugrid(lon, lat, xatts=lonatts, yatts=latatts, **kwargs) # Get axes if not islon(lon): lon = create_lon(lon, **lonatts) if not islat(lat): lat = create_lat(lat, **latatts) # Create rectangular grid return cdms2.createRectGrid(lat, lon, mask=mask, **kwargs) else: # Curvilinear return create_curv_grid(lon, lat, xatts=lonatts, yatts=latatts, mask=mask, **kwargs)
def clone_grid(grid): """Clone a grid""" if grid is None: return id = grid.id lon = grid.getLongitude().clone() lat = grid.getLatitude().clone() if cdms2.isVariable(lon): iaxis = lon.getAxis(1).clone() jaxis = lon.getAxis(0).clone() lon.setAxisList([jaxis, iaxis]) lat.setAxisList([jaxis, iaxis]) grid = create_grid(lon, lat, gtype=get_grid_type(grid)) grid.id = id return grid
[docs]def get_unstruct_indices(grid, lon=None, lat=None): """Get indices of valid unstructured cells according to a geographic selection""" grid = get_grid(grid, intercept=True, gtype='unstruct', strict=False) lons = grid.getLongitude() lats = grid.getLatitude() valid = N.ones(len(lons), '?') if isinstance(lon, slice): vv = N.zeros(lon.shape, '?') vv[lon] = True valid &= vv del vv elif lon is not None: b = lon[2] if len(lon) >= 2 else 'cc' left = N.ma.greater_equal if b[0] == 'c' else N.ma.greater right = N.ma.less_equal if b[1] == 'c' else N.ma.less valid &= left(lons, lon[0]) valid &= right(lons, lon[1]) elif isinstance(lat, slice): vv = N.zeros(lat.shape, '?') vv[lat] = True valid &= vv del vv elif lat is not None: b = lat[2] if len(lon) >= 2 else 'cc' left = N.ma.greater_equal if b[0] == 'c' else N.ma.greater right = N.ma.less_equal if b[1] == 'c' else N.ma.less valid &= left(lats, lat[0]) valid &= right(lats, lat[1]) return N.nonzero(valid)[0]
#def get_gridded_selector(grid, lon=None, lat=None, mode='selector', # getmask=False): # """Class to help XY selections on grids""" # assert mode in ('selector', 'dict'), ('mode must be one of dict or ' # 'selector') # # # Grid # grid = get_grid(grid, intercept=False, strict=False) # # # Init selector # sel = {} # # # Selections # if grid is not None: # # assert isgrid(grid, ['rect', 'curv']) # islice, jslice, mask = coord2slice(grid, lon=lon, lat=lat) # sel[grid.getAxis(1).id] = islice # sel[grid.getAxis(0).id] = jslice # if mode == 'selector' and getmask: # sel['mask'] = mask # # # Output # if mode == 'dict': # return sel # return cdms2.selectors.Selector(**sel)
[docs]class GriddedSelector(object): """A :class:`cdms2.selectors.Selector` with grid preprocessing""" def __init__(self, grid, lon=None, lat=None, update_kwargs=False, apply_mask=True): # Grid self.grid = grid = get_grid(grid, intercept=False, strict=False) self.grid_type = grid_type = get_grid_type(grid) # Selection specs if grid_type in ['rect', 'curv']: self.islice, self.jslice, self.mask = coord2slice(grid, lon=lon, lat=lat) elif grid_type == 'unstruct': self.indices = get_unstruct_indices(grid, lon=lon, lat=lat) self.update_kwargs = update_kwargs self.apply_mask = apply_mask
[docs] def select(self, var, *args, **kwargs): """Apply selection""" # No grid if not self.grid_type: return var(*args, **kwargs) # Unstructured if self.grid_type == 'unstruct': var = MV2.take(var, self.indices, axis=-1) lons = self.grid.getLongitude() lats = self.grid.getLatitude() grid = create_grid(lons.asma()[self.indices], lats.asma()[self.indices], gtype='unstruct', lonatts=lons.attributes, latatts=lats.attributes) var = var(*args, **kwargs) set_grid(var, grid) return var # Rectangular and curvilinear set_grid(var, self.grid) sel = {var.getAxis(-1).id: self.islice, var.getAxis(-2).id: self.jslice} if self.update_kwargs: kwargs.update(sel) else: sel.update(kwargs) kwargs = sel var = var(*args, **kwargs) if self.apply_mask and self.mask is not None and self.mask.any(): if self.mask.shape == var.shape: mask = self.mask else: mask = N.resize(self.mask, var.shape) var[:] = MV2.masked_where(mask, var, copy=0) del mask return var
__call__ = select
[docs]def gridsel(gg, lon=None, lat=None): """Extract a subregion from an axis or a grid Lat and lon generic selection are used for spatial selections. .. note:: Properly works on curved grids thanks to :func:`coord2slice`. :Params: - **gg**: cdms2 grid or tuple of cdms2 axes. - **lon/lat**, optional: A slice, or a tuple of coordinates, or ':'. :Return: An extraction in the same format as input format :Examples: >>> gg = gridsel(gg, lon=(-6,4), lat=slice(0,34)) >>> lon,lat = gridsel((lon,lat), lat=':', lon=(0,2,'ccb')) """ # Format selector specs if lon==':' or lon==slice(None): lon = None elif isinstance(lon, tuple): if len(lon)==2: lon += 'cc', if len(lon)==3: lon += None, if lat==':' or lat==slice(None): lat = None elif isinstance(lat, tuple): if len(lat)==2: lat += 'cc', if len(lat)==3: lat += None, if lon is None and lat is None: gg # Format input to always treat a grid astuple = isinstance(gg, tuple) gg = get_grid(gg, strict=False, intercept=True) # Unstructured grids if isgrid(gg, 'unstruct'): lons = gg.getLongitude() lats = gg.getLatitude() ii = get_unstruct_indices(gg, lon=lon, lat=lat) ggo = create_grid(lons.asma()[ii], lats.asma()[ii], gtype='unstruct', lonatts=lons.attributes, latatts=lats.attributes) ggo.id = gg.id return ggo # Selection if isgrid(gg, 'curv'): # Curvilinear # Convert to slices islice, jslice, mask = coord2slice(gg, lon=lon, lat=lat) # Select islice = islice.indices(gg.shape[1]) jslice = jslice.indices(gg.shape[0]) gg = gg.subSlice(jslice, islice) if mask.any(): gg.setMask(mask) else: # Rectangular axlon = gg.getLongitude() axlat = gg.getLatitude() # Selection using slices if isinstance(lon, slice): ii = lon.indices(len(axlon)) axlon = axlon.subaxis(*ii) gg = create_grid(axlon, axlat) lon = None if isinstance(lat, slice): ii = lat.indices(len(axlat)) axlat = axlat.subaxis(*ii) gg = create_grid(axlon, axlat) lat = None # Selection using coordinates if lon is not None or lat is not None: if lat is None: lat = (-90, 90) if lon is None: lon = (-180, 180) gg = gg.subGridRegion(lat, lon) # Out if astuple: return gg.getLongitude(), gg.getLatitude() return gg
select_region = gridsel
[docs]def varsel(var, lon=None, lat=None, grid=None, cache=None, **kwargs): """Extract a subregion of a variable Lat and lon generic selection are used for spatial selections. If `var` has no grid or a rectangular grid, it simply equivalent to:: var(lon=lon, lat=lat, **kwargs) .. note:: Properly works on curved grids thanks to :func:`coord2slice`. :Params: - **var**: MV2 array. - **lon/lat**: Coordinates intervals or slices. - Extra keywords are passed to the variable. """ # Gridded selector with caching if isinstance(cache, dict) and 'gridded_selector' in cache: gs = cache['gridded_selector'] grid = gs.grid else: if grid is None: grid = var.getGrid() if grid is None: return var(**kwargs) gs = GriddedSelector(grid, lon=lon, lat=lat) if isinstance(cache, dict): cache['gridded_selector'] = gs # Apply it return gs(var, **kwargs)
# # Rectangular grid # if isgrid(grid, 'rect') and not ext_grid: # return var(lon=lon, lat=lat, **kwargs) # # # Unstructured grid # if isgrid(grid, 'unstruct'): # indices = get_unstruct_indices(grid, lon=lon, lat=lat) # lons = grid.getLongitude()[indices] # lats = grid.getLatitude()[indices] # var = MV2.take(var, indices, axis=-1) # set_grid(var, create_grid(lons, lats)) # FIXME: grid attributes # return var(**kwargs) # # # Curved grid # kw = get_gridded_selector(grid, lon=lon, lat=lat, getmask=True, # mode='dict') # kw.update(kwargs) # return var(**kw)
[docs]def axis1d_from_bounds(axis1d,atts=None,numeric=False): """Create a numeric of formatted 1D axis from bounds - **axis1d**: Input 1d axis from which we get bounds - *numeric*: Return a simple numeric array - *atts*: Attributes for outputs axis :Example: >>> xx = axis1d_from_bounds(xxb) """ # Get bounds mybounds1d = axis1d.getBounds() if mybounds1d is None: mybounds1d = bounds1d(axis1d) axis1d.setBounds(mybounds1d) # Numerical values numax = N.zeros(len(axis1d)+1,'d') numax[0] = mybounds1d[0,0] numax[-1] = mybounds1d[-1,1] numax[1:-1] = 0.5*(mybounds1d[1:,0]+mybounds1d[:-1,1]) if numeric: return numax # Formatted axis ax = cdms.createAxis(numax,savespace=1) if atts is not None: for att,val in atts.items(): setattr(ax,att,val) return ax
[docs]def get_xy(gg, proj=False, mesh=None, num=False, checklims=True, **kwargs): """Get axes from gg :Params: - **gg**: (xx,yy) or grid (1D or 2D), cdms variable (see :func:`get_grid`), or a dict of lon/lat/x/y, or a (2,npts) numpy array. - **proj**, optional: If True or basemap instance, convert to meters. If None, check if lon and lat axes to force conversion. [default: False] - **mesh**, optional: Get axes as 2D arrays. - **num**, optional: Get axes as numpy arrays. - **checklims**, optional: For 2D axes only, mask longitudes outside (-720,720), and clip latitude outside (-90,90). :Example: >>> lon, lat = get_xy((xaxis,yaxis)) >>> lon, lat = get_xy(grid) >>> x, y = get_xy(dict(lon=(-10,0), lat=(42,43,'cc')), proj=True) """ # Get axes if hasattr(gg, 'xy') and callable(gg.xy): xx, yy = gg.xy() elif hasattr(gg, 'x') and hasattr(gg, 'y'): xx = gg.x yy = gg.y if callable(xx): xx = xx() yy = yy() elif isinstance(gg, (tuple, list)): if len(gg)==4: xmin, ymin, xmax, ymax = gg xx = N.array([xmin, xmax]) yy = N.array([ymin, ymax]) else: xx, yy = gg if isinstance(xx, tuple): # (xmin,xmax,'ccb') xx = N.array(xx[:2]) yy = N.array(yy[:2]) elif isinstance(gg, N.ndarray) and not cdms2.isVariable(gg) and \ gg.ndim==2 and gg.shape[0]==2: xx, yy = gg elif isinstance(gg, dict): xy = [] for keys in [['x','lon', 'longitude'], ['y','lat', 'latitude']]: for key in keys: if key in gg: pp = gg[key] if isinstance(pp, tuple): pp = pp[:2] xy.append(N.asarray(pp)) break else: raise KeyError('Dictionay has no %s key'%' or '.join(keys)) xx,yy = xy else: if cdms.isVariable(gg) and gg.getGrid() is not None: gg = gg.getGrid() try: xx = gg.getLongitude() if xx is None: xx = gg.getAxis(-1) yy = gg.getLatitude() if yy is None: yy = gg.getAxis(-2) except: gg = cdms.createVariable(gg) assert gg.ndim > 1, 'Input must be at least a 2D array in this case' xx = gg.getAxis(-1) yy = gg.getAxis(-2) # Pure numeric? xxn = _cdat2num_(xx) yyn = _cdat2num_(yy) if num: xx = xxn yy = yyn # Check limits of 2D axes if checklims: if xxn[:].ndim==2: if isaxis(xx): xx = xx.clone() xx[:] = N.ma.masked_outside(xx[:], -720., 720., copy=False) else: xx = N.ma.masked_outside(xx[:], -720., 720., copy=False) if yyn[:].ndim==2: if isaxis(yy): yy = yy.clone() yy[:] = N.ma.clip(yy[:], -720., 720.) else: yy = N.ma.clip(yy[:], -90., 90.) # Convert to meters? proj = kwargs.pop('m', proj) if proj is None:# and islon(xx) and islat(yy): proj = True if proj is not False: xx, yy = deg2xy(xx, yy, proj=proj, mesh=mesh, **kwargs) # Mesh ? if mesh is True:# and (xx[:].ndim == 1 or yy[:].ndim == 1): return meshgrid(xx[:], yy[:]) elif mesh is False: if xxn[:].ndim == 2: xx = xxn[0] if yyn[:].ndim == 2: yy = xxn[:, 0] return xx, yy
def _cdat2num_(xy): """Convert CDAT axis or variable to numeric (masked) form""" if hasattr(xy, 'asma'): xy = xy.asma() elif hasattr(xy, 'getValue'): xy = xy.getValue() if not isinstance(xy, N.ndarray): xy = N.asarray(xy) return xy
[docs]def deg2xy(lon, lat, proj=None, inverse=False, mesh=None, **kwargs): """Convert from degrees to map (m) coordinates using map projection, and reverse :Params: - **lon**: Longitudes in degrees - **lat**: Latitude in degrees - **proj**, optional: Proj object for projection. If False, returns (lon,lat). If None, a new instance using :func:`~vacumm.misc.grid.basemap.get_proj` is created, where proj is passed as a parameter. - **inverse**, optional: Inverse transform (from meters to degrees) :Example: >>> x, y = deg2xy(lon, lat) >>> x2d, y2d = deg2xy(lon, lat, mesh=True) """ # Check shapes lon, lat = check_xy_shape(lon, lat, mesh=mesh) # Nothing to do proj = kwargs.pop('m', proj) if proj is False: return lon, lat # Check if we are sure to not have degrees xmin = lon.min() ymin = lat.min() xmax = lon.max() ymax = lat.max() if not inverse and xmin >= -0.1 and ymin >= -0.1 and (xmax > 720. or ymax > 90.): return lon, lat # Need a projection instance if not callable(proj): proj = get_proj((lon,lat), proj=proj) # Transform return proj(lon, lat, inverse=inverse)
def _dist2x2d_(xx, yy, mode): kw = dict(pairwise=True, mode=mode) return (get_distances(xx[:, :-1], yy[:, :-1], xx[:, 1:], yy[:, 1:], **kw), get_distances(xx[:-1], yy[:-1], xx[1:], yy[1:], **kw)) def _dist1x1d_(xxyy, xy, axis, mode): kw = dict(pairwise=True, mode=mode) dslices = get_axis_slices(xxyy, axis) if axis==-1: return
[docs]def resol(axy, mode='median', axis=None, meters=False, cache=True, lat=None, checklims=True, **kwargs): """Get the resolution of an axis or a grid :Params: - **axy**: It can be either - a 1D axis or array - a grid of 1D or 2D axes or tuple of (lon,lat) - **mode**, optional: - ``"raw"``: Get the local resolution between "grid points". - ``"averaged"``: Return an averaged resolution (do not use if the grid highly anisotropic!!). - ``"local"``: The local resolution is averaged and extrapolated to grid points. - A string: An :mod:`numpy` attribute is used to compute the resolution. For instance ``"median"`` mode implies the use of :func:`numpy.median`. - A callable function: Directly used to compute the resolution. - **meters**, optional: Get the resolution in meters instead of degrees. - **axis**, optional: Direction on which to compute resolution if a single 2D axis is passed. - **lat**, optional:: Latitude to use for projection of 1D zonal axis. .. warning:: If not provided but needed, it defaults to 45. and a warning is emitted. .. warning:: If you work on a pair of 2D axes, resolution is computed along grid axes, and NOT along X and Y. In this case, X and Y must have consistent units, and resolution along X and Y are defined by: .. math:: dx_{i,j} = \sqrt{(x_{i+1,j}-x_{i,j})^2+ (y_{i+1,j}-y_{i,j})^2} dy_{i,j} = \sqrt{(x_{i,j+1}-x_{i,j})^2+ (y_{i,j+1}-y_{i,j})^2} :Examples: >>> dx = resol(lon) >>> dx,dy = resol(grid) >>> dx2d = resol(x2d, mode='loc') >>> dx2d, dy2d = resol((x1d, y2d)) """ # Get what to inspect if cdms2.isVariable(axy): if not isaxis(axy): axy = axy.getGrid() assert axy is not None, 'You must pass a variable with a grid' else: axy = axy.asma() # Meters? proj = kwargs.get('proj', False) if proj: meters = True if meters is None: meters = False # Check chache (for cdms grid and axes) if kwargs.get('averaged', False): # compat mode = 'averaged' if cache and not mode.startswith('loc'): suf = 'm' if meters else '' pres = 'res'+'m' if meters else '' if hasattr(axy, '_'+pres): return getattr(axy, '_'+pres) if hasattr(axy, '_x'+pres) and hasattr(axy, '_y'+pres): return getattr(axy, '_x'+pres), getattr(axy, '_y'+pres) if int(cache)>1: return # Numerical values single = None if isinstance(axy, tuple) and len(axy)==1: axy = axy[:] single = False if not isgrid(axy) and not isinstance(axy, tuple): # single axis if single is None: single = True atype = 'x' if islon(axy) else 'y' if isaxis(axy): axy = axy.getValue() xy = axy, else: # grid or pair of axes > convert to 2D arrays single = False xy = get_xy(axy, num=True, proj=False, mesh=True, checklims=checklims) # Local distances res = () distmode = 'haversine' if meters else 'simple' kwdist = dict(mode=distmode) warnlatmsg = ("Your axis resolution along X is computed with a default " "latitude of 45. You should also provide a valid Y axis or " "at least set this latitude properly with the lat parameter.") if len(xy)==2 and (xy[0][:].ndim == 2 or xy[1][:].ndim == 2) : # 2x2D xy = meshgrid(*xy) res = _dist2x2d_(*xy, **kwdist) else: # Single 1D or 2D kwdist.update(pairwise=True) if N.ndim(xy[0][:])==2: # 2D if axis is None: if atype=='y': axis = -2 else: axis = -1 if axis<0: axis += xy[0].ndim ds = get_axis_slices(2, axis) if atype=='x': if lat is None: lat = 45. vcwarn(warnlatmsg) res = get_distances(axy[ds['firsts']], lat, axy[ds['lasts']], lat, **kwdist), else: res = get_distances(0., axy[ds['firsts']], 0., axy[ds['lasts']], **kwdist), else: if atype=='x': if lat is None: lat = 45. vcwarn(warnlatmsg) res = get_distances(axy[:-1], lat, axy[1:], lat, **kwdist), else: res = get_distances(0., axy[:-1], 0., axy[1:], **kwdist), # Averages if mode.startswith('loc'): if xy[0].ndim==2: # 2D eres = () if len(xy)==1: kk = [axis] else: kk = [1, 0] for ik, k in enumerate(kk): # Init output eshape = list(res[ik].shape) eshape[k] += 1 eres += N.ma.resize(res[ik], eshape), # Slices specs sl = get_axis_slices(res[ik].shape, k) sle = get_axis_slices(eshape, k) # Core eres[ik][sle['mid']] = .5*(res[ik][sl['firsts']]+res[ik][sl['lasts']]) # Limits eres[ik][sle['first']] = 1.5*res[ik][sl['first']]-0.5*res[ik][sl['firstp1']] eres[ik][sle['last']] = 1.5*res[ik][sl['last']]-0.5*res[ik][sl['lastm1']] res = eres else: # 1D eres = tuple([N.ma.resize(r, (r.shape[0]+1, )) for r in res]) for ik in xrange(len(xy)): eres[ik][1:-1] = .5*(res[ik][:-1]+res[ik][1:]) eres[ik][0] = 1.5*res[ik][0]-0.5*res[ik][1] eres[ik][-1] = 1.5*res[ik][-1]-0.5*res[ik][-2] res = eres elif mode != 'raw': if not isinstance(mode, tuple): mode = mode, if len(mode)<len(res): mode *= 2 eres = [] for ik, dcell in enumerate(res): m = mode[ik] if m.startswith('ave'): m = 'mean' if isinstance(m, str): func = getattr(N, m) else: func = m eres.append(func(dcell)) res = tuple(eres) # Caching if cache and (isinstance(mode, tuple) or not mode.startswith('loc')): if isgrid(axy): setattr(axy, '_x'+pres, res[0]) setattr(axy, '_y'+pres, res[1]) setattr(axy, '_mres', mode) elif isaxis(axy): setattr(axy, '_'+pres, res) setattr(axy, '_mres', mode) # print ' res', repr(res) if single: return res[0] return res
[docs]def check_xy_shape(xx, yy, mesh=None): """Check that xx and yy have the same shape - **xx**: X positions (in meters or degrees). - **yy**: Y positions (in meters or degrees). - *mesh*: Return a 2D axes if True, or if None and xx and yy have not the same shape [default: None] :Example: >>> xx2d, yy2d = check_xy_shape(xx, yy, mesh=True) """ # Numpy arrays xx = N.asarray(xx[:]) yy = N.asarray(yy[:]) # Check if we want 2D axes if xx.shape != yy.shape: mesh = True if mesh is None: mesh = xx.shape == yy.shape # Go if not mesh: return xx, yy if xx.ndim==2 and yy.ndim==2 and xx.shape==yy.shape: return xx, yy if xx.ndim !=1 or yy.ndim != 1: raise ValueError, 'xx and yy must be 1D for meshgrid transform.' return N.meshgrid(xx, yy)
[docs]def t2uvgrids(gg, getu=True, getv=True, mask=None): """Convert a (C) grid at T-points to a grid at U- and/or V- points :Params: - **gg**: A (x,y) or a cdms grid or a cdms variable with a grid (see :func:`get_grid`) - *getu*: Get the grid at U-points [default: True] - *getv*: Get the grid at V-points [default: True] - *mask*: If False, do not try to guess masks ; if None, try to get them from original grid by conversion to U and V points with t2uvmask() [default: None] :Return: ``ugrid,vgrid`` OR ``ugrid`` OR ``vgrid`` depending on getu and getv :Example: >>> gridu, gridv = t2uvgrids(gridt) """ if not getu and not getv: return # Get rights axes if cdms.isVariable(gg): gg = gg.getGrid() if isgrid(gg) and mask is not False: mask = gg.getMask() if mask is False: mask is None xx, yy = get_xy(gg) if not islon(xx): xx = create_lon(xx) if not islat(yy): yy = create_lon(yy) # U-points if getu: xu = xx.clone() xu[:-1] = .5*(xx[:-1]+xx[1:]) xu[-1] = xx[-1]+.5*(xx[-1]-xx[-2]) xu.long_name += ' at U-points' yu = yy.clone() gu = cdms.createRectGrid(yu, xu) if mask is not None: gu.setMask(t2uvmasks(umask, getv=False)) if not getv: return gu # V-points yv = yy.clone() yv[:-1] = .5*(yy[:-1]+yy[1:]) yv[-1] = yy[-1]+.5*(yy[-1]-yy[-2]) yv.long_name += ' at V-points' xv = xx.clone() gv = cdms.createRectGrid(yv, xv) if mask is not None: gv.setMask(t2uvmasks(vmask, getu=False)) if not getu: return gv return gu, gv
[docs]def rotate_grid(ggi, angle, pivot='center', **kwargs): """Rotate a grid :Params: - **ggi**: Cdms grid ou (lon,lat) or variable (see :func:`get_grid`). - **angle**: Angle in degrees. - **pivot**, optional: it can be either - A tuple of (lon, lat) - A string that specify the vertical and horizontal position. Use the following keys : ``top``, ``bottom``, ``left``, ``right``, ``center``. - Other keywords are passed to :func:`~vacumm.misc.grid.curv_grid` :Returns: A curvilinear cdms grid. :Example: >>> mygrid = rotate_grid((lon,lat), 60., 'top right') >>> mygrid2 = rotate_grid(mygrid, 60., (-5.,45)) """ # Get 2D axes xxi, yyi = meshgrid(*get_xy(ggi)) # Find pivot if isinstance(pivot, tuple): xpivot, ypivot = pivot else: ny, nx = xxi.shape pivot = pivot.lower() if pivot == 'center':pivot = 'center center' sxpivot, sypivot = pivot.split() ipivot = [0, nx/2, -1][['left', 'center', 'right'].index(sxpivot)] jpivot = [0, ny/2, -1][['bottom', 'center', 'top'].index(sypivot)] xpivot = xxi[jpivot, ipivot] ypivot = yyi[jpivot, ipivot] # Rotate angle = angle*N.pi/180. xxo = xpivot + (xxi-xpivot)*N.cos(angle) - (yyi-ypivot)*N.sin(angle) yyo = ypivot + (xxi-xpivot)*N.sin(angle) + (yyi-ypivot)*N.cos(angle) return create_curv_grid(xxo, yyo, **kwargs)
[docs]def monotonic(xx, xref=None): """Make monotonic an array of longitudes :Example: >>> xxm = monotonic(xx, xref=120.) >>> print (N.diff(xxm)<0).any() False """ # Longitude reference modulo = xx.modulo if hasattr(xx, 'modulo') else 360. if xref is not None: xx[:] = (xx[:]-xref)%modulo+xref # Monotonic modulo steps = N.diff(xx, axis=-1) bigsteps = N.abs(steps)>modulo/2. if not N.any(bigsteps): return xx fixsteps = N.where(bigsteps, -modulo*N.sign(steps), 0.) fixsteps.cumsum(axis=-1, out=fixsteps) xx[..., 1:] += fixsteps return xx
[docs]def xextend(vari, n, mode=None): """Extend a variable along x axis .. note:: x is the last axis and must be 1D .. todo:: Make possible to use 2D axes with :func:`xextend` :Parameters: - **vari**: :mod:`cdms2` variable - **n**: size of the extention - if an integer: ``nleft = nright = n`` - else: nleft, nright = n - **mode**, optional: mode of extension - ``None``: choose ``"cyclic"`` is x axis has a "modulo" attribute - ``"cyclic"``: assume x axis is cyclic - ``"constant"``: extrapolate with first or last values - else, extend with masked values and linearly extrapolate positions :Example: >>> varo = xextend(vari, (5, 7), mode='cyclic') """ # Get extension sizes and mode xi = vari.getAxis(-1) if mode is None and hasattr(xi, 'modulo'): mode = 'cyclic' if not isinstance(n, int): nleft, nright = n else: nleft = nright = n # Intialize nxi = vari.shape[-1] nxo = nleft+nxi+nright varo = MV2.zeros(vari.shape[:-1]+(nxo, ), dtype=vari.dtype) varo[:] = MV2.masked varo[..., nleft:nleft+nxi] = vari cp_atts(vari, varo) axes = vari.getAxisList() # Extend if mode == 'cyclic': if not hasattr(xi, 'modulo'): modulo = 360. else: modulo = xi.modulo varo[..., :nleft] = vari[..., nxi-nleft:] varo[..., nxo-nright:] = vari[..., :nright] xleft = xi[nxi-nleft:]-modulo xright = xi[:nright]+modulo else: dx0 = xi[1]-xi[0] dx1 = xi[-1]-xi[-2] xleft = xi[0]-N.arange(1, nleft+1)*dx0 xright = xi[-1]+N.arange(1, nright+1)*dx1 if mode == 'constant': for ileft in xrange(nleft): varo[..., ileft] = vari[..., 0] for iright in xrange(nright): varo[..., nxo-iright-1] = vari[..., -1] # Insert axes xo = cdms2.createAxis(N.concatenate((xleft, xi[:], xright)).astype(xi[:].dtype)) cp_atts(xi, xo) axes[-1] = xo varo.setAxisList(axes) return varo
[docs]def xshift(vari, n, mode=None, monot=True): """Shift a cyclic array along x axis .. note:: x is the last axis and must be 1D .. todo:: Make possible to use 2D axes with :func:`xshift` :Params: - **vari**: :mod:`cdms2` variable - **n**: size of the shift Positive means east bound is moved toward east. If ``mode is None``: - if an integer, use it as grid points - else (a float), use at as degrees. :Example: >>> varo = xshift(vari, 50) """ # Get shift value xi = vari.getAxis(-1).getValue() # if mode=='left': # # if mode is None: # if not isinstance(n, int): # n = float(n) # Intialize varo = vari.clone() xo = xi.copy() if n==0: return varo if n>0: varo[..., :-n] = vari[..., n:] varo[..., -n:] = vari[..., :n] xo[:-n] = xi[n:] xo[:-n] = xi[n:] else: varo[..., n:] = vari[..., :-n] varo[..., :n] = vari[..., -n:] xo[n:] = xi[:-n] xo[n:] = xi[:-n] if xi[-1]>xi[0]: xi = monotonic(xi) varo.getAxis(-1).assignValue(xo) return varo
[docs]def curv2rect(gg, mode="warn", tol=1.e-2, f=None): """Convert a curvilinear grid to a rectangular grid .. warning:: This not an interpolation. Longitudes and latitudes are converted from 2D arrays to 1D arrays using axis averages. :Params: - **gg**: Grid, tuple of axes or MV2 variable. - **mode**, optional: what ot do when it does not seems to be rectangular - ``"warn"``: simply show a warning, - ``"raise"``: raise a :class:`VACUMMError`, - ``"none"`` or ``False``: don't do it. - else just convert it at your own risks. - **tol**, optional: Tolerance (passed to :func:`isrect`). - **f**, optional: File object or name (passed to :func:`isrect`). :Example: >>> rgrid = curv2rect(cgrid) >>> rgrid = curv2rect((lon2d,lat2d)) :Params: - **gg**: A variable or a curvilinear grid (see :func:`get_grid`) """ # MV2 variable if cdms2.isVariable(gg): grid = gg.getGrid() rgrid = curv2rect(grid, mode=mode, tol=tol, f=f) if rgrid is not None and grid is not rgrid: set_grid(gg, rgrid) return gg # Is it rectangular? gg = get_grid(gg) gtype = get_grid_type(gg) if gtype == 'rect': return gg elif gtype != 'curv' or not isrect(gg, tol=tol, f=f): if mode=="warn": vcwarn("Grid seems not rectangular. Not converted.") return gg elif mode=="raise": raise VACUMMError('Cannot convert to rectangular grid') if mode is False or mode=="none": return gg # Convert it xx, yy = get_xy(gg, mesh=True, num=True) x = xx.mean(axis=0) y = yy.mean(axis=1) grido = create_grid(x, y, 'rect') cp_atts(gg.getLongitude(), grido.getLongitude()) cp_atts(gg.getLatitude(), grido.getLatitude()) return grido
[docs]def isrect(gg, tol=1.e-2, mode="real", f=None, nocache=False): """Check wether a grid is trully rectangular :Params: - **gg**: cdms2 grid, tuple of cdms axes or variabe with a grid. - **tol**, optional: Tolerance for coordinates deformation. - **mode**, optional: Simple or real check. - ``"simple"``: Simply check if axes are 1D. - ``"real"``: Also check that axes along one direction do vary along the other direction with tolerance tol. - **f**, optional: netcdf file object. """ # Check that it's a grid gg = get_grid(gg, intercept=False) if gg is None: return False # Already rectangular gtype = get_grid_type(gg) # if not mode.startswith('r'): # return gtype == 'rect' if gtype == 'rect': return True elif gtype == 'unstruct': return False # File grid if not nocache and f is not None: from ...misc.io import ncget_fgrid fgrid = ncget_fgrid(f, gg) else: fgrid = None # From cache if not nocache: for grid in fgrid, gg: if hasattr(grid, '_vacumm_isrect'): return grid._vacumm_isrect # Check if this grid is really rectangular xx = gg.getLongitude().asma() yy = gg.getLatitude().asma() res = ((xx.std(axis=0)[1:] < tol * N.ma.diff(xx, axis=1).mean(axis=0)).all() and (yy.std(axis=1)[1:] < tol * N.ma.diff(yy, axis=0).mean(axis=1)).all()) # # - resolution from cache # for grid in fgrid, gg: # if grid is None: continue # res = resol(grid, cache=2) # if res is not None: # break # else: # res = resol(gg) # if fgrid is not None: # fgrid._res = res # dx, dy = res # # - relative resolution variations # xx, yy = get_xy(gg, num=True) # xx = N.ma.masked_outside(xx, -720., 720., copy=False) # yy = N.ma.clip(yy, -90, 90.) # Dx = N.ma.median(xx.ptp(axis=0)) # Dy = N.ma.median(yy.ptp(axis=-1)) # res = Dx < tol*dx and Dy < tol*dy # Cache result for grid in gg, fgrid: if grid is not None: grid._vacumm_isrect = res return res
[docs]def transect_specs(gg, lon0, lat0, lon1, lat1, subsamp=3, getxy=False, getproj=False): """Get specs for a transect given a grid and starting and ending points :Params: - **lon/lat0/1**: Coordinates of first and last point in degrees. - **subsamp**, optional: Subsampling with respect to grid cell. - **getxy**, optional: Also return projected coordinates. - **getproj**, optional: Also return projection map. :Return: lons, lats[, xx, yy][, proj] :See also: :func:`resol`, :meth:`mpl_toolkits.basemap.Basemap.gcpoints`. """ # Bounds and resolution dx, dy = resol(gg, proj='merc') m = get_map(gg, resol=None, proj='merc') x0, y0 = m(lon0, lat0) x1, y1 = m(lon1, lat1) Dx = N.abs(x1-x0) Dy = N.abs(y1-y0) # Number of points if dy*Dx > dx*Dy: npts = Dx/dx else: npts = Dy/dy npts *= subsamp npts = int(N.ceil(npts)) # Coordinates xx, yy = m.gcpoints(lon0, lat0, lon1, lat1, npts) lons, lats = m(xx, yy, inverse=True) ret = N.array(lons), N.array(lats) if getxy: ret += N.array(xx), N.array(yy) if getproj: ret += m return ret
[docs]def depth2dz(depth, axis=None, mode=None, masked=True): """Convert from depth to layer thickness :Params: - **depth**: 1D or ND array of depth (variable or axis). - **axis**, optional: Axis of vertical dimension. It is guessed of not provided. - **mode**, optional: Mode for defining the reference layer. Possible values: ``'first'``, ``'last'``, ``center`` or ``None``. It is either the first or the last layer. The layer is computed normally like other layer by difference, whereas its opposite (hidden) layer is either masked or has its thickness set the the thickness of its adjacent layer. If ``mode`` is set to ``None``, it set to ``'last'`` if depths are positive up. - **masked**, optional: If True, hidden layer is masked. """ # Init if cdms2.isVariable(depth): dz = depth.clone() dz.long_name = "Layer thickness" dz.id = 'dz' dz.units = 'm' elif isaxis(depth): dz = N.empty(depth.shape) else: dz = depth.copy() if masked and not N.ma.isMA(dz): dz = N.ma.asarray(dz) dz[:] = N.ma.masked # Guess axis to work on if axis is None: axis = _getzdim_(depth) slices = get_axis_slices(depth, axis) # Priority mode if isinstance(mode, basestring): mode = mode.lower() if mode not in ['last', 'first', None, 'center']: mode = None if mode is None: mode = ['first', 'last'][int(isdepthup(depth, axis=axis))] # Compute if depth.shape[axis]==1: dz[:] = 0 return dz if mode=='last': dz[slices['lasts']] = depth[slices['lasts']]-depth[slices['firsts']] if not masked: dz[slices['first']] = dz[slices['firstp1']] elif mode=='first': dz[slices['firsts']] = depth[slices['firsts']]-depth[slices['lasts']] if not masked: dz[slices['last']] = dz[slices['lastm1']] else: # center dz[slices['mid']] = 0.5*(depth[slices['lastsp1']]-depth[slices['firstsm1']]) if not masked: dz[slices['first']] = dz[slices['firstp1']] dz[slices['last']] = dz[slices['lastm1']] dz[:] = abs(dz) return dz
[docs]def get_axis_slices(ndim, axis, **kwargs): """Get standard slices for an axis of a ndim array :Params: - **ndim**: The number of dimensions. It can also be a tuple (like an array shape) or an array. - **axis**: Index of the axis. :Return: A dictionary of tuples of slices. All tuples have a length of ndim, and can be used has a slice for the array (see example). - "all": Select everything. - "first"/"last": First and last. - "firstp1": Second element. - "firstp2": Third element. - "lastm1": Element before the last one. - "lastm2": Second element before the last one. - "firsts": All but the last. - "lasts": All but the first. - "firstsm1": All but the last two. - "lastsp1": All but the first two. - "mid": All but the first and last. :Example: >>> var = N.arange(3*4).reshape(3, 4) >>> ss = get_axis_slices(var, axis=0) >>> var_north = var[ss['last']] >>> var_south = var[ss['first']] """ if hasattr(ndim, 'shape'): ndim = ndim.shape if hasattr(ndim, '__len__'): ndim = len(ndim) sel = [slice(None)]*ndim selmid = list(sel) ; selmid[axis] = slice(1, -1) ; selmid = tuple(selmid) sellasts = list(sel) ; sellasts[axis] = slice(1, None) ; sellasts = tuple(sellasts) selfirsts = list(sel) ; selfirsts[axis] = slice(0, -1) ; selfirsts = tuple(selfirsts) sellastsp1 = list(sel) ; sellastsp1[axis] = slice(2, None) ; sellastsp1 = tuple(sellastsp1) selfirstsm1 = list(sel) ; selfirstsm1[axis] = slice(0, -2) ; selfirstsm1 = tuple(selfirstsm1) sellast = list(sel) ; sellast[axis] = -1 ; sellast = tuple(sellast) selfirst = list(sel) ; selfirst[axis] = 0 ; selfirst = tuple(selfirst) sellastm1 = list(sel) ; sellastm1[axis] = -2 ; sellastm1 = tuple(sellastm1) sellastm2 = list(sel) ; sellastm2[axis] = -3 ; sellastm2 = tuple(sellastm2) selfirstp1 = list(sel) ; selfirstp1[axis] = 1 ; selfirstp1 = tuple(selfirstp1) selfirstp2 = list(sel) ; selfirstp2[axis] = 2 ; selfirstp2 = tuple(selfirstp2) if kwargs: for key, val in kwargs.items(): if isinstance(val, (list, tuple)): val = slice(*val) ksel = list(sel) ksel[axis] = val kwargs[key] = ksel return dict(all=sel, mid=selmid, lasts=sellasts, firsts=selfirsts, lastsp1=sellastsp1, firstsm1=selfirstsm1, last=sellast, first=selfirst, lastm1=sellastm1, firstp1=selfirstp1, lastm2=sellastm2, firstp2=selfirstp2, **kwargs)
[docs]def merge_axis_slices(slices1, slices2): """Merge standard tuples of slices stored in dictionaries created with :func:`get_axis_slices`: :Params: - **slice1/2**: Dictionaries of tuples of slices. :Example: >>> slicesx = get_axis_slices(3, -1) # for X >>> slicesy = get_axis_slices(3, -2) # for Y >>> slices12 = merge_axis_slices(slicesx, slicesy) # for X and Y """ slicesm = {} null = slice(None) for key in slices1: slicesm[key] = merge_axis_slice(slices1[key], slices2[key]) return slicesm
[docs]def merge_axis_slice(sel1, sel2): """Merge a single tuple of slices Theses slices may have been created with :func:`get_axis_slices` :Params: - **sel1/2**: Tuples of slices. :Example: >>> sel1 = (slice(None), -2) >>> sel2 = (slice(1,4), slice(None) >>> merge_axis_slice(sel1, sel2) (slice(1, 4, None), -2) """ ndim1 = len(sel1) ndim2 = len(sel2) sel1 = list(sel1) sel2 = list(sel2) selm = [] if ndim1<ndim2: sel1 = [slice(None)]*(ndim2-ndim1)+sel1 elif ndim1>ndim2: sel2 = [slice(None)]*(ndim1-ndim2)+sel2 for axis in xrange(len(sel1)): if sel1[axis] is None or sel1[axis]==slice(None) or \ sel1[axis] is Ellipsis or sel1[axis]==':': selm.append(sel2[axis]) else: selm.append(sel1[axis]) return tuple(selm)
[docs]def get_zdim(var, axis=None, default=None, strict=True): """Get the index of the Z dimension""" if isinstance(axis, int): return axis ndim = len(var.shape) if ndim==1 and (not strict or isdep(var)): # a single axis return 0 if isaxis(axis) and cdms2.isVariable(var): # find axis index in var axes i = var.getAxisList().index(axis) if i!=-1: axis = i if axis is not None and not strict and hasattr(axis, '__len__') and len(axis)!=0: # find the same axis length good = filter(lambda i:i==len(axis), var.shape) if len(good)==1: return good[0] axis = None if axis is None: if cdms2.isVariable(var): # search for z in order axis = var.getOrder().find('z') if axis==-1: axis=None if not strict and axis is None and ndim>2: axis = ndim-3 if cdms2.isVariable(var) and var.getOrder()[axis]!='-': axis = None if axis is None: axis = default return axis
_getzdim_ = get_zdim
[docs]def isdepthup(depth, axis=None, ro=True): """Guess if depth is positive up It first check "positive" attribute, then guess from values: more positive values means positive down. ..warning:: Bad values must be masked :Params: - **depth**: Depth arrays or axis. - **axis**, optional: Z axis index. - **ro**, optional: Read-only? If False and if depth is an axis, its 'positive' attribute is marked 'up' or 'down' upon the results. :Return: ``None`` if not Z axis found, else ``True`` or ``False`` """ # From attribute positive = getattr(depth, 'positive', None) if isinstance(positive, basestring): if positive.lower() in ['down', 'up']: return positive == 'up' positive = None # Slices axis = get_zdim(depth, axis=axis, default=None) if axis is None: return slices = get_axis_slices(depth, axis) # From values firstval = depth[slices['first']].max() lastval = depth[slices['last']].max() isup = N.abs(firstval)>N.abs(lastval) # Set attribute if not ro and isaxis(depth): depth.positive = 'up' if isup else 'down' return isup
[docs]def makedepthup(vv, depth=None, axis=None, default=None, ro=False, strict=True): """Make depth and variables positive up :Params: - **vv**: A single variable or a list of them. - **depth**, optional: Explicit depths to not guess it with :meth:`getLevel`. If True or False, simply revert along Z dimension. - **axis**, optional: Z dimension (else guessed with :func:`get_zdim`). """ # A list of variables single = not isinstance(vv, (list,tuple)) if single: vv = [vv] # Get depth getdepth = isaxis(depth) or isinstance(depth, N.ndarray) if depth is None: if isaxis(vv[0]): depth = depth else: depth = vv[0].getLevel() if axis is None and isaxis(depth): axis = depth if isinstance(depth, bool): isup = depth else: axis = get_zdim(vv[0], axis, default=default, strict=strict) # No depth found if axis is None: vv = vv[0] if single else vv if getdepth: return vv, depth return vv # Positive up? isup = isdepthup(depth, axis=axis, ro=ro) # Revert depths if not isup and getdepth: depth[:] *= -1 if isaxis(depth): depth.assignValue(-depth[::-1]) depth.positive = 'up' else: depth[:] = depth.take(range(depth.shape[axis]-1, -1, -1), axis=axis) # Revert variables or depths if not isup: for ivar, var in enumerate(vv): if isaxis(var): var.assignValue(-var[::-1]) var.positive = 'up' else: axis = get_zdim(var, axis=axis) if axis is None: continue var[:] = var.take(range(var.shape[axis]-1, -1, -1), axis=axis) depaxis = var.getAxis(axis) depaxis.assignValue(-depaxis[::-1]) vv = vv[0] if single else vv if getdepth: return vv, depth return vv
[docs]def makealtitudeup(vv, depth=None, axis=None, default=None, ro=False, strict=True): """Make depth and variables positive up :Params: - **vv**: A single variable or a list of them. - **depth**, optional: Explicit depths to not guess it with :meth:`getLevel`. If True or False, simply revert along Z dimension. - **axis**, optional: Z dimension (else guessed with :func:`get_zdim`). """ # A list of variables single = not isinstance(vv, (list,tuple)) if single: vv = [vv] # Get depth getdepth = isaxis(depth) or isinstance(depth, N.ndarray) if depth is None: if isaxis(vv[0]): depth = depth else: depth = vv[0].getLevel() if axis is None and isaxis(depth): axis = depth if isinstance(depth, bool): isup = depth else: axis = get_zdim(vv[0], axis, default=default, strict=strict) # No depth found if axis is None: vv = vv[0] if single else vv if getdepth: return vv, depth return vv # Positive up? isup = isdepthup(depth, axis=axis, ro=ro) # Revert depths if not isup and getdepth: # depth[:] *= -1 if isaxis(depth): depth.assignValue(depth[::-1]) depth.positive = 'up' else: depth[:] = depth.take(range(depth.shape[axis]-1, -1, -1), axis=axis) # Revert variables or depths if not isup: for ivar, var in enumerate(vv): if isaxis(var): var.assignValue(var[::-1]) var.positive = 'up' else: axis = get_zdim(var, axis=axis) if axis is None: continue var[:] = var.take(range(var.shape[axis]-1, -1, -1), axis=axis) depaxis = var.getAxis(axis) depaxis.assignValue(depaxis[::-1]) vv = vv[0] if single else vv if getdepth: return vv, depth return vv
[docs]def dz2depth(dz, ref=None, refloc=None, copyaxes=True, mode='edge', zerolid=False): """Conversion from layer thickness to depths :Params: - **dz**: Layer thickness from bottom to top. - **ref**: Reference depth for integration, which depends on ``refloc``: - ``"top"`` or ``"eta"`` or ``"ssh"``: Sea surface height. - ``"bottom"`` or ``"depth"`` or ``"bathy"``: Bottom depth. - Else, estimated by checking the standard_name of the variable, if some values are negatives ('top') or if maximal value is greater then 15. ('bottom'). - **refloc**, optional: ``"top"`` | ``"eta"``, ``"bottom"`` | ``"depth"``, or ``None``. - **mode**, optional: - ``"edge"`` or ``"edge+"``: Compute depths at layer edges (interfaces). Adding a + includes the bottom layer and add a vertical level. - ``"middle"``: Compute depths at the middle of layers - **zerolid**, optional: Force the surface to be at a zero depth """ # Init depths if mode is None: mode = 'edge' mode = str(mode) ext = '+' in mode mode = mode[:3] assert mode in ('edg', 'mid'), ("Invalid mode: Please choose one of: " "edge or middle") ext = ext and mode=='edg' withtime = dz.getTime() is not None nt = dz.shape[0] if withtime else 1 nz = dz.shape[int(withtime)] if ext: nz += 1 shape = (nt, nz) + dz.shape[int(withtime)+1:] depths = MV2.zeros(shape, dz.dtype) depths.long_name = 'Depths' depths.units = 'm' depths.id = 'depths' # Format dz dzm = dz.asma() if not withtime: dzm = dzm.reshape((1, )+dzm.shape) #TODO: dz2depth: dzshift #TODO: dz2depth: positive up/down # Guess reference if ref is None: ref = 0. if isinstance(refloc, basestring): refloc = refloc.lower() if refloc in ["eta", "ssh"]: refloc = 'top' elif refloc in ["depth", "bathy"]: refloc = 'bottom' if refloc not in ['top', 'bottom']: refloc = None refsn = getattr(ref, 'standard_name', None) ref = ref.asma() if cdms2.isVariable(ref) else N.ma.asarray(ref) if refloc is None: # From standard_name (using cf) if refsn is not None: try: from vacumm.data.cf import VAR_SPECS if refsn in VAR_SPECS['ssh']['standard_names']: refloc = 'top' elif True in [(refsn in VAR_SPECS[vn]['standard_names']) for vn in ['bathy', 'bathy_u', 'bathy_v']]: refloc = 'bottom' except: pass # From values if refloc is None: if (ref<=0).any(): refloc = 'top' else: vmax = ref.max() refloc = 'bottom' if ref.max()>15 else 'top' # Integrate depths[:, int(ext):] = dzm.cumsum(axis=1) # Add reference if refloc == 'top': # zero at top for it in xrange(nt): depths[it] -= depths[it, -1] if zerolid: ref = 0 depths[:] += ref # Substract surface for zerolid if zerolid and refloc != 'top': for dep in depths: dep[:] -= dep[-1] # Middle of layers if mode=='mid': if refloc=='top': depths[:] += dzm * 0.5 else: depths[:] -= dzm * 0.5 del dzm # Format axes if not withtime: depths = depths[0] if copyaxes: axes = dz.getAxisList() if ext: iaxis = int(withtime) zaxis = extend1d(dz.getAxis(iaxis), (1, 0), mode='linear') axes[iaxis] = zaxis depths.setAxisList(axes) grid = dz.getGrid() if grid is not None: depths.setGrid(grid) return depths
def are_same_grids(grid0, grid1): """Check if two grids are similars""" if grid0.shape != grid1.shape: return False lon0 = grid0.getLongitude()[:] lat0 = grid0.getLatitude()[:] lon1 = grid1.getLongitude()[:] lat1 = grid1.getLatitude()[:] if (lon0.shape!=lon1.shape) or (lat0.shape!=lat0.shape): return False for a0, a1 in [(lon0, lon1), (lat0, lat1)]: if not N.ma.allclose(a0, a1): return False return True #def get_triangles(x1d, y1d): # """Get delaunay triangles of random positions""" # return _qhull.delaunay(x1d, y1d)[0]
[docs]def get_tri(xy, ttype='scipy', triangles=None, mask=None, cache=None): """Get a :class:`scipy.spatial.Delaunay` (scipy) or a :class:`matplotlib.tri.Triangulation` (mpl) instance""" assert ttype in ['scipy', 'mpl'] if isinstance(cache, dict) and 'tri' in cache: return cache['tri'] # Scipy Delaunay if ttype == 'scipy': if istri(xy, 'scipy'): return xy if istri(xy, 'mpl'): xy = N.array([xy.x, xy.y]).T elif isugrid(xy): x = xy.getLongitude()[:] y = xy.getLatitude()[:] xy = N.asarray(xy).T else: xy = N.asarray(xy) if xy.shape[1] != 2: xy = xy.T import scipy.spatial as S tri = S.Delaunay(xy) # Matplotlib Triangulation else: if istri(xy, 'mpl'): return xy if istri(xy, 'scipy'): triangles = xy.simplices x = xy.points[:, 0] y = xy.points[:, 1] elif isugrid(xy): x = xy.getLongitude()[:] y = xy.getLatitude()[:] elif isinstance(xy, N.ndarray) and xy.shape[1] == 2: x = xy[:, 0] y = xy[:, 1] else: x, y = xy from matplotlib.tri import Triangulation tri = Triangulation(x, y, triangles=triangles) if mask is not None: if mask.ndim != 2 or mask.shape[-1] == x.size: mask = get_tri_mask(tri, mask) if mask.any(): tri.set_mask(mask) if isinstance(cache, dict): cache['tri'] = tri return tri
[docs]def istri(tri, ttype='scipy'): """Check if tri is a :class:`scipy.spatial.Delaunay` (scipy) or a :class:`matplotlib.tri.Triangulation` instance (mpl)""" assert ttype in ['scipy', 'mpl'] if ttype == 'scipy': import scipy.spatial as S return isinstance(tri, S.Delaunay) elif ttype == 'mpl': from matplotlib.tri import Triangulation return isinstance(tri, Triangulation) return False
def get_tri_type(tri): """Get the triangulation instance type, either 'scipy', 'mpl' or None""" from matplotlib.tri import Triangulation if isinstance(tri, Triangulation): return "mpl" import scipy.spatial as S if isinstance(tri, S.Delaunay): return "scipy" def get_tri_mask(tri, dmask): """Get the triangulation mask from points mask""" if hasattr(tri, 'simplices'): triangles = tri.simplices else: triangles = tri.triangles tmask = N.zeros(triangles.shape[0], '?') if N.ma.isMA(dmask): dmask = dmask.mask while dmask.ndim > 1: dmask = dmask.any(axis=0) if dmask.all(): tmask[:] = True elif dmask.any(): for i in range(triangles.shape[1]): tmask |= dmask[triangles[:, i]] return tmask ###################################################################### ###################################################################### from ...misc.atime import ch_units,compress from ...misc import cp_atts,get_atts,set_atts,intersect, numod, check_case, kwfilter from ...misc.axes import (check_axes, islon, islat, islev, istime, create_lon, create_lat, isaxis, isdep) from ...misc.phys import units, constants from .basemap import get_map, cached_map, cache_map, get_proj from .masking import t2uvmasks from .regridding import extend1d from ...__init__ import VACUMMError, VACUMMWarning, vcwarn