ras-commander 0.42.0__py3-none-any.whl → 0.43.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.
- ras_commander/Decorators.py +111 -0
- ras_commander/HdfBase.py +189 -0
- ras_commander/HdfBndry.py +496 -0
- ras_commander/HdfMesh.py +299 -0
- ras_commander/HdfPlan.py +190 -0
- ras_commander/HdfResultsMesh.py +648 -0
- ras_commander/HdfResultsPlan.py +392 -0
- ras_commander/HdfResultsXsec.py +227 -0
- ras_commander/HdfStruc.py +138 -0
- ras_commander/HdfUtils.py +461 -0
- ras_commander/HdfXsec.py +272 -0
- ras_commander/RasCmdr.py +2 -1
- ras_commander/RasExamples.py +49 -116
- ras_commander/RasGeo.py +2 -2
- ras_commander/RasGpt.py +6 -129
- ras_commander/RasPlan.py +2 -2
- ras_commander/RasPrj.py +55 -9
- ras_commander/RasUnsteady.py +2 -1
- ras_commander/RasUtils.py +198 -73
- ras_commander/__init__.py +31 -9
- {ras_commander-0.42.0.dist-info → ras_commander-0.43.0.dist-info}/METADATA +1 -1
- ras_commander-0.43.0.dist-info/RECORD +26 -0
- ras_commander/RasHdf.py +0 -1619
- ras_commander-0.42.0.dist-info/RECORD +0 -16
- /ras_commander/{logging_config.py → LoggingConfig.py} +0 -0
- {ras_commander-0.42.0.dist-info → ras_commander-0.43.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.42.0.dist-info → ras_commander-0.43.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.42.0.dist-info → ras_commander-0.43.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,111 @@
|
|
1
|
+
from functools import wraps
|
2
|
+
from pathlib import Path
|
3
|
+
from typing import Union
|
4
|
+
import logging
|
5
|
+
import h5py
|
6
|
+
import inspect
|
7
|
+
|
8
|
+
|
9
|
+
def log_call(func):
|
10
|
+
@wraps(func)
|
11
|
+
def wrapper(*args, **kwargs):
|
12
|
+
logger = logging.getLogger(func.__module__)
|
13
|
+
logger.info(f"Calling {func.__name__}")
|
14
|
+
result = func(*args, **kwargs)
|
15
|
+
logger.info(f"Finished {func.__name__}")
|
16
|
+
return result
|
17
|
+
return wrapper
|
18
|
+
|
19
|
+
|
20
|
+
def standardize_input(file_type: str = 'plan_hdf'):
|
21
|
+
"""
|
22
|
+
Decorator to standardize input for HDF file operations.
|
23
|
+
|
24
|
+
This decorator processes various input types and converts them to a Path object
|
25
|
+
pointing to the correct HDF file. It handles the following input types:
|
26
|
+
- h5py.File objects
|
27
|
+
- pathlib.Path objects
|
28
|
+
- Strings (file paths or plan/geom numbers)
|
29
|
+
- Integers (interpreted as plan/geom numbers)
|
30
|
+
|
31
|
+
The decorator also manages RAS object references and logging.
|
32
|
+
|
33
|
+
Args:
|
34
|
+
file_type (str): Specifies whether to look for 'plan_hdf' or 'geom_hdf' files.
|
35
|
+
|
36
|
+
Returns:
|
37
|
+
A decorator that wraps the function to standardize its input to a Path object.
|
38
|
+
"""
|
39
|
+
def decorator(func):
|
40
|
+
@wraps(func)
|
41
|
+
def wrapper(*args, **kwargs):
|
42
|
+
logger = logging.getLogger(func.__module__)
|
43
|
+
|
44
|
+
# Handle both static method calls and regular function calls
|
45
|
+
if args and isinstance(args[0], type):
|
46
|
+
# Static method call, remove the class argument
|
47
|
+
args = args[1:]
|
48
|
+
|
49
|
+
hdf_input = kwargs.pop('hdf_path', None) or kwargs.pop('hdf_input', None) or (args[0] if args else None)
|
50
|
+
ras_object = kwargs.pop('ras_object', None) or (args[1] if len(args) > 1 else None)
|
51
|
+
|
52
|
+
hdf_path = None
|
53
|
+
|
54
|
+
# If hdf_input is already an h5py.File object, use its filename
|
55
|
+
if isinstance(hdf_input, h5py.File):
|
56
|
+
hdf_path = Path(hdf_input.filename)
|
57
|
+
# Handle Path objects
|
58
|
+
elif isinstance(hdf_input, Path):
|
59
|
+
if hdf_input.is_file():
|
60
|
+
hdf_path = hdf_input
|
61
|
+
# Handle string inputs
|
62
|
+
elif isinstance(hdf_input, str):
|
63
|
+
# Check if it's a file path
|
64
|
+
if Path(hdf_input).is_file():
|
65
|
+
hdf_path = Path(hdf_input)
|
66
|
+
# Check if it's a number (with or without 'p' prefix)
|
67
|
+
elif hdf_input.isdigit() or (len(hdf_input) == 3 and hdf_input[0] == 'p' and hdf_input[1:].isdigit()):
|
68
|
+
if ras_object is None:
|
69
|
+
raise ValueError("RAS object is required when using plan or geom numbers.")
|
70
|
+
number = hdf_input if hdf_input.isdigit() else hdf_input[1:]
|
71
|
+
|
72
|
+
if file_type == 'plan_hdf':
|
73
|
+
plan_info = ras_object.plan_df[ras_object.plan_df['plan_number'] == number]
|
74
|
+
if not plan_info.empty:
|
75
|
+
hdf_path = Path(plan_info.iloc[0]['HDF_Results_Path'])
|
76
|
+
elif file_type == 'geom_hdf':
|
77
|
+
geom_info = ras_object.geom_df[ras_object.geom_df['geom_number'] == number]
|
78
|
+
if not geom_info.empty:
|
79
|
+
hdf_path = Path(geom_info.iloc[0]['HDF_Path'])
|
80
|
+
else:
|
81
|
+
raise ValueError(f"Invalid file type: {file_type}")
|
82
|
+
# Handle integer inputs (assuming they're plan or geom numbers)
|
83
|
+
elif isinstance(hdf_input, int):
|
84
|
+
if ras_object is None:
|
85
|
+
raise ValueError("RAS object is required when using plan or geom numbers.")
|
86
|
+
number = f"{hdf_input:02d}"
|
87
|
+
|
88
|
+
if file_type == 'plan_hdf':
|
89
|
+
plan_info = ras_object.plan_df[ras_object.plan_df['plan_number'] == number]
|
90
|
+
if not plan_info.empty:
|
91
|
+
hdf_path = Path(plan_info.iloc[0]['HDF_Results_Path'])
|
92
|
+
elif file_type == 'geom_hdf':
|
93
|
+
geom_info = ras_object.geom_df[ras_object.geom_df['geom_number'] == number]
|
94
|
+
if not geom_info.empty:
|
95
|
+
hdf_path = Path(geom_info.iloc[0]['HDF_Path'])
|
96
|
+
else:
|
97
|
+
raise ValueError(f"Invalid file type: {file_type}")
|
98
|
+
|
99
|
+
if hdf_path is None or not hdf_path.is_file():
|
100
|
+
error_msg = f"HDF file not found: {hdf_input}"
|
101
|
+
logger.error(error_msg)
|
102
|
+
raise FileNotFoundError(error_msg)
|
103
|
+
|
104
|
+
logger.info(f"Using HDF file: {hdf_path}")
|
105
|
+
|
106
|
+
# Pass all original arguments and keywords, replacing hdf_input with standardized hdf_path
|
107
|
+
new_args = (hdf_path,) + args[1:]
|
108
|
+
return func(*new_args, **kwargs)
|
109
|
+
|
110
|
+
return wrapper
|
111
|
+
return decorator
|
ras_commander/HdfBase.py
ADDED
@@ -0,0 +1,189 @@
|
|
1
|
+
import re
|
2
|
+
from datetime import datetime, timedelta
|
3
|
+
import h5py
|
4
|
+
import numpy as np
|
5
|
+
import pandas as pd
|
6
|
+
import xarray as xr # Added import for xarray
|
7
|
+
from typing import List, Tuple, Union, Optional, Dict
|
8
|
+
from pathlib import Path
|
9
|
+
import logging
|
10
|
+
import dask.array as da
|
11
|
+
|
12
|
+
from .HdfUtils import HdfUtils
|
13
|
+
from .Decorators import standardize_input, log_call
|
14
|
+
from .LoggingConfig import setup_logging, get_logger
|
15
|
+
|
16
|
+
logger = get_logger(__name__)
|
17
|
+
|
18
|
+
class HdfBase:
|
19
|
+
"""
|
20
|
+
Base class for HEC-RAS HDF file operations.
|
21
|
+
|
22
|
+
This class provides fundamental methods for interacting with HEC-RAS HDF files,
|
23
|
+
including time-related operations and mesh data retrieval. It serves as a foundation
|
24
|
+
for more specialized HDF classes.
|
25
|
+
|
26
|
+
The methods in this class are designed to work with both plan and geometry HDF files,
|
27
|
+
providing low-level access to file structure and content.
|
28
|
+
|
29
|
+
Note:
|
30
|
+
- All methods in this class are static, allowing for use without instantiation.
|
31
|
+
- This class is not meant to be used directly in most cases, but rather as a base
|
32
|
+
for more specialized HDF classes.
|
33
|
+
"""
|
34
|
+
|
35
|
+
@staticmethod
|
36
|
+
def _get_simulation_start_time(hdf_file: h5py.File) -> datetime:
|
37
|
+
"""
|
38
|
+
Get the simulation start time from the HDF file.
|
39
|
+
|
40
|
+
Args:
|
41
|
+
hdf_file (h5py.File): Open HDF file object.
|
42
|
+
|
43
|
+
Returns:
|
44
|
+
datetime: The simulation start time.
|
45
|
+
|
46
|
+
Raises:
|
47
|
+
ValueError: If Plan Information is not found in the HDF file.
|
48
|
+
"""
|
49
|
+
plan_info = hdf_file.get("Plan Data/Plan Information")
|
50
|
+
if plan_info is None:
|
51
|
+
raise ValueError("Plan Information not found in HDF file")
|
52
|
+
time_str = plan_info.attrs.get('Simulation Start Time')
|
53
|
+
return datetime.strptime(time_str.decode('utf-8'), "%d%b%Y %H:%M:%S")
|
54
|
+
|
55
|
+
@staticmethod
|
56
|
+
def _get_unsteady_datetimes(hdf_file: h5py.File) -> List[datetime]:
|
57
|
+
"""
|
58
|
+
Get the list of unsteady datetimes from the HDF file.
|
59
|
+
|
60
|
+
Args:
|
61
|
+
hdf_file (h5py.File): Open HDF file object.
|
62
|
+
|
63
|
+
Returns:
|
64
|
+
List[datetime]: A list of datetime objects representing the unsteady timestamps.
|
65
|
+
"""
|
66
|
+
group_path = "Results/Unsteady/Output/Output Blocks/Base Output/Unsteady Time Series/Time Date Stamp (ms)"
|
67
|
+
raw_datetimes = hdf_file[group_path][:]
|
68
|
+
return [HdfBase._parse_ras_datetime_ms(x.decode("utf-8")) for x in raw_datetimes]
|
69
|
+
|
70
|
+
|
71
|
+
@staticmethod
|
72
|
+
def _get_2d_flow_area_names_and_counts(hdf_file: h5py.File) -> List[Tuple[str, int]]:
|
73
|
+
"""
|
74
|
+
Get the names and cell counts of 2D flow areas from the HDF file.
|
75
|
+
|
76
|
+
Args:
|
77
|
+
hdf_file (h5py.File): Open HDF file object.
|
78
|
+
|
79
|
+
Returns:
|
80
|
+
List[Tuple[str, int]]: A list of tuples containing the name and cell count of each 2D flow area.
|
81
|
+
"""
|
82
|
+
d2_flow_areas = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
83
|
+
if d2_flow_areas is None:
|
84
|
+
return []
|
85
|
+
return [(HdfBase._convert_ras_hdf_string(d2_flow_area[0]), d2_flow_area[-1]) for d2_flow_area in d2_flow_areas[:]]
|
86
|
+
|
87
|
+
@staticmethod
|
88
|
+
def _parse_ras_datetime(datetime_str: str) -> datetime:
|
89
|
+
"""
|
90
|
+
Parse a datetime string from a RAS file into a datetime object.
|
91
|
+
|
92
|
+
Args:
|
93
|
+
datetime_str (str): The datetime string to parse.
|
94
|
+
|
95
|
+
Returns:
|
96
|
+
datetime: The parsed datetime object.
|
97
|
+
"""
|
98
|
+
return datetime.strptime(datetime_str, "%d%b%Y %H:%M:%S")
|
99
|
+
|
100
|
+
@staticmethod
|
101
|
+
def _parse_ras_simulation_window_datetime(datetime_str: str) -> datetime:
|
102
|
+
"""
|
103
|
+
Parse a datetime string from a RAS simulation window into a datetime object.
|
104
|
+
|
105
|
+
Args:
|
106
|
+
datetime_str (str): The datetime string to parse.
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
datetime: The parsed datetime object.
|
110
|
+
"""
|
111
|
+
return datetime.strptime(datetime_str, "%d%b%Y %H%M")
|
112
|
+
|
113
|
+
@staticmethod
|
114
|
+
def _parse_duration(duration_str: str) -> timedelta:
|
115
|
+
"""
|
116
|
+
Parse a duration string into a timedelta object.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
duration_str (str): The duration string to parse.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
timedelta: The parsed duration as a timedelta object.
|
123
|
+
"""
|
124
|
+
hours, minutes, seconds = map(int, duration_str.split(':'))
|
125
|
+
return timedelta(hours=hours, minutes=minutes, seconds=seconds)
|
126
|
+
|
127
|
+
@staticmethod
|
128
|
+
def _parse_ras_datetime_ms(datetime_str: str) -> datetime:
|
129
|
+
"""
|
130
|
+
Parse a datetime string with milliseconds from a RAS file.
|
131
|
+
|
132
|
+
Args:
|
133
|
+
datetime_str (str): The datetime string to parse.
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
datetime: The parsed datetime object.
|
137
|
+
"""
|
138
|
+
milliseconds = int(datetime_str[-3:])
|
139
|
+
microseconds = milliseconds * 1000
|
140
|
+
parsed_dt = HdfBase._parse_ras_datetime(datetime_str[:-4]).replace(microsecond=microseconds)
|
141
|
+
return parsed_dt
|
142
|
+
|
143
|
+
@staticmethod
|
144
|
+
def _convert_ras_hdf_string(value: Union[str, bytes]) -> Union[bool, datetime, List[datetime], timedelta, str]:
|
145
|
+
"""
|
146
|
+
Convert a string value from an HEC-RAS HDF file into a Python object.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
value (Union[str, bytes]): The value to convert.
|
150
|
+
|
151
|
+
Returns:
|
152
|
+
Union[bool, datetime, List[datetime], timedelta, str]: The converted value.
|
153
|
+
"""
|
154
|
+
if isinstance(value, bytes):
|
155
|
+
s = value.decode("utf-8")
|
156
|
+
else:
|
157
|
+
s = value
|
158
|
+
|
159
|
+
if s == "True":
|
160
|
+
return True
|
161
|
+
elif s == "False":
|
162
|
+
return False
|
163
|
+
|
164
|
+
ras_datetime_format1_re = r"\d{2}\w{3}\d{4} \d{2}:\d{2}:\d{2}"
|
165
|
+
ras_datetime_format2_re = r"\d{2}\w{3}\d{4} \d{2}\d{2}"
|
166
|
+
ras_duration_format_re = r"\d{2}:\d{2}:\d{2}"
|
167
|
+
|
168
|
+
if re.match(rf"^{ras_datetime_format1_re}", s):
|
169
|
+
if re.match(rf"^{ras_datetime_format1_re} to {ras_datetime_format1_re}$", s):
|
170
|
+
split = s.split(" to ")
|
171
|
+
return [
|
172
|
+
HdfBase._parse_ras_datetime(split[0]),
|
173
|
+
HdfBase._parse_ras_datetime(split[1]),
|
174
|
+
]
|
175
|
+
return HdfBase._parse_ras_datetime(s)
|
176
|
+
elif re.match(rf"^{ras_datetime_format2_re}", s):
|
177
|
+
if re.match(rf"^{ras_datetime_format2_re} to {ras_datetime_format2_re}$", s):
|
178
|
+
split = s.split(" to ")
|
179
|
+
return [
|
180
|
+
HdfBase._parse_ras_simulation_window_datetime(split[0]),
|
181
|
+
HdfBase._parse_ras_simulation_window_datetime(split[1]),
|
182
|
+
]
|
183
|
+
return HdfBase._parse_ras_simulation_window_datetime(s)
|
184
|
+
elif re.match(rf"^{ras_duration_format_re}$", s):
|
185
|
+
return HdfBase._parse_duration(s)
|
186
|
+
return s
|
187
|
+
|
188
|
+
|
189
|
+
|