Example #1
0
class Res1D:
    def __init__(self, file_path=None):
        self.file_path = file_path
        self.file = None
        self._closed = True
        self._time_index = None
        self._data_types = None
        self._reach_names = None
        self.__reaches = None
        # Load the file on initialization
        self._load_file()

    def _load_file(self):
        """Load the file."""
        if not os.path.exists(self.file_path):
            raise FileExistsError(f"File {self.file_path} does not exist.")
        self.file = ResultData()
        self.file.Connection = Connection.Create(self.file_path)
        self.file.Load()
        self._closed = False

    def close(self):
        """Close the file handle."""
        self.file.Dispose()
        self._closed = True

    def __enter__(self):
        return self

    def __exit__(self, *excinfo):
        self.close()

    @property
    @_not_closed
    def data_types(self):
        """List of the data types"""
        if self._data_types:
            return self._data_types
        return [q.Id for q in self.file.get_Quantities()]

    @property
    def _reaches(self):
        if self.__reaches:
            return self.__reaches
        return list(self.file.Reaches)

    @property
    @_not_closed
    def reach_names(self):
        """A list of the reach names"""
        if self._reach_names:
            return self._reach_names
        return [reach.Name for reach in self._reaches]

    @staticmethod
    def _chainages(reach, data_type_idx):
        """Generates chainages given a reach object and a data_type_idx"""
        data_item = list(reach.DataItems)[data_type_idx]
        index_list = list(data_item.IndexList)
        gridpoints = list(reach.GridPoints)
        gridpoints_filtered = [gridpoints[i] for i in index_list]
        for gp in gridpoints_filtered:
            yield float(gp.Chainage)

    @staticmethod
    def _data_types_reach(reach):
        """A list of the data types IDs contained in a reach."""
        return [di.get_Quantity().Id for di in list(reach.get_DataItems())]

    @property
    @_not_closed
    def time_index(self):
        """panda.DatetimeIndex of the time index"""
        if self._time_index:
            return self._time_index
        time_stamps = []
        for t in self.file.TimesList:
            time_stamps.append(
                pd.Timestamp(
                    year=t.get_Year(),
                    month=t.get_Month(),
                    day=t.get_Day(),
                    hour=t.get_Hour(),
                    minute=t.get_Minute(),
                    second=t.get_Second()
                )
            )
        self._time_index = pd.DatetimeIndex(time_stamps)
        return self._time_index

    def _get_values(self, points):
        df = pd.DataFrame()
        p = zip(points["variable"], points["reach"], points["chainage"])
        for variable_type, reach, chainage in p:
            d = (self.file.Reaches.get_Item(reach.index)
                 .get_DataItems()
                 .get_Item(variable_type.index)
                 .CreateTimeSeriesData(chainage.index))
            name = f"{variable_type.value} {reach.value} {chainage.value:.3f}"
            d = pd.Series(list(d), name=name)
            df[name] = d
        return df

    def _get_data(self, points):
        df = self._get_values(points)
        df.index = self.time_index
        return df

    def _validate_queries(self, queries, chainage_tolerance=0.1):
        """Check whether the queries point to existing data in the file."""
        for q in queries:
            # Raise an error if the data type is not found globally
            if q.variable_type not in self.data_types:
                raise DataNotFoundInFile(
                    f"Data type '{q.variable_type}' was not found.")
            if q.reach_name is not None:
                if q.reach_name not in self.reach_names:
                    raise DataNotFoundInFile(
                        f"Reach '{q.reach_name}' was not found.")
            if q.chainage is not None:
                found_chainage = False
                for reach in self._reaches:
                    if found_chainage:
                        break
                    # Look for the targeted reach
                    if q.reach_name != reach.Name:
                        continue
                    # Raise an error if the data type isn't found in this reach
                    data_types_in_reach = self._data_types_reach(reach)
                    if q.variable_type not in data_types_in_reach:
                        raise DataNotFoundInFile(
                            f"Data type '{q.variable_type}' was not found.")
                    data_type_idx = data_types_in_reach.index(q.variable_type)
                    for chainage in self._chainages(reach, data_type_idx):
                        # Look for the targeted chainage
                        chainage_diff = chainage - q.chainage
                        if abs(chainage_diff) < chainage_tolerance:
                            found_chainage = True
                            break
                if not found_chainage:
                    raise DataNotFoundInFile(
                        f"Chainage {q.chainage} was not found.")

    def _build_queries(self, queries):
        """"
        A query can be in an undefined state if reach_name and/or chainage
        isn't set. This function takes care of building lists of queries
        for these cases. Chainages are rounded to three decimal places.

        >>> self._build_queries([QueryData("WaterLevel", "reach1")])
        [
            QueryData("WaterLevel", "reach1", 0),
            QueryData("WaterLevel", "reach1", 10)
        ]
        """
        built_queries = []
        for q in queries:
            # e.g. QueryData("WaterLevel", "reach1", 1)
            if q.reach_name and q.chainage:
                built_queries.append(q)
                continue
            # e.g QueryData("WaterLevel", "reach1") or QueryData("WaterLevel")
            q_variable_type = q.variable_type
            q_reach_name = q.reach_name
            for reach, reach_name in zip(self._reaches, self.reach_names):
                if q_reach_name is not None:  # When reach_name is set.
                    if reach_name != q_reach_name:
                        continue
                data_types_in_reach = self._data_types_reach(reach)
                if q.variable_type not in data_types_in_reach:
                    continue
                data_type_idx = data_types_in_reach.index(q.variable_type)
                for curr_chain in self._chainages(reach, data_type_idx):
                    if q_variable_type in DATA_TYPES_HANDLED_IN_QUERIES:
                        chainage = curr_chain
                    else:
                        continue

                    q = QueryData(
                        q_variable_type, reach_name, round(chainage, 3)
                    )
                    built_queries.append(q)
        return built_queries

    def _find_points(self, queries, chainage_tolerance=0.1):
        """From a list of queries returns a dictionary with the required
        information for each requested point to extract its time series
        later on."""

        PointInfo = namedtuple('PointInfo', ['index', 'value'])

        found_points = defaultdict(list)
        # Find the point given its variable type, reach, and chainage
        for q in queries:
            for reach_idx, curr_reach in enumerate(self._reaches):
                # Look for the targeted reach
                if q.reach_name != curr_reach.Name:
                    continue
                reach = PointInfo(reach_idx, q.reach_name)
                for data_type_idx, data_type in enumerate(self.data_types):
                    if q.variable_type.lower() == data_type.lower():
                        break
                data_type_info = PointInfo(data_type_idx, q.variable_type)
                for idx, curr_chain in enumerate(self._chainages(curr_reach, data_type_idx)):
                    # Look for the targeted chainage
                    chainage_diff = curr_chain - q.chainage
                    is_chainage = abs(chainage_diff) < chainage_tolerance
                    if not is_chainage:
                        continue
                    # idx is the index in the item data, not in the
                    # gridpoints data.
                    chainage = PointInfo(idx, q.chainage)
                    found_points["chainage"].append(chainage)
                    found_points["variable"].append(data_type_info)
                    found_points["reach"].append(reach)
                    break  # Break at the first chainage found.

        return dict(found_points)

    @_not_closed
    def read(self, queries):
        """Read the requested data from the res1d file and
        return a Pandas DataFrame.

        Parameters
        ----------
        queries: list
            `QueryData` objects that define the requested data.
        Returns
        -------
        pd.DataFrame
        """
        self._validate_queries(queries)
        built_queries = self._build_queries(queries)
        found_points = self._find_points(built_queries)
        df = self._get_data(found_points)
        return df
Example #2
0
def read(res1DFile, extractionPoints):
    import clr
    import pandas as pd
    import datetime
    import numpy as np
    import os.path

    clr.AddReference("DHI.Mike1D.ResultDataAccess")
    from DHI.Mike1D.ResultDataAccess import ResultData

    clr.AddReference("DHI.Mike1D.Generic")
    from DHI.Mike1D.Generic import Connection

    clr.AddReference("System")

    if os.path.isfile(res1DFile) is False:
        print("ERROR, File Not Found: " + res1DFile)

    # Create a ResultData object and read the data file.
    rd = ResultData()
    rd.Connection = Connection.Create(res1DFile)
    rd.Load()

    reachNums = []
    dataItemTypes = []
    indices = []

    tol = 0.1

    # Find the Item
    for ep in extractionPoints:
        item = -1
        reachNumber = -1
        idx = -1
        for i in range(0, rd.Reaches.Count):
            if rd.Reaches.get_Item(i).Name.lower().strip() == ep.BranchName.lower().strip():

                reach = rd.Reaches.get_Item(i)
                for j in range(0, reach.GridPoints.Count):
                    # print(str(j))
                    if abs(float(reach.GridPoints.get_Item(j).Chainage) - ep.Chainage) < tol:
                        if 'waterlevel' in ep.VariableType.lower().strip().replace(" ", ""):
                            idx = int(j / 2)
                        elif 'discharge' in ep.VariableType.lower().strip().replace(" ", ""):

                            idx = int((j - 1) / 2)
                        elif 'pollutant' in ep.VariableType.lower().strip().replace(" ", ""):
                            idx = int((j - 1) / 2)
                        else:
                            print('ERROR. Variable Type must be either Water Level, Discharge, or Pollutant')
                        reachNumber = i
                        break
                        break
                        break

        for i in range(0, rd.get_Quantities().Count):
            if ep.VariableType.lower().strip().replace(" ", "") == rd.get_Quantities().get_Item(
                    i).Description.lower().strip().replace(" ", ""):
                item = i
                break

        indices.append(idx)
        reachNums.append(reachNumber)
        dataItemTypes.append(item)

    if -1 in reachNums:
        print('ERROR. Reach Not Found')
        quit()
    if -1 in dataItemTypes:
        print('ERROR. Item Not Found')
        quit()
    if -1 in indices:
        print('ERROR. Chainage Not Found')
        quit()

        # Get the Data
    df = pd.DataFrame()
    for i in range(0, len(indices)):
        d = rd.Reaches.get_Item(reachNums[i]).get_DataItems().get_Item(dataItemTypes[i]).CreateTimeSeriesData(
            indices[i])
        name = extractionPoints[i].VariableType + ' ' + str(extractionPoints[i].BranchName) + ' ' + str(
            extractionPoints[i].Chainage)
        d = pd.Series(list(d))
        d = d.rename(name)
        df[name] = d

    # Get the Times
    times = []
    for i in range(0, rd.TimesList.Count):
        it = rd.TimesList.get_Item(i)
        t = pd.Timestamp(
            datetime.datetime(it.get_Year(), it.get_Month(), it.get_Day(), it.get_Hour(), it.get_Minute(),
                              it.get_Second()))
        times.append(t)

    df.index = pd.DatetimeIndex(times)

    rd.Dispose()

    return df