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/HdfBndry.py
CHANGED
@@ -122,6 +122,12 @@ class HdfBndry:
|
|
122
122
|
-------
|
123
123
|
gpd.GeoDataFrame
|
124
124
|
A GeoDataFrame containing the breaklines.
|
125
|
+
|
126
|
+
Notes
|
127
|
+
-----
|
128
|
+
- Zero-length breaklines are logged and skipped.
|
129
|
+
- Single-point breaklines are logged and skipped.
|
130
|
+
- These invalid breaklines should be removed in RASMapper to prevent potential issues.
|
125
131
|
"""
|
126
132
|
try:
|
127
133
|
with h5py.File(hdf_path, 'r') as hdf_file:
|
@@ -129,17 +135,95 @@ class HdfBndry:
|
|
129
135
|
if breaklines_path not in hdf_file:
|
130
136
|
logger.warning(f"Breaklines path '{breaklines_path}' not found in HDF file.")
|
131
137
|
return gpd.GeoDataFrame()
|
138
|
+
|
132
139
|
bl_line_data = hdf_file[breaklines_path]
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
140
|
+
attributes = bl_line_data["Attributes"][()]
|
141
|
+
|
142
|
+
# Initialize lists to store valid breakline data
|
143
|
+
valid_ids = []
|
144
|
+
valid_names = []
|
145
|
+
valid_geoms = []
|
146
|
+
|
147
|
+
# Track invalid breaklines for summary
|
148
|
+
zero_length_count = 0
|
149
|
+
single_point_count = 0
|
150
|
+
other_error_count = 0
|
151
|
+
|
152
|
+
# Process each breakline
|
153
|
+
for idx, (pnt_start, pnt_cnt, part_start, part_cnt) in enumerate(bl_line_data["Polyline Info"][()]):
|
154
|
+
name = HdfUtils.convert_ras_string(attributes["Name"][idx])
|
155
|
+
|
156
|
+
# Check for zero-length breaklines
|
157
|
+
if pnt_cnt == 0:
|
158
|
+
zero_length_count += 1
|
159
|
+
logger.debug(f"Zero-length breakline found (FID: {idx}, Name: {name})")
|
160
|
+
continue
|
161
|
+
|
162
|
+
# Check for single-point breaklines
|
163
|
+
if pnt_cnt == 1:
|
164
|
+
single_point_count += 1
|
165
|
+
logger.debug(f"Single-point breakline found (FID: {idx}, Name: {name})")
|
166
|
+
continue
|
167
|
+
|
168
|
+
try:
|
169
|
+
points = bl_line_data["Polyline Points"][()][pnt_start:pnt_start + pnt_cnt]
|
170
|
+
|
171
|
+
# Additional validation of points array
|
172
|
+
if len(points) < 2:
|
173
|
+
single_point_count += 1
|
174
|
+
logger.debug(f"Invalid point count in breakline (FID: {idx}, Name: {name})")
|
175
|
+
continue
|
176
|
+
|
177
|
+
if part_cnt == 1:
|
178
|
+
geom = LineString(points)
|
179
|
+
else:
|
180
|
+
parts = bl_line_data["Polyline Parts"][()][part_start:part_start + part_cnt]
|
181
|
+
geom = MultiLineString([
|
182
|
+
points[part_pnt_start:part_pnt_start + part_pnt_cnt]
|
183
|
+
for part_pnt_start, part_pnt_cnt in parts
|
184
|
+
if part_pnt_cnt > 1 # Skip single-point parts
|
185
|
+
])
|
186
|
+
# Skip if no valid parts remain
|
187
|
+
if len(geom.geoms) == 0:
|
188
|
+
other_error_count += 1
|
189
|
+
logger.debug(f"No valid parts in multipart breakline (FID: {idx}, Name: {name})")
|
190
|
+
continue
|
191
|
+
|
192
|
+
valid_ids.append(idx)
|
193
|
+
valid_names.append(name)
|
194
|
+
valid_geoms.append(geom)
|
195
|
+
|
196
|
+
except Exception as e:
|
197
|
+
other_error_count += 1
|
198
|
+
logger.debug(f"Error processing breakline {idx}: {str(e)}")
|
199
|
+
continue
|
200
|
+
|
201
|
+
# Log summary of invalid breaklines
|
202
|
+
total_invalid = zero_length_count + single_point_count + other_error_count
|
203
|
+
if total_invalid > 0:
|
204
|
+
logger.info(
|
205
|
+
f"Breakline processing summary:\n"
|
206
|
+
f"- Zero-length breaklines: {zero_length_count}\n"
|
207
|
+
f"- Single-point breaklines: {single_point_count}\n"
|
208
|
+
f"- Other invalid breaklines: {other_error_count}\n"
|
209
|
+
f"Consider removing these invalid breaklines using RASMapper."
|
210
|
+
)
|
211
|
+
|
212
|
+
# Create GeoDataFrame with valid breaklines
|
213
|
+
if not valid_ids:
|
214
|
+
logger.warning("No valid breaklines found in the HDF file.")
|
215
|
+
return gpd.GeoDataFrame()
|
216
|
+
|
138
217
|
return gpd.GeoDataFrame(
|
139
|
-
{
|
218
|
+
{
|
219
|
+
"bl_id": valid_ids,
|
220
|
+
"Name": valid_names,
|
221
|
+
"geometry": valid_geoms
|
222
|
+
},
|
140
223
|
geometry="geometry",
|
141
|
-
crs=HdfBase.get_projection(hdf_file)
|
224
|
+
crs=HdfBase.get_projection(hdf_file)
|
142
225
|
)
|
226
|
+
|
143
227
|
except Exception as e:
|
144
228
|
logger.error(f"Error reading breaklines: {str(e)}")
|
145
229
|
return gpd.GeoDataFrame()
|
ras_commander/HdfInfiltration.py
CHANGED
@@ -47,7 +47,7 @@ from pathlib import Path
|
|
47
47
|
import pandas as pd
|
48
48
|
import geopandas as gpd
|
49
49
|
import h5py
|
50
|
-
|
50
|
+
|
51
51
|
from .Decorators import log_call, standardize_input
|
52
52
|
|
53
53
|
class HdfInfiltration:
|
@@ -232,6 +232,12 @@ class HdfInfiltration:
|
|
232
232
|
Returns:
|
233
233
|
DataFrame with soil statistics including percentages and areas
|
234
234
|
"""
|
235
|
+
|
236
|
+
try:
|
237
|
+
from rasterstats import zonal_stats
|
238
|
+
except ImportError as e:
|
239
|
+
logger.error("Failed to import rasterstats. Please run 'pip install rasterstats' and try again.")
|
240
|
+
raise e
|
235
241
|
# Initialize areas dictionary
|
236
242
|
mukey_areas = {mukey: 0 for mukey in raster_map.values()}
|
237
243
|
|
ras_commander/HdfPlan.py
CHANGED
@@ -50,6 +50,8 @@ import pandas as pd
|
|
50
50
|
from datetime import datetime
|
51
51
|
from pathlib import Path
|
52
52
|
from typing import Dict, List, Optional
|
53
|
+
import re
|
54
|
+
import numpy as np
|
53
55
|
|
54
56
|
from .HdfBase import HdfBase
|
55
57
|
from .HdfUtils import HdfUtils
|
@@ -178,7 +180,7 @@ class HdfPlan:
|
|
178
180
|
@staticmethod
|
179
181
|
@log_call
|
180
182
|
@standardize_input(file_type='plan_hdf')
|
181
|
-
def get_plan_parameters(hdf_path: Path) ->
|
183
|
+
def get_plan_parameters(hdf_path: Path) -> pd.DataFrame:
|
182
184
|
"""
|
183
185
|
Get plan parameter attributes from a HEC-RAS HDF plan file.
|
184
186
|
|
@@ -186,7 +188,10 @@ class HdfPlan:
|
|
186
188
|
hdf_path (Path): Path to the HEC-RAS plan HDF file.
|
187
189
|
|
188
190
|
Returns:
|
189
|
-
|
191
|
+
pd.DataFrame: A DataFrame containing the plan parameters with columns:
|
192
|
+
- Parameter: Name of the parameter
|
193
|
+
- Value: Value of the parameter (decoded if byte string)
|
194
|
+
- Plan: Plan number (01-99) extracted from the filename (ProjectName.pXX.hdf)
|
190
195
|
|
191
196
|
Raises:
|
192
197
|
ValueError: If there's an error retrieving the plan parameter attributes.
|
@@ -197,14 +202,48 @@ class HdfPlan:
|
|
197
202
|
if plan_params_path not in hdf_file:
|
198
203
|
raise ValueError(f"Plan Parameters not found in {hdf_path}")
|
199
204
|
|
200
|
-
|
205
|
+
# Extract parameters
|
206
|
+
params_dict = {}
|
201
207
|
for key in hdf_file[plan_params_path].attrs.keys():
|
202
208
|
value = hdf_file[plan_params_path].attrs[key]
|
209
|
+
|
210
|
+
# Handle different types of values
|
203
211
|
if isinstance(value, bytes):
|
204
212
|
value = HdfUtils.convert_ras_string(value)
|
205
|
-
|
213
|
+
elif isinstance(value, np.ndarray):
|
214
|
+
# Handle array values
|
215
|
+
if value.dtype.kind in {'S', 'a'}: # Array of byte strings
|
216
|
+
value = [v.decode('utf-8') if isinstance(v, bytes) else v for v in value]
|
217
|
+
else:
|
218
|
+
value = value.tolist() # Convert numpy array to list
|
219
|
+
|
220
|
+
# If it's a single-item list, extract the value
|
221
|
+
if len(value) == 1:
|
222
|
+
value = value[0]
|
223
|
+
|
224
|
+
params_dict[key] = value
|
206
225
|
|
207
|
-
|
226
|
+
# Create DataFrame from parameters
|
227
|
+
df = pd.DataFrame.from_dict(params_dict, orient='index', columns=['Value'])
|
228
|
+
df.index.name = 'Parameter'
|
229
|
+
df = df.reset_index()
|
230
|
+
|
231
|
+
# Extract plan number from filename
|
232
|
+
filename = Path(hdf_path).name
|
233
|
+
plan_match = re.search(r'\.p(\d{2})\.', filename)
|
234
|
+
if plan_match:
|
235
|
+
plan_num = plan_match.group(1)
|
236
|
+
else:
|
237
|
+
plan_num = "00" # Default if no match found
|
238
|
+
logger.warning(f"Could not extract plan number from filename: {filename}")
|
239
|
+
|
240
|
+
df['Plan'] = plan_num
|
241
|
+
|
242
|
+
# Reorder columns to put Plan first
|
243
|
+
df = df[['Plan', 'Parameter', 'Value']]
|
244
|
+
|
245
|
+
return df
|
246
|
+
|
208
247
|
except Exception as e:
|
209
248
|
raise ValueError(f"Failed to get plan parameter attributes: {str(e)}")
|
210
249
|
|
ras_commander/HdfResultsMesh.py
CHANGED
@@ -609,12 +609,8 @@ class HdfResultsMesh:
|
|
609
609
|
Returns
|
610
610
|
-------
|
611
611
|
gpd.GeoDataFrame
|
612
|
-
A GeoDataFrame containing the summary output data with attributes as metadata.
|
613
|
-
|
614
|
-
Raises
|
615
|
-
------
|
616
|
-
ValueError
|
617
|
-
If the HDF file cannot be opened or read, or if the requested data is not found.
|
612
|
+
A GeoDataFrame containing the summary output data with decoded attributes as metadata.
|
613
|
+
Returns empty GeoDataFrame if variable is not found.
|
618
614
|
"""
|
619
615
|
try:
|
620
616
|
dfs = []
|
@@ -623,13 +619,19 @@ class HdfResultsMesh:
|
|
623
619
|
logger.info(f"Processing summary output for variable: {var}")
|
624
620
|
d2_flow_areas = hdf_file.get("Geometry/2D Flow Areas/Attributes")
|
625
621
|
if d2_flow_areas is None:
|
622
|
+
logger.info("No 2D Flow Areas found in HDF file")
|
626
623
|
return gpd.GeoDataFrame()
|
627
624
|
|
628
625
|
for d2_flow_area in d2_flow_areas[:]:
|
629
626
|
mesh_name = HdfUtils.convert_ras_string(d2_flow_area[0])
|
630
627
|
cell_count = d2_flow_area[-1]
|
631
628
|
logger.debug(f"Processing mesh: {mesh_name} with {cell_count} cells")
|
632
|
-
|
629
|
+
|
630
|
+
try:
|
631
|
+
group = HdfResultsMesh.get_mesh_summary_output_group(hdf_file, mesh_name, var)
|
632
|
+
except ValueError:
|
633
|
+
logger.info(f"Variable '{var}' not present in output file for mesh '{mesh_name}', skipping")
|
634
|
+
continue
|
633
635
|
|
634
636
|
data = group[:]
|
635
637
|
logger.debug(f"Data shape for {var} in {mesh_name}: {data.shape}")
|
@@ -639,7 +641,7 @@ class HdfResultsMesh:
|
|
639
641
|
if data.ndim == 2 and data.shape[0] == 2:
|
640
642
|
# Handle 2D datasets (e.g. Maximum Water Surface)
|
641
643
|
row_variables = group.attrs.get('Row Variables', [b'Value', b'Time'])
|
642
|
-
row_variables = [v.decode('utf-8').strip() for v in row_variables]
|
644
|
+
row_variables = [v.decode('utf-8').strip() if isinstance(v, bytes) else v for v in row_variables]
|
643
645
|
|
644
646
|
df = pd.DataFrame({
|
645
647
|
"mesh_name": [mesh_name] * data.shape[1],
|
@@ -676,14 +678,22 @@ class HdfResultsMesh:
|
|
676
678
|
on=['mesh_name', 'cell_id'],
|
677
679
|
how='left')
|
678
680
|
|
679
|
-
# Add group attributes as metadata
|
681
|
+
# Add group attributes as metadata with proper decoding
|
680
682
|
df.attrs['mesh_name'] = mesh_name
|
681
683
|
for attr_name, attr_value in group.attrs.items():
|
682
684
|
if isinstance(attr_value, bytes):
|
683
|
-
|
685
|
+
# Decode single byte string
|
686
|
+
decoded_value = attr_value.decode('utf-8')
|
684
687
|
elif isinstance(attr_value, np.ndarray):
|
685
|
-
attr_value
|
686
|
-
|
688
|
+
if attr_value.dtype.kind in {'S', 'a'}: # Array of byte strings
|
689
|
+
# Decode array of byte strings
|
690
|
+
decoded_value = [v.decode('utf-8') if isinstance(v, bytes) else v for v in attr_value]
|
691
|
+
else:
|
692
|
+
# Convert other numpy arrays to list
|
693
|
+
decoded_value = attr_value.tolist()
|
694
|
+
else:
|
695
|
+
decoded_value = attr_value
|
696
|
+
df.attrs[attr_name] = decoded_value
|
687
697
|
|
688
698
|
dfs.append(df)
|
689
699
|
|
@@ -700,7 +710,7 @@ class HdfResultsMesh:
|
|
700
710
|
if crs:
|
701
711
|
gdf.set_crs(crs, inplace=True)
|
702
712
|
|
703
|
-
# Combine attributes from all meshes
|
713
|
+
# Combine attributes from all meshes with decoded values
|
704
714
|
combined_attrs = {}
|
705
715
|
for df in dfs:
|
706
716
|
for key, value in df.attrs.items():
|
@@ -737,6 +747,6 @@ class HdfResultsMesh:
|
|
737
747
|
output_path = f"Results/Unsteady/Output/Output Blocks/Base Output/Summary Output/2D Flow Areas/{mesh_name}/{var}"
|
738
748
|
output_item = hdf_file.get(output_path)
|
739
749
|
if output_item is None:
|
740
|
-
raise ValueError(f"
|
750
|
+
raise ValueError(f"Dataset not found at path '{output_path}'")
|
741
751
|
return output_item
|
742
752
|
|