from dataset import Dataset from group import Group from field import Field import numpy class Dimension(object): """ A Dimension is used to define the array shape of a Variable. It may be shared among Variables, which provides a simple yet powerful way of associating Variables. When a Dimension is shared, it has a unique name within the Group. If unlimited, a Dimension's length may increase. If variableLength, then the actual length is data dependent, and can only be found by reading the data. A variableLength Dimension cannot be shared or unlimited. """ def __init__(self, name, length, unlimited=False, shared=True, variableLength=False): """ Create a dimenion instance with a given name and length >>> d1 = Dimension('latitude', 90) >>> print d1 >>> print d1.isUnlimited False >>> print d1.isShared True >>> print d1.isVariableLength False """ self.name = name self.length = length self._unlimited = unlimited self._shared = shared self._variableLength = variableLength @property def isUnlimited(self): return self._unlimited @property def isShared(self): return self._shared @property def isVariableLength(self): return self._variableLength def __repr__(self): return "" % (self.__class__.__name__, self.name, self.length) class Variable(object): """ A Variable is a container for data. It has a DataType, a set of Dimensions that define its array shape, and optionally a set of Attributes. Any shared Dimension it uses must be in the same Group or a parent Group. """ def __init__(self, name, group, dimensions=[], attributes={}, dtype=numpy.float32, data=None): """ Create a variable instance with given name and group and optional ordered list of dimensions, attributes dictionary and alternative data type (dtype). dtype defaults to numpy.float32. If a dimension is a dimension instance then if it is shared it will be added to the groups dimension list and the local reference will be replace by the dimension name. If it is not shared the local reference will remain the dimension instance. If a dimension is a string then a dimension by that name will be searched for in the groups dimension list. Failure to find a match will throw an exception. >>> ds = Dataset() >>> group = Group('', ds) >>> lat = Dimension('latitude', 90) >>> lon = Dimension('longitude', 180) >>> var = Variable('tmax', group, dimensions=[lat, lon], attributes={'units': 'degrees K'}) >>> print var >>> print var.attributes {'units': 'degrees K'} >>> print var.dimensions [, ] >>> var2 = Variable('tmax', group, dimensions=['latitude', 'longitude'], attributes={'units': 'degrees K'}) Dimensions can also be specified by name if they already exist in the group >>> print var2.dimensions [, ] >>> print var.shape (90, 180) """ self.name = name self.group = group self.attributes = attributes self.dtype = dtype self._dimensions = [] self._data = data # Process dimensions for dimension in dimensions: # If dimension is a Dimension instance if isinstance(dimension, Dimension): # If it is shared if dimension.isShared: # Try to add it to the groups dimension list if not dimension in self.group.dimensions: self.group.dimensions.append(dimension) # Replace the local reference by its name self._dimensions.append(dimension.name) # If its not shared else: self._dimensions.append(dimension) # If the dimension is a string elif isinstance(dimension, unicode) or isinstance(dimension, str): # If a dimension of this name is in the groups dimension list then we're okay if dimension in map(lambda d: d.name, self.group.dimensions): self._dimensions.append(dimension) @property def dimensions(self): result = [] for dimension in self._dimensions: # If its a string then we need to find the instance in the groups dimension list if isinstance(dimension, unicode) or isinstance(dimension, str): # Try to find a dimension of this name found = False for group_dimension in self.group.dimensions: if group_dimension.name == dimension: result.append(group_dimension) found = True break # If we don't find it then throw an exception if not found: result.append(None) # If its a dimension instance then just append it elif isinstance(dimension, Dimension): result.append(dimension) return result @property def shape(self): shape = tuple() for dimension in self.dimensions: shape = shape + (dimension.length, ) return shape def __repr__(self): return "" % (self.__class__.__name__, self.name) def get_attribute(self, name): """ Return the value of the attribute with given name or None if such an attribute does not exist """ if self.attributes.has_key(name): return self.attributes[name] else: return None def getAttribute(self, name): """ Depreciated: use get_attribute Return the value of the attribute with given name or None if such an attribute does not exist """ if self.attributes.has_key(name): return self.attributes[name] else: return None def __getitem__(self, slice): """ Returns a slice of the variables data array """ if self._data != None: return self._data[slice] else: return None def getSubset(self, **kwargs): print kwargs slices = [] for i in range(0,len(self.shape())): slices.append(slice(None, None)) print slices for key in kwargs: if (key in self.dimensions): index = self.dimensions.index(key) if (key in self.group.variables): vals = self.group.variables[key][:] # For single values if (type(kwargs[key]) != tuple): start = end = kwargs[key] match = True # for tuples elif (type(kwargs[key]) == tuple): start = kwargs[key][0] end = kwargs[key][1] # Special case for time slices if (key == 'time'): units = self.group.variables['time'].attributes['units'] calendar = self.group.variables['time'].attributes['calendar'] print units start = netCDF4.date2num(start, units, calendar) end = netCDF4.date2num(end, units, calendar) index0 = numpy.searchsorted(vals, start, "left") index1 = numpy.searchsorted(vals, end, "left") print vals print key, start, end, vals[index0], vals[index1] slices[index] = slice(index0, index1) print slices def asField(self): return Field(self) def __add__(self, other): """ Overloads the addition operator by delegating to the underlying numpy array addition >>> import handlers.netcdf4 as netcdf4 >>> import handlers.csag2 as csag2 >>> ds = netcdf4.netCDF4Dataset('../testdata/gfs.subset.nc') >>> tmax = ds.root.variables['TMAX_2maboveground'] >>> tmin = ds.root.variables['TMIN_2maboveground'] >>> t = tmin+tmax >>> print t """ # We can only do this if the two variables have the same shape if (self.shape == other.shape): # We need to construct a name for this variable name = '(%s+%s)' % (self.name, other.name) # Create the new variable new_variable = Variable(name, self.group, dimensions=self.dimensions, attributes=self.attributes) # Then we do the operation new_variable._data = self[:] + other[:] else: raise CDMError("Cannot add variables of different shapes") return new_variable def __sub__(self, other): """ Overloads the subtraction operator by delegating to the underlying numpy array addition >>> import handlers.netcdf4 as netcdf4 >>> import handlers.csag2 as csag2 >>> ds = netcdf4.netCDF4Dataset('../testdata/gfs.subset.nc') >>> tmax = ds.root.variables['TMAX_2maboveground'] >>> tmin = ds.root.variables['TMIN_2maboveground'] >>> t = tmax-tmin >>> print t >>> print tmin[2,45,60], tmax[2,45,60], t[2,45,60] 285.4 285.7 0.300018 """ # We can only do this if the two variables have the same shape if (self.shape == other.shape): # We need to construct a name for this variable name = '(%s-%s)' % (self.name, other.name) # Create the new variable new_variable = Variable(name, self.group, dimensions=self.dimensions, attributes=self.attributes) # Then we do the operation new_variable._data = self[:] - other[:] else: raise CDMError("Cannot add variables of different shapes") return new_variable