ras-commander 0.70.0__py3-none-any.whl → 0.72.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/RasGeo.py CHANGED
@@ -24,19 +24,19 @@ Example:
24
24
  # Function logic here
25
25
 
26
26
 
27
-
28
- -----
29
-
30
27
  All of the methods in this class are static and are designed to be used without instantiation.
31
28
 
32
29
  List of Functions in RasGeo:
33
- - clear_geompre_files()
34
-
35
-
30
+ - clear_geompre_files(): Clears geometry preprocessor files for specified plan files
31
+ - get_mannings_baseoverrides(): Reads base Manning's n table from a geometry file
32
+ - get_mannings_regionoverrides(): Reads Manning's n region overrides from a geometry file
33
+ - set_mannings_baseoverrides(): Writes base Manning's n values to a geometry file
34
+ - set_mannings_regionoverrides(): Writes regional Manning's n overrides to a geometry file
36
35
  """
37
36
  import os
38
37
  from pathlib import Path
39
38
  from typing import List, Union
39
+ import pandas as pd # Added pandas import
40
40
  from .RasPlan import RasPlan
41
41
  from .RasPrj import ras
42
42
  from .LoggingConfig import get_logger
@@ -56,13 +56,18 @@ class RasGeo:
56
56
  ras_object = None
57
57
  ) -> None:
58
58
  """
59
- Clear HEC-RAS geometry preprocessor files for specified plan files or all plan files in the project directory.
60
-
59
+ Clear HEC-RAS geometry preprocessor files for specified plan files.
60
+
61
+ Geometry preprocessor files (.c* extension) contain computed hydraulic properties derived
62
+ from the geometry. These should be cleared when the geometry changes to ensure that
63
+ HEC-RAS recomputes all hydraulic tables with updated geometry information.
64
+
61
65
  Limitations/Future Work:
62
66
  - This function only deletes the geometry preprocessor file.
63
67
  - It does not clear the IB tables.
64
68
  - It also does not clear geometry preprocessor tables from the geometry HDF.
65
- - All of these features will need to be added to reliably remove geometry preprocessor files for 1D and 2D projects.
69
+ - All of these features will need to be added to reliably remove geometry preprocessor
70
+ files for 1D and 2D projects.
66
71
 
67
72
  Parameters:
68
73
  plan_files (Union[str, Path, List[Union[str, Path]]], optional):
@@ -71,20 +76,20 @@ class RasGeo:
71
76
  ras_object: An optional RAS object instance.
72
77
 
73
78
  Returns:
74
- None
75
-
76
- Examples:
77
- # Clear all geometry preprocessor files in the project directory
78
- RasGeo.clear_geompre_files()
79
+ None: The function deletes files and updates the ras object's geometry dataframe
80
+
81
+ Example:
82
+ # Clone a plan and geometry
83
+ new_plan_number = RasPlan.clone_plan("01")
84
+ new_geom_number = RasPlan.clone_geom("01")
79
85
 
80
- # Clear a single plan file
81
- RasGeo.clear_geompre_files(r'path/to/plan.p01')
86
+ # Set the new geometry for the cloned plan
87
+ RasPlan.set_geom(new_plan_number, new_geom_number)
88
+ plan_path = RasPlan.get_plan_path(new_plan_number)
82
89
 
83
- # Clear multiple plan files
84
- RasGeo.clear_geompre_files([r'path/to/plan1.p01', r'path/to/plan2.p02'])
85
-
86
- Note:
87
- This function updates the ras object's geometry dataframe after clearing the preprocessor files.
90
+ # Clear geometry preprocessor files to ensure clean results
91
+ RasGeo.clear_geompre_files(plan_path)
92
+ print(f"Cleared geometry preprocessor files for plan {new_plan_number}")
88
93
  """
89
94
  ras_obj = ras_object or ras
90
95
  ras_obj.check_initialized()
@@ -129,6 +134,256 @@ class RasGeo:
129
134
  logger.error(f"Failed to update geometry dataframe: {str(e)}")
130
135
  raise
131
136
 
137
+ @log_call
138
+ def get_mannings_baseoverrides(geom_file_path):
139
+ """
140
+ Reads the base Manning's n table from a HEC-RAS geometry file.
141
+
142
+ Parameters:
143
+ -----------
144
+ geom_file_path : str or Path
145
+ Path to the geometry file (.g##)
146
+
147
+ Returns:
148
+ --------
149
+ pandas.DataFrame
150
+ DataFrame with Table Number, Land Cover Name, and Base Manning's n Value
151
+ """
152
+ import pandas as pd
153
+ from pathlib import Path
154
+
155
+ # Convert to Path object if it's a string
156
+ if isinstance(geom_file_path, str):
157
+ geom_file_path = Path(geom_file_path)
158
+
159
+ base_table_rows = []
160
+ table_number = None
161
+
162
+ # Read the geometry file
163
+ with open(geom_file_path, 'r') as f:
164
+ lines = f.readlines()
165
+
166
+ # Parse the file
167
+ reading_base_table = False
168
+ for line in lines:
169
+ line = line.strip()
170
+
171
+ # Find the table number
172
+ if line.startswith('LCMann Table='):
173
+ table_number = line.split('=')[1]
174
+ reading_base_table = True
175
+ continue
176
+
177
+ # Stop reading when we hit a line without a comma or starting with LCMann
178
+ if reading_base_table and (not ',' in line or line.startswith('LCMann')):
179
+ reading_base_table = False
180
+ continue
181
+
182
+ # Parse data rows in base table
183
+ if reading_base_table and ',' in line:
184
+ # Check if there are multiple commas in the line
185
+ parts = line.split(',')
186
+ if len(parts) > 2:
187
+ # Handle case where land cover name contains commas
188
+ name = ','.join(parts[:-1])
189
+ value = parts[-1]
190
+ else:
191
+ name, value = parts
192
+
193
+ try:
194
+ base_table_rows.append([table_number, name, float(value)])
195
+ except ValueError:
196
+ # Log the error and continue
197
+ print(f"Error parsing line: {line}")
198
+ continue
199
+
200
+ # Create DataFrame
201
+ if base_table_rows:
202
+ df = pd.DataFrame(base_table_rows, columns=['Table Number', 'Land Cover Name', 'Base Manning\'s n Value'])
203
+ return df
204
+ else:
205
+ return pd.DataFrame(columns=['Table Number', 'Land Cover Name', 'Base Manning\'s n Value'])
206
+
207
+
208
+ @log_call
209
+ def get_mannings_regionoverrides(geom_file_path):
210
+ """
211
+ Reads the Manning's n region overrides from a HEC-RAS geometry file.
212
+
213
+ Parameters:
214
+ -----------
215
+ geom_file_path : str or Path
216
+ Path to the geometry file (.g##)
217
+
218
+ Returns:
219
+ --------
220
+ pandas.DataFrame
221
+ DataFrame with Table Number, Land Cover Name, MainChannel value, and region name
222
+ """
223
+ import pandas as pd
224
+ from pathlib import Path
225
+
226
+ # Convert to Path object if it's a string
227
+ if isinstance(geom_file_path, str):
228
+ geom_file_path = Path(geom_file_path)
229
+
230
+ region_rows = []
231
+ current_region = None
232
+ current_table = None
233
+
234
+ # Read the geometry file
235
+ with open(geom_file_path, 'r') as f:
236
+ lines = f.readlines()
237
+
238
+ # Parse the file
239
+ reading_region_table = False
240
+ for line in lines:
241
+ line = line.strip()
242
+
243
+ # Find region name
244
+ if line.startswith('LCMann Region Name='):
245
+ current_region = line.split('=')[1]
246
+ continue
247
+
248
+ # Find region table number
249
+ if line.startswith('LCMann Region Table='):
250
+ current_table = line.split('=')[1]
251
+ reading_region_table = True
252
+ continue
253
+
254
+ # Stop reading when we hit a line without a comma or starting with LCMann
255
+ if reading_region_table and (not ',' in line or line.startswith('LCMann')):
256
+ reading_region_table = False
257
+ continue
258
+
259
+ # Parse data rows in region table
260
+ if reading_region_table and ',' in line and current_region is not None:
261
+ # Check if there are multiple commas in the line
262
+ parts = line.split(',')
263
+ if len(parts) > 2:
264
+ # Handle case where land cover name contains commas
265
+ name = ','.join(parts[:-1])
266
+ value = parts[-1]
267
+ else:
268
+ name, value = parts
269
+
270
+ try:
271
+ region_rows.append([current_table, name, float(value), current_region])
272
+ except ValueError:
273
+ # Log the error and continue
274
+ print(f"Error parsing line: {line}")
275
+ continue
276
+
277
+ # Create DataFrame
278
+ if region_rows:
279
+ return pd.DataFrame(region_rows, columns=['Table Number', 'Land Cover Name', 'MainChannel', 'Region Name'])
280
+ else:
281
+ return pd.DataFrame(columns=['Table Number', 'Land Cover Name', 'MainChannel', 'Region Name'])
282
+
283
+
284
+
285
+ @staticmethod
286
+ @log_call
287
+ def set_mannings_baseoverrides(geom_file_path, mannings_data):
288
+ """
289
+ Writes base Manning's n values to a HEC-RAS geometry file.
290
+
291
+ Parameters:
292
+ -----------
293
+ geom_file_path : str or Path
294
+ Path to the geometry file (.g##)
295
+ mannings_data : DataFrame
296
+ DataFrame with columns 'Table Number', 'Land Cover Name', and 'Base Manning\'s n Value'
297
+
298
+ Returns:
299
+ --------
300
+ bool
301
+ True if successful
302
+ """
303
+ from pathlib import Path
304
+ import shutil
305
+ import pandas as pd
306
+ import datetime
307
+
308
+ # Convert to Path object if it's a string
309
+ if isinstance(geom_file_path, str):
310
+ geom_file_path = Path(geom_file_path)
311
+
312
+ # Create backup
313
+ backup_path = geom_file_path.with_suffix(geom_file_path.suffix + '.bak')
314
+ shutil.copy2(geom_file_path, backup_path)
315
+
316
+ # Read the entire file
317
+ with open(geom_file_path, 'r') as f:
318
+ lines = f.readlines()
319
+
320
+ # Find the Manning's table section
321
+ table_number = str(mannings_data['Table Number'].iloc[0])
322
+ start_idx = None
323
+ end_idx = None
324
+
325
+ for i, line in enumerate(lines):
326
+ if line.strip() == f"LCMann Table={table_number}":
327
+ start_idx = i
328
+ # Find the end of this table (next LCMann directive or end of file)
329
+ for j in range(i+1, len(lines)):
330
+ if lines[j].strip().startswith('LCMann'):
331
+ end_idx = j
332
+ break
333
+ if end_idx is None: # If we reached the end of the file
334
+ end_idx = len(lines)
335
+ break
336
+
337
+ if start_idx is None:
338
+ raise ValueError(f"Manning's table {table_number} not found in the geometry file")
339
+
340
+ # Extract existing land cover names from the file
341
+ existing_landcover = []
342
+ for i in range(start_idx+1, end_idx):
343
+ line = lines[i].strip()
344
+ if ',' in line:
345
+ parts = line.split(',')
346
+ if len(parts) > 2:
347
+ # Handle case where land cover name contains commas
348
+ name = ','.join(parts[:-1])
349
+ else:
350
+ name = parts[0]
351
+ existing_landcover.append(name)
352
+
353
+ # Check if all land cover names in the dataframe match the file
354
+ df_landcover = mannings_data['Land Cover Name'].tolist()
355
+ if set(df_landcover) != set(existing_landcover):
356
+ missing = set(existing_landcover) - set(df_landcover)
357
+ extra = set(df_landcover) - set(existing_landcover)
358
+ error_msg = "Land cover names don't match between file and dataframe.\n"
359
+ if missing:
360
+ error_msg += f"Missing in dataframe: {missing}\n"
361
+ if extra:
362
+ error_msg += f"Extra in dataframe: {extra}"
363
+ raise ValueError(error_msg)
364
+
365
+ # Create new content for the table
366
+ new_content = [f"LCMann Table={table_number}\n"]
367
+
368
+ # Add base table entries
369
+ for _, row in mannings_data.iterrows():
370
+ new_content.append(f"{row['Land Cover Name']},{row['Base Manning\'s n Value']}\n")
371
+
372
+ # Replace the section in the original file
373
+ updated_lines = lines[:start_idx] + new_content + lines[end_idx:]
374
+
375
+ # Update the time stamp
376
+ current_time = datetime.datetime.now().strftime("%b/%d/%Y %H:%M:%S")
377
+ for i, line in enumerate(updated_lines):
378
+ if line.strip().startswith("LCMann Time="):
379
+ updated_lines[i] = f"LCMann Time={current_time}\n"
380
+ break
381
+
382
+ # Write the updated file
383
+ with open(geom_file_path, 'w') as f:
384
+ f.writelines(updated_lines)
385
+
386
+ return True
132
387
 
133
388
 
134
389
 
@@ -136,3 +391,134 @@ class RasGeo:
136
391
 
137
392
 
138
393
 
394
+ @staticmethod
395
+ @log_call
396
+ def set_mannings_regionoverrides(geom_file_path, mannings_data):
397
+ """
398
+ Writes regional Manning's n overrides to a HEC-RAS geometry file.
399
+
400
+ Parameters:
401
+ -----------
402
+ geom_file_path : str or Path
403
+ Path to the geometry file (.g##)
404
+ mannings_data : DataFrame
405
+ DataFrame with columns 'Table Number', 'Land Cover Name', 'MainChannel', and 'Region Name'
406
+
407
+ Returns:
408
+ --------
409
+ bool
410
+ True if successful
411
+ """
412
+ from pathlib import Path
413
+ import shutil
414
+ import pandas as pd
415
+ import datetime
416
+
417
+ # Convert to Path object if it's a string
418
+ if isinstance(geom_file_path, str):
419
+ geom_file_path = Path(geom_file_path)
420
+
421
+ # Create backup
422
+ backup_path = geom_file_path.with_suffix(geom_file_path.suffix + '.bak')
423
+ shutil.copy2(geom_file_path, backup_path)
424
+
425
+ # Read the entire file
426
+ with open(geom_file_path, 'r') as f:
427
+ lines = f.readlines()
428
+
429
+ # Group data by region
430
+ regions = mannings_data.groupby('Region Name')
431
+
432
+ # Find the Manning's region sections
433
+ for region_name, region_data in regions:
434
+ table_number = str(region_data['Table Number'].iloc[0])
435
+
436
+ # Find the region section
437
+ region_start_idx = None
438
+ region_table_idx = None
439
+ region_end_idx = None
440
+ region_polygon_line = None
441
+
442
+ for i, line in enumerate(lines):
443
+ if line.strip() == f"LCMann Region Name={region_name}":
444
+ region_start_idx = i
445
+
446
+ if region_start_idx is not None and line.strip() == f"LCMann Region Table={table_number}":
447
+ region_table_idx = i
448
+
449
+ # Find the end of this region (next LCMann Region or end of file)
450
+ for j in range(i+1, len(lines)):
451
+ if lines[j].strip().startswith('LCMann Region Name=') or lines[j].strip().startswith('LCMann Region Polygon='):
452
+ if lines[j].strip().startswith('LCMann Region Polygon='):
453
+ region_polygon_line = lines[j]
454
+ region_end_idx = j
455
+ break
456
+ if region_end_idx is None: # If we reached the end of the file
457
+ region_end_idx = len(lines)
458
+ break
459
+
460
+ if region_start_idx is None or region_table_idx is None:
461
+ raise ValueError(f"Region {region_name} with table {table_number} not found in the geometry file")
462
+
463
+ # Extract existing land cover names from the file
464
+ existing_landcover = []
465
+ for i in range(region_table_idx+1, region_end_idx):
466
+ line = lines[i].strip()
467
+ if ',' in line and not line.startswith('LCMann'):
468
+ parts = line.split(',')
469
+ if len(parts) > 2:
470
+ # Handle case where land cover name contains commas
471
+ name = ','.join(parts[:-1])
472
+ else:
473
+ name = parts[0]
474
+ existing_landcover.append(name)
475
+
476
+ # Check if all land cover names in the dataframe match the file
477
+ df_landcover = region_data['Land Cover Name'].tolist()
478
+ if set(df_landcover) != set(existing_landcover):
479
+ missing = set(existing_landcover) - set(df_landcover)
480
+ extra = set(df_landcover) - set(existing_landcover)
481
+ error_msg = f"Land cover names for region {region_name} don't match between file and dataframe.\n"
482
+ if missing:
483
+ error_msg += f"Missing in dataframe: {missing}\n"
484
+ if extra:
485
+ error_msg += f"Extra in dataframe: {extra}"
486
+ raise ValueError(error_msg)
487
+
488
+ # Create new content for the region
489
+ new_content = [
490
+ f"LCMann Region Name={region_name}\n",
491
+ f"LCMann Region Table={table_number}\n"
492
+ ]
493
+
494
+ # Add region table entries
495
+ for _, row in region_data.iterrows():
496
+ new_content.append(f"{row['Land Cover Name']},{row['MainChannel']}\n")
497
+
498
+ # Add the region polygon line if it exists
499
+ if region_polygon_line:
500
+ new_content.append(region_polygon_line)
501
+
502
+ # Replace the section in the original file
503
+ if region_polygon_line:
504
+ # If we have a polygon line, include it in the replacement
505
+ updated_lines = lines[:region_start_idx] + new_content + lines[region_end_idx+1:]
506
+ else:
507
+ # If no polygon line, just replace up to the end index
508
+ updated_lines = lines[:region_start_idx] + new_content + lines[region_end_idx:]
509
+
510
+ # Update the lines for the next region
511
+ lines = updated_lines
512
+
513
+ # Update the time stamp
514
+ current_time = datetime.datetime.now().strftime("%b/%d/%Y %H:%M:%S")
515
+ for i, line in enumerate(lines):
516
+ if line.strip().startswith("LCMann Region Time="):
517
+ lines[i] = f"LCMann Region Time={current_time}\n"
518
+ break
519
+
520
+ # Write the updated file
521
+ with open(geom_file_path, 'w') as f:
522
+ f.writelines(lines)
523
+
524
+ return True