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
ras_commander/HdfXsec.py
ADDED
@@ -0,0 +1,272 @@
|
|
1
|
+
from pathlib import Path
|
2
|
+
import h5py
|
3
|
+
import numpy as np
|
4
|
+
import pandas as pd
|
5
|
+
from geopandas import GeoDataFrame
|
6
|
+
from shapely.geometry import LineString, MultiLineString
|
7
|
+
from typing import List # Import List to avoid NameError
|
8
|
+
from .Decorators import standardize_input, log_call
|
9
|
+
from .HdfBase import HdfBase
|
10
|
+
from .HdfUtils import HdfUtils
|
11
|
+
from .LoggingConfig import get_logger
|
12
|
+
|
13
|
+
logger = get_logger(__name__)
|
14
|
+
|
15
|
+
class HdfXsec:
|
16
|
+
"""
|
17
|
+
HdfXsec class for handling cross-section related operations on HEC-RAS HDF files.
|
18
|
+
|
19
|
+
This class provides methods to extract and process cross-section data, elevation information,
|
20
|
+
and river reach data from HEC-RAS HDF geometry files. It includes functionality to retrieve
|
21
|
+
cross-section attributes, elevation profiles, and river reach geometries.
|
22
|
+
|
23
|
+
The class uses static methods, allowing for direct calls without instantiation. It relies on
|
24
|
+
utility functions from HdfBase and HdfUtils classes for various operations such as projection
|
25
|
+
handling and data conversion.
|
26
|
+
|
27
|
+
Note:
|
28
|
+
This class is designed to work with HEC-RAS geometry HDF files and requires them to have
|
29
|
+
a specific structure and naming convention for the data groups and attributes.
|
30
|
+
"""
|
31
|
+
|
32
|
+
@staticmethod
|
33
|
+
@log_call
|
34
|
+
@standardize_input(file_type='geom_hdf')
|
35
|
+
def cross_sections(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
36
|
+
"""
|
37
|
+
Return the model 1D cross sections.
|
38
|
+
|
39
|
+
This method extracts cross-section data from the HEC-RAS geometry HDF file,
|
40
|
+
including attributes and geometry information.
|
41
|
+
|
42
|
+
Parameters
|
43
|
+
----------
|
44
|
+
hdf_path : Path
|
45
|
+
Path to the HEC-RAS geometry HDF file.
|
46
|
+
datetime_to_str : bool, optional
|
47
|
+
If True, convert datetime objects to strings. Default is False.
|
48
|
+
|
49
|
+
Returns
|
50
|
+
-------
|
51
|
+
GeoDataFrame
|
52
|
+
A GeoDataFrame containing the cross sections with their attributes and geometries.
|
53
|
+
|
54
|
+
Raises
|
55
|
+
------
|
56
|
+
KeyError
|
57
|
+
If the required datasets are not found in the HDF file.
|
58
|
+
"""
|
59
|
+
try:
|
60
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
61
|
+
xs_data = hdf_file["Geometry/Cross Sections"]
|
62
|
+
|
63
|
+
if "Attributes" not in xs_data:
|
64
|
+
logger.warning(f"No 'Attributes' dataset group in {hdf_path}")
|
65
|
+
return GeoDataFrame()
|
66
|
+
|
67
|
+
# Convert attribute values
|
68
|
+
v_conv_val = np.vectorize(HdfUtils._convert_ras_hdf_value)
|
69
|
+
xs_attrs = xs_data["Attributes"][()]
|
70
|
+
xs_dict = {"xs_id": range(xs_attrs.shape[0])}
|
71
|
+
xs_dict.update(
|
72
|
+
{name: v_conv_val(xs_attrs[name]) for name in xs_attrs.dtype.names}
|
73
|
+
)
|
74
|
+
|
75
|
+
xs_df = pd.DataFrame(xs_dict)
|
76
|
+
|
77
|
+
# Create geometry from coordinate pairs
|
78
|
+
xs_df['geometry'] = xs_df.apply(lambda row: LineString([
|
79
|
+
(row['XS_X_Coord_1'], row['XS_Y_Coord_1']),
|
80
|
+
(row['XS_X_Coord_2'], row['XS_Y_Coord_2'])
|
81
|
+
]), axis=1)
|
82
|
+
|
83
|
+
# Convert to GeoDataFrame
|
84
|
+
gdf = GeoDataFrame(xs_df, geometry='geometry', crs=HdfUtils.projection(hdf_path))
|
85
|
+
|
86
|
+
# Convert datetime columns to strings if requested
|
87
|
+
if datetime_to_str:
|
88
|
+
gdf = HdfUtils.df_datetimes_to_str(gdf)
|
89
|
+
|
90
|
+
return gdf
|
91
|
+
|
92
|
+
except KeyError as e:
|
93
|
+
logger.error(f"Error accessing cross-section data in {hdf_path}: {str(e)}")
|
94
|
+
return GeoDataFrame()
|
95
|
+
|
96
|
+
@staticmethod
|
97
|
+
@log_call
|
98
|
+
@standardize_input(file_type='geom_hdf')
|
99
|
+
def cross_sections_elevations(hdf_path: Path, round_to: int = 2) -> pd.DataFrame:
|
100
|
+
"""
|
101
|
+
Return the model cross section elevation information.
|
102
|
+
|
103
|
+
This method extracts cross-section elevation data from the HEC-RAS geometry HDF file,
|
104
|
+
including station-elevation pairs for each cross-section.
|
105
|
+
|
106
|
+
Parameters
|
107
|
+
----------
|
108
|
+
hdf_path : Path
|
109
|
+
Path to the HEC-RAS geometry HDF file.
|
110
|
+
round_to : int, optional
|
111
|
+
Number of decimal places to round to. Default is 2.
|
112
|
+
|
113
|
+
Returns
|
114
|
+
-------
|
115
|
+
pd.DataFrame
|
116
|
+
A DataFrame containing the cross section elevation information.
|
117
|
+
|
118
|
+
Raises
|
119
|
+
------
|
120
|
+
KeyError
|
121
|
+
If the required datasets are not found in the HDF file.
|
122
|
+
"""
|
123
|
+
try:
|
124
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
125
|
+
path = "/Geometry/Cross Sections"
|
126
|
+
if path not in hdf_file:
|
127
|
+
logger.warning(f"No 'Cross Sections' group found in {hdf_path}")
|
128
|
+
return pd.DataFrame()
|
129
|
+
|
130
|
+
xselev_data = hdf_file[path]
|
131
|
+
|
132
|
+
if "Station Elevation Info" not in xselev_data or "Station Elevation Values" not in xselev_data:
|
133
|
+
logger.warning(f"Required datasets not found in Cross Sections group in {hdf_path}")
|
134
|
+
return pd.DataFrame()
|
135
|
+
|
136
|
+
# Get cross-section data
|
137
|
+
xs_df = HdfXsec.cross_sections(hdf_path)
|
138
|
+
if xs_df.empty:
|
139
|
+
return pd.DataFrame()
|
140
|
+
|
141
|
+
# Extract elevation data
|
142
|
+
elevations = []
|
143
|
+
for part_start, part_cnt in xselev_data["Station Elevation Info"][()]:
|
144
|
+
xzdata = xselev_data["Station Elevation Values"][()][
|
145
|
+
part_start : part_start + part_cnt
|
146
|
+
]
|
147
|
+
elevations.append(xzdata)
|
148
|
+
|
149
|
+
# Create DataFrame with elevation info
|
150
|
+
xs_elev_df = xs_df[
|
151
|
+
["xs_id", "River", "Reach", "RS", "Left Bank", "Right Bank"]
|
152
|
+
].copy()
|
153
|
+
xs_elev_df["Left Bank"] = xs_elev_df["Left Bank"].round(round_to).astype(str)
|
154
|
+
xs_elev_df["Right Bank"] = xs_elev_df["Right Bank"].round(round_to).astype(str)
|
155
|
+
xs_elev_df["elevation info"] = elevations
|
156
|
+
|
157
|
+
return xs_elev_df
|
158
|
+
|
159
|
+
except KeyError as e:
|
160
|
+
logger.error(f"Error accessing cross-section elevation data in {hdf_path}: {str(e)}")
|
161
|
+
return pd.DataFrame()
|
162
|
+
except Exception as e:
|
163
|
+
logger.error(f"Unexpected error in cross_sections_elevations: {str(e)}")
|
164
|
+
return pd.DataFrame()
|
165
|
+
|
166
|
+
@staticmethod
|
167
|
+
@log_call
|
168
|
+
@standardize_input(file_type='geom_hdf')
|
169
|
+
def river_reaches(hdf_path: Path, datetime_to_str: bool = False) -> GeoDataFrame:
|
170
|
+
"""
|
171
|
+
Return the model 1D river reach lines.
|
172
|
+
|
173
|
+
This method extracts river reach data from the HEC-RAS geometry HDF file,
|
174
|
+
including attributes and geometry information.
|
175
|
+
|
176
|
+
Parameters
|
177
|
+
----------
|
178
|
+
hdf_path : Path
|
179
|
+
Path to the HEC-RAS geometry HDF file.
|
180
|
+
datetime_to_str : bool, optional
|
181
|
+
If True, convert datetime objects to strings. Default is False.
|
182
|
+
|
183
|
+
Returns
|
184
|
+
-------
|
185
|
+
GeoDataFrame
|
186
|
+
A GeoDataFrame containing the river reaches with their attributes and geometries.
|
187
|
+
"""
|
188
|
+
try:
|
189
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
190
|
+
if "Geometry/River Centerlines" not in hdf_file:
|
191
|
+
return GeoDataFrame()
|
192
|
+
|
193
|
+
river_data = hdf_file["Geometry/River Centerlines"]
|
194
|
+
v_conv_val = np.vectorize(HdfUtils._convert_ras_hdf_value)
|
195
|
+
river_attrs = river_data["Attributes"][()]
|
196
|
+
river_dict = {"river_id": range(river_attrs.shape[0])}
|
197
|
+
river_dict.update(
|
198
|
+
{name: v_conv_val(river_attrs[name]) for name in river_attrs.dtype.names}
|
199
|
+
)
|
200
|
+
|
201
|
+
# Get polylines for river reaches
|
202
|
+
geoms = HdfXsec._get_polylines(hdf_path, "Geometry/River Centerlines")
|
203
|
+
|
204
|
+
river_gdf = GeoDataFrame(
|
205
|
+
river_dict,
|
206
|
+
geometry=geoms,
|
207
|
+
crs=HdfUtils.projection(hdf_path),
|
208
|
+
)
|
209
|
+
if datetime_to_str:
|
210
|
+
river_gdf["Last Edited"] = river_gdf["Last Edited"].apply(
|
211
|
+
lambda x: pd.Timestamp.isoformat(x)
|
212
|
+
)
|
213
|
+
return river_gdf
|
214
|
+
except Exception as e:
|
215
|
+
logger.error(f"Error reading river reaches: {str(e)}")
|
216
|
+
return GeoDataFrame()
|
217
|
+
|
218
|
+
@staticmethod
|
219
|
+
def _get_polylines(hdf_path: Path, path: str, info_name: str = "Polyline Info", parts_name: str = "Polyline Parts", points_name: str = "Polyline Points") -> List[LineString]:
|
220
|
+
"""
|
221
|
+
Helper method to extract polylines from HDF file.
|
222
|
+
|
223
|
+
This method is used internally to extract polyline geometries for various features
|
224
|
+
such as river reaches.
|
225
|
+
|
226
|
+
Parameters
|
227
|
+
----------
|
228
|
+
hdf_path : Path
|
229
|
+
Path to the HEC-RAS geometry HDF file.
|
230
|
+
path : str
|
231
|
+
Path within the HDF file to the polyline data.
|
232
|
+
info_name : str, optional
|
233
|
+
Name of the dataset containing polyline info. Default is "Polyline Info".
|
234
|
+
parts_name : str, optional
|
235
|
+
Name of the dataset containing polyline parts. Default is "Polyline Parts".
|
236
|
+
points_name : str, optional
|
237
|
+
Name of the dataset containing polyline points. Default is "Polyline Points".
|
238
|
+
|
239
|
+
Returns
|
240
|
+
-------
|
241
|
+
List[LineString]
|
242
|
+
A list of LineString geometries representing the polylines.
|
243
|
+
"""
|
244
|
+
try:
|
245
|
+
with h5py.File(hdf_path, 'r') as hdf_file:
|
246
|
+
polyline_info_path = f"{path}/{info_name}"
|
247
|
+
polyline_parts_path = f"{path}/{parts_name}"
|
248
|
+
polyline_points_path = f"{path}/{points_name}"
|
249
|
+
|
250
|
+
polyline_info = hdf_file[polyline_info_path][()]
|
251
|
+
polyline_parts = hdf_file[polyline_parts_path][()]
|
252
|
+
polyline_points = hdf_file[polyline_points_path][()]
|
253
|
+
|
254
|
+
geoms = []
|
255
|
+
for pnt_start, pnt_cnt, part_start, part_cnt in polyline_info:
|
256
|
+
points = polyline_points[pnt_start : pnt_start + pnt_cnt]
|
257
|
+
if part_cnt == 1:
|
258
|
+
geoms.append(LineString(points))
|
259
|
+
else:
|
260
|
+
parts = polyline_parts[part_start : part_start + part_cnt]
|
261
|
+
geoms.append(
|
262
|
+
MultiLineString(
|
263
|
+
list(
|
264
|
+
points[part_pnt_start : part_pnt_start + part_pnt_cnt]
|
265
|
+
for part_pnt_start, part_pnt_cnt in parts
|
266
|
+
)
|
267
|
+
)
|
268
|
+
)
|
269
|
+
return geoms
|
270
|
+
except Exception as e:
|
271
|
+
logger.error(f"Error getting polylines: {str(e)}")
|
272
|
+
return []
|
ras_commander/RasCmdr.py
CHANGED
@@ -47,7 +47,8 @@ from threading import Lock, Thread, current_thread
|
|
47
47
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
48
48
|
from itertools import cycle
|
49
49
|
from typing import Union, List, Optional, Dict
|
50
|
-
from
|
50
|
+
from .LoggingConfig import get_logger
|
51
|
+
from .Decorators import log_call
|
51
52
|
|
52
53
|
logger = get_logger(__name__)
|
53
54
|
|
ras_commander/RasExamples.py
CHANGED
@@ -36,7 +36,7 @@ import logging
|
|
36
36
|
import re
|
37
37
|
from tqdm import tqdm
|
38
38
|
from ras_commander import get_logger
|
39
|
-
from ras_commander.
|
39
|
+
from ras_commander.LoggingConfig import log_call
|
40
40
|
|
41
41
|
logger = get_logger(__name__)
|
42
42
|
|
@@ -56,7 +56,7 @@ class RasExamples:
|
|
56
56
|
"""
|
57
57
|
self.base_url = 'https://github.com/HydrologicEngineeringCenter/hec-downloads/releases/download/'
|
58
58
|
self.valid_versions = [
|
59
|
-
"6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
59
|
+
"6.6", "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
60
60
|
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
61
61
|
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
62
62
|
]
|
@@ -71,6 +71,41 @@ class RasExamples:
|
|
71
71
|
logger.info(f"Example projects folder: {self.projects_dir}")
|
72
72
|
self._load_project_data()
|
73
73
|
|
74
|
+
@log_call
|
75
|
+
def get_example_projects(self, version_number='6.6'):
|
76
|
+
"""
|
77
|
+
Download and extract HEC-RAS example projects for a specified version.
|
78
|
+
"""
|
79
|
+
logger.info(f"Getting example projects for version {version_number}")
|
80
|
+
if version_number not in self.valid_versions:
|
81
|
+
error_msg = f"Invalid version number. Valid versions are: {', '.join(self.valid_versions)}"
|
82
|
+
logger.error(error_msg)
|
83
|
+
raise ValueError(error_msg)
|
84
|
+
|
85
|
+
zip_url = f"{self.base_url}1.0.33/Example_Projects_{version_number.replace('.', '_')}.zip"
|
86
|
+
|
87
|
+
self.examples_dir.mkdir(parents=True, exist_ok=True)
|
88
|
+
|
89
|
+
self.zip_file_path = self.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"
|
90
|
+
|
91
|
+
if not self.zip_file_path.exists():
|
92
|
+
logger.info(f"Downloading HEC-RAS Example Projects from {zip_url}. \nThe file is over 400 MB, so it may take a few minutes to download....")
|
93
|
+
try:
|
94
|
+
response = requests.get(zip_url, stream=True)
|
95
|
+
response.raise_for_status()
|
96
|
+
with open(self.zip_file_path, 'wb') as file:
|
97
|
+
shutil.copyfileobj(response.raw, file)
|
98
|
+
logger.info(f"Downloaded to {self.zip_file_path}")
|
99
|
+
except requests.exceptions.RequestException as e:
|
100
|
+
logger.error(f"Failed to download the zip file: {e}")
|
101
|
+
raise
|
102
|
+
else:
|
103
|
+
logger.info("HEC-RAS Example Projects zip file already exists. Skipping download.")
|
104
|
+
|
105
|
+
self._load_project_data()
|
106
|
+
return self.projects_dir
|
107
|
+
|
108
|
+
|
74
109
|
@log_call
|
75
110
|
def _load_project_data(self):
|
76
111
|
"""
|
@@ -129,10 +164,10 @@ class RasExamples:
|
|
129
164
|
with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
|
130
165
|
for file in zip_ref.namelist():
|
131
166
|
parts = Path(file).parts
|
132
|
-
if len(parts) >
|
167
|
+
if len(parts) > 1:
|
133
168
|
folder_data.append({
|
134
|
-
'Category': parts[
|
135
|
-
'Project': parts[
|
169
|
+
'Category': parts[0],
|
170
|
+
'Project': parts[1]
|
136
171
|
})
|
137
172
|
|
138
173
|
self.folder_df = pd.DataFrame(folder_data).drop_duplicates()
|
@@ -157,39 +192,6 @@ class RasExamples:
|
|
157
192
|
else:
|
158
193
|
logger.warning("No folder data to save to CSV.")
|
159
194
|
|
160
|
-
@log_call
|
161
|
-
def get_example_projects(self, version_number='6.5'):
|
162
|
-
"""
|
163
|
-
Download and extract HEC-RAS example projects for a specified version.
|
164
|
-
"""
|
165
|
-
logger.info(f"Getting example projects for version {version_number}")
|
166
|
-
if version_number not in self.valid_versions:
|
167
|
-
error_msg = f"Invalid version number. Valid versions are: {', '.join(self.valid_versions)}"
|
168
|
-
logger.error(error_msg)
|
169
|
-
raise ValueError(error_msg)
|
170
|
-
|
171
|
-
zip_url = f"{self.base_url}1.0.31/Example_Projects_{version_number.replace('.', '_')}.zip"
|
172
|
-
|
173
|
-
self.examples_dir.mkdir(parents=True, exist_ok=True)
|
174
|
-
|
175
|
-
self.zip_file_path = self.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"
|
176
|
-
|
177
|
-
if not self.zip_file_path.exists():
|
178
|
-
logger.info(f"Downloading HEC-RAS Example Projects from {zip_url}. \nThe file is over 400 MB, so it may take a few minutes to download....")
|
179
|
-
try:
|
180
|
-
response = requests.get(zip_url, stream=True)
|
181
|
-
response.raise_for_status()
|
182
|
-
with open(self.zip_file_path, 'wb') as file:
|
183
|
-
shutil.copyfileobj(response.raw, file)
|
184
|
-
logger.info(f"Downloaded to {self.zip_file_path}")
|
185
|
-
except requests.exceptions.RequestException as e:
|
186
|
-
logger.error(f"Failed to download the zip file: {e}")
|
187
|
-
raise
|
188
|
-
else:
|
189
|
-
logger.info("HEC-RAS Example Projects zip file already exists. Skipping download.")
|
190
|
-
|
191
|
-
self._load_project_data()
|
192
|
-
return self.projects_dir
|
193
195
|
|
194
196
|
@log_call
|
195
197
|
def list_categories(self):
|
@@ -232,12 +234,12 @@ class RasExamples:
|
|
232
234
|
for project_name in project_names:
|
233
235
|
logger.info("----- RasExamples Extracting Project -----")
|
234
236
|
logger.info(f"Extracting project '{project_name}'")
|
235
|
-
project_path = self.projects_dir
|
237
|
+
project_path = self.projects_dir
|
236
238
|
|
237
|
-
if project_path.exists():
|
239
|
+
if (project_path / project_name).exists():
|
238
240
|
logger.info(f"Project '{project_name}' already exists. Deleting existing folder...")
|
239
241
|
try:
|
240
|
-
shutil.rmtree(project_path)
|
242
|
+
shutil.rmtree(project_path / project_name)
|
241
243
|
logger.info(f"Existing folder for project '{project_name}' has been deleted.")
|
242
244
|
except Exception as e:
|
243
245
|
logger.error(f"Failed to delete existing project folder '{project_name}': {e}")
|
@@ -263,9 +265,9 @@ class RasExamples:
|
|
263
265
|
with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
|
264
266
|
for file in zip_ref.namelist():
|
265
267
|
parts = Path(file).parts
|
266
|
-
if len(parts) >
|
267
|
-
# Remove the first
|
268
|
-
relative_path = Path(*parts[
|
268
|
+
if len(parts) > 1 and parts[1] == project_name:
|
269
|
+
# Remove the first level (category)
|
270
|
+
relative_path = Path(*parts[1:])
|
269
271
|
extract_path = project_path / relative_path
|
270
272
|
if file.endswith('/'):
|
271
273
|
extract_path.mkdir(parents=True, exist_ok=True)
|
@@ -274,8 +276,8 @@ class RasExamples:
|
|
274
276
|
with zip_ref.open(file) as source, open(extract_path, "wb") as target:
|
275
277
|
shutil.copyfileobj(source, target)
|
276
278
|
|
277
|
-
logger.info(f"Successfully extracted project '{project_name}' to {project_path}")
|
278
|
-
extracted_paths.append(project_path)
|
279
|
+
logger.info(f"Successfully extracted project '{project_name}' to {project_path / project_name}")
|
280
|
+
extracted_paths.append(project_path / project_name)
|
279
281
|
except zipfile.BadZipFile:
|
280
282
|
logger.error(f"Error: The file {self.zip_file_path} is not a valid zip file.")
|
281
283
|
except FileNotFoundError:
|
@@ -376,73 +378,4 @@ class RasExamples:
|
|
376
378
|
raise ValueError(f"Invalid size string: {size_str}")
|
377
379
|
|
378
380
|
number, unit = float(re.findall(r'[\d\.]+', size_str)[0]), re.findall(r'[BKMGT]B?', size_str)[0]
|
379
|
-
return int(number * units[unit])
|
380
|
-
|
381
|
-
# Example usage:
|
382
|
-
# ras_examples = RasExamples()
|
383
|
-
# ras_examples.download_fema_ble_models('/path/to/csv/files', '/path/to/output/folder')
|
384
|
-
# extracted_paths = ras_examples.extract_project(["Bald Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])
|
385
|
-
# for path in extracted_paths:
|
386
|
-
# logger.info(f"Extracted to: {path}")
|
387
|
-
|
388
|
-
|
389
|
-
"""
|
390
|
-
### How to Use the Revised `RasExamples` Class
|
391
|
-
|
392
|
-
1. **Instantiate the Class:**
|
393
|
-
```python
|
394
|
-
ras_examples = RasExamples()
|
395
|
-
```
|
396
|
-
|
397
|
-
2. **Download FEMA BLE Models:**
|
398
|
-
- Ensure you have the required CSV files by visiting [FEMA's Estimated Base Flood Elevation (BFE) Viewer](https://webapps.usgs.gov/infrm/estBFE/) and using the "Download as Table" option for each BLE model you wish to access.
|
399
|
-
- Call the `download_fema_ble_models` method with the appropriate paths:
|
400
|
-
```python
|
401
|
-
ras_examples.download_fema_ble_models('/path/to/csv/files', '/path/to/output/folder')
|
402
|
-
```
|
403
|
-
- Replace `'/path/to/csv/files'` with the directory containing your CSV files.
|
404
|
-
- Replace `'/path/to/output/folder'` with the directory where you want the BLE models to be downloaded and organized.
|
405
|
-
|
406
|
-
3. **Extract Projects (If Needed):**
|
407
|
-
- After downloading, you can extract specific projects using the existing `extract_project` method:
|
408
|
-
```python
|
409
|
-
extracted_paths = ras_examples.extract_project(["Bald Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])
|
410
|
-
for path in extracted_paths:
|
411
|
-
logging.info(f"Extracted to: {path}")
|
412
|
-
```
|
413
|
-
|
414
|
-
4. **Explore Projects and Categories:**
|
415
|
-
- List available categories:
|
416
|
-
```python
|
417
|
-
categories = ras_examples.list_categories()
|
418
|
-
```
|
419
|
-
- List projects within a specific category:
|
420
|
-
```python
|
421
|
-
projects = ras_examples.list_projects(category='SomeCategory')
|
422
|
-
```
|
423
|
-
|
424
|
-
5. **Clean Projects Directory (If Needed):**
|
425
|
-
- To remove all extracted projects:
|
426
|
-
```python
|
427
|
-
ras_examples.clean_projects_directory()
|
428
|
-
```
|
429
|
-
|
430
|
-
### Dependencies
|
431
|
-
|
432
|
-
Ensure that the following Python packages are installed:
|
433
|
-
|
434
|
-
- `pandas`
|
435
|
-
- `requests`
|
436
|
-
|
437
|
-
You can install them using `pip`:
|
438
|
-
|
439
|
-
```bash
|
440
|
-
pip install pandas requests
|
441
|
-
```
|
442
|
-
|
443
|
-
### Notes
|
444
|
-
|
445
|
-
- The class uses Python's `logging` module to provide detailed information about its operations. Ensure that the logging level is set appropriately to capture the desired amount of detail.
|
446
|
-
- The `download_fema_ble_models` method handles large file downloads by streaming data in chunks, which is memory-efficient.
|
447
|
-
- All folder names are sanitized to prevent filesystem errors due to unsafe characters.
|
448
|
-
"""
|
381
|
+
return int(number * units[unit])
|
ras_commander/RasGeo.py
CHANGED
@@ -28,8 +28,8 @@ from pathlib import Path
|
|
28
28
|
from typing import List, Union
|
29
29
|
from .RasPlan import RasPlan
|
30
30
|
from .RasPrj import ras
|
31
|
-
from
|
32
|
-
from
|
31
|
+
from .LoggingConfig import get_logger
|
32
|
+
from .Decorators import log_call
|
33
33
|
|
34
34
|
logger = get_logger(__name__)
|
35
35
|
|
ras_commander/RasGpt.py
CHANGED
@@ -10,133 +10,10 @@ class RasGpt:
|
|
10
10
|
A class containing helper functions for the RAS Commander GPT.
|
11
11
|
"""
|
12
12
|
|
13
|
-
|
13
|
+
# to be implemented later
|
14
|
+
#
|
15
|
+
# This class will contain methods to help LLM's extract useful information from HEC-RAS models in a structured format with token budget etc.
|
16
|
+
# Templates will be used to help with this, based on the example projects (1D Steady, 1D Usteady, 1D Sediment Transport, 1D Water Quality, 2D Unsteady, 2D Steady, 2D Sediment Transport, 2D Water Quality, 2D Geospatial, 3D Unsteady, 3D Steady, 3D Sediment Transport, 3D Water Quality, 3D Geospatial).
|
17
|
+
# These will simply filter the data to only include the relevant information for the area of focus.
|
14
18
|
|
15
|
-
|
16
|
-
@log_call
|
17
|
-
def read_library_guide(cls) -> Optional[str]:
|
18
|
-
"""
|
19
|
-
Reads and returns the contents of the Comprehensive_Library_Guide.md file.
|
20
|
-
|
21
|
-
Returns:
|
22
|
-
Optional[str]: The contents of the file, or None if the file is not found.
|
23
|
-
"""
|
24
|
-
file_path = Path(__file__).parent.parent / "docs" / "Comprehensive_Library_Guide.md"
|
25
|
-
return cls._read_file(file_path)
|
26
|
-
|
27
|
-
|
28
|
-
# ADD FOR read_reaadme and read_function_list
|
29
|
-
# Need to add a function list separate from the Library Guide
|
30
|
-
|
31
|
-
# ADD for read_example_list which will read the example folder README.ModuleNotFoundError
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
@classmethod
|
38
|
-
@log_call
|
39
|
-
def read_style_guide(cls) -> Optional[str]:
|
40
|
-
"""
|
41
|
-
Reads and returns the contents of the STYLE_GUIDE.md file.
|
42
|
-
|
43
|
-
Returns:
|
44
|
-
Optional[str]: The contents of the file, or None if the file is not found.
|
45
|
-
"""
|
46
|
-
file_path = Path(__file__).parent.parent / "docs" / "STYLE_GUIDE.md"
|
47
|
-
return cls._read_file(file_path)
|
48
|
-
|
49
|
-
|
50
|
-
# READ CLASS FILE FUNCTIONS:
|
51
|
-
|
52
|
-
@classmethod
|
53
|
-
@log_call
|
54
|
-
def read_class_rascmdr(cls) -> Optional[str]:
|
55
|
-
"""
|
56
|
-
Reads and returns the contents of the RasCmdr.py file.
|
57
|
-
|
58
|
-
Returns:
|
59
|
-
Optional[str]: The contents of the file, or None if the file is not found.
|
60
|
-
"""
|
61
|
-
file_path = Path(__file__).parent / "RasCmdr.py"
|
62
|
-
return cls._read_file(file_path)
|
63
|
-
|
64
|
-
# add one for each class file
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
# Public Helper Functions:
|
71
|
-
|
72
|
-
|
73
|
-
@classmethod
|
74
|
-
@log_call
|
75
|
-
def get_file_structure(cls, directory: Optional[str] = None) -> str:
|
76
|
-
"""
|
77
|
-
Returns a string representation of the file structure of the ras_commander package.
|
78
|
-
|
79
|
-
Args:
|
80
|
-
directory (Optional[str]): The directory to start from. If None, uses the package root.
|
81
|
-
|
82
|
-
Returns:
|
83
|
-
str: A string representation of the file structure.
|
84
|
-
"""
|
85
|
-
if directory is None:
|
86
|
-
directory = Path(__file__).parent
|
87
|
-
|
88
|
-
return cls._get_directory_structure(directory)
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
# Private Helper Functions:
|
94
|
-
|
95
|
-
@staticmethod
|
96
|
-
def _read_file(file_path: Path) -> Optional[str]:
|
97
|
-
"""
|
98
|
-
Helper method to read the contents of a file.
|
99
|
-
|
100
|
-
Args:
|
101
|
-
file_path (Path): The path to the file to be read.
|
102
|
-
|
103
|
-
Returns:
|
104
|
-
Optional[str]: The contents of the file, or None if the file is not found.
|
105
|
-
"""
|
106
|
-
try:
|
107
|
-
with open(file_path, 'r', encoding='utf-8') as file:
|
108
|
-
return file.read()
|
109
|
-
except FileNotFoundError:
|
110
|
-
logger.error(f"File not found: {file_path}")
|
111
|
-
return None
|
112
|
-
except Exception as e:
|
113
|
-
logger.error(f"Error reading file {file_path}: {str(e)}")
|
114
|
-
return None
|
115
|
-
|
116
|
-
|
117
|
-
@staticmethod
|
118
|
-
def _get_directory_structure(directory: Path, prefix: str = "") -> str:
|
119
|
-
"""
|
120
|
-
Helper method to recursively build the directory structure string.
|
121
|
-
|
122
|
-
Args:
|
123
|
-
directory (Path): The directory to process.
|
124
|
-
prefix (str): The prefix to use for the current level.
|
125
|
-
|
126
|
-
Returns:
|
127
|
-
str: A string representation of the directory structure.
|
128
|
-
"""
|
129
|
-
if not directory.is_dir():
|
130
|
-
return ""
|
131
|
-
|
132
|
-
output = []
|
133
|
-
for item in sorted(directory.iterdir()):
|
134
|
-
if item.name.startswith('.'):
|
135
|
-
continue
|
136
|
-
if item.is_dir():
|
137
|
-
output.append(f"{prefix}{item.name}/")
|
138
|
-
output.append(RasGpt._get_directory_structure(item, prefix + " "))
|
139
|
-
else:
|
140
|
-
output.append(f"{prefix}{item.name}")
|
141
|
-
|
142
|
-
return "\n".join(output)
|
19
|
+
#
|
ras_commander/RasPlan.py
CHANGED
@@ -36,8 +36,8 @@ from pathlib import Path
|
|
36
36
|
from typing import Union, Any
|
37
37
|
import logging
|
38
38
|
import re
|
39
|
-
from
|
40
|
-
from
|
39
|
+
from .LoggingConfig import get_logger
|
40
|
+
from .Decorators import log_call
|
41
41
|
|
42
42
|
logger = get_logger(__name__)
|
43
43
|
|