class SimplexCoverage(AbstractCoverage): """ A concrete implementation of AbstractCoverage consisting of 2 domains (temporal and spatial) and a collection of parameters associated with one or both of the domains. Each parameter is defined by a ParameterContext object (provided via the ParameterDictionary) and has content represented by a concrete implementation of the AbstractParameterValue class. """ def __init__(self, name, parameter_dictionary, spatial_domain, temporal_domain=None): """ Constructor for SimplexCoverage @param name The name of the coverage @param parameter_dictionary a ParameterDictionary object expected to contain one or more valid ParameterContext objects @param spatial_domain a concrete instance of AbstractDomain for the spatial domain component @param temporal_domain a concrete instance of AbstractDomain for the temporal domain component """ AbstractCoverage.__init__(self) self.name = name self.parameter_dictionary = parameter_dictionary self.spatial_domain = spatial_domain self.temporal_domain = temporal_domain or GridDomain(GridShape('temporal',[0]), CRS.standard_temporal(), MutabilityEnum.EXTENSIBLE) self.range_dictionary = DotDict() self.range_value = DotDict() self._pcmap = {} self._temporal_param_name = None if isinstance(self.parameter_dictionary, ParameterDictionary): for p in self.parameter_dictionary: self._append_parameter(self.parameter_dictionary.get_context(p)) def append_parameter(self, parameter_context): """ Append a ParameterContext to the coverage @deprecated use a ParameterDictionary during construction of the coverage """ log.warn('SimplexCoverage.append_parameter() is deprecated and will be removed shortly: use a ParameterDictionary during construction of the coverage') self._append_parameter(parameter_context) def _append_parameter(self, parameter_context): """ Appends a ParameterContext object to the internal set for this coverage. The supplied ParameterContext is added to self.range_dictionary. An AbstractParameterValue of the type indicated by ParameterContext.param_type is added to self.range_value. If the ParameterContext indicates that the parameter is a coordinate parameter, it is associated with the indicated axis of the appropriate CRS. @param parameter_context The ParameterContext to append to the coverage @throws StandardError If the ParameterContext.axis indicates that it is temporal and a temporal parameter already exists in the coverage """ pname = parameter_context.name # Determine the correct array shape (default is the shape of the spatial_domain) # If there is only one extent in the spatial domain and it's size is 0, collapse to time only # CBM TODO: This determination must be made based on the 'variability' of the parameter (temporal, spatial, both), not by assumption if len(self.spatial_domain.shape.extents) == 1 and self.spatial_domain.shape.extents[0] == 0: shp = self.temporal_domain.shape.extents else: shp = self.temporal_domain.shape.extents + self.spatial_domain.shape.extents # Assign the pname to the CRS (if applicable) and select the appropriate domain (default is the spatial_domain) dom = self.spatial_domain if not parameter_context.reference_frame is None and AxisTypeEnum.is_member(parameter_context.reference_frame, AxisTypeEnum.TIME): if self._temporal_param_name is None: self._temporal_param_name = pname else: raise StandardError("temporal_parameter already defined.") dom = self.temporal_domain shp = self.temporal_domain.shape.extents dom.crs.axes[parameter_context.reference_frame] = parameter_context.name elif parameter_context.reference_frame in self.spatial_domain.crs.axes: dom.crs.axes[parameter_context.reference_frame] = parameter_context.name self._pcmap[pname] = (len(self._pcmap), parameter_context, dom) self.range_dictionary[pname] = parameter_context self.range_value[pname] = RangeMember(shp, parameter_context) def get_parameter(self, param_name): """ Get a Parameter object by name The Parameter object contains the ParameterContext and AbstractParameterValue associated with the param_name @param param_name The local name of the parameter to return @returns A Parameter object containing the context and value for the specified parameter @throws KeyError The coverage does not contain a parameter with name 'param_name' """ if param_name in self.range_dictionary: p = Parameter(self.range_dictionary[param_name], self._pcmap[param_name][2], self.range_value[param_name]) return p else: raise KeyError('Coverage does not contain parameter \'{0}\''.format(param_name)) def list_parameters(self, coords_only=False, data_only=False): """ List the names of the parameters contained in the coverage @param coords_only List only the coordinate parameters @param data_only List only the data parameters (non-coordinate) - superseded by coords_only @returns A list of parameter names """ if coords_only: lst=[x for x, v in self.range_dictionary.iteritems() if v.is_coordinate] elif data_only: lst=[x for x, v in self.range_dictionary.iteritems() if not v.is_coordinate] else: lst=[x for x in self.range_dictionary.iterkeys()] lst.sort() return lst def insert_timesteps(self, count, origin=None): """ Insert count # of timesteps beginning at the origin The specified # of timesteps are inserted into the temporal value array at the indicated origin. This also expands the temporal dimension of the AbstractParameterValue for each parameters @param count The number of timesteps to insert @param origin The starting location, from which to begin the insertion """ if not origin is None: raise SystemError('Only append is currently supported') # Expand the shape of the temporal_dimension shp = self.temporal_domain.shape shp.extents[0] += count # Expand the temporal dimension of each of the parameters that are temporal TODO: Indicate which are temporal! for n in self._pcmap: arr = self.range_value[n].content pc = self.range_dictionary[n] narr = np.empty((count,) + arr.shape[1:], dtype=pc.param_type.value_encoding) narr.fill(pc.fill_value) arr = np.append(arr, narr, 0) self.range_value[n].content = arr def set_time_values(self, value, tdoa): """ Convenience method for setting time values @param value The value to set @param tdoa The temporal DomainOfApplication; default to full Domain """ return self.set_parameter_values(self._temporal_param_name, value, tdoa, None) def get_time_values(self, tdoa=None, return_value=None): """ Convenience method for retrieving time values Delegates to get_parameter_values, supplying the temporal parameter name and sdoa == None @param tdoa The temporal DomainOfApplication; default to full Domain @param return_value If supplied, filled with response value """ return self.get_parameter_values(self._temporal_param_name, tdoa, None, return_value) @property def num_timesteps(self): """ The current number of timesteps """ return self.temporal_domain.shape.extents[0] def set_parameter_values(self, param_name, value, tdoa=None, sdoa=None): """ Assign value to the specified parameter Assigns the value to param_name within the coverage. Temporal and spatial DomainOfApplication objects can be applied to constrain the assignment. See DomainOfApplication for details @param param_name The name of the parameter @param value The value to set @param tdoa The temporal DomainOfApplication @param sdoa The spatial DomainOfApplication @throws KeyError The coverage does not contain a parameter with name 'param_name' """ if not param_name in self.range_value: raise KeyError('Parameter \'{0}\' not found in coverage_model'.format(param_name)) tdoa = _get_valid_DomainOfApplication(tdoa, self.temporal_domain.shape.extents) sdoa = _get_valid_DomainOfApplication(sdoa, self.spatial_domain.shape.extents) log.debug('Temporal doa: %s', tdoa.slices) log.debug('Spatial doa: %s', sdoa.slices) slice_ = [] slice_.extend(tdoa.slices) slice_.extend(sdoa.slices) log.debug('Setting slice: %s', slice_) #TODO: Do we need some validation that slice_ is the same rank and/or shape as values? self.range_value[param_name][slice_] = value def get_parameter_values(self, param_name, tdoa=None, sdoa=None, return_value=None): """ Retrieve the value for a parameter Returns the value from param_name. Temporal and spatial DomainOfApplication objects can be used to constrain the response. See DomainOfApplication for details. @param param_name The name of the parameter @param tdoa The temporal DomainOfApplication @param sdoa The spatial DomainOfApplication @param return_value If supplied, filled with response value @throws KeyError The coverage does not contain a parameter with name 'param_name' """ if not param_name in self.range_value: raise KeyError('Parameter \'{0}\' not found in coverage'.format(param_name)) return_value = return_value or np.zeros([0]) tdoa = _get_valid_DomainOfApplication(tdoa, self.temporal_domain.shape.extents) sdoa = _get_valid_DomainOfApplication(sdoa, self.spatial_domain.shape.extents) log.debug('Temporal doa: %s', tdoa.slices) log.debug('Spatial doa: %s', sdoa.slices) slice_ = [] slice_.extend(tdoa.slices) slice_.extend(sdoa.slices) log.debug('Getting slice: %s', slice_) return_value = self.range_value[param_name][slice_] return return_value def get_parameter_context(self, param_name): """ Retrieve the ParameterContext object for the specified parameter @param param_name The name of the parameter for which to retrieve context @returns A ParameterContext object @throws KeyError The coverage does not contain a parameter with name 'param_name' """ if not param_name in self.range_dictionary: raise KeyError('Parameter \'{0}\' not found in coverage'.format(param_name)) return self.range_dictionary[param_name] @property def info(self): """ Returns a detailed string representation of the coverage contents @returns string of coverage contents """ lst = [] indent = ' ' lst.append('ID: {0}'.format(self._id)) lst.append('Name: {0}'.format(self.name)) lst.append('Temporal Domain:\n{0}'.format(self.temporal_domain.__str__(indent*2))) lst.append('Spatial Domain:\n{0}'.format(self.spatial_domain.__str__(indent*2))) lst.append('Parameters:') for x in self.range_value: lst.append('{0}{1} {2}\n{3}'.format(indent*2,x,self.range_value[x].shape,self.range_dictionary[x].__str__(indent*4))) return '\n'.join(lst) def __str__(self): lst = [] indent = ' ' lst.append('ID: {0}'.format(self._id)) lst.append('Name: {0}'.format(self.name)) lst.append('TemporalDomain: Shape=>{0} Axes=>{1}'.format(self.temporal_domain.shape.extents, self.temporal_domain.crs.axes)) lst.append('SpatialDomain: Shape=>{0} Axes=>{1}'.format(self.spatial_domain.shape.extents, self.spatial_domain.crs.axes)) lst.append('Coordinate Parameters: {0}'.format(self.list_parameters(coords_only=True))) lst.append('Data Parameters: {0}'.format(self.list_parameters(coords_only=False, data_only=True))) return '\n'.join(lst)