ras-commander 0.61.0__py3-none-any.whl → 0.65.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/HdfBndry.py +91 -7
- ras_commander/HdfInfiltration.py +7 -1
- ras_commander/HdfPlan.py +44 -5
- ras_commander/HdfResultsMesh.py +24 -14
- ras_commander/HdfResultsPlan.py +380 -364
- ras_commander/HdfStruc.py +30 -8
- ras_commander/RasMapper.py +1 -82
- ras_commander/RasPlan.py +262 -52
- ras_commander/RasPrj.py +186 -41
- ras_commander/__init__.py +3 -7
- {ras_commander-0.61.0.dist-info → ras_commander-0.65.0.dist-info}/METADATA +48 -47
- {ras_commander-0.61.0.dist-info → ras_commander-0.65.0.dist-info}/RECORD +15 -15
- {ras_commander-0.61.0.dist-info → ras_commander-0.65.0.dist-info}/LICENSE +0 -0
- {ras_commander-0.61.0.dist-info → ras_commander-0.65.0.dist-info}/WHEEL +0 -0
- {ras_commander-0.61.0.dist-info → ras_commander-0.65.0.dist-info}/top_level.txt +0 -0
ras_commander/HdfStruc.py
CHANGED
@@ -81,9 +81,20 @@ class HdfStruc:
|
|
81
81
|
try:
|
82
82
|
with h5py.File(hdf_path, 'r') as hdf:
|
83
83
|
if "Geometry/Structures" not in hdf:
|
84
|
-
logger.
|
84
|
+
logger.error(f"No Structures Found in the HDF, Empty Geodataframe Returned: {hdf_path}")
|
85
85
|
return GeoDataFrame()
|
86
86
|
|
87
|
+
# Check if required datasets exist
|
88
|
+
required_datasets = [
|
89
|
+
"Geometry/Structures/Centerline Info",
|
90
|
+
"Geometry/Structures/Centerline Points"
|
91
|
+
]
|
92
|
+
|
93
|
+
for dataset in required_datasets:
|
94
|
+
if dataset not in hdf:
|
95
|
+
logger.error(f"No Structures Found in the HDF, Empty Geodataframe Returned: {hdf_path}")
|
96
|
+
return GeoDataFrame()
|
97
|
+
|
87
98
|
def get_dataset_df(path: str) -> pd.DataFrame:
|
88
99
|
"""
|
89
100
|
Converts an HDF5 dataset to a pandas DataFrame.
|
@@ -244,7 +255,7 @@ class HdfStruc:
|
|
244
255
|
@staticmethod
|
245
256
|
@log_call
|
246
257
|
@standardize_input(file_type='geom_hdf')
|
247
|
-
def get_geom_structures_attrs(hdf_path: Path) ->
|
258
|
+
def get_geom_structures_attrs(hdf_path: Path) -> pd.DataFrame:
|
248
259
|
"""
|
249
260
|
Extracts structure attributes from a HEC-RAS geometry HDF file.
|
250
261
|
|
@@ -255,9 +266,9 @@ class HdfStruc:
|
|
255
266
|
|
256
267
|
Returns
|
257
268
|
-------
|
258
|
-
|
259
|
-
|
260
|
-
Returns empty
|
269
|
+
pd.DataFrame
|
270
|
+
DataFrame containing structure attributes from the Geometry/Structures group.
|
271
|
+
Returns empty DataFrame if no structures are found.
|
261
272
|
|
262
273
|
Notes
|
263
274
|
-----
|
@@ -268,8 +279,19 @@ class HdfStruc:
|
|
268
279
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
269
280
|
if "Geometry/Structures" not in hdf_file:
|
270
281
|
logger.info(f"No structures found in the geometry file: {hdf_path}")
|
271
|
-
return
|
272
|
-
|
282
|
+
return pd.DataFrame()
|
283
|
+
|
284
|
+
# Get attributes and decode byte strings
|
285
|
+
attrs_dict = {}
|
286
|
+
for key, value in dict(hdf_file["Geometry/Structures"].attrs).items():
|
287
|
+
if isinstance(value, bytes):
|
288
|
+
attrs_dict[key] = value.decode('utf-8')
|
289
|
+
else:
|
290
|
+
attrs_dict[key] = value
|
291
|
+
|
292
|
+
# Create DataFrame with a single row index
|
293
|
+
return pd.DataFrame(attrs_dict, index=[0])
|
294
|
+
|
273
295
|
except Exception as e:
|
274
296
|
logger.error(f"Error reading geometry structures attributes: {str(e)}")
|
275
|
-
return
|
297
|
+
return pd.DataFrame()
|
ras_commander/RasMapper.py
CHANGED
@@ -21,85 +21,4 @@ from .HdfInfiltration import HdfInfiltration
|
|
21
21
|
|
22
22
|
class RasMapper:
|
23
23
|
"""Class for handling RAS Mapper operations and data extraction"""
|
24
|
-
|
25
|
-
@staticmethod
|
26
|
-
@log_call
|
27
|
-
def get_raster_map(hdf_path: Path) -> dict:
|
28
|
-
"""Read the raster map from HDF file and create value mapping
|
29
|
-
|
30
|
-
Args:
|
31
|
-
hdf_path: Path to the HDF file
|
32
|
-
|
33
|
-
Returns:
|
34
|
-
Dictionary mapping raster values to mukeys
|
35
|
-
"""
|
36
|
-
with h5py.File(hdf_path, 'r') as hdf:
|
37
|
-
raster_map_data = hdf['Raster Map'][:]
|
38
|
-
return {int(item[0]): item[1].decode('utf-8') for item in raster_map_data}
|
39
|
-
|
40
|
-
@staticmethod
|
41
|
-
@log_call
|
42
|
-
def clip_raster_with_boundary(raster_path: Path, boundary_path: Path):
|
43
|
-
"""Clip a raster using a boundary polygon
|
44
|
-
|
45
|
-
Args:
|
46
|
-
raster_path: Path to the raster file
|
47
|
-
boundary_path: Path to the boundary shapefile
|
48
|
-
|
49
|
-
Returns:
|
50
|
-
Tuple of (clipped_image, transform, nodata_value)
|
51
|
-
"""
|
52
|
-
watershed = gpd.read_file(boundary_path)
|
53
|
-
raster = rasterio.open(raster_path)
|
54
|
-
|
55
|
-
out_image, out_transform = mask(raster, watershed.geometry, crop=True)
|
56
|
-
nodata = raster.nodatavals[0]
|
57
|
-
|
58
|
-
return out_image[0], out_transform, nodata
|
59
|
-
|
60
|
-
@staticmethod
|
61
|
-
@log_call
|
62
|
-
def calculate_zonal_stats(boundary_path: Path, raster_data, transform, nodata):
|
63
|
-
"""Calculate zonal statistics for a boundary
|
64
|
-
|
65
|
-
Args:
|
66
|
-
boundary_path: Path to boundary shapefile
|
67
|
-
raster_data: Numpy array of raster values
|
68
|
-
transform: Raster transform
|
69
|
-
nodata: Nodata value
|
70
|
-
|
71
|
-
Returns:
|
72
|
-
List of statistics by zone
|
73
|
-
"""
|
74
|
-
watershed = gpd.read_file(boundary_path)
|
75
|
-
return zonal_stats(watershed, raster_data,
|
76
|
-
affine=transform,
|
77
|
-
nodata=nodata,
|
78
|
-
categorical=True)
|
79
|
-
|
80
|
-
# Example usage:
|
81
|
-
"""
|
82
|
-
# Initialize paths
|
83
|
-
raster_path = Path('input_files/gSSURGO_InfiltrationDC.tif')
|
84
|
-
boundary_path = Path('input_files/WF_Boundary_Simple.shp')
|
85
|
-
hdf_path = raster_path.with_suffix('.hdf')
|
86
|
-
|
87
|
-
# Get raster mapping
|
88
|
-
raster_map = RasMapper.get_raster_map(hdf_path)
|
89
|
-
|
90
|
-
# Clip raster with boundary
|
91
|
-
clipped_data, transform, nodata = RasMapper.clip_raster_with_boundary(
|
92
|
-
raster_path, boundary_path)
|
93
|
-
|
94
|
-
# Calculate zonal statistics
|
95
|
-
stats = RasMapper.calculate_zonal_stats(
|
96
|
-
boundary_path, clipped_data, transform, nodata)
|
97
|
-
|
98
|
-
# Calculate soil statistics
|
99
|
-
soil_stats = HdfInfiltration.calculate_soil_statistics(
|
100
|
-
stats, raster_map)
|
101
|
-
|
102
|
-
# Get significant mukeys (>1%)
|
103
|
-
significant_mukeys = HdfInfiltration.get_significant_mukeys(
|
104
|
-
soil_stats, threshold=1.0)
|
105
|
-
"""
|
24
|
+
# PLACEHOLDER FOR FUTURE DEVELOPMENT
|
ras_commander/RasPlan.py
CHANGED
@@ -50,6 +50,10 @@ List of Functions in RasPlan:
|
|
50
50
|
- update_plan_description(): Update the description in a plan file
|
51
51
|
- read_plan_description(): Read the description from a plan file
|
52
52
|
- update_simulation_date(): Update simulation start and end dates
|
53
|
+
- get_shortid(): Get the Short Identifier from a plan file
|
54
|
+
- set_shortid(): Set the Short Identifier in a plan file
|
55
|
+
- get_plan_title(): Get the Plan Title from a plan file
|
56
|
+
- set_plan_title(): Set the Plan Title in a plan file
|
53
57
|
|
54
58
|
|
55
59
|
"""
|
@@ -101,11 +105,10 @@ class RasPlan:
|
|
101
105
|
ras_obj = ras_object or ras
|
102
106
|
ras_obj.check_initialized()
|
103
107
|
|
104
|
-
# Ensure plan_number and new_geom are strings
|
105
108
|
plan_number = str(plan_number).zfill(2)
|
106
109
|
new_geom = str(new_geom).zfill(2)
|
107
110
|
|
108
|
-
#
|
111
|
+
# Update all dataframes
|
109
112
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
110
113
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
111
114
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
@@ -115,18 +118,15 @@ class RasPlan:
|
|
115
118
|
logger.error(f"Geometry {new_geom} not found in project.")
|
116
119
|
raise ValueError(f"Geometry {new_geom} not found in project.")
|
117
120
|
|
118
|
-
# Update
|
119
|
-
ras_obj.plan_df
|
121
|
+
# Update all geometry-related columns
|
122
|
+
mask = ras_obj.plan_df['plan_number'] == plan_number
|
123
|
+
ras_obj.plan_df.loc[mask, 'geometry_number'] = new_geom
|
124
|
+
ras_obj.plan_df.loc[mask, 'Geom File'] = new_geom
|
125
|
+
geom_path = ras_obj.project_folder / f"{ras_obj.project_name}.g{new_geom}"
|
126
|
+
ras_obj.plan_df.loc[mask, 'Geom Path'] = str(geom_path)
|
120
127
|
|
121
|
-
|
122
|
-
|
123
|
-
logger.debug(ras_obj.plan_df)
|
124
|
-
|
125
|
-
# Update the project file
|
126
|
-
prj_file_path = ras_obj.prj_file
|
127
|
-
RasUtils.update_file(prj_file_path, RasPlan._update_geom_in_file, plan_number, new_geom)
|
128
|
-
|
129
|
-
# Re-initialize the ras object to reflect changes
|
128
|
+
# Update project file and reinitialize
|
129
|
+
RasUtils.update_file(ras_obj.prj_file, RasPlan._update_geom_in_file, plan_number, new_geom)
|
130
130
|
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
131
131
|
|
132
132
|
return ras_obj.plan_df
|
@@ -173,21 +173,26 @@ class RasPlan:
|
|
173
173
|
ras_obj = ras_object or ras
|
174
174
|
ras_obj.check_initialized()
|
175
175
|
|
176
|
-
# Update the flow dataframe in the ras instance to ensure it is current
|
177
176
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
178
177
|
|
179
178
|
if new_steady_flow_number not in ras_obj.flow_df['flow_number'].values:
|
180
179
|
raise ValueError(f"Steady flow number {new_steady_flow_number} not found in project file.")
|
181
180
|
|
182
|
-
# Resolve the full path of the plan file
|
183
181
|
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
184
182
|
if not plan_file_path:
|
185
183
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
186
184
|
|
187
185
|
RasUtils.update_file(plan_file_path, RasPlan._update_steady_in_file, new_steady_flow_number)
|
188
186
|
|
189
|
-
# Update
|
187
|
+
# Update dataframes and additional columns
|
190
188
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
189
|
+
mask = ras_obj.plan_df['plan_number'] == plan_number
|
190
|
+
flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.f{new_steady_flow_number}"
|
191
|
+
ras_obj.plan_df.loc[mask, 'Flow File'] = new_steady_flow_number
|
192
|
+
ras_obj.plan_df.loc[mask, 'Flow Path'] = str(flow_path)
|
193
|
+
ras_obj.plan_df.loc[mask, 'unsteady_number'] = None
|
194
|
+
|
195
|
+
# Update remaining dataframes
|
191
196
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
192
197
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
193
198
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
@@ -223,32 +228,37 @@ class RasPlan:
|
|
223
228
|
ras_obj = ras_object or ras
|
224
229
|
ras_obj.check_initialized()
|
225
230
|
|
226
|
-
# Update the unsteady dataframe in the ras instance to ensure it is current
|
227
231
|
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
228
232
|
|
229
233
|
if new_unsteady_flow_number not in ras_obj.unsteady_df['unsteady_number'].values:
|
230
234
|
raise ValueError(f"Unsteady number {new_unsteady_flow_number} not found in project file.")
|
231
235
|
|
232
|
-
# Get the full path of the plan file
|
233
236
|
plan_file_path = RasPlan.get_plan_path(plan_number, ras_obj)
|
234
237
|
if not plan_file_path:
|
235
238
|
raise FileNotFoundError(f"Plan file not found: {plan_number}")
|
236
239
|
|
237
240
|
try:
|
238
|
-
RasUtils.update_file(plan_file_path,
|
241
|
+
RasUtils.update_file(plan_file_path, lambda lines: [
|
242
|
+
f"Flow File=u{new_unsteady_flow_number}\n" if line.startswith("Flow File=") else line
|
243
|
+
for line in lines
|
244
|
+
])
|
245
|
+
|
246
|
+
# Update dataframes and additional columns
|
247
|
+
ras_obj.plan_df = ras_obj.get_plan_entries()
|
248
|
+
mask = ras_obj.plan_df['plan_number'] == plan_number
|
249
|
+
flow_path = ras_obj.project_folder / f"{ras_obj.project_name}.u{new_unsteady_flow_number}"
|
250
|
+
ras_obj.plan_df.loc[mask, 'Flow File'] = new_unsteady_flow_number
|
251
|
+
ras_obj.plan_df.loc[mask, 'Flow Path'] = str(flow_path)
|
252
|
+
ras_obj.plan_df.loc[mask, 'unsteady_number'] = new_unsteady_flow_number
|
253
|
+
|
254
|
+
# Update remaining dataframes
|
255
|
+
ras_obj.geom_df = ras_obj.get_geom_entries()
|
256
|
+
ras_obj.flow_df = ras_obj.get_flow_entries()
|
257
|
+
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
258
|
+
|
239
259
|
except Exception as e:
|
240
260
|
raise Exception(f"Failed to update unsteady flow file: {e}")
|
241
261
|
|
242
|
-
# Update the ras object's dataframes
|
243
|
-
ras_obj.plan_df = ras_obj.get_plan_entries()
|
244
|
-
ras_obj.geom_df = ras_obj.get_geom_entries()
|
245
|
-
ras_obj.flow_df = ras_obj.get_flow_entries()
|
246
|
-
ras_obj.unsteady_df = ras_obj.get_unsteady_entries()
|
247
|
-
|
248
|
-
@staticmethod
|
249
|
-
def _update_unsteady_in_file(lines, new_unsteady_flow_number):
|
250
|
-
return [f"Unsteady File=u{new_unsteady_flow_number}\n" if line.startswith("Unsteady File=u") else line for line in lines]
|
251
|
-
|
252
262
|
@staticmethod
|
253
263
|
@log_call
|
254
264
|
def set_num_cores(plan_number, num_cores, ras_object=None):
|
@@ -601,36 +611,31 @@ class RasPlan:
|
|
601
611
|
ras_obj = ras_object or ras
|
602
612
|
ras_obj.check_initialized()
|
603
613
|
|
604
|
-
# Update plan entries without reinitializing the entire project
|
605
614
|
ras_obj.plan_df = ras_obj.get_prj_entries('Plan')
|
606
|
-
|
607
615
|
new_plan_num = RasPlan.get_next_number(ras_obj.plan_df['plan_number'])
|
616
|
+
|
608
617
|
template_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{template_plan}"
|
609
618
|
new_plan_path = ras_obj.project_folder / f"{ras_obj.project_name}.p{new_plan_num}"
|
610
619
|
|
611
|
-
|
612
|
-
|
613
|
-
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
620
|
-
new_shortid = new_plan_shortid[:24]
|
621
|
-
lines[i] = f"Short Identifier={new_shortid}\n"
|
622
|
-
break
|
623
|
-
return lines
|
624
|
-
|
625
|
-
# Use RasUtils to clone the file and update the short identifier
|
626
|
-
RasUtils.clone_file(template_plan_path, new_plan_path, update_shortid)
|
627
|
-
|
628
|
-
# Use RasUtils to update the project file
|
620
|
+
# Clone file and update project file
|
621
|
+
RasUtils.clone_file(template_plan_path, new_plan_path,
|
622
|
+
lambda lines: [
|
623
|
+
f"Short Identifier={new_plan_shortid or (match.group(1) + '_copy')[:24]}\n"
|
624
|
+
if (match := re.match(r'^Short Identifier=(.*)$', line.strip(), re.IGNORECASE))
|
625
|
+
else line
|
626
|
+
for line in lines
|
627
|
+
])
|
628
|
+
|
629
629
|
RasUtils.update_project_file(ras_obj.prj_file, 'Plan', new_plan_num, ras_object=ras_obj)
|
630
|
-
|
631
|
-
# Re-initialize the ras global object
|
632
630
|
ras_obj.initialize(ras_obj.project_folder, ras_obj.ras_exe_path)
|
631
|
+
|
632
|
+
# Copy additional columns from template to new plan
|
633
|
+
template_row = ras_obj.plan_df[ras_obj.plan_df['plan_number'] == template_plan].iloc[0]
|
634
|
+
for col in ['Geom File', 'Geom Path', 'Flow File', 'Flow Path']:
|
635
|
+
if col in template_row.index:
|
636
|
+
ras_obj.plan_df.loc[ras_obj.plan_df['plan_number'] == new_plan_num, col] = template_row[col]
|
633
637
|
|
638
|
+
# Update all dataframes
|
634
639
|
ras_obj.plan_df = ras_obj.get_plan_entries()
|
635
640
|
ras_obj.geom_df = ras_obj.get_geom_entries()
|
636
641
|
ras_obj.flow_df = ras_obj.get_flow_entries()
|
@@ -881,7 +886,8 @@ class RasPlan:
|
|
881
886
|
'Run HTab', 'Run Post Process', 'Run Sediment', 'Run UNET', 'Run WQNET',
|
882
887
|
'Short Identifier', 'Simulation Date', 'UNET D1 Cores', 'UNET D2 Cores', 'PS Cores',
|
883
888
|
'UNET Use Existing IB Tables', 'UNET 1D Methodology', 'UNET D2 Solver Type',
|
884
|
-
'UNET D2 Name', 'Run RASMapper', 'Run HTab', 'Run
|
889
|
+
'UNET D2 Name', 'Run RASMapper', 'Run HTab', 'Run UNet', 'Run PostProcess',
|
890
|
+
'Run WQNet', 'UNET P2 Cores', 'Run Post Process', 'Run WQNET'
|
885
891
|
}
|
886
892
|
|
887
893
|
if key not in supported_plan_keys:
|
@@ -1257,3 +1263,207 @@ class RasPlan:
|
|
1257
1263
|
ras_object.plan_df = ras_object.get_plan_entries()
|
1258
1264
|
ras_object.unsteady_df = ras_object.get_unsteady_entries()
|
1259
1265
|
|
1266
|
+
@staticmethod
|
1267
|
+
@log_call
|
1268
|
+
def get_shortid(plan_number_or_path: Union[str, Path], ras_object=None) -> str:
|
1269
|
+
"""
|
1270
|
+
Get the Short Identifier from a HEC-RAS plan file.
|
1271
|
+
|
1272
|
+
Args:
|
1273
|
+
plan_number_or_path (Union[str, Path]): The plan number or path to the plan file.
|
1274
|
+
ras_object (Optional[RasPrj]): The RAS project object. If None, uses the global 'ras' object.
|
1275
|
+
|
1276
|
+
Returns:
|
1277
|
+
str: The Short Identifier from the plan file.
|
1278
|
+
|
1279
|
+
Raises:
|
1280
|
+
ValueError: If the plan file is not found.
|
1281
|
+
IOError: If there's an error reading from the plan file.
|
1282
|
+
|
1283
|
+
Example:
|
1284
|
+
>>> shortid = RasPlan.get_shortid('01')
|
1285
|
+
>>> print(f"Plan's Short Identifier: {shortid}")
|
1286
|
+
"""
|
1287
|
+
logger = get_logger(__name__)
|
1288
|
+
ras_obj = ras_object or ras
|
1289
|
+
ras_obj.check_initialized()
|
1290
|
+
|
1291
|
+
# Get the Short Identifier using get_plan_value
|
1292
|
+
shortid = RasPlan.get_plan_value(plan_number_or_path, "Short Identifier", ras_obj)
|
1293
|
+
|
1294
|
+
if shortid is None:
|
1295
|
+
logger.warning(f"Short Identifier not found in plan: {plan_number_or_path}")
|
1296
|
+
return ""
|
1297
|
+
|
1298
|
+
logger.info(f"Retrieved Short Identifier: {shortid}")
|
1299
|
+
return shortid
|
1300
|
+
|
1301
|
+
@staticmethod
|
1302
|
+
@log_call
|
1303
|
+
def set_shortid(plan_number_or_path: Union[str, Path], new_shortid: str, ras_object=None) -> None:
|
1304
|
+
"""
|
1305
|
+
Set the Short Identifier in a HEC-RAS plan file.
|
1306
|
+
|
1307
|
+
Args:
|
1308
|
+
plan_number_or_path (Union[str, Path]): The plan number or path to the plan file.
|
1309
|
+
new_shortid (str): The new Short Identifier to set (max 24 characters).
|
1310
|
+
ras_object (Optional[RasPrj]): The RAS project object. If None, uses the global 'ras' object.
|
1311
|
+
|
1312
|
+
Raises:
|
1313
|
+
ValueError: If the plan file is not found or if new_shortid is too long.
|
1314
|
+
IOError: If there's an error updating the plan file.
|
1315
|
+
|
1316
|
+
Example:
|
1317
|
+
>>> RasPlan.set_shortid('01', 'NewShortIdentifier')
|
1318
|
+
"""
|
1319
|
+
logger = get_logger(__name__)
|
1320
|
+
ras_obj = ras_object or ras
|
1321
|
+
ras_obj.check_initialized()
|
1322
|
+
|
1323
|
+
# Ensure new_shortid is not too long (HEC-RAS limits short identifiers to 24 characters)
|
1324
|
+
if len(new_shortid) > 24:
|
1325
|
+
logger.warning(f"Short Identifier too long (24 char max). Truncating: {new_shortid}")
|
1326
|
+
new_shortid = new_shortid[:24]
|
1327
|
+
|
1328
|
+
# Get the plan file path
|
1329
|
+
plan_file_path = Path(plan_number_or_path)
|
1330
|
+
if not plan_file_path.is_file():
|
1331
|
+
plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_obj)
|
1332
|
+
if not plan_file_path.exists():
|
1333
|
+
logger.error(f"Plan file not found: {plan_file_path}")
|
1334
|
+
raise ValueError(f"Plan file not found: {plan_file_path}")
|
1335
|
+
|
1336
|
+
try:
|
1337
|
+
# Read the file
|
1338
|
+
with open(plan_file_path, 'r') as file:
|
1339
|
+
lines = file.readlines()
|
1340
|
+
|
1341
|
+
# Update the Short Identifier line
|
1342
|
+
updated = False
|
1343
|
+
for i, line in enumerate(lines):
|
1344
|
+
if line.startswith("Short Identifier="):
|
1345
|
+
lines[i] = f"Short Identifier={new_shortid}\n"
|
1346
|
+
updated = True
|
1347
|
+
break
|
1348
|
+
|
1349
|
+
# If Short Identifier line not found, add it after Plan Title
|
1350
|
+
if not updated:
|
1351
|
+
for i, line in enumerate(lines):
|
1352
|
+
if line.startswith("Plan Title="):
|
1353
|
+
lines.insert(i+1, f"Short Identifier={new_shortid}\n")
|
1354
|
+
updated = True
|
1355
|
+
break
|
1356
|
+
|
1357
|
+
# If Plan Title not found either, add at the beginning
|
1358
|
+
if not updated:
|
1359
|
+
lines.insert(0, f"Short Identifier={new_shortid}\n")
|
1360
|
+
|
1361
|
+
# Write the updated content back to the file
|
1362
|
+
with open(plan_file_path, 'w') as file:
|
1363
|
+
file.writelines(lines)
|
1364
|
+
|
1365
|
+
logger.info(f"Updated Short Identifier in plan file to: {new_shortid}")
|
1366
|
+
|
1367
|
+
except IOError as e:
|
1368
|
+
logger.error(f"Error updating Short Identifier in plan file {plan_file_path}: {e}")
|
1369
|
+
raise ValueError(f"Error updating Short Identifier: {e}")
|
1370
|
+
|
1371
|
+
# Refresh RasPrj dataframes if ras_object provided
|
1372
|
+
if ras_object:
|
1373
|
+
ras_object.plan_df = ras_object.get_plan_entries()
|
1374
|
+
|
1375
|
+
@staticmethod
|
1376
|
+
@log_call
|
1377
|
+
def get_plan_title(plan_number_or_path: Union[str, Path], ras_object=None) -> str:
|
1378
|
+
"""
|
1379
|
+
Get the Plan Title from a HEC-RAS plan file.
|
1380
|
+
|
1381
|
+
Args:
|
1382
|
+
plan_number_or_path (Union[str, Path]): The plan number or path to the plan file.
|
1383
|
+
ras_object (Optional[RasPrj]): The RAS project object. If None, uses the global 'ras' object.
|
1384
|
+
|
1385
|
+
Returns:
|
1386
|
+
str: The Plan Title from the plan file.
|
1387
|
+
|
1388
|
+
Raises:
|
1389
|
+
ValueError: If the plan file is not found.
|
1390
|
+
IOError: If there's an error reading from the plan file.
|
1391
|
+
|
1392
|
+
Example:
|
1393
|
+
>>> title = RasPlan.get_plan_title('01')
|
1394
|
+
>>> print(f"Plan Title: {title}")
|
1395
|
+
"""
|
1396
|
+
logger = get_logger(__name__)
|
1397
|
+
ras_obj = ras_object or ras
|
1398
|
+
ras_obj.check_initialized()
|
1399
|
+
|
1400
|
+
# Get the Plan Title using get_plan_value
|
1401
|
+
title = RasPlan.get_plan_value(plan_number_or_path, "Plan Title", ras_obj)
|
1402
|
+
|
1403
|
+
if title is None:
|
1404
|
+
logger.warning(f"Plan Title not found in plan: {plan_number_or_path}")
|
1405
|
+
return ""
|
1406
|
+
|
1407
|
+
logger.info(f"Retrieved Plan Title: {title}")
|
1408
|
+
return title
|
1409
|
+
|
1410
|
+
@staticmethod
|
1411
|
+
@log_call
|
1412
|
+
def set_plan_title(plan_number_or_path: Union[str, Path], new_title: str, ras_object=None) -> None:
|
1413
|
+
"""
|
1414
|
+
Set the Plan Title in a HEC-RAS plan file.
|
1415
|
+
|
1416
|
+
Args:
|
1417
|
+
plan_number_or_path (Union[str, Path]): The plan number or path to the plan file.
|
1418
|
+
new_title (str): The new Plan Title to set.
|
1419
|
+
ras_object (Optional[RasPrj]): The RAS project object. If None, uses the global 'ras' object.
|
1420
|
+
|
1421
|
+
Raises:
|
1422
|
+
ValueError: If the plan file is not found.
|
1423
|
+
IOError: If there's an error updating the plan file.
|
1424
|
+
|
1425
|
+
Example:
|
1426
|
+
>>> RasPlan.set_plan_title('01', 'Updated Plan Scenario')
|
1427
|
+
"""
|
1428
|
+
logger = get_logger(__name__)
|
1429
|
+
ras_obj = ras_object or ras
|
1430
|
+
ras_obj.check_initialized()
|
1431
|
+
|
1432
|
+
# Get the plan file path
|
1433
|
+
plan_file_path = Path(plan_number_or_path)
|
1434
|
+
if not plan_file_path.is_file():
|
1435
|
+
plan_file_path = RasUtils.get_plan_path(plan_number_or_path, ras_obj)
|
1436
|
+
if not plan_file_path.exists():
|
1437
|
+
logger.error(f"Plan file not found: {plan_file_path}")
|
1438
|
+
raise ValueError(f"Plan file not found: {plan_file_path}")
|
1439
|
+
|
1440
|
+
try:
|
1441
|
+
# Read the file
|
1442
|
+
with open(plan_file_path, 'r') as file:
|
1443
|
+
lines = file.readlines()
|
1444
|
+
|
1445
|
+
# Update the Plan Title line
|
1446
|
+
updated = False
|
1447
|
+
for i, line in enumerate(lines):
|
1448
|
+
if line.startswith("Plan Title="):
|
1449
|
+
lines[i] = f"Plan Title={new_title}\n"
|
1450
|
+
updated = True
|
1451
|
+
break
|
1452
|
+
|
1453
|
+
# If Plan Title line not found, add it at the beginning
|
1454
|
+
if not updated:
|
1455
|
+
lines.insert(0, f"Plan Title={new_title}\n")
|
1456
|
+
|
1457
|
+
# Write the updated content back to the file
|
1458
|
+
with open(plan_file_path, 'w') as file:
|
1459
|
+
file.writelines(lines)
|
1460
|
+
|
1461
|
+
logger.info(f"Updated Plan Title in plan file to: {new_title}")
|
1462
|
+
|
1463
|
+
except IOError as e:
|
1464
|
+
logger.error(f"Error updating Plan Title in plan file {plan_file_path}: {e}")
|
1465
|
+
raise ValueError(f"Error updating Plan Title: {e}")
|
1466
|
+
|
1467
|
+
# Refresh RasPrj dataframes if ras_object provided
|
1468
|
+
if ras_object:
|
1469
|
+
ras_object.plan_df = ras_object.get_plan_entries()
|