ras-commander 0.48.0__py3-none-any.whl → 0.50.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.
@@ -6,6 +6,61 @@ from the https://github.com/fema-ffrd/rashdf library,
6
6
  released under MIT license and Copyright (c) 2024 fema-ffrd
7
7
 
8
8
  The file has been forked and modified for use in RAS Commander.
9
+
10
+ -----
11
+
12
+ All methods in this class are static and designed to be used without instantiation.
13
+
14
+ Public Functions:
15
+ - get_mesh_summary(): Get summary output data for a variable
16
+ - get_mesh_timeseries(): Get timeseries output for a mesh and variable
17
+ - get_mesh_faces_timeseries(): Get timeseries for all face-based variables
18
+ - get_mesh_cells_timeseries(): Get timeseries for mesh cells
19
+ - get_mesh_last_iter(): Get last iteration count for cells
20
+ - get_mesh_max_ws(): Get maximum water surface elevation at each cell
21
+ - get_mesh_min_ws(): Get minimum water surface elevation at each cell
22
+ - get_mesh_max_face_v(): Get maximum face velocity at each face
23
+ - get_mesh_min_face_v(): Get minimum face velocity at each face
24
+ - get_mesh_max_ws_err(): Get maximum water surface error at each cell
25
+ - get_mesh_max_iter(): Get maximum iteration count at each cell
26
+
27
+ Private Functions:
28
+ - _get_mesh_timeseries_output_path(): Get HDF path for timeseries output #REDUNDANT??
29
+ - _get_mesh_cells_timeseries_output(): Internal handler for cell timeseries #REDUNDANT??
30
+ - _get_mesh_timeseries_output(): Internal handler for mesh timeseries # FACES??
31
+ - _get_mesh_timeseries_output_values_units(): Get values and units for timeseries
32
+ - _get_available_meshes(): Get list of available meshes in HDF #USE HDFBASE OR HDFUTIL
33
+ - get_mesh_summary_output(): Internal handler for summary output
34
+ - get_mesh_summary_output_group(): Get HDF group for summary output #REDUNDANT?? Include in Above
35
+
36
+ The class works with HEC-RAS version 6.0+ plan HDF files and uses HdfBase and
37
+ HdfUtils for common operations. Methods use @log_call decorator for logging and
38
+ @standardize_input decorator to handle different input types.
39
+
40
+
41
+
42
+
43
+
44
+
45
+ REVISIONS MADE:
46
+
47
+ Use get_ prefix for functions that return data.
48
+ BUT, we will never set results data, so we should use get_ for results data.
49
+
50
+ Renamed functions:
51
+ - mesh_summary_output() to get_mesh_summary()
52
+ - mesh_timeseries_output() to get_mesh_timeseries()
53
+ - mesh_faces_timeseries_output() to get_mesh_faces_timeseries()
54
+ - mesh_cells_timeseries_output() to get_mesh_cells_timeseries()
55
+ - mesh_last_iter() to get_mesh_last_iter()
56
+ - mesh_max_ws() to get_mesh_max_ws()
57
+
58
+
59
+
60
+
61
+
62
+
63
+
9
64
  """
10
65
 
11
66
  import numpy as np
@@ -19,81 +74,81 @@ from .HdfBase import HdfBase
19
74
  from .HdfUtils import HdfUtils
20
75
  from .Decorators import log_call, standardize_input
21
76
  from .LoggingConfig import setup_logging, get_logger
77
+ import geopandas as gpd
22
78
 
23
79
  logger = get_logger(__name__)
24
80
 
25
81
  class HdfResultsMesh:
26
82
  """
27
- A class for handling mesh-related results from HEC-RAS HDF files.
28
-
29
- This class provides methods to extract and analyze mesh summary outputs,
30
- timeseries data, and various mesh-specific results such as water surface
31
- elevations, velocities, and errors.
32
-
33
- The class works with HEC-RAS plan HDF files and uses HdfBase and HdfUtils
34
- for common operations and utilities.
83
+ Handles mesh-related results from HEC-RAS HDF files.
35
84
 
36
- Methods in this class use the @log_call decorator for logging and the
37
- @standardize_input decorator to handle different input types (e.g.,
38
- plan number, file path).
85
+ Provides methods to extract and analyze:
86
+ - Mesh summary outputs
87
+ - Timeseries data
88
+ - Water surface elevations
89
+ - Velocities
90
+ - Error metrics
39
91
 
40
- Attributes:
41
- None
42
-
43
- Note:
44
- This class is designed to work with HEC-RAS version 6.0 and later.
92
+ Works with HEC-RAS 6.0+ plan HDF files.
45
93
  """
46
94
 
47
95
  @staticmethod
48
96
  @log_call
49
97
  @standardize_input(file_type='plan_hdf')
50
- def mesh_summary_output(hdf_path: Path, var: str, round_to: str = "100ms") -> pd.DataFrame:
98
+ def get_mesh_summary(hdf_path: Path, var: str, round_to: str = "100ms") -> pd.DataFrame:
51
99
  """
52
- Return the summary output data for a given variable.
100
+ Get timeseries output for a specific mesh and variable.
53
101
 
54
102
  Args:
55
- hdf_path (Path): Path to the HEC-RAS plan HDF file.
56
- var (str): The summary output variable to retrieve.
57
- round_to (str): The time unit to round the datetimes to. Default: "100ms" (100 milliseconds).
103
+ hdf_path (Path): Path to the HDF file
104
+ mesh_name (str): Name of the mesh
105
+ var (str): Variable to retrieve (see valid options below)
106
+ truncate (bool): Whether to truncate trailing zeros (default True)
58
107
 
59
108
  Returns:
60
- pd.DataFrame: DataFrame containing the summary output data.
109
+ xr.DataArray: DataArray with dimensions:
110
+ - time: Timestamps
111
+ - face_id/cell_id: IDs for faces/cells
112
+ And attributes:
113
+ - units: Variable units
114
+ - mesh_name: Name of mesh
115
+ - variable: Variable name
61
116
 
62
- Raises:
63
- ValueError: If there's an error processing the summary output data.
117
+ Valid variables include:
118
+ "Water Surface", "Face Velocity", "Cell Velocity X"...
64
119
  """
65
120
  try:
66
121
  with h5py.File(hdf_path, 'r') as hdf_file:
67
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, var, round_to)
122
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, var, round_to)
68
123
  except Exception as e:
69
- logger.error(f"Error in mesh_summary_output: {str(e)}")
124
+ logger.error(f"Error in get_mesh_summary: {str(e)}")
70
125
  logger.error(f"Variable: {var}")
71
126
  raise ValueError(f"Failed to get summary output: {str(e)}")
72
127
 
73
128
  @staticmethod
74
129
  @log_call
75
130
  @standardize_input(file_type='plan_hdf')
76
- def mesh_timeseries_output(hdf_path: Path, mesh_name: str, var: str, truncate: bool = True) -> xr.DataArray:
131
+ def get_mesh_timeseries(hdf_path: Path, mesh_name: str, var: str, truncate: bool = True) -> xr.DataArray:
77
132
  """
78
133
  Get timeseries output for a specific mesh and variable.
79
134
 
80
135
  Args:
81
- hdf_path (Path): Path to the HDF file.
82
- mesh_name (str): Name of the mesh.
83
- var (str): Variable to retrieve. Valid options include:
84
- "Water Surface", "Face Velocity", "Cell Velocity X", "Cell Velocity Y",
85
- "Face Flow", "Face Water Surface", "Cell Volume", "Cell Volume Error",
86
- "Cell Water Surface Error", "Cell Courant", "Face Courant",
87
- "Cell Hydraulic Depth", "Cell Invert Depth",
88
- "Cell Cumulative Precipitation Depth", "Cell Divergence Term",
89
- "Cell Eddy Viscosity X", "Cell Eddy Viscosity Y", "Cell Flow Balance",
90
- "Cell Storage Term", "Cell Water Source Term", "Face Cumulative Volume",
91
- "Face Eddy Viscosity", "Face Flow Period Average", "Face Friction Term",
92
- "Face Pressure Gradient Term", "Face Shear Stress", "Face Tangential Velocity"
93
- truncate (bool): Whether to truncate the output (default True).
136
+ hdf_path (Path): Path to the HDF file
137
+ mesh_name (str): Name of the mesh
138
+ var (str): Variable to retrieve (see valid options below)
139
+ truncate (bool): Whether to truncate trailing zeros (default True)
94
140
 
95
141
  Returns:
96
- xr.DataArray: DataArray containing the timeseries output.
142
+ xr.DataArray: DataArray with dimensions:
143
+ - time: Timestamps
144
+ - face_id/cell_id: IDs for faces/cells
145
+ And attributes:
146
+ - units: Variable units
147
+ - mesh_name: Name of mesh
148
+ - variable: Variable name
149
+
150
+ Valid variables include:
151
+ "Water Surface", "Face Velocity", "Cell Velocity X"...
97
152
  """
98
153
  with h5py.File(hdf_path, 'r') as hdf_file:
99
154
  return HdfResultsMesh._get_mesh_timeseries_output(hdf_file, mesh_name, var, truncate)
@@ -101,7 +156,7 @@ class HdfResultsMesh:
101
156
  @staticmethod
102
157
  @log_call
103
158
  @standardize_input(file_type='plan_hdf')
104
- def mesh_faces_timeseries_output(hdf_path: Path, mesh_name: str) -> xr.Dataset:
159
+ def get_mesh_faces_timeseries(hdf_path: Path, mesh_name: str) -> xr.Dataset:
105
160
  """
106
161
  Get timeseries output for all face-based variables of a specific mesh.
107
162
 
@@ -117,7 +172,7 @@ class HdfResultsMesh:
117
172
 
118
173
  for var in face_vars:
119
174
  try:
120
- da = HdfResultsMesh.mesh_timeseries_output(hdf_path, mesh_name, var)
175
+ da = HdfResultsMesh.get_mesh_timeseries(hdf_path, mesh_name, var)
121
176
  # Assign the variable name as the DataArray name
122
177
  da.name = var.lower().replace(' ', '_')
123
178
  datasets.append(da)
@@ -137,34 +192,34 @@ class HdfResultsMesh:
137
192
  @staticmethod
138
193
  @log_call
139
194
  @standardize_input(file_type='plan_hdf')
140
- def mesh_cells_timeseries_output(hdf_path: Path, mesh_names: Optional[Union[str, List[str]]] = None, var: Optional[str] = None, truncate: bool = False, ras_object: Optional[Any] = None) -> Dict[str, xr.Dataset]:
195
+ def get_mesh_cells_timeseries(hdf_path: Path, mesh_names: Optional[Union[str, List[str]]] = None, var: Optional[str] = None, truncate: bool = False, ras_object: Optional[Any] = None) -> Dict[str, xr.Dataset]:
141
196
  """
142
- Get mesh cells timeseries output for specified meshes and variables.
197
+ Get mesh cells timeseries output.
143
198
 
144
199
  Args:
145
- hdf_path (Union[str, Path]): Path to the HDF file.
146
- mesh_names (Optional[Union[str, List[str]]]): Name(s) of the mesh(es). If None, processes all available meshes.
147
- var (Optional[str]): Name of the variable to retrieve. If None, retrieves all variables.
148
- truncate (bool): If True, truncates the output to remove trailing zeros.
149
- ras_object (Optional[Any]): RAS object, if available.
200
+ hdf_path (Path): Path to HDF file
201
+ mesh_names (str|List[str], optional): Mesh name(s). If None, processes all meshes
202
+ var (str, optional): Variable name. If None, retrieves all variables
203
+ truncate (bool): Remove trailing zeros if True
204
+ ras_object (Any, optional): RAS object if available
150
205
 
151
206
  Returns:
152
- Dict[str, xr.Dataset]: A dictionary of xarray Datasets, one for each mesh, containing the mesh cells timeseries output.
153
-
154
- Raises:
155
- ValueError: If there's an error processing the timeseries output data.
207
+ Dict[str, xr.Dataset]: Dictionary mapping mesh names to datasets containing:
208
+ - Time-indexed variables
209
+ - Cell/face IDs
210
+ - Variable metadata
156
211
  """
157
212
  try:
158
213
  with h5py.File(hdf_path, 'r') as hdf_file:
159
- return HdfResultsMesh._mesh_cells_timeseries_output(hdf_file, mesh_names, var, truncate)
214
+ return HdfResultsMesh._get_mesh_cells_timeseries_output(hdf_file, mesh_names, var, truncate)
160
215
  except Exception as e:
161
- logger.error(f"Error in mesh_cells_timeseries_output: {str(e)}")
216
+ logger.error(f"Error in get_mesh_cells_timeseries: {str(e)}")
162
217
  raise ValueError(f"Error processing timeseries output data: {e}")
163
218
 
164
219
  @staticmethod
165
220
  @log_call
166
221
  @standardize_input(file_type='plan_hdf')
167
- def mesh_last_iter(hdf_path: Path) -> pd.DataFrame:
222
+ def get_mesh_last_iter(hdf_path: Path) -> pd.DataFrame:
168
223
  """
169
224
  Get last iteration count for each mesh cell.
170
225
 
@@ -174,33 +229,28 @@ class HdfResultsMesh:
174
229
  Returns:
175
230
  pd.DataFrame: DataFrame containing last iteration counts.
176
231
  """
177
- return HdfResultsMesh._get_mesh_summary_output(hdf_path, "Cell Last Iteration")
232
+ return HdfResultsMesh.get_mesh_summary_output(hdf_path, "Cell Last Iteration")
178
233
 
179
234
 
180
235
  @staticmethod
181
236
  @log_call
182
237
  @standardize_input(file_type='plan_hdf')
183
- def mesh_max_ws(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
238
+ def get_mesh_max_ws(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
184
239
  """
185
- Get maximum iteration count for each mesh cell.
240
+ Get maximum water surface elevation for each mesh cell.
186
241
 
187
242
  Args:
188
243
  hdf_path (Path): Path to the HDF file.
189
244
  round_to (str): Time rounding specification (default "100ms").
190
245
 
191
246
  Returns:
192
- pd.DataFrame: DataFrame containing maximum iteration counts.
193
-
194
- Raises:
195
- ValueError: If there's an error processing the maximum iteration data.
196
-
197
- Note: The Maximum Iteration is labeled as "Cell Last Iteration" in the HDF file
247
+ gpd.GeoDataFrame: GeoDataFrame containing maximum water surface elevations with geometry.
198
248
  """
199
249
  try:
200
250
  with h5py.File(hdf_path, 'r') as hdf_file:
201
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Maximum Water Surface", round_to)
251
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Maximum Water Surface", round_to)
202
252
  except Exception as e:
203
- logger.error(f"Error in mesh_max_ws: {str(e)}")
253
+ logger.error(f"Error in get_mesh_max_ws: {str(e)}")
204
254
  raise ValueError(f"Failed to get maximum water surface: {str(e)}")
205
255
 
206
256
 
@@ -210,7 +260,7 @@ class HdfResultsMesh:
210
260
  @staticmethod
211
261
  @log_call
212
262
  @standardize_input(file_type='plan_hdf')
213
- def mesh_min_ws(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
263
+ def get_mesh_min_ws(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
214
264
  """
215
265
  Get minimum water surface elevation for each mesh cell.
216
266
 
@@ -219,19 +269,19 @@ class HdfResultsMesh:
219
269
  round_to (str): Time rounding specification (default "100ms").
220
270
 
221
271
  Returns:
222
- pd.DataFrame: DataFrame containing minimum water surface elevations.
272
+ gpd.GeoDataFrame: GeoDataFrame containing minimum water surface elevations with geometry.
223
273
  """
224
274
  try:
225
275
  with h5py.File(hdf_path, 'r') as hdf_file:
226
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Minimum Water Surface", round_to)
276
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Minimum Water Surface", round_to)
227
277
  except Exception as e:
228
- logger.error(f"Error in mesh_min_ws: {str(e)}")
278
+ logger.error(f"Error in get_mesh_min_ws: {str(e)}")
229
279
  raise ValueError(f"Failed to get minimum water surface: {str(e)}")
230
280
 
231
281
  @staticmethod
232
282
  @log_call
233
283
  @standardize_input(file_type='plan_hdf')
234
- def mesh_max_face_v(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
284
+ def get_mesh_max_face_v(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
235
285
  """
236
286
  Get maximum face velocity for each mesh face.
237
287
 
@@ -241,21 +291,18 @@ class HdfResultsMesh:
241
291
 
242
292
  Returns:
243
293
  pd.DataFrame: DataFrame containing maximum face velocities.
244
-
245
- Raises:
246
- ValueError: If there's an error processing the maximum face velocity data.
247
294
  """
248
295
  try:
249
296
  with h5py.File(hdf_path, 'r') as hdf_file:
250
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Maximum Face Velocity", round_to)
297
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Maximum Face Velocity", round_to)
251
298
  except Exception as e:
252
- logger.error(f"Error in mesh_max_face_v: {str(e)}")
299
+ logger.error(f"Error in get_mesh_max_face_v: {str(e)}")
253
300
  raise ValueError(f"Failed to get maximum face velocity: {str(e)}")
254
301
 
255
302
  @staticmethod
256
303
  @log_call
257
304
  @standardize_input(file_type='plan_hdf')
258
- def mesh_min_face_v(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
305
+ def get_mesh_min_face_v(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
259
306
  """
260
307
  Get minimum face velocity for each mesh cell.
261
308
 
@@ -271,15 +318,15 @@ class HdfResultsMesh:
271
318
  """
272
319
  try:
273
320
  with h5py.File(hdf_path, 'r') as hdf_file:
274
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Minimum Face Velocity", round_to)
321
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Minimum Face Velocity", round_to)
275
322
  except Exception as e:
276
- logger.error(f"Error in mesh_min_face_v: {str(e)}")
323
+ logger.error(f"Error in get_mesh_min_face_v: {str(e)}")
277
324
  raise ValueError(f"Failed to get minimum face velocity: {str(e)}")
278
325
 
279
326
  @staticmethod
280
327
  @log_call
281
328
  @standardize_input(file_type='plan_hdf')
282
- def mesh_max_ws_err(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
329
+ def get_mesh_max_ws_err(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
283
330
  """
284
331
  Get maximum water surface error for each mesh cell.
285
332
 
@@ -295,16 +342,16 @@ class HdfResultsMesh:
295
342
  """
296
343
  try:
297
344
  with h5py.File(hdf_path, 'r') as hdf_file:
298
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Cell Maximum Water Surface Error", round_to)
345
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Cell Maximum Water Surface Error", round_to)
299
346
  except Exception as e:
300
- logger.error(f"Error in mesh_max_ws_err: {str(e)}")
347
+ logger.error(f"Error in get_mesh_max_ws_err: {str(e)}")
301
348
  raise ValueError(f"Failed to get maximum water surface error: {str(e)}")
302
349
 
303
350
 
304
351
  @staticmethod
305
352
  @log_call
306
353
  @standardize_input(file_type='plan_hdf')
307
- def mesh_max_iter(hdf_path: Path, round_to: str = "100ms") -> pd.DataFrame:
354
+ def get_mesh_max_iter(hdf_path: Path, round_to: str = "100ms") -> gpd.GeoDataFrame:
308
355
  """
309
356
  Get maximum iteration count for each mesh cell.
310
357
 
@@ -313,22 +360,22 @@ class HdfResultsMesh:
313
360
  round_to (str): Time rounding specification (default "100ms").
314
361
 
315
362
  Returns:
316
- pd.DataFrame: DataFrame containing maximum iteration counts.
317
-
318
- Raises:
319
- ValueError: If there's an error processing the maximum iteration data.
320
-
321
- Note: The Maximum Iteration is labeled as "Cell Last Iteration" in the HDF file
363
+ gpd.GeoDataFrame: GeoDataFrame containing maximum iteration counts with geometry.
364
+ Includes columns:
365
+ - mesh_name: Name of the mesh
366
+ - cell_id: ID of the cell
367
+ - cell_last_iteration: Maximum number of iterations
368
+ - cell_last_iteration_time: Time when max iterations occurred
369
+ - geometry: Point geometry representing cell center
322
370
  """
323
371
  try:
324
372
  with h5py.File(hdf_path, 'r') as hdf_file:
325
- return HdfResultsMesh._get_mesh_summary_output(hdf_file, "Cell Last Iteration", round_to)
373
+ return HdfResultsMesh.get_mesh_summary_output(hdf_file, "Cell Last Iteration", round_to)
326
374
  except Exception as e:
327
- logger.error(f"Error in mesh_max_iter: {str(e)}")
375
+ logger.error(f"Error in get_mesh_max_iter: {str(e)}")
328
376
  raise ValueError(f"Failed to get maximum iteration count: {str(e)}")
329
377
 
330
378
 
331
-
332
379
 
333
380
 
334
381
  @staticmethod
@@ -347,10 +394,13 @@ class HdfResultsMesh:
347
394
 
348
395
 
349
396
  @staticmethod
350
- def _mesh_cells_timeseries_output(hdf_file: h5py.File, mesh_names: Optional[Union[str, List[str]]] = None, var: Optional[str] = None, truncate: bool = False) -> Dict[str, xr.Dataset]:
397
+ def _get_mesh_cells_timeseries_output(hdf_file: h5py.File,
398
+ mesh_names: Optional[Union[str, List[str]]] = None,
399
+ var: Optional[str] = None,
400
+ truncate: bool = False) -> Dict[str, xr.Dataset]:
351
401
  """
352
402
  Get mesh cells timeseries output for specified meshes and variables.
353
-
403
+
354
404
  Args:
355
405
  hdf_file (h5py.File): Open HDF file object.
356
406
  mesh_names (Optional[Union[str, List[str]]]): Name(s) of the mesh(es). If None, processes all available meshes.
@@ -381,8 +431,8 @@ class HdfResultsMesh:
381
431
  }
382
432
 
383
433
  try:
384
- start_time = HdfBase._get_simulation_start_time(hdf_file)
385
- time_stamps = HdfBase._get_unsteady_datetimes(hdf_file)
434
+ start_time = HdfBase.get_simulation_start_time(hdf_file)
435
+ time_stamps = HdfBase.get_unsteady_timestamps(hdf_file)
386
436
 
387
437
  if mesh_names is None:
388
438
  mesh_names = HdfResultsMesh._get_available_meshes(hdf_file)
@@ -470,19 +520,26 @@ class HdfResultsMesh:
470
520
  dataset = hdf_file[path]
471
521
  values = dataset[:]
472
522
  units = dataset.attrs.get("Units", "").decode("utf-8")
473
- times = HdfBase._get_unsteady_datetimes(hdf_file)
523
+
524
+ # Get start time and timesteps
525
+ start_time = HdfBase.get_simulation_start_time(hdf_file)
526
+ # Updated to use the new function name from HdfUtils
527
+ timesteps = HdfUtils.convert_timesteps_to_datetimes(
528
+ np.array(hdf_file["Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/Time"][:]),
529
+ start_time
530
+ )
474
531
 
475
532
  if truncate:
476
533
  non_zero = np.nonzero(values)[0]
477
534
  if len(non_zero) > 0:
478
535
  start, end = non_zero[0], non_zero[-1] + 1
479
536
  values = values[start:end]
480
- times = times[start:end]
537
+ timesteps = timesteps[start:end]
481
538
 
482
539
  # Determine if this is a face-based or cell-based variable
483
540
  id_dim = "face_id" if "Face" in var else "cell_id"
484
541
  dims = ["time", id_dim] if values.ndim == 2 else ["time"]
485
- coords = {"time": times}
542
+ coords = {"time": timesteps}
486
543
  if values.ndim == 2:
487
544
  coords[id_dim] = np.arange(values.shape[1])
488
545
 
@@ -530,16 +587,13 @@ class HdfResultsMesh:
530
587
  Returns:
531
588
  List[str]: A list of mesh names.
532
589
  """
533
- mesh_names = []
534
- base_path = "Geometry/2D Flow Areas"
535
- if base_path in hdf_file:
536
- for name in hdf_file[base_path]:
537
- if isinstance(hdf_file[f"{base_path}/{name}"], h5py.Group):
538
- mesh_names.append(name)
539
- return mesh_names
590
+ return HdfMesh.get_mesh_area_names(hdf_file)
591
+
540
592
 
541
593
  @staticmethod
542
- def _get_mesh_summary_output(hdf_file: h5py.File, var: str, round_to: str = "100ms") -> pd.DataFrame:
594
+ @log_call
595
+ @standardize_input(file_type='plan_hdf')
596
+ def get_mesh_summary_output(hdf_file: h5py.File, var: str, round_to: str = "100ms") -> gpd.GeoDataFrame:
543
597
  """
544
598
  Get the summary output data for a given variable from the HDF file.
545
599
 
@@ -554,8 +608,8 @@ class HdfResultsMesh:
554
608
 
555
609
  Returns
556
610
  -------
557
- pd.DataFrame
558
- A DataFrame containing the summary output data with attributes as metadata.
611
+ gpd.GeoDataFrame
612
+ A GeoDataFrame containing the summary output data with attributes as metadata.
559
613
 
560
614
  Raises
561
615
  ------
@@ -564,12 +618,18 @@ class HdfResultsMesh:
564
618
  """
565
619
  try:
566
620
  dfs = []
567
- start_time = HdfBase._get_simulation_start_time(hdf_file)
621
+ start_time = HdfBase.get_simulation_start_time(hdf_file)
568
622
 
569
623
  logger.info(f"Processing summary output for variable: {var}")
570
- for mesh_name, cell_count in HdfBase._get_2d_flow_area_names_and_counts(hdf_file):
624
+ d2_flow_areas = hdf_file.get("Geometry/2D Flow Areas/Attributes")
625
+ if d2_flow_areas is None:
626
+ return gpd.GeoDataFrame()
627
+
628
+ for d2_flow_area in d2_flow_areas[:]:
629
+ mesh_name = HdfUtils.convert_ras_string(d2_flow_area[0])
630
+ cell_count = d2_flow_area[-1]
571
631
  logger.debug(f"Processing mesh: {mesh_name} with {cell_count} cells")
572
- group = HdfResultsMesh._get_mesh_summary_output_group(hdf_file, mesh_name, var)
632
+ group = HdfResultsMesh.get_mesh_summary_output_group(hdf_file, mesh_name, var)
573
633
 
574
634
  data = group[:]
575
635
  logger.debug(f"Data shape for {var} in {mesh_name}: {data.shape}")
@@ -585,7 +645,7 @@ class HdfResultsMesh:
585
645
  "mesh_name": [mesh_name] * data.shape[1],
586
646
  "cell_id" if "Face" not in var else "face_id": range(data.shape[1]),
587
647
  f"{var.lower().replace(' ', '_')}": data[0, :],
588
- f"{var.lower().replace(' ', '_')}_time": HdfUtils._ras_timesteps_to_datetimes(
648
+ f"{var.lower().replace(' ', '_')}_time": HdfUtils.convert_timesteps_to_datetimes(
589
649
  data[1, :], start_time, time_unit="days", round_to=round_to
590
650
  )
591
651
  })
@@ -604,13 +664,13 @@ class HdfResultsMesh:
604
664
 
605
665
  # Add geometry based on variable type
606
666
  if "Face" in var:
607
- face_df = HdfMesh.mesh_cell_faces(hdf_file)
667
+ face_df = HdfMesh.get_mesh_cell_faces(hdf_file)
608
668
  if not face_df.empty:
609
669
  df = df.merge(face_df[['mesh_name', 'face_id', 'geometry']],
610
670
  on=['mesh_name', 'face_id'],
611
671
  how='left')
612
672
  else:
613
- cell_df = HdfMesh.mesh_cell_points(hdf_file)
673
+ cell_df = HdfMesh.get_mesh_cell_points(hdf_file)
614
674
  if not cell_df.empty:
615
675
  df = df.merge(cell_df[['mesh_name', 'cell_id', 'geometry']],
616
676
  on=['mesh_name', 'cell_id'],
@@ -628,10 +688,18 @@ class HdfResultsMesh:
628
688
  dfs.append(df)
629
689
 
630
690
  if not dfs:
631
- return pd.DataFrame()
691
+ return gpd.GeoDataFrame()
632
692
 
633
693
  result = pd.concat(dfs, ignore_index=True)
634
694
 
695
+ # Convert to GeoDataFrame
696
+ gdf = gpd.GeoDataFrame(result, geometry='geometry')
697
+
698
+ # Get CRS from HdfUtils
699
+ crs = HdfBase.get_projection(hdf_file)
700
+ if crs:
701
+ gdf.set_crs(crs, inplace=True)
702
+
635
703
  # Combine attributes from all meshes
636
704
  combined_attrs = {}
637
705
  for df in dfs:
@@ -641,18 +709,17 @@ class HdfResultsMesh:
641
709
  elif combined_attrs[key] != value:
642
710
  combined_attrs[key] = f"Multiple values: {combined_attrs[key]}, {value}"
643
711
 
644
- result.attrs.update(combined_attrs)
712
+ gdf.attrs.update(combined_attrs)
645
713
 
646
- logger.info(f"Processed {len(result)} rows of summary output data")
647
- return result
714
+ logger.info(f"Processed {len(gdf)} rows of summary output data")
715
+ return gdf
648
716
 
649
717
  except Exception as e:
650
718
  logger.error(f"Error processing summary output data: {e}")
651
719
  raise ValueError(f"Error processing summary output data: {e}")
652
-
653
720
 
654
721
  @staticmethod
655
- def _get_mesh_summary_output_group(hdf_file: h5py.File, mesh_name: str, var: str) -> Union[h5py.Group, h5py.Dataset]:
722
+ def get_mesh_summary_output_group(hdf_file: h5py.File, mesh_name: str, var: str) -> Union[h5py.Group, h5py.Dataset]:
656
723
  """
657
724
  Return the HDF group for a given mesh and summary output variable.
658
725
 
@@ -673,63 +740,3 @@ class HdfResultsMesh:
673
740
  raise ValueError(f"Could not find HDF group or dataset at path '{output_path}'")
674
741
  return output_item
675
742
 
676
- @staticmethod
677
- def plot_mesh_variable(variable_df: pd.DataFrame, variable_name: str, colormap: str = 'viridis', point_size: int = 10) -> None:
678
- """
679
- Plot any mesh variable with consistent styling.
680
-
681
- Args:
682
- variable_df (pd.DataFrame): DataFrame containing the variable data
683
- variable_name (str): Name of the variable (for labels)
684
- colormap (str): Matplotlib colormap to use. Default: 'viridis'
685
- point_size (int): Size of the scatter points. Default: 10
686
-
687
- Returns:
688
- None
689
-
690
- Raises:
691
- ImportError: If matplotlib is not installed
692
- ValueError: If required columns are missing from variable_df
693
- """
694
- try:
695
- import matplotlib.pyplot as plt
696
- except ImportError:
697
- logger.error("matplotlib is required for plotting. Please install it with 'pip install matplotlib'")
698
- raise ImportError("matplotlib is required for plotting")
699
-
700
- # Get cell coordinates if not in variable_df
701
- if 'geometry' not in variable_df.columns:
702
- cell_coords = HdfMesh.mesh_cell_points(plan_hdf_path)
703
- merged_df = pd.merge(variable_df, cell_coords, on=['mesh_name', 'cell_id'])
704
- else:
705
- merged_df = variable_df
706
-
707
- # Extract coordinates, handling None values
708
- merged_df = merged_df.dropna(subset=['geometry'])
709
- merged_df['x'] = merged_df['geometry'].apply(lambda geom: geom.x if geom is not None else None)
710
- merged_df['y'] = merged_df['geometry'].apply(lambda geom: geom.y if geom is not None else None)
711
-
712
- # Drop any rows with None coordinates
713
- merged_df = merged_df.dropna(subset=['x', 'y'])
714
-
715
- if len(merged_df) == 0:
716
- logger.error("No valid coordinates found for plotting")
717
- raise ValueError("No valid coordinates found for plotting")
718
-
719
- # Create plot
720
- fig, ax = plt.subplots(figsize=(12, 8))
721
- scatter = ax.scatter(merged_df['x'], merged_df['y'],
722
- c=merged_df[variable_name],
723
- cmap=colormap,
724
- s=point_size)
725
-
726
- # Customize plot
727
- ax.set_title(f'{variable_name} per Cell')
728
- ax.set_xlabel('X Coordinate')
729
- ax.set_ylabel('Y Coordinate')
730
- plt.colorbar(scatter, label=variable_name)
731
- ax.grid(True, linestyle='--', alpha=0.7)
732
- plt.rcParams.update({'font.size': 12})
733
- plt.tight_layout()
734
- plt.show()
735
-