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 +14 -8
- rashdf/geom.py +12 -12
- rashdf/plan.py +869 -1
- rashdf/utils.py +42 -0
- {rashdf-0.2.2.dist-info → rashdf-0.3.0.dist-info}/METADATA +2 -1
- rashdf-0.3.0.dist-info/RECORD +12 -0
- rashdf-0.2.2.dist-info/RECORD +0 -12
- {rashdf-0.2.2.dist-info → rashdf-0.3.0.dist-info}/LICENSE +0 -0
- {rashdf-0.2.2.dist-info → rashdf-0.3.0.dist-info}/WHEEL +0 -0
- {rashdf-0.2.2.dist-info → rashdf-0.3.0.dist-info}/entry_points.txt +0 -0
- {rashdf-0.2.2.dist-info → rashdf-0.3.0.dist-info}/top_level.txt +0 -0
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 "
|
|
112
|
-
|
|
112
|
+
if re.match(r"^.*\.p\d\d\.hdf$", args.hdf_file):
|
|
113
|
+
ras_hdf_class = RasPlanHdf
|
|
113
114
|
else:
|
|
114
|
-
|
|
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
|
-
#
|
|
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
|
-
#
|
|
147
|
-
# convert any datetime columns to string
|
|
148
|
-
# TODO: besides Geopackage, which of the standard Fiona
|
|
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
|
|
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["/
|
|
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"/
|
|
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["/
|
|
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"/
|
|
118
|
+
f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Cells Face and Orientation Info"
|
|
119
119
|
][()]
|
|
120
120
|
cell_face_values = self[
|
|
121
|
-
f"/
|
|
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["/
|
|
173
|
-
cell_pnt_coords = self["/
|
|
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"/
|
|
199
|
+
f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces FacePoint Indexes"
|
|
200
200
|
][()]
|
|
201
201
|
facepoints_coordinates = self[
|
|
202
|
-
f"/
|
|
202
|
+
f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/FacePoints Coordinate"
|
|
203
203
|
][()]
|
|
204
204
|
faces_perimeter_info = self[
|
|
205
|
-
f"/
|
|
205
|
+
f"{self.FLOW_AREA_2D_PATH}/{mesh_name}/Faces Perimeter Info"
|
|
206
206
|
][()]
|
|
207
207
|
faces_perimeter_values = self[
|
|
208
|
-
f"/
|
|
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
|
|
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.
|
|
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,,
|
rashdf-0.2.2.dist-info/RECORD
DELETED
|
@@ -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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|