rashdf 0.2.2__py3-none-any.whl → 0.3.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
cli.py CHANGED
@@ -1,6 +1,6 @@
1
1
  """rashdf CLI."""
2
2
 
3
- from rashdf import RasGeomHdf
3
+ from rashdf import RasGeomHdf, RasPlanHdf
4
4
  from rashdf.utils import df_datetimes_to_str
5
5
 
6
6
  import fiona
@@ -10,6 +10,7 @@ import argparse
10
10
  from ast import literal_eval
11
11
  from pathlib import Path
12
12
  import sys
13
+ import re
13
14
  from typing import List, Optional
14
15
  import warnings
15
16
 
@@ -108,17 +109,22 @@ def export(args: argparse.Namespace) -> Optional[str]:
108
109
  for driver in fiona_supported_drivers():
109
110
  print(driver)
110
111
  return
111
- if "://" in args.hdf_file:
112
- geom_hdf = RasGeomHdf.open_uri(args.hdf_file)
112
+ if re.match(r"^.*\.p\d\d\.hdf$", args.hdf_file):
113
+ ras_hdf_class = RasPlanHdf
113
114
  else:
114
- geom_hdf = RasGeomHdf(args.hdf_file)
115
+ ras_hdf_class = RasGeomHdf
116
+ if re.match(r"^\w+://", args.hdf_file):
117
+ geom_hdf = ras_hdf_class.open_uri(args.hdf_file)
118
+ else:
119
+ geom_hdf = ras_hdf_class(args.hdf_file)
115
120
  func = getattr(geom_hdf, args.func)
116
121
  gdf: GeoDataFrame = func()
117
122
  kwargs = literal_eval(args.kwargs) if args.kwargs else {}
118
123
  if args.to_crs:
119
124
  gdf = gdf.to_crs(args.to_crs)
120
125
  if not args.output_file:
121
- # convert any datetime columns to strings
126
+ # If an output file path isn't provided, write the GeoDataFrame to stdout
127
+ # as GeoJSON. Convert any datetime columns to strings.
122
128
  gdf = df_datetimes_to_str(gdf)
123
129
  with warnings.catch_warnings():
124
130
  # Squash warnings about converting the CRS to OGC URN format.
@@ -143,9 +149,9 @@ def export(args: argparse.Namespace) -> Optional[str]:
143
149
  output_file_path = Path(args.output_file)
144
150
  output_file_ext = output_file_path.suffix
145
151
  if output_file_ext not in [".gpkg"]:
146
- # unless the user specifies a format that supports datetime,
147
- # convert any datetime columns to string
148
- # TODO: besides Geopackage, which of the standard Fiona formats allow datetime?
152
+ # Unless the user specifies a format that supports datetime,
153
+ # convert any datetime columns to string.
154
+ # TODO: besides Geopackage, which of the standard Fiona drivers allow datetime?
149
155
  gdf = df_datetimes_to_str(gdf)
150
156
  gdf.to_file(args.output_file, **kwargs)
151
157
 
rashdf/geom.py CHANGED
@@ -66,12 +66,12 @@ class RasGeomHdf(RasHdf):
66
66
  List[str]
67
67
  A list of the 2D mesh area names (str) within the RAS geometry if 2D areas exist.
68
68
  """
69
- if "/Geometry/2D Flow Areas" not in self:
69
+ if self.FLOW_AREA_2D_PATH not in self:
70
70
  return list()
71
71
  return list(
72
72
  [
73
73
  convert_ras_hdf_string(n)
74
- for n in self["/Geometry/2D Flow Areas/Attributes"][()]["Name"]
74
+ for n in self[f"{self.FLOW_AREA_2D_PATH}/Attributes"][()]["Name"]
75
75
  ]
76
76
  )
77
77
 
@@ -87,7 +87,7 @@ class RasGeomHdf(RasHdf):
87
87
  if not mesh_area_names:
88
88
  return GeoDataFrame()
89
89
  mesh_area_polygons = [
90
- Polygon(self[f"/Geometry/2D Flow Areas/{n}/Perimeter"][()])
90
+ Polygon(self[f"{self.FLOW_AREA_2D_PATH}/{n}/Perimeter"][()])
91
91
  for n in mesh_area_names
92
92
  ]
93
93
  return GeoDataFrame(
@@ -112,13 +112,13 @@ class RasGeomHdf(RasHdf):
112
112
 
113
113
  cell_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
114
114
  for i, mesh_name in enumerate(mesh_area_names):
115
- cell_cnt = self["/Geometry/2D Flow Areas/Cell Info"][()][i][1]
115
+ cell_cnt = self[f"{self.FLOW_AREA_2D_PATH}/Cell Info"][()][i][1]
116
116
  cell_ids = list(range(cell_cnt))
117
117
  cell_face_info = self[
118
- f"/Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Info"
118
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Info"
119
119
  ][()]
120
120
  cell_face_values = self[
121
- f"/Geometry/2D Flow Areas/{mesh_name}/Cells Face and Orientation Values"
121
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Values"
122
122
  ][()][:, 0]
123
123
  face_id_lists = list(
124
124
  np.vectorize(
@@ -169,8 +169,8 @@ class RasGeomHdf(RasHdf):
169
169
  return GeoDataFrame()
170
170
  pnt_dict = {"mesh_name": [], "cell_id": [], "geometry": []}
171
171
  for i, mesh_name in enumerate(mesh_area_names):
172
- starting_row, count = self["/Geometry/2D Flow Areas/Cell Info"][()][i]
173
- cell_pnt_coords = self["/Geometry/2D Flow Areas/Cell Points"][()][
172
+ starting_row, count = self[f"{self.FLOW_AREA_2D_PATH}/Cell Info"][()][i]
173
+ cell_pnt_coords = self[f"{self.FLOW_AREA_2D_PATH}/Cell Points"][()][
174
174
  starting_row : starting_row + count
175
175
  ]
176
176
  pnt_dict["mesh_name"] += [mesh_name] * cell_pnt_coords.shape[0]
@@ -196,16 +196,16 @@ class RasGeomHdf(RasHdf):
196
196
  face_dict = {"mesh_name": [], "face_id": [], "geometry": []}
197
197
  for mesh_name in mesh_area_names:
198
198
  facepoints_index = self[
199
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces FacePoint Indexes"
199
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
200
200
  ][()]
201
201
  facepoints_coordinates = self[
202
- f"/Geometry/2D Flow Areas/{mesh_name}/FacePoints Coordinate"
202
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
203
203
  ][()]
204
204
  faces_perimeter_info = self[
205
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Info"
205
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
206
206
  ][()]
207
207
  faces_perimeter_values = self[
208
- f"/Geometry/2D Flow Areas/{mesh_name}/Faces Perimeter Values"
208
+ f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Values"
209
209
  ][()]
210
210
  face_id = -1
211
211
  for pnt_a_index, pnt_b_index in facepoints_index:
rashdf/plan.py CHANGED
@@ -1,8 +1,132 @@
1
1
  """HEC-RAS Plan HDF class."""
2
2
 
3
3
  from .geom import RasGeomHdf
4
- from typing import Dict
4
+ from .utils import (
5
+ df_datetimes_to_str,
6
+ ras_timesteps_to_datetimes,
7
+ parse_ras_datetime_ms,
8
+ )
9
+
5
10
  from geopandas import GeoDataFrame
11
+ import h5py
12
+ import numpy as np
13
+ from pandas import DataFrame
14
+ import pandas as pd
15
+ import xarray as xr
16
+
17
+ from datetime import datetime
18
+ from enum import Enum
19
+ from typing import Dict, List, Optional, Tuple, Union
20
+
21
+
22
+ class RasPlanHdfError(Exception):
23
+ """HEC-RAS Plan HDF error class."""
24
+
25
+ pass
26
+
27
+
28
+ class SummaryOutputVar(Enum):
29
+ """Summary output variables."""
30
+
31
+ MAXIMUM_WATER_SURFACE = "Maximum Water Surface"
32
+ MINIMUM_WATER_SURFACE = "Minimum Water Surface"
33
+ MAXIMUM_FACE_VELOCITY = "Maximum Face Velocity"
34
+ MINIMUM_FACE_VELOCITY = "Minimum Face Velocity"
35
+ CELL_MAXIMUM_WATER_SURFACE_ERROR = "Cell Maximum Water Surface Error"
36
+ CELL_CUMULATIVE_ITERATION = "Cell Cumulative Iteration"
37
+ CELL_LAST_ITERATION = "Cell Last Iteration"
38
+
39
+
40
+ SUMMARY_OUTPUT_VARS_CELLS = [
41
+ SummaryOutputVar.MAXIMUM_WATER_SURFACE,
42
+ SummaryOutputVar.MINIMUM_WATER_SURFACE,
43
+ SummaryOutputVar.CELL_MAXIMUM_WATER_SURFACE_ERROR,
44
+ SummaryOutputVar.CELL_CUMULATIVE_ITERATION,
45
+ SummaryOutputVar.CELL_LAST_ITERATION,
46
+ ]
47
+
48
+ SUMMARY_OUTPUT_VARS_FACES = [
49
+ SummaryOutputVar.MAXIMUM_FACE_VELOCITY,
50
+ SummaryOutputVar.MINIMUM_FACE_VELOCITY,
51
+ ]
52
+
53
+
54
+ class TimeSeriesOutputVar(Enum):
55
+ """Time series output variables."""
56
+
57
+ # Default Outputs
58
+ WATER_SURFACE = "Water Surface"
59
+ FACE_VELOCITY = "Face Velocity"
60
+
61
+ # Optional Outputs
62
+ CELL_COURANT = "Cell Courant"
63
+ CELL_CUMULATIVE_PRECIPITATION_DEPTH = "Cell Cumulative Precipitation Depth"
64
+ CELL_DIVERGENCE_TERM = "Cell Divergence Term"
65
+ CELL_EDDY_VISCOSITY_X = "Cell Eddy Viscosity - Eddy Viscosity X"
66
+ CELL_EDDY_VISCOSITY_Y = "Cell Eddy Viscosity - Eddy Viscosity Y"
67
+ CELL_FLOW_BALANCE = "Cell Flow Balance"
68
+ CELL_HYDRAULIC_DEPTH = "Cell Hydraulic Depth"
69
+ CELL_INVERT_DEPTH = "Cell Invert Depth"
70
+ CELL_STORAGE_TERM = "Cell Storage Term"
71
+ CELL_VELOCITY_X = "Cell Velocity - Velocity X"
72
+ CELL_VELOCITY_Y = "Cell Velocity - Velocity Y"
73
+ CELL_VOLUME = "Cell Volume"
74
+ CELL_VOLUME_ERROR = "Cell Volume Error"
75
+ CELL_WATER_SOURCE_TERM = "Cell Water Source Term"
76
+ CELL_WATER_SURFACE_ERROR = "Cell Water Surface Error"
77
+
78
+ FACE_COURANT = "Face Courant"
79
+ FACE_CUMULATIVE_VOLUME = "Face Cumulative Volume"
80
+ FACE_EDDY_VISCOSITY = "Face Eddy Viscosity"
81
+ FACE_FLOW = "Face Flow"
82
+ FACE_FLOW_PERIOD_AVERAGE = "Face Flow Period Average"
83
+ FACE_FRICTION_TERM = "Face Friction Term"
84
+ FACE_PRESSURE_GRADIENT_TERM = "Face Pressure Gradient Term"
85
+ FACE_SHEAR_STRESS = "Face Shear Stress"
86
+ FACE_TANGENTIAL_VELOCITY = "Face Tangential Velocity"
87
+ FACE_WATER_SURFACE = "Face Water Surface"
88
+ FACE_WIND_TERM = "Face Wind Term"
89
+
90
+
91
+ TIME_SERIES_OUTPUT_VARS_CELLS = [
92
+ TimeSeriesOutputVar.WATER_SURFACE,
93
+ TimeSeriesOutputVar.CELL_COURANT,
94
+ TimeSeriesOutputVar.CELL_CUMULATIVE_PRECIPITATION_DEPTH,
95
+ TimeSeriesOutputVar.CELL_DIVERGENCE_TERM,
96
+ TimeSeriesOutputVar.CELL_EDDY_VISCOSITY_X,
97
+ TimeSeriesOutputVar.CELL_EDDY_VISCOSITY_Y,
98
+ TimeSeriesOutputVar.CELL_FLOW_BALANCE,
99
+ TimeSeriesOutputVar.CELL_HYDRAULIC_DEPTH,
100
+ TimeSeriesOutputVar.CELL_INVERT_DEPTH,
101
+ TimeSeriesOutputVar.CELL_STORAGE_TERM,
102
+ TimeSeriesOutputVar.CELL_VELOCITY_X,
103
+ TimeSeriesOutputVar.CELL_VELOCITY_Y,
104
+ TimeSeriesOutputVar.CELL_VOLUME,
105
+ TimeSeriesOutputVar.CELL_VOLUME_ERROR,
106
+ TimeSeriesOutputVar.CELL_WATER_SOURCE_TERM,
107
+ TimeSeriesOutputVar.CELL_WATER_SURFACE_ERROR,
108
+ ]
109
+
110
+ TIME_SERIES_OUTPUT_VARS_FACES = [
111
+ TimeSeriesOutputVar.FACE_COURANT,
112
+ TimeSeriesOutputVar.FACE_CUMULATIVE_VOLUME,
113
+ TimeSeriesOutputVar.FACE_EDDY_VISCOSITY,
114
+ TimeSeriesOutputVar.FACE_FLOW,
115
+ TimeSeriesOutputVar.FACE_FLOW_PERIOD_AVERAGE,
116
+ TimeSeriesOutputVar.FACE_FRICTION_TERM,
117
+ TimeSeriesOutputVar.FACE_PRESSURE_GRADIENT_TERM,
118
+ TimeSeriesOutputVar.FACE_SHEAR_STRESS,
119
+ TimeSeriesOutputVar.FACE_TANGENTIAL_VELOCITY,
120
+ TimeSeriesOutputVar.FACE_VELOCITY,
121
+ TimeSeriesOutputVar.FACE_WATER_SURFACE,
122
+ # TODO: investigate why "Face Wind Term" data gets written as a 1D array
123
+ # TimeSeriesOutputVar.FACE_WIND_TERM,
124
+ ]
125
+
126
+ TIME_SERIES_OUTPUT_VARS_DEFAULT = [
127
+ TimeSeriesOutputVar.WATER_SURFACE,
128
+ TimeSeriesOutputVar.FACE_VELOCITY,
129
+ ]
6
130
 
7
131
 
8
132
  class RasPlanHdf(RasGeomHdf):
@@ -14,6 +138,11 @@ class RasPlanHdf(RasGeomHdf):
14
138
  RESULTS_UNSTEADY_PATH = "Results/Unsteady"
15
139
  RESULTS_UNSTEADY_SUMMARY_PATH = f"{RESULTS_UNSTEADY_PATH}/Summary"
16
140
  VOLUME_ACCOUNTING_PATH = f"{RESULTS_UNSTEADY_PATH}/Volume Accounting"
141
+ BASE_OUTPUT_PATH = f"{RESULTS_UNSTEADY_PATH}/Output/Output Blocks/Base Output"
142
+ SUMMARY_OUTPUT_2D_FLOW_AREAS_PATH = (
143
+ f"{BASE_OUTPUT_PATH}/Summary Output/2D Flow Areas"
144
+ )
145
+ UNSTEADY_TIME_SERIES_PATH = f"{BASE_OUTPUT_PATH}/Unsteady Time Series"
17
146
 
18
147
  def __init__(self, name: str, **kwargs):
19
148
  """Open a HEC-RAS Plan HDF file.
@@ -27,6 +156,745 @@ class RasPlanHdf(RasGeomHdf):
27
156
  """
28
157
  super().__init__(name, **kwargs)
29
158
 
159
+ def _simulation_start_time(self) -> datetime:
160
+ """Return the simulation start time from the plan file.
161
+
162
+ Returns
163
+ -------
164
+ datetime
165
+ The simulation start time.
166
+ """
167
+ plan_info = self.get_plan_info_attrs()
168
+ return plan_info["Simulation Start Time"]
169
+
170
+ def _2d_flow_area_names_and_counts(self) -> List[Tuple[str, int]]:
171
+ """
172
+ Return a list of 2D flow area names and cell counts.
173
+
174
+ Returns
175
+ -------
176
+ List[Tuple[str, int]]
177
+ A list of tuples, where each tuple contains a 2D flow area name
178
+ and the number of cells in that area.
179
+ """
180
+ d2_flow_areas = self[f"{self.FLOW_AREA_2D_PATH}/Attributes"][:]
181
+ return [
182
+ (d2_flow_area[0].decode("utf-8"), d2_flow_area[-1])
183
+ for d2_flow_area in d2_flow_areas
184
+ ]
185
+
186
+ def _mesh_summary_output_group(
187
+ self, mesh_name: str, output_var: SummaryOutputVar
188
+ ) -> h5py.Group:
189
+ """Return the HDF group for a 2D flow area summary output variable.
190
+
191
+ Parameters
192
+ ----------
193
+ mesh_name : str
194
+ The name of the 2D flow area mesh.
195
+ output_var : str
196
+ The name of the output variable.
197
+
198
+ Returns
199
+ -------
200
+ h5py.Group
201
+ The HDF group for the output variable.
202
+ """
203
+ output_path = (
204
+ f"{self.SUMMARY_OUTPUT_2D_FLOW_AREAS_PATH}/{mesh_name}/{output_var.value}"
205
+ )
206
+ output_group = self.get(output_path)
207
+ if output_group is None:
208
+ raise RasPlanHdfError(
209
+ f"Could not find HDF group at path '{output_path}'."
210
+ " Does the Plan HDF file contain 2D output data?"
211
+ )
212
+ return output_group
213
+
214
+ def _mesh_summary_output_min_max_values(
215
+ self, mesh_name: str, var: SummaryOutputVar
216
+ ) -> np.ndarray:
217
+ """Return values for a "Maximum"/"Minimum" summary output variable.
218
+
219
+ Parameters
220
+ ----------
221
+ mesh_name : str
222
+ The name of the 2D flow area mesh.
223
+ var : SummaryOutputVar
224
+ The summary output variable to retrieve.
225
+
226
+ Returns
227
+ -------
228
+ np.ndarray
229
+ An array of maximum water surface elevation values.
230
+ """
231
+ max_ws_group = self._mesh_summary_output_group(mesh_name, var)
232
+ max_ws_raw = max_ws_group[:]
233
+ max_ws_values = max_ws_raw[0]
234
+ return max_ws_values
235
+
236
+ def _summary_output_min_max_time_unit(self, dataset: h5py.Dataset) -> str:
237
+ """Return the time unit for "Maximum"/"Minimum" summary output datasets.
238
+
239
+ I.e., for summary output such as "Maximum Water Surface", "Minimum Water Surface", etc.
240
+
241
+ Should normally return the string: "days".
242
+
243
+ Parameters
244
+ ----------
245
+ mesh_name : str
246
+ The name of the 2D flow area mesh.
247
+
248
+ Returns
249
+ -------
250
+ str
251
+ The time unit for the maximum water surface elevation data.
252
+ """
253
+ if "Units per row" in dataset.attrs:
254
+ units = dataset.attrs["Units per row"]
255
+ else:
256
+ units = dataset.attrs["Units"]
257
+ # expect an array of size 2, with the first element being length or velocity units
258
+ # and the second element being time units (e.g., ["ft", "days"])
259
+ time_unit = units[1]
260
+ return time_unit.decode("utf-8")
261
+
262
+ def _mesh_summary_output_min_max_times(
263
+ self,
264
+ mesh_name: str,
265
+ var: SummaryOutputVar,
266
+ time_unit: str = "days",
267
+ round_to: str = "0.1 s",
268
+ ) -> np.ndarray[np.datetime64]:
269
+ """Return an array of times for min/max summary output data.
270
+
271
+ Parameters
272
+ ----------
273
+ mesh_name : str
274
+ The name of the 2D flow area mesh.
275
+ var : SummaryOutputVar
276
+ The summary output variable to retrieve.
277
+ time_unit : str, optional
278
+ The time unit for the maximum water surface elevation data.
279
+ Default: "days".
280
+ round_to : str, optional
281
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
282
+
283
+ Returns
284
+ -------
285
+ np.ndarray[np.datetime64]
286
+ An array of times for the maximum water surface elevation data.
287
+ """
288
+ start_time = self._simulation_start_time()
289
+ max_ws_group = self._mesh_summary_output_group(mesh_name, var)
290
+ time_unit = self._summary_output_min_max_time_unit(max_ws_group)
291
+ max_ws_raw = max_ws_group[:]
292
+ max_ws_times_raw = max_ws_raw[1]
293
+ # we get weirdly specific datetime values if we don't round to e.g., 0.1 seconds;
294
+ # otherwise datetimes don't align with the actual timestep values in the plan file
295
+ max_ws_times = ras_timesteps_to_datetimes(
296
+ max_ws_times_raw, start_time, time_unit=time_unit, round_to=round_to
297
+ )
298
+ return max_ws_times
299
+
300
+ def _mesh_summary_output_min_max(
301
+ self,
302
+ var: SummaryOutputVar,
303
+ value_col: str = "value",
304
+ time_col: str = "time",
305
+ round_to: str = "0.1 s",
306
+ ) -> DataFrame:
307
+ """Return the min/max values and times for a summary output variable.
308
+
309
+ Valid for:
310
+ - Maximum Water Surface
311
+ - Minimum Water Surface
312
+ - Maximum Face Velocity
313
+ - Minimum Face Velocity
314
+ - Cell Maximum Water Surface Error
315
+
316
+ Parameters
317
+ ----------
318
+ var : SummaryOutputVar
319
+ The summary output variable to retrieve.
320
+ round_to : str, optional
321
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
322
+
323
+ Returns
324
+ -------
325
+ DataFrame
326
+ A DataFrame with columns 'mesh_name', 'cell_id' or 'face_id', 'value', and 'time'.
327
+ """
328
+ dfs = []
329
+ for mesh_name, cell_count in self._2d_flow_area_names_and_counts():
330
+ values = self._mesh_summary_output_min_max_values(mesh_name, var)
331
+ times = self._mesh_summary_output_min_max_times(
332
+ mesh_name, var, round_to=round_to
333
+ )
334
+ if var in [
335
+ SummaryOutputVar.MAXIMUM_FACE_VELOCITY,
336
+ SummaryOutputVar.MINIMUM_FACE_VELOCITY,
337
+ ]:
338
+ geom_id_col = "face_id"
339
+ else:
340
+ geom_id_col = "cell_id"
341
+ # The 2D mesh output data contains values for more cells than are actually
342
+ # in the mesh. The the true number of cells for a mesh is found in the table:
343
+ # "/Geometry/2D Flow Areas/Attributes". The number of cells in the 2D output
344
+ # data instead matches the number of cells in the "Cells Center Coordinate"
345
+ # array, which contains extra points along the perimeter of the mesh. These
346
+ # extra points are appended to the end of the mesh data and contain bogus
347
+ # output values (e.g., 0.0, NaN). We need to filter out these bogus values.
348
+ values = values[:cell_count]
349
+ times = times[:cell_count]
350
+ df = DataFrame(
351
+ {
352
+ "mesh_name": [mesh_name] * len(values),
353
+ geom_id_col: range(len(values)),
354
+ value_col: values,
355
+ time_col: times,
356
+ }
357
+ )
358
+ dfs.append(df)
359
+ df = pd.concat(dfs, ignore_index=True)
360
+ return df
361
+
362
+ def _mesh_summary_output_basic(
363
+ self, var: SummaryOutputVar, value_col: str = "value"
364
+ ) -> DataFrame:
365
+ """Return values and times for a summary output variable.
366
+
367
+ Valid for:
368
+ - Cell Cumulative Iteration (i.e. Cumulative Max Iterations)
369
+ - Cell Last Iteration
370
+
371
+ Parameters
372
+ ----------
373
+ var : SummaryOutputVar
374
+ The summary output variable to retrieve.
375
+
376
+ Returns
377
+ -------
378
+ DataFrame
379
+ A DataFrame with columns 'mesh_name', 'cell_id' or 'face_id', 'value', and 'time'.
380
+ """
381
+ dfs = []
382
+ for mesh_name, cell_count in self._2d_flow_area_names_and_counts():
383
+ group = self._mesh_summary_output_group(mesh_name, var)
384
+ values = group[:][:cell_count]
385
+ df = DataFrame(
386
+ {
387
+ "mesh_name": [mesh_name] * len(values),
388
+ "cell_id": range(len(values)),
389
+ value_col: values,
390
+ }
391
+ )
392
+ dfs.append(df)
393
+ df = pd.concat(dfs, ignore_index=True)
394
+ return df
395
+
396
+ def mesh_max_iter(self) -> DataFrame:
397
+ """Return the number of times each cell in the mesh reached the max number of iterations.
398
+
399
+ Returns
400
+ -------
401
+ DataFrame
402
+ A DataFrame with columns 'mesh_name', 'cell_id', and 'max_iterations'.
403
+ """
404
+ df = self._mesh_summary_output_basic(
405
+ SummaryOutputVar.CELL_CUMULATIVE_ITERATION, value_col="max_iter"
406
+ )
407
+ return df
408
+
409
+ def mesh_last_iter(self) -> DataFrame:
410
+ """Return the number of times each cell in the mesh was the last cell to converge.
411
+
412
+ Returns
413
+ -------
414
+ DataFrame
415
+ A DataFrame with columns 'mesh_name', 'cell_id', and 'last_iter'.
416
+ """
417
+ df = self._mesh_summary_output_basic(
418
+ SummaryOutputVar.CELL_LAST_ITERATION, value_col="last_iter"
419
+ )
420
+ return df
421
+
422
+ def mesh_max_ws(self, round_to: str = "0.1 s") -> DataFrame:
423
+ """Return the max water surface elevation for each cell in the mesh.
424
+
425
+ Includes the corresponding time of max water surface elevation.
426
+
427
+ Parameters
428
+ ----------
429
+ round_to : str, optional
430
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
431
+ See Pandas documentation for valid time units:
432
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html#offset-aliases
433
+
434
+ Returns
435
+ -------
436
+ DataFrame
437
+ A DataFrame with columns 'mesh_name', 'cell_id', 'max_ws', and 'max_ws_time'.
438
+ """
439
+ df = self._mesh_summary_output_min_max(
440
+ SummaryOutputVar.MAXIMUM_WATER_SURFACE,
441
+ value_col="max_ws",
442
+ time_col="max_ws_time",
443
+ round_to=round_to,
444
+ )
445
+ return df
446
+
447
+ def mesh_min_ws(self, round_to: str = "0.1 s") -> DataFrame:
448
+ """Return the min water surface elevation for each cell in the mesh.
449
+
450
+ Includes the corresponding time of min water surface elevation.
451
+
452
+ Parameters
453
+ ----------
454
+ round_to : str, optional
455
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
456
+ See Pandas documentation for valid time units:
457
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
458
+
459
+ Returns
460
+ -------
461
+ DataFrame
462
+ A DataFrame with columns 'mesh_name', 'cell_id', 'min_ws', and 'min_ws_time'.
463
+ """
464
+ df = self._mesh_summary_output_min_max(
465
+ SummaryOutputVar.MINIMUM_WATER_SURFACE,
466
+ value_col="min_ws",
467
+ time_col="min_ws_time",
468
+ round_to=round_to,
469
+ )
470
+ return df
471
+
472
+ def mesh_max_face_v(self, round_to: str = "0.1 s") -> DataFrame:
473
+ """Return the max face velocity for each face in the mesh.
474
+
475
+ Includes the corresponding time of max face velocity.
476
+
477
+ Parameters
478
+ ----------
479
+ round_to : str, optional
480
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
481
+ See Pandas documentation for valid time units:
482
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
483
+
484
+ Returns
485
+ -------
486
+ DataFrame
487
+ A DataFrame with columns 'mesh_name', 'face_id', 'max_v', and 'max_v_time'.
488
+ """
489
+ df = self._mesh_summary_output_min_max(
490
+ SummaryOutputVar.MAXIMUM_FACE_VELOCITY,
491
+ value_col="max_v",
492
+ time_col="max_v_time",
493
+ round_to=round_to,
494
+ )
495
+ return df
496
+
497
+ def mesh_min_face_v(self, round_to: str = "0.1 s") -> DataFrame:
498
+ """Return the min face velocity for each face in the mesh.
499
+
500
+ Includes the corresponding time of min face velocity.
501
+
502
+ Parameters
503
+ ----------
504
+ round_to : str, optional
505
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
506
+ See Pandas documentation for valid time units:
507
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
508
+
509
+ Returns
510
+ -------
511
+ DataFrame
512
+ A DataFrame with columns 'mesh_name', 'face_id', 'min_v', and 'min_v_time'.
513
+ """
514
+ df = self._mesh_summary_output_min_max(
515
+ SummaryOutputVar.MINIMUM_FACE_VELOCITY,
516
+ value_col="min_v",
517
+ time_col="min_v_time",
518
+ round_to=round_to,
519
+ )
520
+ return df
521
+
522
+ def mesh_max_ws_err(self, round_to: str = "0.1 s") -> DataFrame:
523
+ """Return the max water surface error for each cell in the mesh.
524
+
525
+ Includes the corresponding time of max water surface error.
526
+
527
+ Parameters
528
+ ----------
529
+ round_to : str, optional
530
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
531
+ See Pandas documentation for valid time units:
532
+ https://pandas.pydata.org/pandas-docs/stable/user_guide/timeseries.html
533
+
534
+ Returns
535
+ -------
536
+ DataFrame
537
+ A DataFrame with columns 'mesh_name', 'cell_id', 'max_ws_err', and 'max_ws_err_time'.
538
+ """
539
+ df = self._mesh_summary_output_min_max(
540
+ SummaryOutputVar.CELL_MAXIMUM_WATER_SURFACE_ERROR,
541
+ value_col="max_ws_err",
542
+ time_col="max_ws_err_time",
543
+ round_to=round_to,
544
+ )
545
+ return df
546
+
547
+ def mesh_summary_output(
548
+ self, var: SummaryOutputVar, round_to: str = "0.1 s"
549
+ ) -> DataFrame:
550
+ """Return the summary output data for a given variable.
551
+
552
+ Parameters
553
+ ----------
554
+ var : SummaryOutputVar
555
+ The summary output variable to retrieve.
556
+
557
+ Returns
558
+ -------
559
+ DataFrame
560
+ A DataFrame with columns 'mesh_name', 'cell_id' or 'face_id', a value column, and a time column.
561
+ """
562
+ methods_with_times = {
563
+ SummaryOutputVar.MAXIMUM_WATER_SURFACE: self.mesh_max_ws,
564
+ SummaryOutputVar.MINIMUM_WATER_SURFACE: self.mesh_min_ws,
565
+ SummaryOutputVar.MAXIMUM_FACE_VELOCITY: self.mesh_max_face_v,
566
+ SummaryOutputVar.MINIMUM_FACE_VELOCITY: self.mesh_min_face_v,
567
+ SummaryOutputVar.CELL_MAXIMUM_WATER_SURFACE_ERROR: self.mesh_max_ws_err,
568
+ }
569
+ other_methods = {
570
+ SummaryOutputVar.CELL_CUMULATIVE_ITERATION: self.mesh_max_iter,
571
+ SummaryOutputVar.CELL_LAST_ITERATION: self.mesh_last_iter,
572
+ }
573
+ if var in methods_with_times:
574
+ df = methods_with_times[var](round_to=round_to)
575
+ else:
576
+ df = other_methods[var]()
577
+ return df
578
+
579
+ def _summary_output_vars(
580
+ self, cells_or_faces: Optional[str] = None
581
+ ) -> List[SummaryOutputVar]:
582
+ """Return a list of available summary output variables from the Plan HDF file.
583
+
584
+ Returns
585
+ -------
586
+ List[SummaryOutputVar]
587
+ A list of summary output variables.
588
+ """
589
+ mesh_names_counts = self._2d_flow_area_names_and_counts()
590
+ mesh_names = [mesh_name for mesh_name, _ in mesh_names_counts]
591
+ vars = set()
592
+ for mesh_name in mesh_names:
593
+ path = f"{self.SUMMARY_OUTPUT_2D_FLOW_AREAS_PATH}/{mesh_name}"
594
+ datasets = self[path].keys()
595
+ for dataset in datasets:
596
+ try:
597
+ var = SummaryOutputVar(dataset)
598
+ except ValueError:
599
+ continue
600
+ vars.add(var)
601
+ if cells_or_faces == "cells":
602
+ vars = vars.intersection(SUMMARY_OUTPUT_VARS_CELLS)
603
+ elif cells_or_faces == "faces":
604
+ vars = vars.intersection(SUMMARY_OUTPUT_VARS_FACES)
605
+ return sorted(list(vars), key=lambda x: x.value)
606
+
607
+ def _mesh_summary_outputs_gdf(
608
+ self,
609
+ geom_func: str,
610
+ cells_or_faces: str = "cells",
611
+ include_output: Union[bool, List[SummaryOutputVar]] = True,
612
+ round_to: str = "0.1 s",
613
+ datetime_to_str: bool = False,
614
+ ) -> GeoDataFrame:
615
+ """Return a GeoDataFrame with mesh geometry and summary output data.
616
+
617
+ Parameters
618
+ ----------
619
+ geom_func : str
620
+ The method name to call to get the mesh geometry.
621
+ cells_or_faces : str, optional
622
+ The type of geometry to include in the GeoDataFrame.
623
+ Must be either "cells" or "faces". (default: "cells")
624
+ include_output : Union[bool, List[SummaryOutputVar]], optional
625
+ If True, include all available summary output data in the GeoDataFrame.
626
+ If a list of SummaryOutputVar values, include only the specified summary output data.
627
+ If False, do not include any summary output data.
628
+ (default: True)
629
+ round_to : str, optional
630
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
631
+ datetime_to_str : bool, optional
632
+ If True, convert datetime columns to strings. (default: False)
633
+ """
634
+ gdf = getattr(super(), geom_func)()
635
+ if include_output is False:
636
+ return gdf
637
+ if include_output is True:
638
+ summary_output_vars = self._summary_output_vars(
639
+ cells_or_faces=cells_or_faces
640
+ )
641
+ elif isinstance(include_output, list):
642
+ summary_output_vars = []
643
+ for var in include_output:
644
+ if not isinstance(var, SummaryOutputVar):
645
+ var = SummaryOutputVar(var)
646
+ summary_output_vars.append(var)
647
+ else:
648
+ raise ValueError(
649
+ "include_output must be a boolean or a list of SummaryOutputVar values."
650
+ )
651
+ if cells_or_faces == "cells":
652
+ geom_id_col = "cell_id"
653
+ elif cells_or_faces == "faces":
654
+ geom_id_col = "face_id"
655
+ else:
656
+ raise ValueError('cells_or_faces must be either "cells" or "faces".')
657
+ for var in summary_output_vars:
658
+ df = self.mesh_summary_output(var, round_to=round_to)
659
+ gdf = gdf.merge(df, on=["mesh_name", geom_id_col], how="left")
660
+ if datetime_to_str:
661
+ gdf = df_datetimes_to_str(gdf)
662
+ return gdf
663
+
664
+ def mesh_cell_points(
665
+ self,
666
+ include_output: Union[bool, List[SummaryOutputVar], List[str]] = True,
667
+ round_to: str = "0.1 s",
668
+ datetime_to_str: bool = False,
669
+ ) -> GeoDataFrame:
670
+ """Return the cell points for each cell in the mesh, including summary output.
671
+
672
+ Parameters
673
+ ----------
674
+ include_output : Union[bool, List[SummaryOutputVar], List[str]], optional
675
+ If True, include all available summary output data in the GeoDataFrame.
676
+ If a list of SummaryOutputVar values, include only the specified summary output data.
677
+ If a list of strings, include only the specified summary output data by name.
678
+ If False, do not include any summary output data.
679
+ (default: True)
680
+ round_to : str, optional
681
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
682
+ datetime_to_str : bool, optional
683
+ If True, convert datetime columns to strings. (default: False)
684
+
685
+ Returns
686
+ -------
687
+ GeoDataFrame
688
+ A GeoDataFrame with columns 'mesh_name', 'cell_id', 'geometry', and columns for each
689
+ summary output variable.
690
+ """
691
+ return self._mesh_summary_outputs_gdf(
692
+ "mesh_cell_points",
693
+ "cells",
694
+ include_output=include_output,
695
+ round_to=round_to,
696
+ datetime_to_str=datetime_to_str,
697
+ )
698
+
699
+ def mesh_cell_polygons(
700
+ self,
701
+ include_output: Union[bool, List[SummaryOutputVar], List[str]] = True,
702
+ round_to: str = "0.1 s",
703
+ datetime_to_str: bool = False,
704
+ ) -> GeoDataFrame:
705
+ """Return the cell polygons for each cell in the mesh, including output.
706
+
707
+ Parameters
708
+ ----------
709
+ include_output : Union[bool, List[SummaryOutputVar], List[str]], optional
710
+ If True, include all available summary output data in the GeoDataFrame.
711
+ If a list of SummaryOutputVar values, include only the specified summary output data.
712
+ If a list of strings, include only the specified summary output data by name.
713
+ If False, do not include any summary output data.
714
+ (default: True)
715
+ round_to : str, optional
716
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
717
+ datetime_to_str : bool, optional
718
+ If True, convert datetime columns to strings. (default: False)
719
+
720
+ Returns
721
+ -------
722
+ GeoDataFrame
723
+ A GeoDataFrame with columns 'mesh_name', 'cell_id', 'geometry', and columns for each
724
+ summary output variable.
725
+ """
726
+ return self._mesh_summary_outputs_gdf(
727
+ "mesh_cell_polygons",
728
+ "cells",
729
+ include_output=include_output,
730
+ round_to=round_to,
731
+ datetime_to_str=datetime_to_str,
732
+ )
733
+
734
+ def mesh_cell_faces(
735
+ self,
736
+ include_output: Union[bool, List[SummaryOutputVar], List[str]] = True,
737
+ round_to: str = "0.1 s",
738
+ datetime_to_str: bool = False,
739
+ ) -> GeoDataFrame:
740
+ """Return the cell faces for each cell in the mesh, including output.
741
+
742
+ Parameters
743
+ ----------
744
+ include_output : Union[bool, List[SummaryOutputVar], List[str]], optional
745
+ If True, include all available summary output data in the GeoDataFrame.
746
+ If a list of SummaryOutputVar values, include only the specified summary output data.
747
+ If a list of strings, include only the specified summary output data by name.
748
+ If False, do not include any summary output data.
749
+ (default: True)
750
+ round_to : str, optional
751
+ The time unit to round the datetimes to. Default: "0.1 s" (seconds).
752
+ datetime_to_str : bool, optional
753
+ If True, convert datetime columns to strings. (default: False)
754
+
755
+ Returns
756
+ -------
757
+ GeoDataFrame
758
+ A GeoDataFrame with columns 'mesh_name', 'cell_id', 'geometry', and columns for each
759
+ summary output variable.
760
+ """
761
+ return self._mesh_summary_outputs_gdf(
762
+ "mesh_cell_faces",
763
+ "faces",
764
+ include_output=include_output,
765
+ round_to=round_to,
766
+ datetime_to_str=datetime_to_str,
767
+ )
768
+
769
+ def unsteady_datetimes(self) -> List[datetime]:
770
+ """Return the unsteady timeseries datetimes from the plan file.
771
+
772
+ Returns
773
+ -------
774
+ List[datetime]
775
+ A list of datetimes for the unsteady timeseries data.
776
+ """
777
+ group_path = f"{self.UNSTEADY_TIME_SERIES_PATH}/Time Date Stamp (ms)"
778
+ raw_datetimes = self[group_path][:]
779
+ dt = [parse_ras_datetime_ms(x.decode("utf-8")) for x in raw_datetimes]
780
+ return dt
781
+
782
+ def _mesh_timeseries_output_values_units(
783
+ self,
784
+ mesh_name: str,
785
+ var: TimeSeriesOutputVar,
786
+ ) -> Tuple[np.ndarray, str]:
787
+ path = f"{self.UNSTEADY_TIME_SERIES_PATH}/2D Flow Areas/{mesh_name}/{var.value}"
788
+ group = self.get(path)
789
+ try:
790
+ import dask.array as da
791
+
792
+ # TODO: user-specified chunks?
793
+ values = da.from_array(group, chunks=group.chunks)
794
+ except ImportError:
795
+ values = group[:]
796
+ units = group.attrs.get("Units")
797
+ if units is not None:
798
+ units = units.decode("utf-8")
799
+ return values, units
800
+
801
+ def mesh_timeseries_output(
802
+ self,
803
+ mesh_name: str,
804
+ var: Union[str, TimeSeriesOutputVar],
805
+ ) -> xr.DataArray:
806
+ """Return the time series output data for a given variable.
807
+
808
+ Parameters
809
+ ----------
810
+ mesh_name : str
811
+ The name of the 2D flow area mesh.
812
+ var : TimeSeriesOutputVar
813
+ The time series output variable to retrieve.
814
+
815
+ Returns
816
+ -------
817
+ xr.DataArray
818
+ An xarray DataArray with dimensions 'time' and 'cell_id'.
819
+ """
820
+ times = self.unsteady_datetimes()
821
+ mesh_names_counts = {
822
+ name: count for name, count in self._2d_flow_area_names_and_counts()
823
+ }
824
+ if mesh_name not in mesh_names_counts:
825
+ raise ValueError(f"Mesh '{mesh_name}' not found in the Plan HDF file.")
826
+ if isinstance(var, str):
827
+ var = TimeSeriesOutputVar(var)
828
+ values, units = self._mesh_timeseries_output_values_units(mesh_name, var)
829
+ if var in TIME_SERIES_OUTPUT_VARS_CELLS:
830
+ cell_count = mesh_names_counts[mesh_name]
831
+ values = values[:, :cell_count]
832
+ id_coord = "cell_id"
833
+ elif var in TIME_SERIES_OUTPUT_VARS_FACES:
834
+ id_coord = "face_id"
835
+ else:
836
+ raise ValueError(f"Invalid time series output variable: {var.value}")
837
+ da = xr.DataArray(
838
+ values,
839
+ name=var.value,
840
+ dims=["time", id_coord],
841
+ coords={
842
+ "time": times,
843
+ id_coord: range(values.shape[1]),
844
+ },
845
+ attrs={
846
+ "mesh_name": mesh_name,
847
+ "variable": var.value,
848
+ "units": units,
849
+ },
850
+ )
851
+ return da
852
+
853
+ def _mesh_timeseries_outputs(
854
+ self, mesh_name: str, vars: List[TimeSeriesOutputVar]
855
+ ) -> xr.Dataset:
856
+ datasets = {}
857
+ for var in vars:
858
+ var_path = f"{self.UNSTEADY_TIME_SERIES_PATH}/2D Flow Areas/{mesh_name}/{var.value}"
859
+ if self.get(var_path) is None:
860
+ continue
861
+ da = self.mesh_timeseries_output(mesh_name, var)
862
+ datasets[var.value] = da
863
+ ds = xr.Dataset(datasets, attrs={"mesh_name": mesh_name})
864
+ return ds
865
+
866
+ def mesh_timeseries_output_cells(self, mesh_name: str) -> xr.Dataset:
867
+ """Return the time series output data for cells in a 2D flow area mesh.
868
+
869
+ Parameters
870
+ ----------
871
+ mesh_name : str
872
+ The name of the 2D flow area mesh.
873
+
874
+ Returns
875
+ -------
876
+ xr.Dataset
877
+ An xarray Dataset with DataArrays for each time series output variable.
878
+ """
879
+ ds = self._mesh_timeseries_outputs(mesh_name, TIME_SERIES_OUTPUT_VARS_CELLS)
880
+ return ds
881
+
882
+ def mesh_timeseries_output_faces(self, mesh_name: str) -> xr.Dataset:
883
+ """Return the time series output data for faces in a 2D flow area mesh.
884
+
885
+ Parameters
886
+ ----------
887
+ mesh_name : str
888
+ The name of the 2D flow area mesh.
889
+
890
+ Returns
891
+ -------
892
+ xr.Dataset
893
+ An xarray Dataset with DataArrays for each time series output variable.
894
+ """
895
+ ds = self._mesh_timeseries_outputs(mesh_name, TIME_SERIES_OUTPUT_VARS_FACES)
896
+ return ds
897
+
30
898
  def get_plan_info_attrs(self) -> Dict:
31
899
  """Return plan information attributes from a HEC-RAS HDF plan file.
32
900
 
rashdf/utils.py CHANGED
@@ -10,6 +10,25 @@ from typing import Any, List, Tuple, Union, Optional
10
10
  from shapely import LineString, Polygon, polygonize_full
11
11
 
12
12
 
13
+ def parse_ras_datetime_ms(datetime_str: str) -> datetime:
14
+ """Parse a datetime string with milliseconds from a RAS file into a datetime object.
15
+
16
+ If the datetime has a time of 2400, then it is converted to midnight of the next day.
17
+
18
+ Parameters
19
+ ----------
20
+ datetime_str (str): The datetime string to be parsed. The string should be in the format "ddMMMyyyy HH:mm:ss:fff".
21
+
22
+ Returns
23
+ -------
24
+ datetime: A datetime object representing the parsed datetime.
25
+ """
26
+ milliseconds = int(datetime_str[-3:])
27
+ microseconds = milliseconds * 1000
28
+ parsed_dt = parse_ras_datetime(datetime_str[:-4]).replace(microsecond=microseconds)
29
+ return parsed_dt
30
+
31
+
13
32
  def parse_ras_datetime(datetime_str: str) -> datetime:
14
33
  """Parse a datetime string from a RAS file into a datetime object.
15
34
 
@@ -266,3 +285,26 @@ def df_datetimes_to_str(df: pd.DataFrame) -> pd.DataFrame:
266
285
  lambda x: pd.Timestamp(x).isoformat() if pd.notnull(x) else None
267
286
  )
268
287
  return df_result
288
+
289
+
290
+ def ras_timesteps_to_datetimes(
291
+ timesteps: np.ndarray, start_time: datetime, time_unit: str, round_to="0.1 s"
292
+ ) -> List[datetime]:
293
+ """
294
+ Convert an array of RAS timesteps into an array of datetime objects.
295
+
296
+ Parameters
297
+ ----------
298
+ timesteps (np.ndarray): An array of RAS timesteps.
299
+ start_time (datetime): The start time of the simulation.
300
+ time_unit (str): The time unit of the timesteps.
301
+ round_to (str): The time unit to round the datetimes to. (Default: "0.1 s")
302
+
303
+ Returns
304
+ -------
305
+ List[datetime]: A list of datetime objects corresponding to the timesteps.
306
+ """
307
+ return [
308
+ start_time + pd.Timedelta(timestep, unit=time_unit).round(round_to)
309
+ for timestep in timesteps.astype(np.float64)
310
+ ]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: rashdf
3
- Version: 0.2.2
3
+ Version: 0.3.0
4
4
  Summary: Read data from HEC-RAS HDF files.
5
5
  Project-URL: repository, https://github.com/fema-ffrd/rashdf
6
6
  Classifier: Development Status :: 4 - Beta
@@ -16,6 +16,7 @@ License-File: LICENSE
16
16
  Requires-Dist: h5py
17
17
  Requires-Dist: geopandas
18
18
  Requires-Dist: pyarrow
19
+ Requires-Dist: xarray
19
20
  Provides-Extra: dev
20
21
  Requires-Dist: pre-commit ; extra == 'dev'
21
22
  Requires-Dist: ruff ; extra == 'dev'
@@ -0,0 +1,12 @@
1
+ cli.py,sha256=dnTMEBid99xqorBFKZnUwOTDyTmIg08D83bSCkJ6104,5389
2
+ rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
3
+ rashdf/base.py,sha256=lHYVDwFTA1qFI34QYZ55QKcp7b8CeZsmDfESdkYISbg,2432
4
+ rashdf/geom.py,sha256=Kf8e9u0U-dRi6lVcSPxPMeAYKwgZDIpNZ2o0Qu4WEJA,17609
5
+ rashdf/plan.py,sha256=W4Q4yjneDEJSeubnMDUgQ7T12fcWDYPL0RDZrsmFZ0A,34556
6
+ rashdf/utils.py,sha256=93arHtIT-iL9dIpbYr7esjrxv1uJabTRJSruyjvr8mw,10168
7
+ rashdf-0.3.0.dist-info/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
+ rashdf-0.3.0.dist-info/METADATA,sha256=-5zzZFS6V4Uiv8pjpwznAXnPj1KpZPwuJpE6aOOXBtk,5658
9
+ rashdf-0.3.0.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10
+ rashdf-0.3.0.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
+ rashdf-0.3.0.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
+ rashdf-0.3.0.dist-info/RECORD,,
@@ -1,12 +0,0 @@
1
- cli.py,sha256=QMTNosimfe95bQn2_ovLB98GeaQ0gwMTMA1rboMEknw,5119
2
- rashdf/__init__.py,sha256=XXFtJDgLPCimqAhfsFz_pTWYECJiRT0i-Kb1uflXmVU,156
3
- rashdf/base.py,sha256=lHYVDwFTA1qFI34QYZ55QKcp7b8CeZsmDfESdkYISbg,2432
4
- rashdf/geom.py,sha256=8bdATVPc_HxECA9evnwyL3nXC5YHEY78I3JpUUP4_xM,17597
5
- rashdf/plan.py,sha256=8xdDkAvk9sTrBlCcXepuNvGRbp0JjlyHdRMFN5PRiHE,2734
6
- rashdf/utils.py,sha256=yEZCm06KjpL8pebbW4MIDIcX2vHp-IzVXsKJGKznWBc,8703
7
- rashdf-0.2.2.dist-info/LICENSE,sha256=L_0QaLpQVHPcglVjiaJPnOocwzP8uXevDRjUPr9DL1Y,1065
8
- rashdf-0.2.2.dist-info/METADATA,sha256=4MuaGRrWegMZ4RVGtuYl6KjWpksteMTTj36gwAvGjCo,5636
9
- rashdf-0.2.2.dist-info/WHEEL,sha256=GJ7t_kWBFywbagK5eo9IoUwLW6oyOeTKmQ-9iHFVNxQ,92
10
- rashdf-0.2.2.dist-info/entry_points.txt,sha256=LHHMR1lLy4wRyscMuW1RlYDXemtPgqQhNcILz0DtStY,36
11
- rashdf-0.2.2.dist-info/top_level.txt,sha256=SrmLb6FFTJtM_t6O1v0M0JePshiQJMHr0yYVkHL7ztk,11
12
- rashdf-0.2.2.dist-info/RECORD,,
File without changes