ras-commander 0.1.6__py2.py3-none-any.whl → 0.21.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/README.md +187 -0
- ras_commander/RasCommander.py +456 -0
- ras_commander/RasExamples.py +304 -0
- ras_commander/RasGeo.py +83 -0
- ras_commander/RasPlan.py +1216 -0
- ras_commander/RasPrj.py +400 -0
- ras_commander/RasUnsteady.py +53 -0
- ras_commander/RasUtils.py +283 -0
- ras_commander/__init__.py +33 -0
- ras_commander-0.21.0.dist-info/METADATA +342 -0
- ras_commander-0.21.0.dist-info/RECORD +14 -0
- {ras_commander-0.1.6.dist-info → ras_commander-0.21.0.dist-info}/WHEEL +1 -1
- ras_commander/_version.py +0 -16
- ras_commander/execution.py +0 -315
- ras_commander/file_operations.py +0 -173
- ras_commander/geometry_operations.py +0 -184
- ras_commander/plan_operations.py +0 -307
- ras_commander/project_config.py +0 -64
- ras_commander/project_init.py +0 -174
- ras_commander/project_management.py +0 -227
- ras_commander/project_setup.py +0 -15
- ras_commander/unsteady_operations.py +0 -172
- ras_commander/utilities.py +0 -195
- ras_commander-0.1.6.dist-info/METADATA +0 -133
- ras_commander-0.1.6.dist-info/RECORD +0 -17
- {ras_commander-0.1.6.dist-info → ras_commander-0.21.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.1.6.dist-info → ras_commander-0.21.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,304 @@
|
|
1
|
+
import os
|
2
|
+
import requests
|
3
|
+
import zipfile
|
4
|
+
import pandas as pd
|
5
|
+
from pathlib import Path
|
6
|
+
import shutil
|
7
|
+
from typing import Union, List
|
8
|
+
import csv
|
9
|
+
from datetime import datetime
|
10
|
+
|
11
|
+
class RasExamples:
|
12
|
+
"""
|
13
|
+
A class for quickly loading HEC-RAS example projects for testing and development of ras_commander.
|
14
|
+
|
15
|
+
This class provides functionality to download, extract, and manage HEC-RAS example projects.
|
16
|
+
It supports both default HEC-RAS example projects and custom projects from user-provided URLs.
|
17
|
+
|
18
|
+
Expected folder structure: Notes:
|
19
|
+
ras_commander/
|
20
|
+
├── examples/ # This is examples_dir
|
21
|
+
│ ├── example_projects/ # This is projects_dir
|
22
|
+
│ │ ├── Balde Eagle Creek/ # Individual Projects from Zip file
|
23
|
+
│ │ ├── Muncie/
|
24
|
+
│ │ └── ...
|
25
|
+
│ ├── Example_Projects_6_5.zip # HEC-RAS Example Projects zip file will be downloaded here
|
26
|
+
│ ├── example_projects.csv # CSV file containing cached project metadata
|
27
|
+
│ └── 01_project_initialization.py # ras_commander library examples are also at this level
|
28
|
+
│ └── ...
|
29
|
+
└── ras_commander/ # Code for the ras_commander library
|
30
|
+
|
31
|
+
Attributes:
|
32
|
+
base_url (str): Base URL for downloading HEC-RAS example projects.
|
33
|
+
valid_versions (list): List of valid HEC-RAS versions for example projects.
|
34
|
+
base_dir (Path): Base directory for storing example projects.
|
35
|
+
examples_dir (Path): Directory for example projects and related files. (assumed to be parent )
|
36
|
+
projects_dir (Path): Directory where example projects are extracted.
|
37
|
+
zip_file_path (Path): Path to the downloaded zip file.
|
38
|
+
folder_df (pd.DataFrame): DataFrame containing folder structure information.
|
39
|
+
csv_file_path (Path): Path to the CSV file for caching project metadata.
|
40
|
+
|
41
|
+
|
42
|
+
Future Improvements:
|
43
|
+
- Implement the ability for user-provided example projects (provided as a zip file) for their own repeatable examples.
|
44
|
+
- If the zip file is in the same folder structure as the HEC-RAS example projects, simple replace Example_Projects_6_5.zip and the folder structure will be automatically extracted from the zip file.
|
45
|
+
- The actual RAS example projects haven't been updated much, but there is the structure here to handle future versions. Although this version of the code is probably fine for a few years, until HEC-RAS 2025 comes out.
|
46
|
+
|
47
|
+
"""
|
48
|
+
|
49
|
+
def __init__(self):
|
50
|
+
"""
|
51
|
+
Initialize the RasExamples class.
|
52
|
+
|
53
|
+
This constructor sets up the necessary attributes and paths for managing HEC-RAS example projects.
|
54
|
+
It initializes the base URL for downloads, valid versions, directory paths, and other essential
|
55
|
+
attributes. It also creates the projects directory if it doesn't exist and loads the project data.
|
56
|
+
|
57
|
+
The method also prints the location of the example projects folder and calls _load_project_data()
|
58
|
+
to initialize the project data.
|
59
|
+
"""
|
60
|
+
self.base_url = 'https://github.com/HydrologicEngineeringCenter/hec-downloads/releases/download/'
|
61
|
+
self.valid_versions = [
|
62
|
+
"6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
63
|
+
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
64
|
+
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
65
|
+
]
|
66
|
+
self.base_dir = Path.cwd()
|
67
|
+
self.examples_dir = self.base_dir
|
68
|
+
self.projects_dir = self.examples_dir / 'example_projects'
|
69
|
+
self.zip_file_path = None
|
70
|
+
self.folder_df = None
|
71
|
+
self.csv_file_path = self.examples_dir / 'example_projects.csv'
|
72
|
+
|
73
|
+
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
74
|
+
print(f"Example projects folder: {self.projects_dir}")
|
75
|
+
self._load_project_data()
|
76
|
+
|
77
|
+
def _load_project_data(self):
|
78
|
+
"""
|
79
|
+
Load project data from CSV if up-to-date, otherwise extract from zip.
|
80
|
+
|
81
|
+
Checks for existing CSV file and compares modification times with zip file.
|
82
|
+
Extracts folder structure if necessary and saves to CSV.
|
83
|
+
"""
|
84
|
+
self._find_zip_file()
|
85
|
+
|
86
|
+
if not self.zip_file_path:
|
87
|
+
print("No example projects zip file found. Downloading...")
|
88
|
+
self.get_example_projects()
|
89
|
+
|
90
|
+
zip_modified_time = os.path.getmtime(self.zip_file_path)
|
91
|
+
|
92
|
+
if self.csv_file_path.exists():
|
93
|
+
csv_modified_time = os.path.getmtime(self.csv_file_path)
|
94
|
+
|
95
|
+
if csv_modified_time >= zip_modified_time:
|
96
|
+
print("Loading project data from CSV...")
|
97
|
+
self.folder_df = pd.read_csv(self.csv_file_path)
|
98
|
+
print(f"Loaded {len(self.folder_df)} projects from CSV, use list_categories() and list_projects() to explore them")
|
99
|
+
return
|
100
|
+
|
101
|
+
print("Extracting folder structure from zip file...")
|
102
|
+
self._extract_folder_structure()
|
103
|
+
self._save_to_csv()
|
104
|
+
|
105
|
+
def _find_zip_file(self):
|
106
|
+
"""Locate the example projects zip file in the examples directory."""
|
107
|
+
for version in self.valid_versions:
|
108
|
+
potential_zip = self.examples_dir / f"Example_Projects_{version.replace('.', '_')}.zip"
|
109
|
+
if potential_zip.exists():
|
110
|
+
self.zip_file_path = potential_zip
|
111
|
+
print(f"Found zip file: {self.zip_file_path}")
|
112
|
+
break
|
113
|
+
|
114
|
+
def _extract_folder_structure(self):
|
115
|
+
"""
|
116
|
+
Extract folder structure from the zip file.
|
117
|
+
|
118
|
+
Populates folder_df with category and project information.
|
119
|
+
"""
|
120
|
+
folder_data = []
|
121
|
+
try:
|
122
|
+
with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
|
123
|
+
for file in zip_ref.namelist():
|
124
|
+
parts = Path(file).parts
|
125
|
+
if len(parts) > 2:
|
126
|
+
folder_data.append({
|
127
|
+
'Category': parts[1],
|
128
|
+
'Project': parts[2]
|
129
|
+
})
|
130
|
+
|
131
|
+
self.folder_df = pd.DataFrame(folder_data).drop_duplicates()
|
132
|
+
print(f"Extracted {len(self.folder_df)} projects")
|
133
|
+
print("folder_df:")
|
134
|
+
display(self.folder_df)
|
135
|
+
except Exception as e:
|
136
|
+
print(f"An error occurred while extracting the folder structure: {str(e)}")
|
137
|
+
self.folder_df = pd.DataFrame(columns=['Category', 'Project'])
|
138
|
+
|
139
|
+
def _save_to_csv(self):
|
140
|
+
"""Save the extracted folder structure to CSV file."""
|
141
|
+
if self.folder_df is not None and not self.folder_df.empty:
|
142
|
+
self.folder_df.to_csv(self.csv_file_path, index=False)
|
143
|
+
print(f"Saved project data to {self.csv_file_path}")
|
144
|
+
|
145
|
+
def get_example_projects(self, version_number='6.5'):
|
146
|
+
"""
|
147
|
+
Download and extract HEC-RAS example projects for a specified version.
|
148
|
+
|
149
|
+
Args:
|
150
|
+
version_number (str): HEC-RAS version number. Defaults to '6.5'.
|
151
|
+
|
152
|
+
Returns:
|
153
|
+
Path: Path to the extracted example projects.
|
154
|
+
|
155
|
+
Raises:
|
156
|
+
ValueError: If an invalid version number is provided.
|
157
|
+
"""
|
158
|
+
print(f"Getting example projects for version {version_number}")
|
159
|
+
if version_number not in self.valid_versions:
|
160
|
+
raise ValueError(f"Invalid version number. Valid versions are: {', '.join(self.valid_versions)}")
|
161
|
+
|
162
|
+
zip_url = f"{self.base_url}1.0.31/Example_Projects_{version_number.replace('.', '_')}.zip"
|
163
|
+
|
164
|
+
self.examples_dir.mkdir(parents=True, exist_ok=True)
|
165
|
+
|
166
|
+
self.zip_file_path = self.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"
|
167
|
+
|
168
|
+
if not self.zip_file_path.exists():
|
169
|
+
print(f"Downloading HEC-RAS Example Projects from {zip_url}. \n The file is over 400 MB, so it may take a few minutes to download....")
|
170
|
+
response = requests.get(zip_url)
|
171
|
+
with open(self.zip_file_path, 'wb') as file:
|
172
|
+
file.write(response.content)
|
173
|
+
print(f"Downloaded to {self.zip_file_path}")
|
174
|
+
else:
|
175
|
+
print("HEC-RAS Example Projects zip file already exists. Skipping download.")
|
176
|
+
|
177
|
+
self._load_project_data()
|
178
|
+
return self.projects_dir
|
179
|
+
|
180
|
+
def list_categories(self):
|
181
|
+
"""
|
182
|
+
List all categories of example projects.
|
183
|
+
|
184
|
+
Returns:
|
185
|
+
list: Available categories.
|
186
|
+
"""
|
187
|
+
if self.folder_df is None or 'Category' not in self.folder_df.columns:
|
188
|
+
print("No categories available. Make sure the zip file is properly loaded.")
|
189
|
+
return []
|
190
|
+
categories = self.folder_df['Category'].unique()
|
191
|
+
print(f"Available categories: {', '.join(categories)}")
|
192
|
+
return categories.tolist()
|
193
|
+
|
194
|
+
def list_projects(self, category=None):
|
195
|
+
"""
|
196
|
+
List all projects or projects in a specific category.
|
197
|
+
|
198
|
+
Args:
|
199
|
+
category (str, optional): Category to filter projects.
|
200
|
+
|
201
|
+
Returns:
|
202
|
+
list: List of project names.
|
203
|
+
"""
|
204
|
+
if self.folder_df is None:
|
205
|
+
print("No projects available. Make sure the zip file is properly loaded.")
|
206
|
+
return []
|
207
|
+
if category:
|
208
|
+
projects = self.folder_df[self.folder_df['Category'] == category]['Project'].unique()
|
209
|
+
else:
|
210
|
+
projects = self.folder_df['Project'].unique()
|
211
|
+
return projects.tolist()
|
212
|
+
|
213
|
+
def extract_project(self, project_names: Union[str, List[str]]):
|
214
|
+
"""
|
215
|
+
Extract one or more specific projects from the zip file.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
project_names (str or List[str]): Name(s) of the project(s) to extract.
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
List[Path]: List of paths to the extracted project(s).
|
222
|
+
|
223
|
+
Raises:
|
224
|
+
ValueError: If any project is not found.
|
225
|
+
"""
|
226
|
+
if isinstance(project_names, str):
|
227
|
+
project_names = [project_names]
|
228
|
+
|
229
|
+
extracted_paths = []
|
230
|
+
|
231
|
+
for project_name in project_names:
|
232
|
+
print("----- RasExamples Extracting Project -----")
|
233
|
+
print(f"Extracting project '{project_name}'")
|
234
|
+
project_path = self.projects_dir / project_name
|
235
|
+
|
236
|
+
if project_path.exists():
|
237
|
+
print(f"Project '{project_name}' already exists. Deleting existing folder...")
|
238
|
+
shutil.rmtree(project_path)
|
239
|
+
print(f"Existing folder for project '{project_name}' has been deleted.")
|
240
|
+
|
241
|
+
if self.folder_df is None or self.folder_df.empty:
|
242
|
+
raise ValueError("No project information available. Make sure the zip file is properly loaded.")
|
243
|
+
|
244
|
+
project_info = self.folder_df[self.folder_df['Project'] == project_name]
|
245
|
+
if project_info.empty:
|
246
|
+
raise ValueError(f"Project '{project_name}' not found in the zip file.")
|
247
|
+
|
248
|
+
category = project_info['Category'].iloc[0]
|
249
|
+
|
250
|
+
# Ensure the project directory exists
|
251
|
+
project_path.mkdir(parents=True, exist_ok=True)
|
252
|
+
|
253
|
+
try:
|
254
|
+
with zipfile.ZipFile(self.zip_file_path, 'r') as zip_ref:
|
255
|
+
for file in zip_ref.namelist():
|
256
|
+
parts = Path(file).parts
|
257
|
+
if len(parts) > 2 and parts[2] == project_name:
|
258
|
+
# Remove the first two levels (category and project name)
|
259
|
+
relative_path = Path(*parts[3:])
|
260
|
+
extract_path = project_path / relative_path
|
261
|
+
if file.endswith('/'):
|
262
|
+
extract_path.mkdir(parents=True, exist_ok=True)
|
263
|
+
else:
|
264
|
+
extract_path.parent.mkdir(parents=True, exist_ok=True)
|
265
|
+
with zip_ref.open(file) as source, open(extract_path, "wb") as target:
|
266
|
+
shutil.copyfileobj(source, target)
|
267
|
+
|
268
|
+
print(f"Successfully extracted project '{project_name}' to {project_path}")
|
269
|
+
extracted_paths.append(project_path)
|
270
|
+
except zipfile.BadZipFile:
|
271
|
+
print(f"Error: The file {self.zip_file_path} is not a valid zip file.")
|
272
|
+
except FileNotFoundError:
|
273
|
+
print(f"Error: The file {self.zip_file_path} was not found.")
|
274
|
+
except Exception as e:
|
275
|
+
print(f"An unexpected error occurred while extracting the project: {str(e)}")
|
276
|
+
#print("----- RasExamples Extraction Complete -----")
|
277
|
+
return extracted_paths
|
278
|
+
|
279
|
+
def is_project_extracted(self, project_name):
|
280
|
+
"""
|
281
|
+
Check if a specific project is already extracted.
|
282
|
+
|
283
|
+
Args:
|
284
|
+
project_name (str): Name of the project to check.
|
285
|
+
|
286
|
+
Returns:
|
287
|
+
bool: True if the project is extracted, False otherwise.
|
288
|
+
"""
|
289
|
+
project_path = self.projects_dir / project_name
|
290
|
+
return project_path.exists()
|
291
|
+
|
292
|
+
def clean_projects_directory(self):
|
293
|
+
"""Remove all extracted projects from the example_projects directory."""
|
294
|
+
print(f"Cleaning projects directory: {self.projects_dir}")
|
295
|
+
if self.projects_dir.exists():
|
296
|
+
shutil.rmtree(self.projects_dir)
|
297
|
+
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
298
|
+
print("Projects directory cleaned.")
|
299
|
+
|
300
|
+
# Example usage:
|
301
|
+
# ras_examples = RasExamples()
|
302
|
+
# extracted_paths = ras_examples.extract_project(["Bald Eagle Creek", "BaldEagleCrkMulti2D", "Muncie"])
|
303
|
+
# for path in extracted_paths:
|
304
|
+
# print(f"Extracted to: {path}")
|
ras_commander/RasGeo.py
ADDED
@@ -0,0 +1,83 @@
|
|
1
|
+
"""
|
2
|
+
Operations for handling geometry files in HEC-RAS projects.
|
3
|
+
"""
|
4
|
+
from pathlib import Path
|
5
|
+
from typing import List, Union
|
6
|
+
from .RasPlan import RasPlan
|
7
|
+
from .RasPrj import ras
|
8
|
+
import re
|
9
|
+
|
10
|
+
class RasGeo:
|
11
|
+
"""
|
12
|
+
A class for operations on HEC-RAS geometry files.
|
13
|
+
"""
|
14
|
+
|
15
|
+
@staticmethod
|
16
|
+
def clear_geompre_files(
|
17
|
+
plan_files: Union[str, Path, List[Union[str, Path]]] = None,
|
18
|
+
ras_object = None
|
19
|
+
) -> None:
|
20
|
+
"""
|
21
|
+
Clear HEC-RAS geometry preprocessor files for specified plan files or all plan files in the project directory.
|
22
|
+
|
23
|
+
Limitations/Future Work:
|
24
|
+
- This function only deletes the geometry preprocessor file.
|
25
|
+
- It does not clear the IB tables.
|
26
|
+
- It also does not clear geometry preprocessor tables from the geometry HDF.
|
27
|
+
- All of these features will need to be added to reliably remove geometry preprocessor files for 1D and 2D projects.
|
28
|
+
|
29
|
+
Parameters:
|
30
|
+
plan_files (Union[str, Path, List[Union[str, Path]]], optional):
|
31
|
+
Full path(s) to the HEC-RAS plan file(s) (.p*).
|
32
|
+
If None, clears all plan files in the project directory.
|
33
|
+
ras_object: An optional RAS object instance.
|
34
|
+
|
35
|
+
Returns:
|
36
|
+
None
|
37
|
+
|
38
|
+
Examples:
|
39
|
+
# Clear all geometry preprocessor files in the project directory
|
40
|
+
RasGeo.clear_geompre_files()
|
41
|
+
|
42
|
+
# Clear a single plan file
|
43
|
+
RasGeo.clear_geompre_files(r'path/to/plan.p01')
|
44
|
+
|
45
|
+
# Clear multiple plan files
|
46
|
+
RasGeo.clear_geompre_files([r'path/to/plan1.p01', r'path/to/plan2.p02'])
|
47
|
+
|
48
|
+
Note:
|
49
|
+
This function updates the ras object's geometry dataframe after clearing the preprocessor files.
|
50
|
+
"""
|
51
|
+
## Explicit Function Steps
|
52
|
+
# 1. Initialize the ras_object, defaulting to the global ras if not provided.
|
53
|
+
# 2. Define a helper function to clear a single geometry preprocessor file.
|
54
|
+
# 3. Determine the list of plan files to process based on the input.
|
55
|
+
# 4. Iterate over each plan file and clear its geometry preprocessor file.
|
56
|
+
ras_obj = ras_object or ras
|
57
|
+
ras_obj.check_initialized()
|
58
|
+
|
59
|
+
def clear_single_file(plan_file: Union[str, Path], ras_obj) -> None:
|
60
|
+
plan_path = Path(plan_file)
|
61
|
+
geom_preprocessor_suffix = '.c' + ''.join(plan_path.suffixes[1:]) if plan_path.suffixes else '.c'
|
62
|
+
geom_preprocessor_file = plan_path.with_suffix(geom_preprocessor_suffix)
|
63
|
+
if geom_preprocessor_file.exists():
|
64
|
+
print(f"Deleting geometry preprocessor file: {geom_preprocessor_file}")
|
65
|
+
geom_preprocessor_file.unlink()
|
66
|
+
print("File deletion completed successfully.")
|
67
|
+
else:
|
68
|
+
print(f"No geometry preprocessor file found for: {plan_file}")
|
69
|
+
|
70
|
+
if plan_files is None:
|
71
|
+
print("Clearing all geometry preprocessor files in the project directory.")
|
72
|
+
plan_files_to_clear = list(ras_obj.project_folder.glob(r'*.p*'))
|
73
|
+
elif isinstance(plan_files, (str, Path)):
|
74
|
+
plan_files_to_clear = [plan_files]
|
75
|
+
elif isinstance(plan_files, list):
|
76
|
+
plan_files_to_clear = plan_files
|
77
|
+
else:
|
78
|
+
raise ValueError("Invalid input. Please provide a string, Path, list of paths, or None.")
|
79
|
+
|
80
|
+
for plan_file in plan_files_to_clear:
|
81
|
+
clear_single_file(plan_file, ras_obj)
|
82
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
83
|
+
|