Source code for skdiveMove.tdrsource

"""Base definition of the TDR input data

"""

import pandas as pd
from skdiveMove.helpers import (get_var_sampling_interval,
                                _append_xr_attr, _load_dataset)

_SPEED_NAMES = ["velocity", "speed"]


class TDRSource:
    """Define TDR data source

    Use :class:`~xarray.Dataset` to ensure pseudo-standard metadata

    Attributes
    ----------
    tdr_file : str
        String indicating the file where the data comes from.
    tdr : xarray.Dataset
        Dataset with input data.
    depth_name : str
        Name of data variable with depth measurements.
    time_name : str
        Name of the time dimension in the dataset.
    has_speed : bool
        Whether input data include speed measurements.
    speed_name : str
        Name of data variable with the speed measurements.

    Examples
    --------
    >>> from skdiveMove.tests import diveMove2skd
    >>> tdrX = diveMove2skd()
    >>> print(tdrX)  # doctest: +ELLIPSIS
    Time-Depth Recorder -- Class TDR object ...

    """
    def __init__(self, dataset, depth_name="depth", time_name="timestamp",
                 subsample=None, has_speed=False, tdr_filename=None):
        """Set up attributes for TDRSource objects

        Parameters
        ----------
        dataset : xarray.Dataset
            Dataset containing depth, and optionally other DataArrays.
        depth_name : str, optional
            Name of data variable with depth measurements.
        time_name : str, optional
            Name of the time dimension in the dataset.
        subsample : str, optional
            Subsample dataset at given frequency specification.  See pandas
            offset aliases, as in :func:`pandas.Timedelta`.
        has_speed : bool, optional
            Weather data includes speed measurements. Column name must be
            one of ["velocity", "speed"].
        tdr_filename : str
            Name of the file from which `dataset` originated.

        """
        self.time_name = time_name
        if subsample is not None:
            self.tdr = (dataset.resample({time_name: subsample})
                        .interpolate("linear"))
            for vname, da in self.tdr.data_vars.items():
                da.attrs["sampling_rate"] = (1.0 /
                                             pd.to_timedelta(subsample)
                                             .seconds)
                da.attrs["sampling_rate_units"] = "Hz"
                _append_xr_attr(da, "history",
                                "Resampled to {}\n".format(subsample))
        else:
            self.tdr = dataset
        self.depth_name = depth_name
        speed_var = [x for x in list(self.tdr.data_vars.keys())
                     if x in _SPEED_NAMES]
        if speed_var and has_speed:
            self.has_speed = True
            self.speed_name = speed_var[0]
        else:
            self.has_speed = False
            self.speed_name = None

        self.tdr_file = tdr_filename

    @classmethod
    def read_netcdf(cls, tdr_file, depth_name="depth", time_name="timestamp",
                    subsample=None, has_speed=False, **kwargs):
        """Instantiate object by loading Dataset from NetCDF file

        Parameters
        ----------
        tdr_file : str
            As first argument for :func:`xarray.load_dataset`.
        depth_name : str, optional
            Name of data variable with depth measurements. Default: "depth".
        time_name : str, optional
            Name of the time dimension in the dataset.
        subsample : str, optional
            Subsample dataset at given frequency specification.  See pandas
            offset aliases.
        has_speed : bool, optional
            Weather data includes speed measurements. Column name must be
            one of ["velocity", "speed"].  Default: False.
        **kwargs
            Optional keyword arguments passed to :func:`xarray.load_dataset`.

        See Also
        --------
        xarray.Dataset, xarray.load_dataset

        Returns
        -------
        obj : TDRSource, ZOC, TDRPhases, or TDR
            Class matches the caller.

        """
        dataset = _load_dataset(tdr_file, **kwargs)
        return cls(dataset, depth_name=depth_name, time_name=time_name,
                   subsample=subsample, has_speed=has_speed,
                   tdr_filename=tdr_file)

    def __str__(self):
        x = self.tdr
        depth_xr = x[self.depth_name]
        depth_ser = depth_xr.to_series()
        objcls = ("Time-Depth Recorder -- Class {} object\n"
                  .format(self.__class__.__name__))
        src = "{0:<20} {1}\n".format("Source File", self.tdr_file)
        itv = ("{0:<20} {1}\n"
               .format("Sampling interval",
                       get_var_sampling_interval(depth_xr)))
        nsamples = "{0:<20} {1}\n".format("Number of Samples",
                                          depth_xr.shape[0])
        beg = "{0:<20} {1}\n".format("Sampling Begins",
                                     depth_ser.index[0])
        end = "{0:<20} {1}\n".format("Sampling Ends",
                                     depth_ser.index[-1])
        dur = "{0:<20} {1}\n".format("Total duration",
                                     depth_ser.index[-1] -
                                     depth_ser.index[0])
        drange = "{0:<20} [{1},{2}]\n".format("Measured depth range",
                                              depth_ser.min(),
                                              depth_ser.max())
        others = "{0:<20} {1}\n".format("Other variables",
                                        [x for x in list(x.keys())
                                         if x != self.depth_name])
        attr_list = "Attributes:\n"
        for key, val in sorted(x.attrs.items()):
            attr_list += "{0:>35}: {1}\n".format(key, val)
        attr_list = attr_list.rstrip("\n")

        return (objcls + src + itv + nsamples + beg + end + dur + drange +
                others + attr_list)

    def _get_depth(self):
        return self.tdr[self.depth_name]

    depth = property(_get_depth)
    """Return depth array

    Returns
    -------
    xarray.DataArray

    """

    def _get_speed(self):
        return self.tdr[self.speed_name]

    speed = property(_get_speed)
    """Return speed array

    Returns
    -------
    xarray.DataArray

    """