ras-commander 0.1.0__py2.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/__init__.py +24 -0
- ras_commander/execution.py +315 -0
- ras_commander/file_operations.py +173 -0
- ras_commander/geometry_operations.py +184 -0
- ras_commander/plan_operations.py +307 -0
- ras_commander/project_config.py +64 -0
- ras_commander/project_init.py +174 -0
- ras_commander/project_management.py +227 -0
- ras_commander/project_setup.py +15 -0
- ras_commander/unsteady_operations.py +172 -0
- ras_commander/utilities.py +195 -0
- ras_commander-0.1.0.dist-info/LICENSE +5 -0
- ras_commander-0.1.0.dist-info/METADATA +135 -0
- ras_commander-0.1.0.dist-info/RECORD +16 -0
- ras_commander-0.1.0.dist-info/WHEEL +6 -0
- ras_commander-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,24 @@
|
|
1
|
+
"""
|
2
|
+
ras_commander
|
3
|
+
A library for automating HEC-RAS operations.
|
4
|
+
"""
|
5
|
+
from .project_init import init_ras_project
|
6
|
+
from .file_operations import FileOperations
|
7
|
+
from .project_management import ProjectManager
|
8
|
+
from .plan_operations import PlanOperations
|
9
|
+
from .geometry_operations import GeometryOperations
|
10
|
+
from .unsteady_operations import UnsteadyOperations
|
11
|
+
from .execution import RasExecutor
|
12
|
+
from .utilities import Utilities
|
13
|
+
|
14
|
+
__all__ = [
|
15
|
+
'init_ras_project',
|
16
|
+
'FileOperations',
|
17
|
+
'ProjectManager',
|
18
|
+
'PlanOperations',
|
19
|
+
'GeometryOperations',
|
20
|
+
'UnsteadyOperations',
|
21
|
+
'RasExecutor',
|
22
|
+
'Utilities'
|
23
|
+
]
|
24
|
+
__version__ = "0.1.0"
|
@@ -0,0 +1,315 @@
|
|
1
|
+
"""
|
2
|
+
Execution operations for runningHEC-RAS simulations using subprocess.
|
3
|
+
Based on the HEC-Commander project's "Command Line is All You Need" approach, leveraging the -c compute flag to run HEC-RAS and orchestrating changes directly in the RAS input files to achieve automation outcomes.
|
4
|
+
"""
|
5
|
+
|
6
|
+
import os
|
7
|
+
import subprocess
|
8
|
+
import shutil
|
9
|
+
from pathlib import Path
|
10
|
+
from concurrent.futures import ThreadPoolExecutor, as_completed
|
11
|
+
from .file_operations import FileOperations
|
12
|
+
from .plan_operations import PlanOperations
|
13
|
+
import subprocess
|
14
|
+
import os
|
15
|
+
import logging
|
16
|
+
import time
|
17
|
+
from .project_config import ProjectConfig
|
18
|
+
import pandas as pd
|
19
|
+
|
20
|
+
class RasExecutor:
|
21
|
+
|
22
|
+
@staticmethod
|
23
|
+
def compute_hecras_plan(compute_plan_file):
|
24
|
+
"""
|
25
|
+
Compute a HEC-RAS plan using the provided plan file.
|
26
|
+
|
27
|
+
This method executes a HEC-RAS plan by running the HEC-RAS executable
|
28
|
+
with the specified plan file. It uses the plan file's path to determine
|
29
|
+
the corresponding project file path.
|
30
|
+
|
31
|
+
Args:
|
32
|
+
compute_plan_file (str): The full path to the HEC-RAS plan file (.p**)
|
33
|
+
|
34
|
+
Returns:
|
35
|
+
bool: True if the plan computation was successful, False otherwise.
|
36
|
+
|
37
|
+
Raises:
|
38
|
+
subprocess.CalledProcessError: If the HEC-RAS execution fails.
|
39
|
+
|
40
|
+
Note:
|
41
|
+
This method assumes that the project file (.prj) is in the same
|
42
|
+
directory as the plan file and has the same name (different extension).
|
43
|
+
"""
|
44
|
+
config = ProjectConfig()
|
45
|
+
config.check_initialized()
|
46
|
+
|
47
|
+
# Derive the project file path from the plan file path
|
48
|
+
compute_project_file = Path(compute_plan_file).with_suffix('.prj')
|
49
|
+
|
50
|
+
cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{compute_plan_file}"'
|
51
|
+
print(f"Running command: {cmd}")
|
52
|
+
try:
|
53
|
+
subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
|
54
|
+
logging.info(f"HEC-RAS execution completed for plan: {Path(compute_plan_file).name}")
|
55
|
+
return True
|
56
|
+
except subprocess.CalledProcessError as e:
|
57
|
+
logging.error(f"Error running plan: {Path(compute_plan_file).name}")
|
58
|
+
logging.error(f"Error message: {e.output}")
|
59
|
+
return False
|
60
|
+
|
61
|
+
|
62
|
+
@staticmethod
|
63
|
+
def compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute):
|
64
|
+
"""
|
65
|
+
Compute a HEC-RAS plan from a specified folder.
|
66
|
+
|
67
|
+
This function allows running a plan directly from a different folder than the project folder,
|
68
|
+
which is useful for the -test function and parallel runs. It uses the HEC-RAS executable path
|
69
|
+
from the ProjectConfig, but derives other paths from the provided arguments.
|
70
|
+
|
71
|
+
Args:
|
72
|
+
plan_for_compute (str): The full path to the HEC-RAS plan file (.p**) to be computed.
|
73
|
+
folder_for_compute (str): The folder containing the HEC-RAS project files.
|
74
|
+
|
75
|
+
Returns:
|
76
|
+
bool: True if the plan computation was successful, False otherwise.
|
77
|
+
|
78
|
+
Raises:
|
79
|
+
subprocess.CalledProcessError: If the HEC-RAS execution fails.
|
80
|
+
|
81
|
+
Note:
|
82
|
+
This function uses the ProjectConfig only to get the hecras_exe_path.
|
83
|
+
Other paths are derived from plan_for_compute and folder_for_compute.
|
84
|
+
"""
|
85
|
+
config = ProjectConfig()
|
86
|
+
config.check_initialized()
|
87
|
+
compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
|
88
|
+
cmd = f'"{config.hecras_exe_path}" -c "{compute_project_file}" "{plan_for_compute}"'
|
89
|
+
print(f"Running command: {cmd}")
|
90
|
+
try:
|
91
|
+
subprocess.run(cmd, check=True, shell=True, capture_output=True, text=True)
|
92
|
+
logging.info(f"HEC-RAS execution completed for plan: {Path(plan_for_compute).name}")
|
93
|
+
return True
|
94
|
+
except subprocess.CalledProcessError as e:
|
95
|
+
logging.error(f"Error running plan: {Path(plan_for_compute).name}")
|
96
|
+
logging.error(f"Error message: {e.output}")
|
97
|
+
return False
|
98
|
+
|
99
|
+
|
100
|
+
@staticmethod
|
101
|
+
def recreate_test_function(project_folder):
|
102
|
+
"""
|
103
|
+
Recreate the -test function of HEC-RAS command line.
|
104
|
+
|
105
|
+
Parameters:
|
106
|
+
project_folder (str): Path to the HEC-RAS project folder
|
107
|
+
|
108
|
+
Returns:
|
109
|
+
None
|
110
|
+
|
111
|
+
|
112
|
+
This function executes all ras plans in a separate folder defined by compute_folder='[Test]', so we need to call the individual functions and use distinct variable names
|
113
|
+
For this function, we are using "compute" in the varable names as these are the values used for the compute operations in a separate copy of the ras project folder
|
114
|
+
|
115
|
+
"""
|
116
|
+
print("Starting the recreate_test_function...")
|
117
|
+
|
118
|
+
# Create the test folder path
|
119
|
+
compute_folder='[Test]'
|
120
|
+
folder_for_compute = Path(project_folder).parent / f"{Path(project_folder).name} {compute_folder}"
|
121
|
+
print(f"Creating the test folder: {folder_for_compute}...")
|
122
|
+
|
123
|
+
# Copy the project folder to the test folder
|
124
|
+
print("Copying project folder to the test folder...")
|
125
|
+
shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
|
126
|
+
print(f"Test folder created at: {folder_for_compute}")
|
127
|
+
|
128
|
+
# Find the project file
|
129
|
+
print("Finding the project file...")
|
130
|
+
compute_project_file = FileOperations.find_hecras_project_file(folder_for_compute)
|
131
|
+
|
132
|
+
if not compute_project_file:
|
133
|
+
print("Project file not found.")
|
134
|
+
return
|
135
|
+
print(f"Project file found: {compute_project_file}")
|
136
|
+
|
137
|
+
# Parse the project file to get plan entries
|
138
|
+
print("Parsing the project file to get plan entries...")
|
139
|
+
ras_compute_plan_entries = FileOperations.get_plan_entries(compute_project_file)
|
140
|
+
print("Parsed project file successfully.")
|
141
|
+
|
142
|
+
# Enforce recomputing of geometry preprocessor and IB tables
|
143
|
+
print("Enforcing recomputing of geometry preprocessor and IB tables...")
|
144
|
+
for plan_file in ras_compute_plan_entries['full_path']:
|
145
|
+
PlanOperations.update_geompre_flags(plan_file, run_htab_value=-1, use_ib_tables_value=-1)
|
146
|
+
print("Recomputing enforced successfully.")
|
147
|
+
|
148
|
+
# Change max cores to 1
|
149
|
+
print("Changing max cores to 2 for all plan files...")
|
150
|
+
for plan_file in ras_compute_plan_entries['full_path']:
|
151
|
+
PlanOperations.set_num_cores(plan_file, num_cores=2)
|
152
|
+
print("Max cores updated successfully.")
|
153
|
+
|
154
|
+
# Run all plans sequentially
|
155
|
+
print("Running all plans sequentially...")
|
156
|
+
for _, plan in ras_compute_plan_entries.iterrows():
|
157
|
+
plan_for_compute = plan["full_path"]
|
158
|
+
RasExecutor.compute_hecras_plan_from_folder(plan_for_compute, folder_for_compute)
|
159
|
+
|
160
|
+
print("All plans have been executed.")
|
161
|
+
print("recreate_test_function completed.")
|
162
|
+
|
163
|
+
@staticmethod
|
164
|
+
def run_plans_parallel(config, max_workers, cores_per_run):
|
165
|
+
"""
|
166
|
+
Run HEC-RAS plans in parallel using ThreadPoolExecutor.
|
167
|
+
|
168
|
+
Parameters:
|
169
|
+
config (ProjectConfig): Configuration object containing project information
|
170
|
+
max_workers (int): Maximum number of parallel runs
|
171
|
+
cores_per_run (int): Number of cores to use per run
|
172
|
+
|
173
|
+
Returns:
|
174
|
+
dict: Dictionary with plan numbers as keys and execution success as values
|
175
|
+
|
176
|
+
This function executes all ras plans in separate folders defined by the max_workers and the numbering of the test folders [Test 1], [Test 2], etc.
|
177
|
+
Each worker operates sequentially on its assigned folder while all workers operate in parallel.
|
178
|
+
|
179
|
+
Revisions:
|
180
|
+
1. Created a pandas DataFrame to map each folder to a plan for execution.
|
181
|
+
2. Implemented logic to assign worker numbers and compute folders.
|
182
|
+
3. Used find_hecras_project_file function to get the full path of project files.
|
183
|
+
4. Updated plan file paths to use compute folders.
|
184
|
+
5. Revised the parallel execution to use separate threads for each worker.
|
185
|
+
6. Implemented a queue system for each worker to handle plans sequentially.
|
186
|
+
7. Updated the cleanup process to use the new folder structure.
|
187
|
+
"""
|
188
|
+
import queue
|
189
|
+
from threading import Thread
|
190
|
+
|
191
|
+
project_folder = Path(config.project_file).parent
|
192
|
+
test_folders = []
|
193
|
+
|
194
|
+
# Create multiple copies of the project folder
|
195
|
+
for i in range(1, max_workers + 1):
|
196
|
+
folder_for_compute = project_folder.parent / f"{project_folder.name} [Test {i}]"
|
197
|
+
shutil.copytree(project_folder, folder_for_compute, dirs_exist_ok=True)
|
198
|
+
test_folders.append(folder_for_compute)
|
199
|
+
print(f"Created test folder: {folder_for_compute}")
|
200
|
+
|
201
|
+
compute_parallel_entries = []
|
202
|
+
for i, (_, plan_row) in enumerate(config.ras_plan_entries.iterrows()):
|
203
|
+
worker_number = i % max_workers
|
204
|
+
compute_folder = test_folders[worker_number]
|
205
|
+
compute_project_file = FileOperations.find_hecras_project_file(compute_folder)
|
206
|
+
compute_plan_file = compute_folder / Path(plan_row['full_path']).name
|
207
|
+
compute_parallel_entries.append({
|
208
|
+
'worker_number': worker_number,
|
209
|
+
'compute_folder': compute_folder,
|
210
|
+
'compute_project_file': compute_project_file,
|
211
|
+
'compute_plan_file': compute_plan_file,
|
212
|
+
'plan_number': plan_row['plan_number']
|
213
|
+
})
|
214
|
+
|
215
|
+
compute_parallel_df = pd.DataFrame(compute_parallel_entries)
|
216
|
+
print("compute_parallel_entries dataframe:")
|
217
|
+
display(compute_parallel_df)
|
218
|
+
|
219
|
+
results = {}
|
220
|
+
worker_queues = [queue.Queue() for _ in range(max_workers)]
|
221
|
+
|
222
|
+
def worker_thread(worker_id):
|
223
|
+
"""
|
224
|
+
Execute HEC-RAS plans assigned to a specific worker thread.
|
225
|
+
|
226
|
+
This function continuously processes plans from the worker's queue until it's empty.
|
227
|
+
It sets the number of cores for each plan, computes the plan, and records the result.
|
228
|
+
|
229
|
+
Parameters:
|
230
|
+
worker_id (int): The ID of the worker thread.
|
231
|
+
|
232
|
+
Notes:
|
233
|
+
- Uses PlanOperations.set_num_cores to set the number of cores for each plan.
|
234
|
+
- Uses RasExecutor.compute_hecras_plan to execute each plan.
|
235
|
+
- Records success or failure in the 'results' dictionary.
|
236
|
+
- Prints status messages for completed or failed plans.
|
237
|
+
"""
|
238
|
+
while True:
|
239
|
+
try:
|
240
|
+
row = worker_queues[worker_id].get(block=False)
|
241
|
+
PlanOperations.set_num_cores(str(row['compute_plan_file']), cores_per_run)
|
242
|
+
success = RasExecutor.compute_hecras_plan(row['compute_plan_file'])
|
243
|
+
results[row['plan_number']] = success
|
244
|
+
print(f"Completed: Plan {row['plan_number']} in worker {worker_id}")
|
245
|
+
except queue.Empty:
|
246
|
+
break
|
247
|
+
except Exception as e:
|
248
|
+
results[row['plan_number']] = False
|
249
|
+
print(f"Failed: Plan {row['plan_number']} in worker {worker_id}. Error: {str(e)}")
|
250
|
+
|
251
|
+
# Distribute plans to worker queues
|
252
|
+
for _, row in compute_parallel_df.iterrows():
|
253
|
+
worker_queues[row['worker_number']].put(row)
|
254
|
+
|
255
|
+
# Start worker threads
|
256
|
+
threads = []
|
257
|
+
for i in range(max_workers):
|
258
|
+
thread = Thread(target=worker_thread, args=(i,))
|
259
|
+
thread.start()
|
260
|
+
threads.append(thread)
|
261
|
+
|
262
|
+
# Wait for all threads to complete
|
263
|
+
for thread in threads:
|
264
|
+
thread.join()
|
265
|
+
|
266
|
+
# Clean up and consolidate results
|
267
|
+
time.sleep(3) # Allow files to close
|
268
|
+
final_test_folder = project_folder.parent / f"{project_folder.name} [Test]"
|
269
|
+
final_test_folder.mkdir(exist_ok=True)
|
270
|
+
|
271
|
+
for test_folder in test_folders:
|
272
|
+
for item in test_folder.iterdir():
|
273
|
+
dest_path = final_test_folder / item.name
|
274
|
+
if dest_path.exists():
|
275
|
+
if dest_path.is_dir():
|
276
|
+
shutil.rmtree(dest_path)
|
277
|
+
else:
|
278
|
+
dest_path.unlink()
|
279
|
+
shutil.move(str(item), final_test_folder)
|
280
|
+
shutil.rmtree(test_folder)
|
281
|
+
print(f"Moved and removed test folder: {test_folder}")
|
282
|
+
|
283
|
+
return results
|
284
|
+
|
285
|
+
@staticmethod
|
286
|
+
def run_all_plans_parallel(project_folder, hecras_exe_path):
|
287
|
+
"""
|
288
|
+
Run all plans in a project folder in parallel.
|
289
|
+
|
290
|
+
Parameters:
|
291
|
+
project_folder (str): The path to the project folder.
|
292
|
+
hecras_exe_path (str): The path to the HEC-RAS executable.
|
293
|
+
|
294
|
+
Returns:
|
295
|
+
dict: A dictionary with plan numbers as keys and execution success status as values.
|
296
|
+
"""
|
297
|
+
config = ProjectConfig.init_ras_project(project_folder, hecras_exe_path)
|
298
|
+
|
299
|
+
if config:
|
300
|
+
print("ras_plan_entries dataframe:")
|
301
|
+
display(config.ras_plan_entries)
|
302
|
+
|
303
|
+
max_workers = 2 # Number of parallel runs
|
304
|
+
cores_per_run = 2 # Number of cores per run
|
305
|
+
|
306
|
+
results = RasExecutor.run_plans_parallel(config, max_workers, cores_per_run)
|
307
|
+
|
308
|
+
print("\nExecution Results:")
|
309
|
+
for plan_number, success in results.items():
|
310
|
+
print(f"Plan {plan_number}: {'Successful' if success else 'Failed'}")
|
311
|
+
|
312
|
+
return results
|
313
|
+
else:
|
314
|
+
print("Failed to initialize project configuration.")
|
315
|
+
return None
|
@@ -0,0 +1,173 @@
|
|
1
|
+
"""
|
2
|
+
File operations for HEC-RAS project files.
|
3
|
+
"""
|
4
|
+
import re
|
5
|
+
from pathlib import Path
|
6
|
+
import pandas as pd
|
7
|
+
|
8
|
+
class FileOperations:
|
9
|
+
"""
|
10
|
+
A class for HEC-RAS file operations.
|
11
|
+
|
12
|
+
|
13
|
+
Revision Notes: All functions from class ProjectManager should be moved here
|
14
|
+
|
15
|
+
|
16
|
+
"""
|
17
|
+
@staticmethod
|
18
|
+
def find_hecras_project_file(folder_path):
|
19
|
+
"""
|
20
|
+
Find the appropriate HEC-RAS project file (.prj) in the given folder.
|
21
|
+
|
22
|
+
Parameters:
|
23
|
+
folder_path (str or Path): Path to the folder containing HEC-RAS files.
|
24
|
+
|
25
|
+
Returns:
|
26
|
+
Path: The full path of the selected .prj file or None if no suitable file is found.
|
27
|
+
"""
|
28
|
+
print(f"running find_hecras_project_file with folder_path: {folder_path}")
|
29
|
+
folder_path = Path(folder_path)
|
30
|
+
print("Searching for .prj files...")
|
31
|
+
prj_files = list(folder_path.glob("*.prj"))
|
32
|
+
# print(f"Found {len(prj_files)} .prj files")
|
33
|
+
# print("Searching for .rasmap files...")
|
34
|
+
rasmap_files = list(folder_path.glob("*.rasmap"))
|
35
|
+
#print(f"Found {len(rasmap_files)} .rasmap files")
|
36
|
+
if len(prj_files) == 1:
|
37
|
+
project_file = prj_files[0]
|
38
|
+
# print(f"Only one .prj file found. Selecting: {project_file}")
|
39
|
+
# print(f"Full path: {project_file.resolve()}")
|
40
|
+
return project_file.resolve()
|
41
|
+
if len(prj_files) > 1:
|
42
|
+
print("Multiple .prj files found.")
|
43
|
+
if len(rasmap_files) == 1:
|
44
|
+
base_filename = rasmap_files[0].stem
|
45
|
+
project_file = folder_path / f"{base_filename}.prj"
|
46
|
+
# print(f"Found single .rasmap file. Using its base name: {base_filename}")
|
47
|
+
# print(f"Full path: {project_file.resolve()}")
|
48
|
+
return project_file.resolve()
|
49
|
+
print("Multiple .prj files and no single .rasmap file. Searching for 'Proj Title=' in .prj files...")
|
50
|
+
for prj_file in prj_files:
|
51
|
+
# print(f"Checking file: {prj_file.name}")
|
52
|
+
with open(prj_file, 'r') as file:
|
53
|
+
if "Proj Title=" in file.read():
|
54
|
+
# print(f"Found 'Proj Title=' in file: {prj_file.name}")
|
55
|
+
# print(f"Full path: {prj_file.resolve()}")
|
56
|
+
return prj_file.resolve()
|
57
|
+
print("No suitable .prj file found after all checks.")
|
58
|
+
return project_file
|
59
|
+
|
60
|
+
@staticmethod
|
61
|
+
def get_project_name(project_path):
|
62
|
+
"""
|
63
|
+
Extract the project name from the given project path.
|
64
|
+
|
65
|
+
Parameters:
|
66
|
+
project_path (Path): Path object representing the project file path
|
67
|
+
|
68
|
+
Returns:
|
69
|
+
str: The project name derived from the file name without extension
|
70
|
+
"""
|
71
|
+
project_name = project_path.stem
|
72
|
+
return project_name
|
73
|
+
|
74
|
+
@staticmethod
|
75
|
+
def get_plan_entries(project_file):
|
76
|
+
"""
|
77
|
+
Parse HEC-RAS project file and create dataframe for plan entries.
|
78
|
+
|
79
|
+
Parameters:
|
80
|
+
project_file (str): Full path to HEC-RAS project file (.prj)
|
81
|
+
|
82
|
+
Returns:
|
83
|
+
pandas DataFrame: DataFrame containing plan entries
|
84
|
+
"""
|
85
|
+
project_path = Path(project_file)
|
86
|
+
project_name = FileOperations.get_project_name(project_path)
|
87
|
+
project_dir = project_path.parent
|
88
|
+
|
89
|
+
with open(project_file, 'r') as f:
|
90
|
+
content = f.read()
|
91
|
+
|
92
|
+
plan_entries = re.findall(r'Plan File=(.*?)(?:\n|$)', content)
|
93
|
+
|
94
|
+
ras_plan_entries = pd.DataFrame({
|
95
|
+
'plan_number': [re.findall(r'\d+', entry)[0] for entry in plan_entries],
|
96
|
+
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in plan_entries],
|
97
|
+
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in plan_entries],
|
98
|
+
'results_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}.hdf") for entry in plan_entries]
|
99
|
+
})
|
100
|
+
|
101
|
+
return ras_plan_entries
|
102
|
+
|
103
|
+
@staticmethod
|
104
|
+
def get_flow_entries(project_file):
|
105
|
+
"""
|
106
|
+
Parse HEC-RAS project file and create dataframe for flow entries.
|
107
|
+
|
108
|
+
Parameters:
|
109
|
+
project_file (str): Full path to HEC-RAS project file (.prj)
|
110
|
+
|
111
|
+
Returns:
|
112
|
+
pandas DataFrame: DataFrame containing flow entries
|
113
|
+
"""
|
114
|
+
project_path = Path(project_file)
|
115
|
+
project_name = FileOperations.get_project_name(project_path)
|
116
|
+
project_dir = project_path.parent
|
117
|
+
with open(project_file, 'r') as f:
|
118
|
+
content = f.read()
|
119
|
+
flow_entries = re.findall(r'Flow File=(.*?)(?:\n|$)', content)
|
120
|
+
ras_flow_entries = pd.DataFrame({
|
121
|
+
'flow_number': [re.findall(r'\d+', entry)[0] for entry in flow_entries],
|
122
|
+
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in flow_entries],
|
123
|
+
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in flow_entries]
|
124
|
+
})
|
125
|
+
return ras_flow_entries
|
126
|
+
|
127
|
+
@staticmethod
|
128
|
+
def get_unsteady_entries(project_file):
|
129
|
+
"""
|
130
|
+
Parse HEC-RAS project file and create dataframe for unsteady entries.
|
131
|
+
|
132
|
+
Parameters:
|
133
|
+
project_file (str): Full path to HEC-RAS project file (.prj)
|
134
|
+
|
135
|
+
Returns:
|
136
|
+
pandas DataFrame: DataFrame containing unsteady entries
|
137
|
+
"""
|
138
|
+
project_path = Path(project_file)
|
139
|
+
project_name = FileOperations.get_project_name(project_path)
|
140
|
+
project_dir = project_path.parent
|
141
|
+
with open(project_file, 'r') as f:
|
142
|
+
content = f.read()
|
143
|
+
unsteady_entries = re.findall(r'Unsteady File=(.*?)(?:\n|$)', content)
|
144
|
+
ras_unsteady_entries = pd.DataFrame({
|
145
|
+
'unsteady_number': [re.findall(r'\d+', entry)[0] for entry in unsteady_entries],
|
146
|
+
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in unsteady_entries],
|
147
|
+
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in unsteady_entries]
|
148
|
+
})
|
149
|
+
return ras_unsteady_entries
|
150
|
+
|
151
|
+
@staticmethod
|
152
|
+
def get_geom_entries(project_file):
|
153
|
+
"""
|
154
|
+
Parse HEC-RAS project file and create dataframe for geometry entries.
|
155
|
+
|
156
|
+
Parameters:
|
157
|
+
project_file (str): Full path to HEC-RAS project file (.prj)
|
158
|
+
|
159
|
+
Returns:
|
160
|
+
pandas DataFrame: DataFrame containing geometry entries
|
161
|
+
"""
|
162
|
+
project_path = Path(project_file)
|
163
|
+
project_name = FileOperations.get_project_name(project_path)
|
164
|
+
project_dir = project_path.parent
|
165
|
+
with open(project_file, 'r') as f:
|
166
|
+
content = f.read()
|
167
|
+
geom_entries = re.findall(r'Geom File=(.*?)(?:\n|$)', content)
|
168
|
+
ras_geom_entries = pd.DataFrame({
|
169
|
+
'geom_number': [re.findall(r'\d+', entry)[0] for entry in geom_entries],
|
170
|
+
'file_name': [f"{project_name}.{entry.strip().zfill(2)}" for entry in geom_entries],
|
171
|
+
'full_path': [str(project_dir / f"{project_name}.{entry.strip().zfill(2)}") for entry in geom_entries]
|
172
|
+
})
|
173
|
+
return ras_geom_entries
|