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
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