コード例 #1
0
ファイル: coverage.py プロジェクト: oceanzus/coverage-model
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)