from numpy import empty as numpy_empty
from numpy import ndarray as numpy_ndarray
from numpy import size as numpy_size
#from numpy.ma import argmax as numpy_argmax
from itertools import izip
from .functions import parse_indices
from .coordinatebounds import CoordinateBounds
from .variable import Variable, SubspaceVariable
from .data.data import Data
# ====================================================================
#
# Coordinate object
#
# ====================================================================
[docs]class Coordinate(Variable):
'''
Base class for a CF dimension or auxiliary coordinate construct.
**Attributes**
=============== ======== ===================================================
Attribute Type Description
=============== ======== ===================================================
`!climatology` ``bool`` Whether or not the bounds are intervals of
climatological time. Presumed to be False if unset.
=============== ======== ===================================================
'''
# def __new__(cls, *args, **kwargs):
# '''
#
#Called to create a new instance with the `!_cyclic` attribute set to
#True. ``*args`` and ``**kwargs`` are passed to the `__init__` method.
#
#'''
# self = super(Coordinate, cls).__new__(Coordinate)
# self._cyclic = True
# return self
# #--- End: def
def __init__(self, properties={}, attributes={}, data=None, bounds=None,
copy=True):
'''
**Initialization**
:Parameters:
properties : dict, optional
Initialize a new instance with CF properties from a
dictionary's key/value pairs.
attributes : dict, optional
Provide the new instance with attributes from a dictionary's
key/value pairs.
data : cf.Data, optional
Provide the new instance with an N-dimensional data array.
bounds : cf.Data or cf.CoordinateBounds, optional
Provide the new instance with cell bounds.
copy : bool, optional
If False then do not copy arguments prior to
initialization. By default arguments are deep copied.
'''
# DO NOT CHANGE _period IN PLACE
self._period = None
self._direction = None
# Set attributes, CF properties and data
super(Coordinate, self).__init__(properties=properties,
attributes=attributes,
data=data,
copy=copy)
# Bounds
if bounds is not None:
self.insert_bounds(bounds, copy=copy)
# Set default standard names based on units
#--- End: def
def _query_contain(self, value):
'''
'''
if not self._hasbounds:
return self == value
# bounds = self.bounds.Data
# mn = bounds.min(axes=-1)
# mx = bounds.max(axes=-1)
return (self.lower_bounds <= value) & (self.upper_bounds >= value)
# return ((mn <= value) & (mx >= value)).squeeze(axes=-1, i=True)
#--- End: def
# def _binary_operation(self, other, method):
# '''
#'''
# self_hasbounds = self._hasbounds
#
# if isinstance(other, self.__class__):
# if other._hasbounds:
# if not self_hasbounds:
# raise TypeError("asdfsdfdfds 0000")
#
# other_bounds = other.bounds
# elif self_hasbounds:
# raise TypeError("asdfsdfdfds 00001")
#
# else:
# other_bounds = other
# if numpy_size(other) > 1:
# raise TypeError("4444444")
#
## elif isinstance(other, (float, int, long)):
## other_bounds = other
##
## elif isinstance(other, numpy_ndarray):
## if self_hasbounds:
## if other.size > 1:
## raise TypeError("4444444")#
##
## other_bounds = other
# #-- End: if
#
# new = super(Coordinate, self)._binary_operation(other, method)
#
# if self_hasbounds:
# new_bounds = self.bounds._binary_operation(other_bounds, method)
#
# inplace = method[2] == 'i'
#
# if not inplace:
# if self_hasbounds:
# new.bounds = new_bounds
#
# return new
# else:
# return self
# #--- End: def
def _change_axis_names(self, dim_name_map):
'''
Change the axis names.
Warning: dim_name_map may be changed in place
:Parameters:
dim_name_map : dict
:Returns:
None
:Examples:
'''
# Change the axis names of the data array
super(Coordinate, self)._change_axis_names(dim_name_map)
if self._hasbounds:
bounds = self.bounds
if bounds._hasData:
b_axes = bounds.Data._axes
if self._hasData:
# Change the dimension names of the bounds
# array. Note that it is assumed that the bounds
# array dimensions are in the same order as the
# coordinate's data array dimensions. It is not
# required that the set of original bounds
# dimension names (bar the trailing dimension)
# equals the set of original coordinate data array
# dimension names. The bounds array dimension
# names will be changed to match the updated
# coordinate data array dimension names.
dim_name_map = {b_axes[-1]: 'bounds'}
for c_dim, b_dim in izip(self.Data._axes, b_axes):
dim_name_map[b_dim] = c_dim
else:
dim_name_map[b_axes[-1]] = 'bounds'
#--- End: if
bounds._change_axis_names(dim_name_map)
#--- End: def
def _equivalent_data(self, other, rtol=None, atol=None, traceback=False,
copy=True):
'''
:Parameters:
copy : bool, optional
If False then the *other* coordinate construct might get
change in place.
:Returns:
None
:Examples:
>>>
'''
if self._hasbounds != other._hasbounds:
# add traceback
return False
if self.shape != other.shape:
# add traceback
return False
if (self.direction() != other.direction() and
self.direction() is not None and other.direction() is not None):
if copy:
other = other.copy()
copy = False
other.flip(i=True)
#--- End: if
# Compare the data arrays
if not super(Coordinate, self)._equivalent_data(other, rtol=rtol,
atol=atol, copy=copy):
return False
if self._hasbounds:
# Both coordinates have bounds
if not self.bounds._equivalent_data(other.bounds, rtol=rtol,
atol=atol, copy=copy):
return False
#--- End: if
# Still here? Then the data are equivalent.
return True
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def attributes(self):
'''
A dictionary of the attributes which are not CF properties.
:Examples:
>>> c.attributes
{}
>>> c.foo = 'bar'
>>> c.attributes
{'foo': 'bar'}
>>> c.attributes.pop('foo')
'bar'
>>> c.attributes
{'foo': 'bar'}
'''
attributes = super(Coordinate, self).attributes
# Remove private attributes
del attributes['_direction']
return attributes
#--- End: def
# ----------------------------------------------------------------
# Attribute (a special attribute)
# ----------------------------------------------------------------
@property
def bounds(self):
'''
The `cf.CoordinateBounds` object containing the cell bounds.
.. seealso:: `lower_bounds`, `upper_bounds`
:Examples:
>>> c
<CF Coordinate: latitude(64) degrees_north>
>>> c.bounds
<CF CoordinateeBounds: latitude(64, 2) degrees_north>
>>> c.bounds = b
AttributeError: Can't set 'bounds' attribute. Consider the insert_bounds method.
>>> c.bounds.max()
<CF Data: 90.0 degrees_north>
>>> c.bounds -= 1
AttributeError: Can't set 'bounds' attribute. Consider the insert_bounds method.
>>> b = c.bounds
>>> b -= 1
>>> c.bounds.max()
<CF Data: 89.0 degrees_north>
'''
return self._get_special_attr('bounds')
#--- End: def
@bounds.setter
def bounds(self, value):
raise AttributeError(
"Can't set 'bounds' attribute. Consider the insert_bounds method.")
# self._set_special_attr('bounds', value)
# self._hasbounds = True
#--- End: def
@bounds.deleter
def bounds(self):
self._del_special_attr('bounds')
self._hasbounds = False
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def cellsize(self):
'''
A `cf.Data` object containing the coordinate cell sizes.
:Examples:
>>> print c.bounds
<CF CoordinateBounds: latitude(47, 2) degrees_north>
>>> print c.bounds.array
[[-90. -87.]
[-87. -80.]
[-80. -67.]]
>>> print d.cellsize
<CF Data: [3.0, ..., 13.0] degrees_north>
>>> print d.cellsize.array
[ 3. 7. 13.]
>>> print c.sin().cellsize.array
[ 0.00137047 0.01382178 0.0643029 ]
>>> del c.bounds
>>> c.cellsize
AttributeError: Can't get cell sizes when coordinates have no bounds
'''
if not self._hasbounds:
raise AttributeError(
"Can't get cell sizes when coordinates have no bounds")
cells = self.bounds.data
print cell.array
cells = (cells[:, 1] - cells[:, 0]).abs()
print cell.array
cells.squeeze(1, i=True)
return cells
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def ctype(self):
'''
The CF coordinate type.
One of ``'T'``, ``'X'``, ``'Y'`` or ``'Z'`` if the coordinate object
is for the respective CF axis type, otherwise None.
.. seealso:: `T`, `X`, `~cf.Coordinate.Y`, `Z`
:Examples:
>>> c.standard_name
'longitude'
>>> c.ctype
'X'
>>> c.units
'days since 2005-12-1'
>>> c.ctype
'T'
>>> c.units
'kelvin'
>>> print c.ctype
None
'''
for t in ('T', 'X', 'Y', 'Z'):
if getattr(self, t):
return t
#--- End: def
# ----------------------------------------------------------------
# Attribute
# ----------------------------------------------------------------
@property
def dtype(self):
'''
Numpy data-type of the data array.
:Examples:
>>> c.dtype
dtype('float64')
>>> import numpy
>>> c.dtype = numpy.dtype('float32')
'''
if self._hasData:
return self.Data.dtype
if self._hasbounds:
return self.bounds.dtype
raise AttributeError("%s doesn't have attribute 'dtype'" %
self.__class__.__name__)
#--- End: def
@dtype.setter
def dtype(self, value):
if self._hasData:
self.Data.dtype = value
if self._hasbounds:
self.bounds.dtype = value
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def isauxiliary(self):
'''
True for auxiliary coordinate constructs, False otherwise.
.. seealso:: `ismeasure`, `isdimension`
:Examples:
>>> c.isauxiliary
False
'''
return False
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def isdimension(self):
'''
True for dimension coordinate constructs, False otherwise.
.. seealso:: `isauxiliary`, `ismeasure`
:Examples:
>>> c.isdimension
False
'''
return False
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def isperiodic(self):
'''
>>> print c.period()
None
>>> c.isperiodic
False
>>> c.period(cf.Data(360, 'degeres_east'))
None
>>> c.isperiodic
True
>>> c.period(None)
<CF Data: 360 degrees_east>
>>> c.isperiodic
False
'''
return self._period is not None
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def lower_bounds(self):
'''
The lower coordinate bounds in a `cf.Data` object.
``c.lower_bounds`` is equivalent to ``c.bounds.data.min(axes=-1)``.
.. seealso:: `bounds`, `upper_bounds`
:Examples:
>>> print c.bounds.array
[[ 5 3]
[ 3 1]
[ 1 -1]]
>>> c.lower_bounds
<CF Data: [3, ..., -1]>
>>> print c.lower_bounds.array
[ 3 1 -1]
'''
if not self._hasbounds:
raise ValueError("Can't get lower bounds when there are no bounds")
return self.bounds.lower_bounds
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def subspace(self):
'''
Return a new coordinate whose data and bounds are subspaced in a
consistent manner.
This attribute may be indexed to select a subspace from dimension
index values.
**Subspacing by indexing**
Subspacing by dimension indices uses an extended Python slicing
syntax, which is similar numpy array indexing. There are two
extensions to the numpy indexing functionality:
* Size 1 dimensions are never removed.
An integer index i takes the i-th element but does not reduce the
rank of the output array by one.
* When advanced indexing is used on more than one dimension, the
advanced indices work independently.
When more than one dimension's slice is a 1-d boolean array or 1-d
sequence of integers, then these indices work independently along
each dimension (similar to the way vector subscripts work in
Fortran), rather than by their elements.
:Examples:
'''
return SubspaceCoordinate(self)
#--- End: def
# ----------------------------------------------------------------
# Attribute: T (read only)
# ----------------------------------------------------------------
@property
def T(self):
'''
True if and only if the coordinate is a CF T axis coordinate.
.. seealso:: `ctype`, `X`, `~cf.Coordinate.Y`, `Z`
:Returns:
out : bool
Whether or not the coordinate is a T axis coordinate.
:Examples:
>>> c.Units
<CF Units: seconds since 1992-10-8>
>>> c.T
True
>>> c.standard_name in ('time', 'forecast_reference_time')
True
>>> c.T
True
>>> c.axis
'T'
>>> c.T
True
'''
if self.ndim > 1:
return self.getprop('axis', None) == 'T'
if (self.Units.isreftime or
self.getprop('standard_name', 'T') in ('time',
'forecast_reference_time') or
self.getprop('axis', None) == 'T'):
return True
else:
return False
#--- End: def
# ----------------------------------------------------------------
# Attribute
# ----------------------------------------------------------------
@property
def Units(self):
'''
The Units object containing the units of the data array.
'''
return Variable.Units.fget(self)
#--- End: def
@Units.setter
def Units(self, value):
Variable.Units.fset(self, value)
# Set the Units on the bounds
if self._hasbounds:
self.bounds.Units = value
# Set the Units on the period
if self._period is not None:
period = self._period.copy()
period.Units = value
self._period = period
self._direction = None
#--- End: def
@Units.deleter
def Units(self):
Variable.Units.fdel(self)
if self._hasbounds:
# Delete the bounds' Units
del self.bounds.Units
if self._period is not None:
# Delete the period's Units
period = self._period.copy()
del period.Units
self._period = period
self._direction = None
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def upper_bounds(self):
'''
The upper coordinate bounds in a `cf.Data` object.
``c.upper_bounds`` is equivalent to ``c.bounds.data.max(axes=-1)``.
.. seealso:: `bounds`, `lower_bounds`
:Examples:
>>> print c.bounds.array
[[ 5 3]
[ 3 1]
[ 1 -1]]
>>> c.upper_bounds
<CF Data: [5, ..., 1]>
>>> c.upper_bounds.array
array([5, 3, 1])
'''
if not self._hasbounds:
raise ValueError("Can't get upper bounds when there are no bounds")
return self.bounds.upper_bounds
#--- End: def
# ----------------------------------------------------------------
# Attribute: X (read only)
# ----------------------------------------------------------------
@property
def X(self):
'''
True if and only if the coordinate is a CF X axis coordinate.
.. seealso:: `ctype`, `T`, `~cf.Coordinate.Y`, `Z`
:Returns:
out : bool
Whether or not the coordinate is a X axis coordinate.
:Examples:
>>> c.Units
<CF Units: degreeE>
>>> c.X
True
>>> c.standard_name
'longitude'
>>> c.X
True
>>> c.axis
'X'
>>> c.X
True
'''
if self.ndim > 1:
return self.getprop('axis', None) == 'X'
if (self.Units.islongitude or
self.getprop('standard_name', 'X') in ('longitude',
'projection_x_coordinate',
'grid_longitude') or
self.getprop('axis', None) == 'X'):
return True
else:
return False
#--- End: def
# ----------------------------------------------------------------
# Attribute: Y (read only)
# ----------------------------------------------------------------
@property
def Y(self):
'''
True if and only if the coordinate is a CF Y axis coordinate.
.. seealso:: `ctype`, `T`, `X`, `Z`
:Returns:
out : bool
Whether or not the coordinate is a Y axis coordinate.
:Examples:
>>> c.Units
<CF Units: degree_north>
>>> c.Y
True
>>> c.standard_name in (
'latitude'
>>> c.Y
True
>>> c.axis
'Y'
>>> c.Y
True
'''
if self.ndim > 1:
return self.getprop('axis', None) == 'Y'
if (self.Units.islatitude or
self.getprop('standard_name', 'Y') in ('latitude',
'projection_y_coordinate',
'grid_latitude') or
self.getprop('axis', None) == 'Y'):
return True
else:
return False
#--- End: def
# ----------------------------------------------------------------
# Attribute: Z (read only)
# ----------------------------------------------------------------
@property
def Z(self):
'''
True if and only if the coordinate is a CF Z axis coordinate.
.. seealso:: `ctype`, `T`, `X`, `~cf.Coordinate.Y`
:Returns:
out : bool
Whether or not the coordinate is a Z axis coordinate.
:Examples:
>>> c.Units
<CF Units: Pa>
>>> c.Z
True
>>> c.Units.equivalent(cf.Units('K')) and c.positive == 'up'
True
>>> c.Z
True
>>> c.axis
'Z'
>>> c.Z
True
>>> c.Units
<CF Units: sigma_level>
>>> c.Z
True
>>> c.standard_name
'ocean_sigma_coordinate'
>>> c.Z
True
'''
if self.ndim > 1:
return self.getprop('axis', None) == 'Z'
units = self.Units
if (units.ispressure or
str(self.getprop('positive', 'Z')).lower() in ('up', 'down') or
self.getprop('axis', None) == 'Z' or
(units and units.units in ('level', 'layer' 'sigma_level')) or
self.getprop('standard_name', None) in
('atmosphere_ln_pressure_coordinate',
'atmosphere_sigma_coordinate',
'atmosphere_hybrid_sigma_pressure_coordinate',
'atmosphere_hybrid_height_coordinate',
'atmosphere_sleve_coordinate',
'ocean_sigma_coordinate',
'ocean_s_coordinate',
'ocean_s_coordinate_g1',
'ocean_s_coordinate_g2',
'ocean_sigma_z_coordinate',
'ocean_double_sigma_coordinate')):
return True
else:
return False
#--- End: def
# ----------------------------------------------------------------
# CF property: axis
# ----------------------------------------------------------------
@property
def axis(self):
'''
The axis CF property.
:Examples:
>>> c.axis = 'Y'
>>> c.axis
'Y'
>>> del c.axis
>>> c.setprop('axis', 'T')
>>> c.getprop('axis')
'T'
>>> c.delprop('axis')
'''
return self.getprop('axis')
#--- End: def
@axis.setter
def axis(self, value):
self.setprop('axis', value)
@axis.deleter
def axis(self):
self.delprop('axis')
# ----------------------------------------------------------------
# CF property: calendar
# ----------------------------------------------------------------
@property
def calendar(self):
'''
The calendar CF property.
This property is a mirror of the calendar stored in the `Units`
attribute.
:Examples:
>>> c.calendar = 'noleap'
>>> c.calendar
'noleap'
>>> del c.calendar
>>> c.setprop('calendar', 'proleptic_gregorian')
>>> c.getprop('calendar')
'proleptic_gregorian'
>>> c.delprop('calendar')
'''
return Variable.calendar.fget(self)
#--- End: def
@calendar.setter
def calendar(self, value):
Variable.calendar.fset(self, value)
# Set the calendar of the bounds
if self._hasbounds:
self.bounds.setprop('calendar', value)
#--- End: def
@calendar.deleter
def calendar(self):
Variable.calendar.fdel(self)
# Delete the calendar of the bounds
if self._hasbounds:
try:
self.bounds.delprop('calendar')
except AttributeError:
pass
#--- End: def
# ----------------------------------------------------------------
# CF property
# ----------------------------------------------------------------
@property
def leap_month(self):
'''
The leap_month CF property.
:Examples:
>>> c.leap_month = 2
>>> c.leap_month
2
>>> del c.leap_month
>>> c.setprop('leap_month', 11)
>>> c.getprop('leap_month')
11
>>> c.delprop('leap_month')
'''
return self.getprop('leap_month')
#--- End: def
@leap_month.setter
def leap_month(self, value):
self.setprop('leap_month', value)
@leap_month.deleter
def leap_month(self):
self.delprop('leap_month')
# ----------------------------------------------------------------
# CF property
# ----------------------------------------------------------------
@property
def leap_year(self):
'''
The leap_year CF property.
:Examples:
>>> c.leap_year = 1984
>>> c.leap_year
1984
>>> del c.leap_year
>>> c.setprop('leap_year', 1984)
>>> c.getprop('leap_year')
1984
>>> c.delprop('leap_year')
'''
return self.getprop('leap_year')
#--- End: def
@leap_year.setter
def leap_year(self, value):
self.setprop('leap_year', value)
@leap_year.deleter
def leap_year(self):
self.delprop('leap_year')
# ----------------------------------------------------------------
# CF property
# ----------------------------------------------------------------
@property
def month_lengths(self):
'''
The month_lengths CF property.
Stored as a tuple but may be set as any array-like object.
:Examples:
>>> c.month_lengths = numpy.array([34, 31, 32, 30, 29, 27, 28, 28, 28, 32, 32, 34])
>>> c.month_lengths
(34, 31, 32, 30, 29, 27, 28, 28, 28, 32, 32, 34)
>>> del c.month_lengths
>>> c.setprop('month_lengths', [34, 31, 32, 30, 29, 27, 28, 28, 28, 32, 32, 34])
>>> c.getprop('month_lengths')
(34, 31, 32, 30, 29, 27, 28, 28, 28, 32, 32, 34)
>>> c.delprop('month_lengths')
'''
return self.getprop('month_lengths')
#--- End: def
@month_lengths.setter
def month_lengths(self, value):
value = tuple(value)
self.setprop('month_lengths', value)
#--- End: def
@month_lengths.deleter
def month_lengths(self):
self.delprop('month_lengths')
# ----------------------------------------------------------------
# CF property: positive
# ----------------------------------------------------------------
@property
def positive(self):
'''
The positive CF property.
:Examples:
>>> c.positive = 'up'
>>> c.positive
'up'
>>> del c.positive
>>> c.setprop('positive', 'down')
>>> c.getprop('positive')
'down'
>>> c.delprop('positive')
'''
return self.getprop('positive')
#--- End: def
@positive.setter
def positive(self, value):
self.setprop('positive', value)
self._direction = None
#--- End: def
@positive.deleter
def positive(self):
self.delprop('positive')
self._direction = None
# ----------------------------------------------------------------
# CF property
# ----------------------------------------------------------------
@property
def standard_name(self):
'''
The standard_name CF property.
:Examples:
>>> c.standard_name = 'time'
>>> c.standard_name
'time'
>>> del c.standard_name
>>> c.setprop('standard_name', 'time')
>>> c.getprop('standard_name')
'time'
>>> c.delprop('standard_name')
'''
return self.getprop('standard_name')
#--- End: def
@standard_name.setter
def standard_name(self, value):
self.setprop('standard_name', value)
@standard_name.deleter
def standard_name(self):
self.delprop('standard_name')
# ----------------------------------------------------------------
# CF property: units
# ----------------------------------------------------------------
# DCH possible inconsistency when setting self.Units.units ??
@property
def units(self):
'''
The units CF property.
This property is a mirror of the units stored in the `Units`
attribute.
:Examples:
>>> c.units = 'degrees_east'
>>> c.units
'degree_east'
>>> del c.units
>>> c.setprop('units', 'days since 2004-06-01')
>>> c.getprop('units')
'days since 2004-06-01'
>>> c.delprop('units')
'''
return Variable.units.fget(self)
#--- End: def
@units.setter
def units(self, value):
Variable.units.fset(self, value)
if self._hasbounds:
# Set the units on the bounds
self.bounds.setprop('units', value)
self._direction = None
#--- End: def
@units.deleter
def units(self):
Variable.units.fdel(self)
self._direction = None
if self._hasbounds:
# Delete the units from the bounds
try:
self.bounds.delprop('units')
except AttributeError:
pass
#--- End: def
[docs] def asauxiliary(self, copy=True):
'''
Return the coordinate recast as an auxiliary coordinate.
:Parameters:
copy : bool, optional
If False then the returned auxiliary coordinate is not
independent. By default the returned auxiliary coordinate is
independent.
:Returns:
out : cf.AuxiliaryCoordinate
The coordinate recast as an auxiliary coordinate.
:Examples:
>>> a = c.asauxiliary()
>>> a = c.asauxiliary(copy=False)
'''
return AuxiliaryCoordinate(attributes=self.attributes,
properties=self.properties,
data=getattr(self, 'Data', None),
bounds=getattr(self, 'bounds', None),
copy=copy)
#--- End: def
[docs] def asdimension(self, copy=True):
'''
Return the coordinate recast as a dimension coordinate.
:Parameters:
copy : bool, optional
If False then the returned dimension coordinate is not
independent. By default the returned dimension coordinate is
independent.
:Returns:
out : cf.DimensionCoordinate
The coordinate recast as a dimension coordinate.
:Examples:
>>> d = c.asdimension()
>>> d = c.asdimension(copy=False)
'''
if self._hasData:
if self.ndim > 1:
raise ValueError(
"Dimension coordinate must be 1-d (not %d-d)" %
self.ndim)
elif self._hasbounds:
if self.bounds.ndim > 2:
raise ValueError(
"Dimension coordinate must be 1-d (not %d-d)" %
self.ndim)
return DimensionCoordinate(attributes=self.attributes,
properties=self.properties,
data=getattr(self, 'Data', None),
bounds=getattr(self, 'bounds', None),
copy=copy)
#--- End: def
[docs] def chunk(self, chunksize=None):
'''
Partition the data array.
'''
if not chunksize:
# Set the default chunk size
chunksize = CHUNKSIZE()
# Partition the coordinate's data
super(Coordinate, self).chunk(chunksize)
# Partition the data of the bounds, if they exist.
if self._hasbounds:
self.bounds.chunk(chunksize)
#--- End: def
[docs] def clip(self, a_min, a_max, units=None, i=False):
'''
Clip (limit) the values in the data array and its bounds in place.
Given an interval, values outside the interval are clipped to the
interval edges.
Parameters :
a_min : scalar
a_max : scalar
units : str or Units
{+i}
:Returns:
None
:Examples:
'''
c = super(Coordinate, self).clip(a_min, a_max, units=units, i=i)
if c._hasbounds:
# Clip the bounds
c.bounds.clip(a_min, a_max, units=units, i=True)
return c
#--- End: def
[docs] def close(self):
'''
Close all files referenced by the coordinate.
Note that a closed file will be automatically reopened if its contents
are subsequently required.
:Returns:
None
:Examples:
>>> c.close()
'''
new = super(Coordinate, self).close()
if self._hasbounds:
self.bounds.close()
#--- End: def
@classmethod
def concatenate(cls, coordinates, axis=0, _preserve=True):
'''
Join a sequence of coordinates together.
:Returns:
out : cf.{+Variable}
'''
coord0 = coordinates[0]
if len(coordinates) == 1:
return coordinates0.copy()
out = Variable.concatenate(coordinates, axis=axis,
_preserve=_preserve)
if coord0._hasbounds:
bounds = Variable.concatenate(
[c.bounds for c in coordinates],
axis=axis, _preserve=_preserve)
out.insert_bounds(bounds, copy=False)
return out
#--- End: def
[docs] def contiguous(self, overlap=True):
'''
Return True if a coordinate is contiguous.
A coordinate is contiguous if its cell boundaries match up, or
overlap, with the boundaries of adjacent cells.
In general, it is only possible for 1 or 0 dimensional coordinates
with bounds to be contiguous, but size 1 coordinates with any number
of dimensions are always contiguous.
An exception occurs if the coordinate is multdimensional and has more
than one element.
:Parameters:
overlap : bool, optional
If False then overlapping cell boundaries are not considered
contiguous. By default cell boundaries are considered
contiguous.
:Returns:
out : bool
Whether or not the coordinate is contiguous.
:Raises:
ValueError :
If the coordinate has more than one dimension.
:Examples:
>>> c.hasbounds
False
>>> c.contiguous()
False
>>> print c.bounds[:, 0]
[ 0.5 1.5 2.5 3.5 ]
>>> print c.bounds[:, 1]
[ 1.5 2.5 3.5 4.5 ]
>>> c.contiuous()
True
>>> print c.bounds[:, 0]
[ 0.5 1.5 2.5 3.5 ]
>>> print c.bounds[:, 1]
[ 2.5 3.5 4.5 5.5 ]
>>> c.contiuous()
True
>>> c.contiuous(overlap=False)
False
'''
if not self._hasbounds:
return False
return self.bounds.contiguous(overlap=overlap, direction=self.direction)
# if monoyine:
# return self.monit()#
#
# return False
#--- End: def
[docs] def cos(self, i=False):
'''
Take the trigonometric cosine of the data array and bounds in place.
Units are accounted for in the calcualtion, so that the the cosine of
90 degrees_east is 0.0, as is the sine of 1.57079632 radians. If the
units are not equivalent to radians (such as Kelvin) then they are
treated as if they were radians.
The Units are changed to '1' (nondimensionsal).
:Parameters:
{+i}
:Returns:
out : cf.Coordinate
:Examples:
>>> c.Units
<CF Units: degrees_east>
>>> print c.array
[[-90 0 90 --]]
>>> c.cos()
>>> c.Units
<CF Units: 1>
>>> print c.array
[[0.0 1.0 0.0 --]]
>>> c.Units
<CF Units: m s-1>
>>> print c.array
[[1 2 3 --]]
>>> c.cos()
>>> c.Units
<CF Units: 1>
>>> print c.array
[[0.540302305868 -0.416146836547 -0.9899924966 --]]
'''
if i:
c = self
else:
c = self.copy()
super(Coordinate, c).cos(i=True)
if c._hasbounds:
c.bounds.cos(i=True)
return c
#--- End: def
def cyclic(self, axes=None, iscyclic=True):
'''
Set the cyclicity of axes of the data array and bounds.
.. seealso:: `cf.DimensionCoordinate.period`
:Parameters:
axes : (sequence of) int
The axes to be set. Each axis is identified by its integer
position. By default no axes are set.
iscyclic: bool, optional
:Returns:
out : list
:Examples:
'''
old = super(Coordinate, self).cyclic(axes, iscyclic)
if axes is not None and self._hasbounds:
axes = _parse_axes(axes)
self.bounds.cyclic(axes, iscyclic)
return old
#--- End: def
def tan(self, i=False):
'''
Take the trigonometric tangent of the data array and bounds in place.
Units are accounted for in the calculation, so that the the tangent of
180 degrees_east is 0.0, as is the sine of 3.141592653589793
radians. If the units are not equivalent to radians (such as Kelvin)
then they are treated as if they were radians.
The Units are changed to '1' (nondimensionsal).
:Parameters:
{+i}
:Returns:
out : cf.Coordinate
:Examples:
'''
if i:
c = self
else:
c = self.copy()
super(Coordinate, c).tan(i=True)
if c._hasbounds:
c.bounds.tan(i=True)
return c
#--- End: def
[docs] def copy(self, _omit_Data=False, _only_Data=False):
'''
Return a deep copy.
Equivalent to ``copy.deepcopy(c)``.
:Returns:
out :
The deep copy.
:Examples:
>>> d = c.copy()
'''
new = super(Coordinate, self).copy(_omit_Data=_omit_Data,
_only_Data=_only_Data,
_omit_special=('bounds',))
if self._hasbounds:
bounds = self.bounds.copy(_omit_Data=_omit_Data,
_only_Data=_only_Data)
new._set_special_attr('bounds', bounds)
return new
#--- End: def
[docs] def delprop(self, prop):
'''
Delete a CF property.
.. seealso:: `getprop`, `hasprop`, `setprop`
:Parameters:
prop : str
The name of the CF property.
:Returns:
None
:Examples:
>>> c.delprop('standard_name')
>>> c.delprop('foo')
AttributeError: Coordinate doesn't have CF property 'foo'
'''
# Delete a special attribute
if prop in self._special_properties:
delattr(self, prop)
return
# Still here? Then delete a simple attribute
# Delete selected simple properties from the bounds
if self._hasbounds and prop in ('standard_name', 'axis', 'positive',
'leap_month', 'leap_year',
'month_lengths'):
try:
self.bounds.delprop(prop)
except AttributeError:
pass
#--- End: if
d = self._private['simple_properties']
if prop in d:
del d[prop]
else:
raise AttributeError("Can't delete non-existent %s CF property %r" %
(self.__class__.__name__, prop))
if self._hasbounds and prop in ('standard_name', 'axis', 'positive',
'leap_month', 'leap_year',
'month_lengths'):
try:
self.bounds.delprop(prop)
except AttributeError:
pass
#--- End: def
def direction(self):
'''
Return None, indicating that it is not specified whether the
coordinate object is increasing or decreasing.
:Returns:
None
:Examples:
>>> print c.direction()
None
'''
return
#--- End: def
[docs] def dump(self, display=True, omit=(), domain=None, key=None, _level=0):
'''
Return a string containing a full description of the coordinate.
:Parameters:
display : bool, optional
If False then return the description as a string. By default
the description is printed, i.e. ``c.dump()`` is equivalent to
``print c.dump(display=False)``.
omit : sequence of strs
Omit the given CF properties from the description.
:Returns:
out : None or str
A string containing the description.
:Examples:
'''
indent0 = ' ' * _level
indent1 = ' ' * (_level+1)
string = []
if domain:
x = ['%s(%d)' % (domain.axis_name(axis), domain.axis_size(axis))
for axis in domain.item_axes(key)]
string.append('%sData(%s) = %s' % (indent0, ', '.join(x),
str(self.Data)))
if self._hasbounds:
x.append(str(self.bounds.shape[-1]))
string.append('%sBounds(%s) = %s' % (indent0, ', '.join(x),
str(self.bounds.Data)))
else:
x = [str(s) for s in self.shape]
string.append('%sData(%s) = %s' % (indent0, ', '.join(x),
str(self.Data)))
if self._hasbounds:
x.append(str(self.bounds.shape[-1]))
string.append('%sBounds(%s) = %s' % (indent0, ', '.join(x),
str(self.bounds.Data)))
#--- End: if
if self._simple_properties():
string.append(self._dump_simple_properties(_level=_level))
string = '\n'.join(string)
if display:
print string
else:
return string
#--- End: def
[docs] def expand_dims(self, position=0, i=False):
'''
Insert a size 1 axis into the data array and bounds in place.
.. seealso:: `flip`, `squeeze`, `transpose`
:Parameters:
position : int, optional
Specify the position amongst the data array axes where the new
axis is to be inserted. By default the new axis is inserted at
position 0, the slowest varying position.
{+i}
:Returns:
out : cf.Coordinate
'''
if (not self._hasData and
(not self._hasbounds or not self.bounds._hasData)):
raise ValueError(
"Can't insert axis into '%s'" % self.__class__.__name__)
if self._hasData:
c = super(Coordinate, self).expand_dims(position, i=i)
elif i:
c = self
else:
c = self.copy()
if c._hasbounds and c.bounds._hasData:
# Expand the coordinate's bounds
position = _parse_axes([position])[0]
c.bounds.expand_dims(position, i=True)
# if i:
# c = self
# else:
# c = self.copy()
#
# # Expand the coordinate's data, if it has any.
# if c._hasData:
## super(Coordinate, self).expand_dims(position, i=True)
# c.Data.expand_dims(position, i=True)
#
# # Expand the coordinate's bounds, if it has any.
# if c._hasbounds and c.bounds._hasData:
## c.bounds.expand_dims(position, i=True)
# c.bounds.Data.expand_dims(position, i=True)
return c
#--- End: def
[docs] def flip(self, axes=None, i=False):
'''
Flip dimensions of the data array and bounds in place.
The trailing dimension of the bounds is flipped if and only if the
coordinate is 1 or 0 dimensional.
:Parameters:
axes : (sequence of) int, optional
Flip the dimensions whose positions are given. By default all
dimensions are flipped.
{+i}
:Returns:
out : cf.Coordinate
:Examples:
>>> c.flip()
>>> c.flip(1)
>>> d = c.subspace[::-1, :, ::-1, :]
>>> c.flip([2, 0]).equals(d)
True
'''
c = super(Coordinate, self).flip(axes, i=i)
# ------------------------------------------------------------
# Flip the requested dimensions in the coordinate's bounds, if
# it has any.
#
# As per section 7.1 in the CF conventions: i) if the
# coordinate is 0 or 1 dimensional then flip all dimensions
# (including the the trailing size 2 dimension); ii) if the
# coordinate has 2 or more dimensions then do not flip the
# trailing dimension.
# ------------------------------------------------------------
if c._hasbounds and c.bounds._hasData:
# Flip the bounds
if not c.ndim:
# Flip the bounds of 0-d coordinates
axes = (-1,)
elif c.ndim == 1:
# Flip the bounds of 1-d coordinates
if axes in (0, -1):
axes = (0, -1)
elif axes is not None:
axes = _parse_axes(axes) + [-1]
else:
# Do not flip the bounds of N-d coordinates (N >= 2)
axes = _parse_axes(axes)
c.bounds.flip(axes, i=True)
#--- End if
direction = c._direction
if direction is not None:
c._direction = not direction
return c
#--- End: def
[docs] def insert_bounds(self, bounds, copy=True):
'''
Insert cell bounds into the coordinate in-place.
:Parameters:
bounds : cf.Data or cf.CoordinateBounds
copy : bool, optional
:Returns:
None
'''
# Check dimensionality
if bounds.ndim != self.ndim + 1:
raise ValueError(
"Can't set coordinate bounds: Incorrect number of dimemsions: %d (expected %d)" %
(bounds.ndim, self.ndim+1))
# Check shape
if bounds.shape[:-1] != self.shape:
raise ValueError(
"Can't set coordinate bounds: Incorrect shape: %s (expected %s)" %
(bounds.shape, self.shape+(bounds.shape[-1],)))
if copy:
bounds = bounds.copy()
# Check units
units = bounds.Units
self_units = self.Units
if units and not units.equivalent(self_units):
raise ValueError(
"Can't set coordinate bounds: Incompatible units: %r (not equivalent to %r)" %
(bounds.Units, self.Units))
bounds.Units = self_units
if not isinstance(bounds, CoordinateBounds):
bounds = CoordinateBounds(data=bounds, copy=False)
# Copy selected coordinate properties to the bounds
for prop in ('standard_name', 'axis', 'positive', 'leap_months',
'leap_years', 'month_lengths'):
value = self.getprop(prop, None)
if value is not None:
bounds.setprop(prop, value)
self._set_special_attr('bounds', bounds)
self._hasbounds = True
self._direction = None
#--- End: def
[docs] def insert_data(self, data, bounds=None, copy=True):
'''
Insert a new data array into the coordinate in place.
A coordinate bounds data array may also inserted if given with the
*bounds* keyword. Coordinate bounds may also be inserted independently
with the `insert_bounds` method.
:Parameters:
data : cf.Data
bounds : cf.Data, optional
copy : bool, optional
:Returns:
None
'''
if data is not None:
super(Coordinate, self).insert_data(data, copy=copy)
if bounds is not None:
self.insert_bounds(bounds, copy=copy)
self._direction = None
#--- End: def
[docs] def override_units(self, new_units, i=False):
'''
{+i}
'''
if i:
c = self
else:
c = self.copy()
super(Coordinate, c).override_units(new_units, i=True)
if c._hasbounds:
c.bounds.override_units(new_units, i=True)
if c._period is not None:
# Never change _period in place
c._period.override_units(new_units, i=False)
return c
#--- End: def
def roll(self, axis, shift, i=False):
'''
{+i}
'''
if self.size <= 1:
if i:
return self
else:
return self.copy()
c = super(Coordinate, self).roll(axis, shift, i=i)
# Roll the bounds, if there are any
if c._hasbounds:
b = c.bounds
if b._hasData:
b.roll(axis, shift, i=True)
#--- End: if
return c
#--- End: def
[docs] def setprop(self, prop, value):
'''
Set a CF property.
.. seealso:: `delprop`, `getprop`, `hasprop`
:Parameters:
prop : str
The name of the CF property.
value :
The value for the property.
:Returns:
None
:Examples:
>>> c.setprop('standard_name', 'time')
>>> c.setprop('foo', 12.5)
'''
# Set a special attribute
if prop in self._special_properties:
setattr(self, prop, value)
return
# Still here? Then set a simple property
self._private['simple_properties'][prop] = value
# Set selected simple properties on the bounds
if self._hasbounds and prop in ('standard_name', 'axis', 'positive',
'leap_month', 'leap_year',
'month_lengths'):
self.bounds.setprop(prop, value)
#--- End: def
[docs] def sin(self, i=False):
'''
Take the trigonometric sine of the data array and bounds in place.
Units are accounted for in the calculation. For example, the the sine
of 90 degrees_east is 1.0, as is the sine of 1.57079632 radians. If
the units are not equivalent to radians (such as Kelvin) then they are
treated as if they were radians.
The Units are changed to '1' (nondimensionsal).
:Parameters:
{+i}
:Returns:
out : cf.Coordinate
:Examples:
>>> c.Units
<CF Units: degrees_north>
>>> print c.array
[[-90 0 90 --]]
>>> c.sin()
>>> c.Units
<CF Units: 1>
>>> print c.array
[[-1.0 0.0 1.0 --]]
>>> c.Units
<CF Units: m s-1>
>>> print c.array
[[1 2 3 --]]
>>> c.sin()
>>> c.Units
<CF Units: 1>
>>> print c.array
[[0.841470984808 0.909297426826 0.14112000806 --]]
'''
if i:
c = self
else:
c = self.copy()
super(Coordinate, c).sin(i=True)
if c._hasbounds:
c.bounds.sin(i=True)
return c
#--- End: def
def log(self, base=10, i=False):
'''
Take the logarithm the data array and bounds element-wise.
:Parameters:
base : number, optional
{+i}
:Returns:
out : cf.Coordinate
'''
if i:
c = self
else:
c = self.copy()
super(Coordinate, c).log(base, i=True)
if c._hasbounds:
c.bounds.log(base, i=True)
return c
#--- End: def
[docs] def squeeze(self, axes=None, i=False):
'''
Remove size 1 dimensions from the data array and bounds in place.
.. seealso:: `expand_dims`, `flip`, `transpose`
:Parameters:
axes : (sequence of) int, optional
The size 1 axes to remove. By default, all size 1 axes are
removed. Size 1 axes for removal may be identified by the
integer positions of dimensions in the data array.
{+i}
:Returns:
out : cf.Coordinate
:Examples:
>>> c.squeeze()
>>> c.squeeze(1)
>>> c.squeeze([1, 2])
'''
c = super(Coordinate, self).squeeze(axes, i=i)
if c._hasbounds and c.bounds._hasData:
# Squeeze the bounds
axes = _parse_axes(axes)
c.bounds.squeeze(axes, i=True)
return c
#--- End: def
[docs] def transpose(self, axes=None, i=False):
'''
Permute the dimensions of the data array and bounds in place.
.. seealso:: `expand_dims`, `flip`, `squeeze`
:Parameters:
axes : (sequence of) int, optional
The new order of the data array. By default, reverse the
dimensions' order, otherwise the axes are permuted according
to the values given. The values of the sequence comprise the
integer positions of the dimensions in the data array in the
desired order.
{+i}
:Returns:
out : cf.Coordinate
:Examples:
>>> c.ndim
3
>>> c.transpose()
>>> c.transpose([1, 2, 0])
'''
c = super(Coordinate, self).transpose(axes, i=i)
ndim = c.ndim
if c._hasbounds and ndim > 1 and c.bounds._hasData:
# Transpose the bounds
if axes is None:
axes = range(ndim-1, -1, -1) + [-1]
else:
axes = _parse_axes(axes) + [-1]
bounds = c.bounds
bounds.transpose(axes, i=True)
if (ndim == 2 and
bounds.shape[-1] == 4 and
axes[0] == 1 and
(c.Units.islongitude or c.Units.islatitude or
c.getprop('standard_name', None) in ('grid_longitude' or
'grid_latitude'))):
# Swap columns 1 and 3 so that the coordinates are
# still contiguous (if they ever were). See section
# 7.1 of the CF conventions.
bounds.subspace[..., [1, 3]] = bounds.subspace[..., [3, 1]]
#--- End: if
return c
#--- End: def
#--- End: class
# ====================================================================
#
# SubspaceCoordinate object
#
# ====================================================================
class SubspaceCoordinate(SubspaceVariable):
__slots__ = []
def __getitem__(self, indices):
'''
x.__getitem__(indices) <==> x[indices]
'''
coord = self.variable
if indices is Ellipsis:
return coord.copy()
indices, roll = parse_indices(coord, indices, True)
if roll:
data = coord.Data
axes = data._axes
cyclic_axes = data._cyclic
for iaxis, shift in roll.iteritems():
if axes[iaxis] not in cyclic_axes:
raise IndexError(
"Can't do a cyclic slice on a non-cyclic axis")
coord = coord.roll(iaxis, shift)
#--- End: for
new = coord
else:
new = coord.copy(_omit_Data=True)
# # Copy the coordinate
# new = coord.copy(_omit_Data=True)
# # Parse the index (so that it's ok for appending the bounds
# # index if required)
# indices = parse_indices(coord, indices)
coord_data = coord.Data
new.Data = coord_data[tuple(indices)]
# Subspace the bounds, if there are any
if not new._hasbounds:
bounds = None
else:
bounds = coord.bounds
if bounds._hasData:
if coord_data.ndim <= 1:
index = indices[0]
if isinstance(index, slice):
if index.step < 0:
# This scalar or 1-d coordinate has been
# reversed so reverse its bounds (as per
# 7.1 of the conventions)
indices.append(slice(None, None, -1))
elif coord_data.size > 1 and index[-1] < index[0]:
# This 1-d coordinate has been reversed so
# reverse its bounds (as per 7.1 of the
# conventions)
indices.append(slice(None, None, -1))
#--- End: if
new.bounds.Data = bounds.Data[tuple(indices)]
#--- End: if
new._direction = None
# Return the new coordinate
return new
#--- End: def
#--- End: class
# ====================================================================
#
# DimensionCoordinate object
#
# ====================================================================
[docs]class DimensionCoordinate(Coordinate):
'''
A CF dimension coordinate construct.
**Attributes**
=============== ======== ===================================================
Attribute Type Description
=============== ======== ===================================================
`!climatology` ``bool`` Whether or not the bounds are intervals of
climatological time. Presumed to be False if unset.
=============== ======== ===================================================
'''
# def _query_contain(self, value):
# '''#
#
#'''
# if not self._hasbounds:
# return self == value#
#
# return (self.lower_bounds <= value) & (self.upper_bounds >= value)
# #--- End: def
def _centre(self, period):
'''
It assumed, but not checked, that the period has been set.
'''
if self.direction():
mx = self.Data[-1]
else:
mx = self.Data[0]
return ((mx // period) * period).squeeze(i=True)
#--- End: def
def _infer_direction(self):
'''
Return True if a coordinate is increasing, otherwise return False.
A coordinate is considered to be increasing if its *raw* data array
values are increasing in index space or if it has no data not bounds
data.
If the direction can not be inferred from the coordinate's data then
the coordinate's units are used.
The direction is inferred from the coordinate's data array values or
its from coordinates. It is not taken directly from its `cf.Data`
object.
:Returns:
out : bool
Whether or not the coordinate is increasing.
:Examples:
>>> c.array
array([ 0 30 60])
>>> c._get_direction()
True
>>> c.array
array([15])
>>> c.bounds.array
array([ 30 0])
>>> c._get_direction()
False
'''
if self._hasData:
# Infer the direction from the dimension coordinate's data
# array
c = self.Data
if c._size > 1:
c = c[0:2].unsafe_array
return c.item(0,) < c.item(1,)
#--- End: if
# Still here?
if self._hasbounds:
# Infer the direction from the dimension coordinate's
# bounds
b = self.bounds
if b._hasData:
b = b.Data
b = b[(0,)*(b.ndim-1)].unsafe_array
return b.item(0,) < b.item(1,)
#--- End: if
# # Still here? Then infer the direction from the dimension
# # coordinate's positive CF property.
# positive = self.getprop('positive', None)
# if positive is not None and positive[0] in 'dD':
# return False
#
# Still here? Then infer the direction from the units.
return not self.Units.ispressure
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def cellsize(self):
'''
A `cf.Data` object containing the coordinate cell sizes.
:Examples:
>>> print c.bounds
<CF CoordinateBounds: latitude(47, 2) degrees_north>
>>> print c.bounds.array
[[-90. -87.]
[-87. -80.]
[-80. -67.]]
>>> print d.cellsize
<CF Data: [3.0, ..., 13.0] degrees_north>
>>> print d.cellsize.array
[ 3. 7. 13.]
>>> print c.sin().cellsize.array
[ 0.00137047 0.01382178 0.0643029 ]
>>> del c.bounds
>>> c.cellsize
AttributeError: Can't get cell sizes when coordinates have no bounds
'''
if not self._hasbounds:
raise AttributeError(
"Can't get cell sizes when coordinates have no bounds")
cells = self.bounds.data
# if bounds_range is not None:
# bounds_range = Data.asdata(bounds_range)#
#
# if not bounds_range.Units:
# bounds_range = bounds_range.override_units(self.Units)
# cells.clip(*bounds_range, units=bounds_range.Units, i=True)
# #--- End: if
if self.direction():
cells = cells[:, 1] - cells[:, 0]
else:
cells = cells[:, 0] - cells[:, 1]
cells.squeeze(1, i=True)
# if units:
# if cells.Units.equivalent(units):
# cells.Units = units
# else:
# raise ValueError("sdfm 845 &&&&")
return cells
#--- End: def
@property
def decreasing(self):
'''
True if the dimension coordinate is increasing, otherwise
False.
A dimension coordinate is increasing if its coordinate values are
increasing in index space.
The direction is inferred from one of, in order of precedence:
* The data array
* The bounds data array
* The `units` CF property
:Returns:
out : bool
Whether or not the coordinate is increasing.
True for dimension coordinate constructs, False otherwise.
>>> c.decreasing
False
>>> c.flip().increasing
True
'''
return not self.direction()
#--- End: def
@property
def increasing(self):
'''
True for dimension coordinate constructs, False otherwise.
>>> c.increasing
True
>>> c.flip().increasing
False
'''
return self.direction()
#--- End: def
@property
def isauxiliary(self):
'''
True for auxiliary coordinate constructs, False otherwise.
.. seealso:: `ismeasure`, `isdimension`
:Examples:
>>> c.isauxiliary
False
'''
return False
#--- End: def
@property
def isdimension(self):
'''
True for dimension coordinate constructs, False otherwise.
.. seealso:: `isauxiliary`, `ismeasure`
:Examples:
>>> c.isdimension
True
'''
return True
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def lower_bounds(self):
'''
The lower dimension coordinate bounds in a `cf.Data` object.
.. seealso:: `bounds`, `upper_bounds`
:Examples:
>>> print c.bounds.array
[[ 5 3]
[ 3 1]
[ 1 -1]]
>>> c.lower_bounds
<CF Data: [3, ..., -1]>
>>> print c.lower_bounds.array
[ 3 1 -1]
'''
if not self._hasbounds or not self.bounds._hasData:
raise ValueError("Can't get lower bounds when there are no bounds")
if self.direction():
i = 0
else:
i = 1
return self.bounds.data[..., i].squeeze(1, i=True)
#--- End: def
# ----------------------------------------------------------------
# Attribute (read only)
# ----------------------------------------------------------------
@property
def upper_bounds(self):
'''
The upper dimension coordinate bounds in a `cf.Data` object.
.. seealso:: `bounds`, `lower_bounds`
:Examples:
>>> print c.bounds.array
[[ 5 3]
[ 3 1]
[ 1 -1]]
>>> c.upper_bounds
<CF Data: [5, ..., 1]>
>>> c.upper_bounds.array
array([5, 3, 1])
'''
if not self._hasbounds or not self.bounds._hasData:
raise ValueError("Can't get upper bounds when there are no bounds")
if self.direction():
i = 1
else:
i = 0
return self.bounds.data[..., i].squeeze(1, i=True)
#--- End: def
[docs] def asdimension(self, copy=True):
'''
Return the dimension coordinate.
:Parameters:
copy : bool, optional
If False then the returned dimension coordinate is not
independent. By default the returned dimension coordinate is
independent.
:Returns:
out : cf.DimensionCoordinate
The dimension coordinate.
:Examples:
>>> d = c.asdimension()
>>> print d is c
True
>>> d = c.asdimension(copy=False)
>>> print d == c
True
>>> print d is c
False
'''
if copy:
return self.copy()
return self
#--- End: def
[docs] def direction(self):
'''
Return True if the dimension coordinate is increasing, otherwise
return False.
A dimension coordinate is increasing if its coordinate values are
increasing in index space.
The direction is inferred from one of, in order of precedence:
* The data array
* The bounds data array
* The `units` CF property
:Returns:
out : bool
Whether or not the coordinate is increasing.
:Examples:
>>> c.array
array([ 0 30 60])
>>> c.direction()
True
>>> c.bounds.array
array([ 30 0])
>>> c.direction()
False
'''
_direction = self._direction
if _direction is not None:
return _direction
_direction = self._infer_direction()
self._direction = _direction
return _direction
#--- End: def
# DimensionCoordinate
[docs] def get_bounds(self, create=False, insert=False, bound=None, copy=True):
'''
Get the cell bounds.
Either return its existing bounds or, if there are none, optionally
create bounds based on the coordinate array values.
:Parameters:
create : bool, optional
If True then create bounds if and only if the the dimension
coordinate does not already have them. Bounds for Voronoi
cells are created unless *bound* is set.
insert : bool, optional
If True then insert the created bounds into the coordinate in
place. By default the created bounds are not inserted. Ignored
if *create* is not True.
bound : *optional*
If set to an upper or lower bound for the whole array, then
bounds are created which include this value and for which each
array element is in the centre of its bounds. By default
bounds for Voronoi cells are created. Ignored if *create* is
not True.
copy : bool, optional
If False then the returned bounds are not independent of the
existing bounds, if any, or those inserted, if *create* and
*insert* are both True. By default the returned bounds are
independent.
:Returns:
out : cf.CoordinateBounds
The existing or created bounds.
:Examples:
>>> c.get_bounds()
>>> c.get_bounds(create=True)
>>> c.get_bounds(create=True, bound=60)
>>> c.get_bounds(create=True, insert=True)
>>> c.get_bounds(create=True, bound=-9000.0, insert=True, copy=False)
'''
if self._hasbounds:
if copy:
return self.bounds.copy()
else:
return self.bounds
if not create:
raise ValueError(
"Dimension coordinates have no bounds and create=%s" % create)
array = self.unsafe_array
size = array.size
if bound is None:
# Creat Voronoi bounds
if size < 2:
raise ValueError("Can't create bounds for Voronoi cells from one value")
bounds_1d = [array.item(0,)*1.5 - array.item(1,)*0.5]
bounds_1d.extend((array[0:-1] + array[1:])*0.5)
bounds_1d.append(array.item(-1,)*1.5 - array.item(-2,)*0.5)
dtype = type(bounds_1d[0])
else:
direction = self.direction()
if not direction and size > 1:
array = array[::-1]
bounds_1d = [bound]
if bound <= array.item(0,):
for i in xrange(size):
bound = 2.0*array.item(i,) - bound
bounds_1d.append(bound)
dtype = type(bounds_1d[-1])
elif bound >= array.item(-1,):
for i in xrange(size-1, -1, -1):
bound = 2.0*array.item(i,) - bound
bounds_1d.append(bound)
dtype = type(bounds_1d[-1])
bounds_1d = bounds_1d[::-1]
else:
raise ValueError("bad bound value")
if not direction:
bounds_1d = bounds_1d[::-1]
#--- End: if
bounds = numpy_empty((size, 2), dtype=dtype)
bounds[:,0] = bounds_1d[:-1]
bounds[:,1] = bounds_1d[1:]
bounds = CoordinateBounds(data=Data(bounds, self.Units), copy=False)
if insert:
self.insert_bounds(bounds, copy=copy)
return bounds
#--- End: def
[docs] def period(self, *value):
'''Set the period for cyclic coordinates.
:Parameters:
value : data-like or None, optional
The period. The absolute value is used.
:Returns:
out : cf.Data or None
The period prior to the change, or the current period if no
*value* was specified. In either case, None is returned if the
period had not been set previously.
:Examples:
>>> print c.period()
None
>>> c.Units
<CF Units: degrees_east>
>>> print c.period(360)
None
>>> c.period()
<CF Data: 360.0 'degrees_east'>
>>> import math
>>> c.period(cf.Data(2*math.pi, 'radians'))
<CF Data: 360.0 degrees_east>
>>> c.period()
<CF Data: 6.28318530718 radians>
>>> c.period(None)
<CF Data: 6.28318530718 radians>
>>> print c.period()
None
>>> print c.period(-360)
None
>>> c.period()
<CF Data: 360.0 degrees_east>
'''
old = self._period
if old is not None:
old = old.copy()
if not value:
return old
value = value[0]
if value is not None:
value = Data.asdata(abs(value*1.0))
units = value.Units
if not units:
value = value.override_units(self.Units)
elif not units.equivalent(self.Units):
raise ValueError(
"Period units (%r) are not equivalent to coordinate units (%r)" %
units, self.Units)
range = self.Data.range()
if range >= value:
raise ValueError(
"The coordinates' range (%r) is not less than the period (%r)" %
range, value)
#--- End: if
self._period = value
return old
#--- End: def
def roll(self, axis, shift, i=False):
'''
{+i}
'''
if self.size <= 1:
if i:
return self
else:
return self.copy()
shift %= self.size
period = self._period
if not shift:
# Null roll
if i:
return self
else:
return self.copy()
elif period is None:
raise ValueError(
"Can't roll %s array by %s positions when no period has been set" %
(shift, self.__class__.__name__))
direction = self.direction()
# if direction:
# mx = self.Data[-1]
# else:
# mx = self.Data[0]
#
# centre = (mx // period) * period
centre = self._centre(period)
c = super(DimensionCoordinate, self).roll(axis, shift, i=i)
isbounded = c._hasbounds
if isbounded:
b = c.bounds
if not b._hasData:
isbounded = False
#--- End: if
if direction:
# Increasing
c.subspace[:shift] -= period
if isbounded:
b.subspace[:shift] -= period
if c.Data[0] <= centre - period:
c += period
if isbounded:
b += period
else:
# Decreasing
c.subspace[:shift] += period
if isbounded:
b.subspace[:shift] += period
if c.Data[0] >= centre + period:
c -= period
if isbounded:
b -= period
#--- End: if
c._direction = direction
#
#
# if self.direction():
# indices = c > c.subspace[-1]
# else:
# indices = c > c.subspace[0]
#
# c.setdata(c - period, None, indices)
#
# isbounded = c._hasbounds
# if isbounded:
# b = c.bounds
# if b._hasData:
# indices.expand_dims(1, i=True)
# b.setdata(b - period, None, indices)
# else:
# isbounded = False
# #--- End: if
#
# shift = None
# if self.direction():
# # Increasing
# if c.datum(0) <= centre - period:
# shift = period
## c += period
# elif c.datum(-1) >= centre + period:
# shift = -period
## c -= period
# else:
# # Decreasing
# if c.datum(0) >= centre + period:
# shift = -period
## c -= period
# elif c.datum(-1) <= centre - period:
# shift = period
## c += period
# #--- End: if
#
# if shift:
# c += shift
# if isbounded:
# b += shift
# #--- End: if
return c
#--- End: def
#--- End: class
# ====================================================================
#
# AuxiliaryCoordinate object
#
# ====================================================================
[docs]class AuxiliaryCoordinate(Coordinate):
'''
A CF auxiliary coordinate construct.
**Attributes**
=============== ======== ===================================================
Attribute Type Description
=============== ======== ===================================================
`!climatology` ``bool`` Whether or not the bounds are intervals of
climatological time. Presumed to be False if unset.
=============== ======== ===================================================
'''
@property
def isauxiliary(self):
'''
True for auxiliary coordinate constructs, False otherwise.
.. seealso:: `ismeasure`, `isdimension`
:Examples:
>>> c.isauxiliary
True
'''
return True
#--- End: def
@property
def isdimension(self):
'''
True for dimension coordinate constructs, False otherwise.
.. seealso:: `isauxiliary`, `ismeasure`
:Examples:
>>> c.isdimension
False
'''
return False
#--- End: def
[docs] def asauxiliary(self, copy=True):
'''
Return the auxiliary coordinate.
:Parameters:
copy : bool, optional
If False then the returned auxiliary coordinate is not
independent. By default the returned auxiliary coordinate is
independent.
:Returns:
out : cf.AuxiliaryCoordinate
The auxiliary coordinate.
:Examples:
>>> d = c.asauxiliary()
>>> print d is c
True
>>> d = c.asauxiliary(copy=False)
>>> print d == c
True
>>> print d is c
False
'''
if copy:
return self.copy()
return self
#--- End: def
#--- End: class
def _parse_axes(axes):
if axes is None:
return axes
return [(i + ndim if i < 0 else i) for i in axes]