ras-commander 0.56.0__tar.gz → 0.57.0__tar.gz
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-0.56.0/ras_commander.egg-info → ras_commander-0.57.0}/PKG-INFO +4 -3
- {ras_commander-0.56.0 → ras_commander-0.57.0}/README.md +1 -1
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasExamples.py +414 -394
- {ras_commander-0.56.0 → ras_commander-0.57.0/ras_commander.egg-info}/PKG-INFO +4 -3
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander.egg-info/requires.txt +1 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/setup.py +3 -2
- {ras_commander-0.56.0 → ras_commander-0.57.0}/LICENSE +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/pyproject.toml +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/Decorators.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfBase.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfBndry.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfFluvialPluvial.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfInfiltration.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfMesh.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfPipe.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfPlan.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfPlot.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfPump.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfResultsMesh.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfResultsPlan.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfResultsPlot.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfResultsXsec.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfStruc.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfUtils.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/HdfXsec.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/LoggingConfig.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasCmdr.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasGeo.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasGpt.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasMapper.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasPlan.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasPrj.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasToGo.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasUnsteady.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/RasUtils.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander/__init__.py +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander.egg-info/SOURCES.txt +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander.egg-info/dependency_links.txt +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/ras_commander.egg-info/top_level.txt +0 -0
- {ras_commander-0.56.0 → ras_commander-0.57.0}/setup.cfg +0 -0
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: ras-commander
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.57.0
|
4
4
|
Summary: A Python library for automating HEC-RAS 6.x operations
|
5
5
|
Home-page: https://github.com/gpt-cmdr/ras-commander
|
6
|
-
Author: William M. Katzenmeyer
|
6
|
+
Author: William M. Katzenmeyer, P.E., C.F.M.
|
7
7
|
Author-email: heccommander@gmail.com
|
8
8
|
Requires-Python: >=3.10
|
9
9
|
Description-Content-Type: text/markdown
|
@@ -20,6 +20,7 @@ Requires-Dist: matplotlib
|
|
20
20
|
Requires-Dist: shapely
|
21
21
|
Requires-Dist: pathlib
|
22
22
|
Requires-Dist: rasterstats
|
23
|
+
Requires-Dist: rtree
|
23
24
|
Dynamic: author
|
24
25
|
Dynamic: author-email
|
25
26
|
Dynamic: description
|
@@ -254,7 +255,7 @@ ras_commander
|
|
254
255
|
|
255
256
|
### Accessing HEC Examples through RasExamples
|
256
257
|
|
257
|
-
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes.
|
258
|
+
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes. All examples in the ras-commander repository currently utilize HEC example projects to provide fully running scripts and notebooks for end user testing, demonstration and adaption.
|
258
259
|
|
259
260
|
Key features:
|
260
261
|
- Download and extract HEC-RAS example projects
|
@@ -223,7 +223,7 @@ ras_commander
|
|
223
223
|
|
224
224
|
### Accessing HEC Examples through RasExamples
|
225
225
|
|
226
|
-
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes.
|
226
|
+
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes. All examples in the ras-commander repository currently utilize HEC example projects to provide fully running scripts and notebooks for end user testing, demonstration and adaption.
|
227
227
|
|
228
228
|
Key features:
|
229
229
|
- Download and extract HEC-RAS example projects
|
@@ -1,395 +1,415 @@
|
|
1
|
-
"""
|
2
|
-
RasExamples - Manage and load HEC-RAS example projects for testing and development
|
3
|
-
|
4
|
-
This module is part of the ras-commander library and uses a centralized logging configuration.
|
5
|
-
|
6
|
-
Logging Configuration:
|
7
|
-
- The logging is set up in the logging_config.py file.
|
8
|
-
- A @log_call decorator is available to automatically log function calls.
|
9
|
-
- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
10
|
-
- Logs are written to both console and a rotating file handler.
|
11
|
-
- The default log file is 'ras_commander.log' in the 'logs' directory.
|
12
|
-
- The default log level is INFO.
|
13
|
-
|
14
|
-
To use logging in this module:
|
15
|
-
1. Use the @log_call decorator for automatic function call logging.
|
16
|
-
2. For additional logging, use logger.[level]() calls (e.g., logger.info(), logger.debug()).
|
17
|
-
3. Obtain the logger using: logger = logging.getLogger(__name__)
|
18
|
-
|
19
|
-
Example:
|
20
|
-
@log_call
|
21
|
-
def my_function():
|
22
|
-
logger = logging.getLogger(__name__)
|
23
|
-
logger.debug("Additional debug information")
|
24
|
-
# Function logic here
|
25
|
-
|
26
|
-
|
27
|
-
-----
|
28
|
-
|
29
|
-
All of the methods in this class are static and are designed to be used without instantiation.
|
30
|
-
|
31
|
-
List of Functions in RasExamples:
|
32
|
-
- get_example_projects()
|
33
|
-
- list_categories()
|
34
|
-
- list_projects()
|
35
|
-
- extract_project()
|
36
|
-
- is_project_extracted()
|
37
|
-
- clean_projects_directory()
|
38
|
-
|
39
|
-
"""
|
40
|
-
import os
|
41
|
-
import requests
|
42
|
-
import zipfile
|
43
|
-
import pandas as pd
|
44
|
-
from pathlib import Path
|
45
|
-
import shutil
|
46
|
-
from typing import Union, List
|
47
|
-
import csv
|
48
|
-
from datetime import datetime
|
49
|
-
import logging
|
50
|
-
import re
|
51
|
-
from tqdm import tqdm
|
52
|
-
from ras_commander import get_logger
|
53
|
-
from ras_commander.LoggingConfig import log_call
|
54
|
-
|
55
|
-
logger = get_logger(__name__)
|
56
|
-
|
57
|
-
class RasExamples:
|
58
|
-
"""
|
59
|
-
A class for quickly loading HEC-RAS example projects for testing and development of ras-commander.
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
self.
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
self.
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
""
|
128
|
-
|
129
|
-
|
130
|
-
if
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
"""
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
264
|
-
|
265
|
-
|
266
|
-
|
267
|
-
|
268
|
-
|
269
|
-
|
270
|
-
|
271
|
-
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
291
|
-
|
292
|
-
|
293
|
-
logger.
|
294
|
-
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
|
300
|
-
|
301
|
-
|
302
|
-
|
303
|
-
|
304
|
-
|
305
|
-
|
306
|
-
""
|
307
|
-
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
|
314
|
-
|
315
|
-
|
316
|
-
|
317
|
-
|
318
|
-
|
319
|
-
|
320
|
-
|
321
|
-
|
322
|
-
|
323
|
-
|
324
|
-
|
325
|
-
|
326
|
-
|
327
|
-
|
328
|
-
|
329
|
-
|
330
|
-
|
331
|
-
|
332
|
-
""
|
333
|
-
|
334
|
-
|
335
|
-
|
336
|
-
|
337
|
-
|
338
|
-
|
339
|
-
|
340
|
-
|
341
|
-
|
342
|
-
|
343
|
-
|
344
|
-
|
345
|
-
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
1
|
+
"""
|
2
|
+
RasExamples - Manage and load HEC-RAS example projects for testing and development
|
3
|
+
|
4
|
+
This module is part of the ras-commander library and uses a centralized logging configuration.
|
5
|
+
|
6
|
+
Logging Configuration:
|
7
|
+
- The logging is set up in the logging_config.py file.
|
8
|
+
- A @log_call decorator is available to automatically log function calls.
|
9
|
+
- Log levels: DEBUG, INFO, WARNING, ERROR, CRITICAL
|
10
|
+
- Logs are written to both console and a rotating file handler.
|
11
|
+
- The default log file is 'ras_commander.log' in the 'logs' directory.
|
12
|
+
- The default log level is INFO.
|
13
|
+
|
14
|
+
To use logging in this module:
|
15
|
+
1. Use the @log_call decorator for automatic function call logging.
|
16
|
+
2. For additional logging, use logger.[level]() calls (e.g., logger.info(), logger.debug()).
|
17
|
+
3. Obtain the logger using: logger = logging.getLogger(__name__)
|
18
|
+
|
19
|
+
Example:
|
20
|
+
@log_call
|
21
|
+
def my_function():
|
22
|
+
logger = logging.getLogger(__name__)
|
23
|
+
logger.debug("Additional debug information")
|
24
|
+
# Function logic here
|
25
|
+
|
26
|
+
|
27
|
+
-----
|
28
|
+
|
29
|
+
All of the methods in this class are static and are designed to be used without instantiation.
|
30
|
+
|
31
|
+
List of Functions in RasExamples:
|
32
|
+
- get_example_projects()
|
33
|
+
- list_categories()
|
34
|
+
- list_projects()
|
35
|
+
- extract_project()
|
36
|
+
- is_project_extracted()
|
37
|
+
- clean_projects_directory()
|
38
|
+
|
39
|
+
"""
|
40
|
+
import os
|
41
|
+
import requests
|
42
|
+
import zipfile
|
43
|
+
import pandas as pd
|
44
|
+
from pathlib import Path
|
45
|
+
import shutil
|
46
|
+
from typing import Union, List
|
47
|
+
import csv
|
48
|
+
from datetime import datetime
|
49
|
+
import logging
|
50
|
+
import re
|
51
|
+
from tqdm import tqdm
|
52
|
+
from ras_commander import get_logger
|
53
|
+
from ras_commander.LoggingConfig import log_call
|
54
|
+
|
55
|
+
logger = get_logger(__name__)
|
56
|
+
|
57
|
+
class RasExamples:
|
58
|
+
"""
|
59
|
+
A class for quickly loading HEC-RAS example projects for testing and development of ras-commander.
|
60
|
+
All methods are class methods, so no initialization is required.
|
61
|
+
"""
|
62
|
+
base_url = 'https://github.com/HydrologicEngineeringCenter/hec-downloads/releases/download/'
|
63
|
+
valid_versions = [
|
64
|
+
"6.6", "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
|
65
|
+
"5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
|
66
|
+
"4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
|
67
|
+
]
|
68
|
+
base_dir = Path.cwd()
|
69
|
+
examples_dir = base_dir
|
70
|
+
projects_dir = examples_dir / 'example_projects'
|
71
|
+
csv_file_path = examples_dir / 'example_projects.csv'
|
72
|
+
|
73
|
+
_folder_df = None
|
74
|
+
_zip_file_path = None
|
75
|
+
|
76
|
+
def __init__(self):
|
77
|
+
"""Initialize RasExamples and ensure data is loaded"""
|
78
|
+
self._ensure_initialized()
|
79
|
+
|
80
|
+
@property
|
81
|
+
def folder_df(self):
|
82
|
+
"""Access the folder DataFrame"""
|
83
|
+
self._ensure_initialized()
|
84
|
+
return self._folder_df
|
85
|
+
|
86
|
+
def _ensure_initialized(self):
|
87
|
+
"""Ensure the class is initialized with required data"""
|
88
|
+
self.projects_dir.mkdir(parents=True, exist_ok=True)
|
89
|
+
if self._folder_df is None:
|
90
|
+
self._load_project_data()
|
91
|
+
|
92
|
+
def _load_project_data(self):
|
93
|
+
"""Load project data from CSV if up-to-date, otherwise extract from zip."""
|
94
|
+
logger.debug("Loading project data")
|
95
|
+
self._find_zip_file()
|
96
|
+
|
97
|
+
if not self._zip_file_path:
|
98
|
+
logger.info("No example projects zip file found. Downloading...")
|
99
|
+
self.get_example_projects()
|
100
|
+
|
101
|
+
try:
|
102
|
+
zip_modified_time = os.path.getmtime(self._zip_file_path)
|
103
|
+
except FileNotFoundError:
|
104
|
+
logger.error(f"Zip file not found at {self._zip_file_path}.")
|
105
|
+
return
|
106
|
+
|
107
|
+
if self.csv_file_path.exists():
|
108
|
+
csv_modified_time = os.path.getmtime(self.csv_file_path)
|
109
|
+
|
110
|
+
if csv_modified_time >= zip_modified_time:
|
111
|
+
logger.info("Loading project data from CSV...")
|
112
|
+
try:
|
113
|
+
self._folder_df = pd.read_csv(self.csv_file_path)
|
114
|
+
logger.info(f"Loaded {len(self._folder_df)} projects from CSV.")
|
115
|
+
return
|
116
|
+
except Exception as e:
|
117
|
+
logger.error(f"Failed to read CSV file: {e}")
|
118
|
+
self._folder_df = None
|
119
|
+
|
120
|
+
logger.info("Extracting folder structure from zip file...")
|
121
|
+
self._extract_folder_structure()
|
122
|
+
self._save_to_csv()
|
123
|
+
|
124
|
+
@classmethod
|
125
|
+
def extract_project(cls, project_names: Union[str, List[str]]):
|
126
|
+
"""Extract one or more specific HEC-RAS projects from the zip file."""
|
127
|
+
logger.debug(f"Extracting projects: {project_names}")
|
128
|
+
|
129
|
+
# Initialize if needed
|
130
|
+
if cls._folder_df is None:
|
131
|
+
cls._find_zip_file()
|
132
|
+
if not cls._zip_file_path:
|
133
|
+
logger.info("No example projects zip file found. Downloading...")
|
134
|
+
cls.get_example_projects()
|
135
|
+
cls._load_project_data()
|
136
|
+
|
137
|
+
if isinstance(project_names, str):
|
138
|
+
project_names = [project_names]
|
139
|
+
|
140
|
+
extracted_paths = []
|
141
|
+
|
142
|
+
for project_name in project_names:
|
143
|
+
logger.info("----- RasExamples Extracting Project -----")
|
144
|
+
logger.info(f"Extracting project '{project_name}'")
|
145
|
+
project_path = cls.projects_dir
|
146
|
+
|
147
|
+
if (project_path / project_name).exists():
|
148
|
+
logger.info(f"Project '{project_name}' already exists. Deleting existing folder...")
|
149
|
+
try:
|
150
|
+
shutil.rmtree(project_path / project_name)
|
151
|
+
logger.info(f"Existing folder for project '{project_name}' has been deleted.")
|
152
|
+
except Exception as e:
|
153
|
+
logger.error(f"Failed to delete existing project folder '{project_name}': {e}")
|
154
|
+
continue
|
155
|
+
|
156
|
+
project_info = cls._folder_df[cls._folder_df['Project'] == project_name]
|
157
|
+
if project_info.empty:
|
158
|
+
error_msg = f"Project '{project_name}' not found in the zip file."
|
159
|
+
logger.error(error_msg)
|
160
|
+
raise ValueError(error_msg)
|
161
|
+
|
162
|
+
try:
|
163
|
+
with zipfile.ZipFile(cls._zip_file_path, 'r') as zip_ref:
|
164
|
+
for file in zip_ref.namelist():
|
165
|
+
parts = Path(file).parts
|
166
|
+
if len(parts) > 1 and parts[1] == project_name:
|
167
|
+
relative_path = Path(*parts[1:])
|
168
|
+
extract_path = project_path / relative_path
|
169
|
+
if file.endswith('/'):
|
170
|
+
extract_path.mkdir(parents=True, exist_ok=True)
|
171
|
+
else:
|
172
|
+
extract_path.parent.mkdir(parents=True, exist_ok=True)
|
173
|
+
with zip_ref.open(file) as source, open(extract_path, "wb") as target:
|
174
|
+
shutil.copyfileobj(source, target)
|
175
|
+
|
176
|
+
logger.info(f"Successfully extracted project '{project_name}' to {project_path / project_name}")
|
177
|
+
extracted_paths.append(project_path / project_name)
|
178
|
+
except Exception as e:
|
179
|
+
logger.error(f"An error occurred while extracting project '{project_name}': {str(e)}")
|
180
|
+
|
181
|
+
return extracted_paths
|
182
|
+
|
183
|
+
@classmethod
|
184
|
+
def _find_zip_file(cls):
|
185
|
+
"""Locate the example projects zip file in the examples directory."""
|
186
|
+
for version in cls.valid_versions:
|
187
|
+
potential_zip = cls.examples_dir / f"Example_Projects_{version.replace('.', '_')}.zip"
|
188
|
+
if potential_zip.exists():
|
189
|
+
cls._zip_file_path = potential_zip
|
190
|
+
logger.info(f"Found zip file: {cls._zip_file_path}")
|
191
|
+
break
|
192
|
+
else:
|
193
|
+
logger.warning("No existing example projects zip file found.")
|
194
|
+
|
195
|
+
@classmethod
|
196
|
+
def get_example_projects(cls, version_number='6.6'):
|
197
|
+
"""
|
198
|
+
Download and extract HEC-RAS example projects for a specified version.
|
199
|
+
"""
|
200
|
+
logger.info(f"Getting example projects for version {version_number}")
|
201
|
+
if version_number not in cls.valid_versions:
|
202
|
+
error_msg = f"Invalid version number. Valid versions are: {', '.join(cls.valid_versions)}"
|
203
|
+
logger.error(error_msg)
|
204
|
+
raise ValueError(error_msg)
|
205
|
+
|
206
|
+
zip_url = f"{cls.base_url}1.0.33/Example_Projects_{version_number.replace('.', '_')}.zip"
|
207
|
+
|
208
|
+
cls.examples_dir.mkdir(parents=True, exist_ok=True)
|
209
|
+
|
210
|
+
cls._zip_file_path = cls.examples_dir / f"Example_Projects_{version_number.replace('.', '_')}.zip"
|
211
|
+
|
212
|
+
if not cls._zip_file_path.exists():
|
213
|
+
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....")
|
214
|
+
try:
|
215
|
+
response = requests.get(zip_url, stream=True)
|
216
|
+
response.raise_for_status()
|
217
|
+
with open(cls._zip_file_path, 'wb') as file:
|
218
|
+
shutil.copyfileobj(response.raw, file)
|
219
|
+
logger.info(f"Downloaded to {cls._zip_file_path}")
|
220
|
+
except requests.exceptions.RequestException as e:
|
221
|
+
logger.error(f"Failed to download the zip file: {e}")
|
222
|
+
raise
|
223
|
+
else:
|
224
|
+
logger.info("HEC-RAS Example Projects zip file already exists. Skipping download.")
|
225
|
+
|
226
|
+
cls._load_project_data()
|
227
|
+
return cls.projects_dir
|
228
|
+
|
229
|
+
@classmethod
|
230
|
+
def _load_project_data(cls):
|
231
|
+
"""Load project data from CSV if up-to-date, otherwise extract from zip."""
|
232
|
+
logger.debug("Loading project data")
|
233
|
+
|
234
|
+
try:
|
235
|
+
zip_modified_time = os.path.getmtime(cls._zip_file_path)
|
236
|
+
except FileNotFoundError:
|
237
|
+
logger.error(f"Zip file not found at {cls._zip_file_path}.")
|
238
|
+
return
|
239
|
+
|
240
|
+
if cls.csv_file_path.exists():
|
241
|
+
csv_modified_time = os.path.getmtime(cls.csv_file_path)
|
242
|
+
|
243
|
+
if csv_modified_time >= zip_modified_time:
|
244
|
+
logger.info("Loading project data from CSV...")
|
245
|
+
try:
|
246
|
+
cls._folder_df = pd.read_csv(cls.csv_file_path)
|
247
|
+
logger.info(f"Loaded {len(cls._folder_df)} projects from CSV.")
|
248
|
+
return
|
249
|
+
except Exception as e:
|
250
|
+
logger.error(f"Failed to read CSV file: {e}")
|
251
|
+
cls._folder_df = None
|
252
|
+
|
253
|
+
logger.info("Extracting folder structure from zip file...")
|
254
|
+
cls._extract_folder_structure()
|
255
|
+
cls._save_to_csv()
|
256
|
+
|
257
|
+
@classmethod
|
258
|
+
def _extract_folder_structure(cls):
|
259
|
+
"""
|
260
|
+
Extract folder structure from the zip file.
|
261
|
+
|
262
|
+
Populates folder_df with category and project information.
|
263
|
+
"""
|
264
|
+
folder_data = []
|
265
|
+
try:
|
266
|
+
with zipfile.ZipFile(cls._zip_file_path, 'r') as zip_ref:
|
267
|
+
for file in zip_ref.namelist():
|
268
|
+
parts = Path(file).parts
|
269
|
+
if len(parts) > 1:
|
270
|
+
folder_data.append({
|
271
|
+
'Category': parts[0],
|
272
|
+
'Project': parts[1]
|
273
|
+
})
|
274
|
+
|
275
|
+
cls._folder_df = pd.DataFrame(folder_data).drop_duplicates()
|
276
|
+
logger.info(f"Extracted {len(cls._folder_df)} projects.")
|
277
|
+
logger.debug(f"folder_df:\n{cls._folder_df}")
|
278
|
+
except zipfile.BadZipFile:
|
279
|
+
logger.error(f"The file {cls._zip_file_path} is not a valid zip file.")
|
280
|
+
cls._folder_df = pd.DataFrame(columns=['Category', 'Project'])
|
281
|
+
except Exception as e:
|
282
|
+
logger.error(f"An error occurred while extracting the folder structure: {str(e)}")
|
283
|
+
cls._folder_df = pd.DataFrame(columns=['Category', 'Project'])
|
284
|
+
|
285
|
+
@classmethod
|
286
|
+
def _save_to_csv(cls):
|
287
|
+
"""Save the extracted folder structure to CSV file."""
|
288
|
+
if cls._folder_df is not None and not cls._folder_df.empty:
|
289
|
+
try:
|
290
|
+
cls._folder_df.to_csv(cls.csv_file_path, index=False)
|
291
|
+
logger.info(f"Saved project data to {cls.csv_file_path}")
|
292
|
+
except Exception as e:
|
293
|
+
logger.error(f"Failed to save project data to CSV: {e}")
|
294
|
+
else:
|
295
|
+
logger.warning("No folder data to save to CSV.")
|
296
|
+
|
297
|
+
@classmethod
|
298
|
+
def list_categories(cls):
|
299
|
+
"""
|
300
|
+
List all categories of example projects.
|
301
|
+
"""
|
302
|
+
if cls._folder_df is None or 'Category' not in cls._folder_df.columns:
|
303
|
+
logger.warning("No categories available. Make sure the zip file is properly loaded.")
|
304
|
+
return []
|
305
|
+
categories = cls._folder_df['Category'].unique()
|
306
|
+
logger.info(f"Available categories: {', '.join(categories)}")
|
307
|
+
return categories.tolist()
|
308
|
+
|
309
|
+
@classmethod
|
310
|
+
def list_projects(cls, category=None):
|
311
|
+
"""
|
312
|
+
List all projects or projects in a specific category.
|
313
|
+
"""
|
314
|
+
if cls._folder_df is None:
|
315
|
+
logger.warning("No projects available. Make sure the zip file is properly loaded.")
|
316
|
+
return []
|
317
|
+
if category:
|
318
|
+
projects = cls._folder_df[cls._folder_df['Category'] == category]['Project'].unique()
|
319
|
+
logger.info(f"Projects in category '{category}': {', '.join(projects)}")
|
320
|
+
else:
|
321
|
+
projects = cls._folder_df['Project'].unique()
|
322
|
+
logger.info(f"All available projects: {', '.join(projects)}")
|
323
|
+
return projects.tolist()
|
324
|
+
|
325
|
+
@classmethod
|
326
|
+
def is_project_extracted(cls, project_name):
|
327
|
+
"""
|
328
|
+
Check if a specific project is already extracted.
|
329
|
+
"""
|
330
|
+
project_path = cls.projects_dir / project_name
|
331
|
+
is_extracted = project_path.exists()
|
332
|
+
logger.info(f"Project '{project_name}' extracted: {is_extracted}")
|
333
|
+
return is_extracted
|
334
|
+
|
335
|
+
@classmethod
|
336
|
+
def clean_projects_directory(cls):
|
337
|
+
"""Remove all extracted projects from the example_projects directory."""
|
338
|
+
logger.info(f"Cleaning projects directory: {cls.projects_dir}")
|
339
|
+
if cls.projects_dir.exists():
|
340
|
+
try:
|
341
|
+
shutil.rmtree(cls.projects_dir)
|
342
|
+
logger.info("All projects have been removed.")
|
343
|
+
except Exception as e:
|
344
|
+
logger.error(f"Failed to remove projects directory: {e}")
|
345
|
+
else:
|
346
|
+
logger.warning("Projects directory does not exist.")
|
347
|
+
cls.projects_dir.mkdir(parents=True, exist_ok=True)
|
348
|
+
logger.info("Projects directory cleaned and recreated.")
|
349
|
+
|
350
|
+
@classmethod
|
351
|
+
def download_fema_ble_model(cls, huc8, output_dir=None):
|
352
|
+
"""
|
353
|
+
Download a FEMA Base Level Engineering (BLE) model for a given HUC8.
|
354
|
+
|
355
|
+
Args:
|
356
|
+
huc8 (str): The 8-digit Hydrologic Unit Code (HUC) for the desired watershed.
|
357
|
+
output_dir (str, optional): The directory to save the downloaded files. If None, uses the current working directory.
|
358
|
+
|
359
|
+
Returns:
|
360
|
+
str: The path to the downloaded and extracted model directory.
|
361
|
+
|
362
|
+
Note:
|
363
|
+
This method downloads the BLE model from the FEMA website and extracts it to the specified directory.
|
364
|
+
"""
|
365
|
+
# Method implementation...
|
366
|
+
|
367
|
+
@classmethod
|
368
|
+
def _make_safe_folder_name(cls, name: str) -> str:
|
369
|
+
"""
|
370
|
+
Convert a string to a safe folder name by replacing unsafe characters with underscores.
|
371
|
+
"""
|
372
|
+
safe_name = re.sub(r'[^a-zA-Z0-9_\-]', '_', name)
|
373
|
+
logger.debug(f"Converted '{name}' to safe folder name '{safe_name}'")
|
374
|
+
return safe_name
|
375
|
+
|
376
|
+
@classmethod
|
377
|
+
def _download_file_with_progress(cls, url: str, dest_folder: Path, file_size: int) -> Path:
|
378
|
+
"""
|
379
|
+
Download a file from a URL to a specified destination folder with progress bar.
|
380
|
+
"""
|
381
|
+
local_filename = dest_folder / url.split('/')[-1]
|
382
|
+
try:
|
383
|
+
with requests.get(url, stream=True) as r:
|
384
|
+
r.raise_for_status()
|
385
|
+
with open(local_filename, 'wb') as f, tqdm(
|
386
|
+
desc=local_filename.name,
|
387
|
+
total=file_size,
|
388
|
+
unit='iB',
|
389
|
+
unit_scale=True,
|
390
|
+
unit_divisor=1024,
|
391
|
+
) as progress_bar:
|
392
|
+
for chunk in r.iter_content(chunk_size=8192):
|
393
|
+
size = f.write(chunk)
|
394
|
+
progress_bar.update(size)
|
395
|
+
logger.info(f"Successfully downloaded {url} to {local_filename}")
|
396
|
+
return local_filename
|
397
|
+
except requests.exceptions.RequestException as e:
|
398
|
+
logger.error(f"Request failed for {url}: {e}")
|
399
|
+
raise
|
400
|
+
except Exception as e:
|
401
|
+
logger.error(f"Failed to write file {local_filename}: {e}")
|
402
|
+
raise
|
403
|
+
|
404
|
+
@classmethod
|
405
|
+
def _convert_size_to_bytes(cls, size_str: str) -> int:
|
406
|
+
"""
|
407
|
+
Convert a human-readable file size to bytes.
|
408
|
+
"""
|
409
|
+
units = {'B': 1, 'KB': 1024, 'MB': 1024**2, 'GB': 1024**3, 'TB': 1024**4}
|
410
|
+
size_str = size_str.upper().replace(' ', '')
|
411
|
+
if not re.match(r'^\d+(\.\d+)?[BKMGT]B?$', size_str):
|
412
|
+
raise ValueError(f"Invalid size string: {size_str}")
|
413
|
+
|
414
|
+
number, unit = float(re.findall(r'[\d\.]+', size_str)[0]), re.findall(r'[BKMGT]B?', size_str)[0]
|
395
415
|
return int(number * units[unit])
|
@@ -1,9 +1,9 @@
|
|
1
1
|
Metadata-Version: 2.2
|
2
2
|
Name: ras-commander
|
3
|
-
Version: 0.
|
3
|
+
Version: 0.57.0
|
4
4
|
Summary: A Python library for automating HEC-RAS 6.x operations
|
5
5
|
Home-page: https://github.com/gpt-cmdr/ras-commander
|
6
|
-
Author: William M. Katzenmeyer
|
6
|
+
Author: William M. Katzenmeyer, P.E., C.F.M.
|
7
7
|
Author-email: heccommander@gmail.com
|
8
8
|
Requires-Python: >=3.10
|
9
9
|
Description-Content-Type: text/markdown
|
@@ -20,6 +20,7 @@ Requires-Dist: matplotlib
|
|
20
20
|
Requires-Dist: shapely
|
21
21
|
Requires-Dist: pathlib
|
22
22
|
Requires-Dist: rasterstats
|
23
|
+
Requires-Dist: rtree
|
23
24
|
Dynamic: author
|
24
25
|
Dynamic: author-email
|
25
26
|
Dynamic: description
|
@@ -254,7 +255,7 @@ ras_commander
|
|
254
255
|
|
255
256
|
### Accessing HEC Examples through RasExamples
|
256
257
|
|
257
|
-
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes.
|
258
|
+
The `RasExamples` class provides functionality for quickly loading and managing HEC-RAS example projects. This is particularly useful for testing and development purposes. All examples in the ras-commander repository currently utilize HEC example projects to provide fully running scripts and notebooks for end user testing, demonstration and adaption.
|
258
259
|
|
259
260
|
Key features:
|
260
261
|
- Download and extract HEC-RAS example projects
|
@@ -28,11 +28,11 @@ class CustomBuildPy(build_py):
|
|
28
28
|
|
29
29
|
setup(
|
30
30
|
name="ras-commander",
|
31
|
-
version="0.
|
31
|
+
version="0.57.0",
|
32
32
|
packages=find_packages(),
|
33
33
|
include_package_data=True,
|
34
34
|
python_requires='>=3.10',
|
35
|
-
author="William M. Katzenmeyer",
|
35
|
+
author="William M. Katzenmeyer, P.E., C.F.M.",
|
36
36
|
author_email="heccommander@gmail.com",
|
37
37
|
description="A Python library for automating HEC-RAS 6.x operations",
|
38
38
|
long_description=open('README.md').read(),
|
@@ -54,6 +54,7 @@ setup(
|
|
54
54
|
'shapely',
|
55
55
|
'pathlib',
|
56
56
|
'rasterstats',
|
57
|
+
'rtree',
|
57
58
|
])
|
58
59
|
|
59
60
|
"""
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|
File without changes
|