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.
@@ -1,307 +0,0 @@
1
- """
2
- Operations for modifying and updating HEC-RAS plan files.
3
- """
4
- import re
5
- from pathlib import Path
6
-
7
-
8
- from .project_config import ProjectConfig
9
-
10
- class PlanOperations:
11
- """
12
- A class for operations on HEC-RAS plan files.
13
- """
14
- @staticmethod
15
- def apply_geometry_to_plan(plan_file, geometry_number):
16
- """
17
- Apply a geometry file to a plan file.
18
-
19
- Parameters:
20
- plan_file (str): Full path to the HEC-RAS plan file (.pXX)
21
- geometry_number (str): Geometry number to apply (e.g., '01')
22
-
23
- Returns:
24
- None
25
-
26
- Raises:
27
- ValueError: If the specified geometry number is not found in the project file
28
- """
29
- config = ProjectConfig()
30
- config.check_initialized()
31
-
32
- if f"g{geometry_number}" not in config.ras_geom_entries['geom_number'].values:
33
- raise ValueError(f"Geometry number g{geometry_number} not found in project file.")
34
- with open(plan_file, 'r') as f:
35
- lines = f.readlines()
36
- with open(plan_file, 'w') as f:
37
- for line in lines:
38
- if line.startswith("Geom File="):
39
- f.write(f"Geom File=g{geometry_number}\n")
40
- print(f"Updated Geom File in {plan_file} to g{geometry_number}")
41
- else:
42
- f.write(line)
43
-
44
- @staticmethod
45
- def apply_flow_to_plan(plan_file, flow_number):
46
- """
47
- Apply a steady flow file to a plan file.
48
-
49
- Parameters:
50
- plan_file (str): Full path to the HEC-RAS plan file (.pXX)
51
- flow_number (str): Flow number to apply (e.g., '02')
52
-
53
- Returns:
54
- None
55
-
56
- Raises:
57
- ValueError: If the specified flow number is not found in the project file
58
- """
59
- config = ProjectConfig()
60
- config.check_initialized()
61
-
62
- if f"{flow_number}" not in config.ras_flow_entries['flow_number'].values:
63
- raise ValueError(f"Flow number f{flow_number} not found in project file.")
64
- with open(plan_file, 'r') as f:
65
- lines = f.readlines()
66
- with open(plan_file, 'w') as f:
67
- for line in lines:
68
- if line.startswith("Flow File="):
69
- f.write(f"Flow File=f{flow_number}\n")
70
- print(f"Updated Flow File in {plan_file} to f{flow_number}")
71
- else:
72
- f.write(line)
73
-
74
- @staticmethod
75
- def copy_plan_from_template(template_plan):
76
- """
77
- Create a new plan file based on a template and update the project file.
78
-
79
- Parameters:
80
-
81
- template_plan (str): Plan file to use as template (e.g., '01')
82
- project_folder, project_name,
83
- Returns:
84
- str: New plan number
85
- """
86
- config = ProjectConfig()
87
- config.check_initialized()
88
-
89
- # Read existing plan numbers
90
- ras_plan_entries = config.ras_plan_entries
91
-
92
- # Get next available plan number
93
- new_plan_num = PlanOperations.get_next_available_number(ras_plan_entries['plan_number'])
94
-
95
- # Copy template plan file to new plan file
96
- template_plan_path = Path(config.project_folder) / f"{config.project_name}.p{template_plan}"
97
- new_plan_path = Path(config.project_folder) / f"{config.project_name}.p{new_plan_num}"
98
- new_plan_path.write_bytes(template_plan_path.read_bytes())
99
- print(f"Copied {template_plan_path} to {new_plan_path}")
100
-
101
- # Update project file with new plan
102
- project_file = config.project_file
103
- with open(project_file, 'a') as f:
104
- f.write(f"\nPlan File=p{new_plan_num}")
105
- print(f"Updated {project_file} with new plan p{new_plan_num}")
106
- config = ProjectConfig()
107
- return f"{new_plan_num}"
108
-
109
- @staticmethod
110
- def get_next_available_number(existing_numbers):
111
- """
112
- Determine the next available number from a list of existing numbers.
113
-
114
- Parameters:
115
- existing_numbers (list): List of existing numbers as strings
116
-
117
- Returns:
118
- str: Next available number as a zero-padded string
119
- """
120
- existing_numbers = sorted(int(num) for num in existing_numbers)
121
- next_number = 1
122
- for num in existing_numbers:
123
- if num == next_number:
124
- next_number += 1
125
- else:
126
- break
127
- return f"{next_number:02d}"
128
-
129
-
130
-
131
- @staticmethod
132
- def apply_unsteady_to_plan(plan_file, unsteady_number):
133
- """
134
- Apply an unsteady flow file to a plan file.
135
-
136
- Parameters:
137
- plan_file (str): Full path to the HEC-RAS plan file (.pXX)
138
- unsteady_number (str): Unsteady flow number to apply (e.g., '01')
139
-
140
- Returns:
141
- None
142
-
143
- Raises:
144
- ValueError: If the specified unsteady number is not found in the project file
145
- """
146
- config = ProjectConfig()
147
- config.check_initialized()
148
-
149
- if f"u{unsteady_number}" not in config.ras_unsteady_entries['unsteady_number'].values:
150
- raise ValueError(f"Unsteady number u{unsteady_number} not found in project file.")
151
- with open(plan_file, 'r') as f:
152
- lines = f.readlines()
153
- with open(plan_file, 'w') as f:
154
- for line in lines:
155
- if line.startswith("Unsteady File=") or line.startswith("Flow File=u"):
156
- f.write(f"Unsteady File=u{unsteady_number}\n")
157
- print(f"Updated Unsteady File in {plan_file} to u{unsteady_number}")
158
- else:
159
- f.write(line)
160
-
161
- @staticmethod
162
- def set_num_cores(plan_file, num_cores):
163
- """
164
- Update the maximum number of cores to use in the HEC-RAS plan file.
165
-
166
- Parameters:
167
- plan_file (str): Full path to the plan file
168
- num_cores (int): Maximum number of cores to use
169
-
170
- Returns:
171
- None
172
- """
173
- config = ProjectConfig()
174
- config.check_initialized()
175
-
176
- cores_pattern = re.compile(r"(UNET D1 Cores= )\d+")
177
- with open(plan_file, 'r') as file:
178
- content = file.read()
179
- new_content = cores_pattern.sub(rf"\g<1>{num_cores}", content)
180
- with open(plan_file, 'w') as file:
181
- file.write(new_content)
182
- print(f"Updated {plan_file} with {num_cores} cores.")
183
-
184
- @staticmethod
185
- def update_geompre_flags(file_path, run_htab_value, use_ib_tables_value):
186
- """
187
- Update the simulation plan file to modify the `Run HTab` and `UNET Use Existing IB Tables` settings.
188
-
189
- Parameters:
190
- file_path (str): Path to the simulation plan file (.p06 or similar) that you want to modify.
191
- run_htab_value (int): Value for the `Run HTab` setting:
192
- - `0` : Do not run the geometry preprocessor, use existing geometry tables.
193
- - `-1` : Run the geometry preprocessor, forcing a recomputation of the geometry tables.
194
- use_ib_tables_value (int): Value for the `UNET Use Existing IB Tables` setting:
195
- - `0` : Use existing interpolation/boundary (IB) tables without recomputing them.
196
- - `-1` : Do not use existing IB tables, force a recomputation.
197
-
198
- Returns:
199
- None
200
-
201
- Raises:
202
- ValueError: If `run_htab_value` or `use_ib_tables_value` are not integers or not within the accepted values (`0` or `-1`).
203
- FileNotFoundError: If the specified file does not exist.
204
- IOError: If there is an error reading or writing the file.
205
- """
206
- config = ProjectConfig()
207
- config.check_initialized()
208
-
209
- if run_htab_value not in [-1, 0]:
210
- raise ValueError("Invalid value for `Run HTab`. Expected `0` or `-1`.")
211
- if use_ib_tables_value not in [-1, 0]:
212
- raise ValueError("Invalid value for `UNET Use Existing IB Tables`. Expected `0` or `-1`.")
213
- try:
214
- print(f"Reading the file: {file_path}")
215
- with open(file_path, 'r') as file:
216
- lines = file.readlines()
217
- print("Updating the file with new settings...")
218
- updated_lines = []
219
- for line in lines:
220
- if line.strip().startswith("Run HTab="):
221
- updated_line = f"Run HTab= {run_htab_value} \n"
222
- updated_lines.append(updated_line)
223
- print(f"Updated 'Run HTab' to {run_htab_value}")
224
- elif line.strip().startswith("UNET Use Existing IB Tables="):
225
- updated_line = f"UNET Use Existing IB Tables= {use_ib_tables_value} \n"
226
- updated_lines.append(updated_line)
227
- print(f"Updated 'UNET Use Existing IB Tables' to {use_ib_tables_value}")
228
- else:
229
- updated_lines.append(line)
230
- print(f"Writing the updated settings back to the file: {file_path}")
231
- with open(file_path, 'w') as file:
232
- file.writelines(updated_lines)
233
- print("File update completed successfully.")
234
- except FileNotFoundError:
235
- raise FileNotFoundError(f"The file '{file_path}' does not exist.")
236
- except IOError as e:
237
- raise IOError(f"An error occurred while reading or writing the file: {e}")
238
-
239
- @staticmethod
240
- def get_plan_full_path(plan_number: str) -> str:
241
- """Return the full path for a given plan number."""
242
- config = ProjectConfig()
243
- config.check_initialized()
244
-
245
- plan_path = config.ras_plan_entries[config.ras_plan_entries['plan_number'] == plan_number]
246
- if not plan_path.empty:
247
- return plan_path['full_path'].iloc[0]
248
- else:
249
- raise ValueError(f"Plan number {plan_number} not found.")
250
-
251
- @staticmethod
252
- def get_results_full_path(plan_number: str) -> str:
253
- """Return the results path for a given plan number."""
254
- config = ProjectConfig()
255
- config.check_initialized()
256
-
257
- results_path = config.ras_plan_entries[config.ras_plan_entries['plan_number'] == plan_number]
258
- if not results_path.empty:
259
- results_file_path = Path(results_path['results_path'].iloc[0])
260
- if results_file_path.exists():
261
- print(f"Results file for Plan number {plan_number} exists at: {results_file_path}")
262
- return str(results_file_path)
263
- else:
264
- print(f"Error: Results file for Plan number {plan_number} does not exist at the expected location: {results_file_path}")
265
- return None
266
- else:
267
- print(f"Error: Results file for Plan number {plan_number} not found in the entries.")
268
- return None
269
-
270
-
271
-
272
-
273
- @staticmethod
274
- def get_flow_full_path(flow_number: str) -> str:
275
- """Return the full path for a given flow number."""
276
- config = ProjectConfig()
277
- config.check_initialized()
278
-
279
- flow_path = config.ras_flow_entries[config.ras_flow_entries['flow_number'] == flow_number]
280
- if not flow_path.empty:
281
- return flow_path['full_path'].iloc[0]
282
- else:
283
- raise ValueError(f"Flow number {flow_number} not found.")
284
-
285
- @staticmethod
286
- def get_unsteady_full_path(unsteady_number: str) -> str:
287
- """Return the full path for a given unsteady number."""
288
- config = ProjectConfig()
289
- config.check_initialized()
290
-
291
- unsteady_path = config.ras_unsteady_entries[config.ras_unsteady_entries['unsteady_number'] == unsteady_number]
292
- if not unsteady_path.empty:
293
- return unsteady_path['full_path'].iloc[0]
294
- else:
295
- raise ValueError(f"Unsteady number {unsteady_number} not found.")
296
-
297
- @staticmethod
298
- def get_geom_full_path(geometry_number: str) -> str:
299
- """Return the full path for a given geometry number."""
300
- config = ProjectConfig()
301
- config.check_initialized()
302
-
303
- geom_path = config.ras_geom_entries[config.ras_geom_entries['geom_number'] == geometry_number]
304
- if not geom_path.empty:
305
- return geom_path['full_path'].iloc[0]
306
- else:
307
- raise ValueError(f"Geometry number {geometry_number} not found.")
@@ -1,64 +0,0 @@
1
- from pathlib import Path
2
- from .project_setup import find_hecras_project_file, load_project_data
3
-
4
- # Notes (do note delete):
5
- # This ProjectConfig class is a singleton, so it will have the same values for all instances.
6
- # The class is very important for the project setup, as it will be used to store all the project's basic information.
7
- # Instead of having to pass around the project folder, project file, etc., we can just pass around the ProjectConfig class instance.
8
- # The class will need to be initialized with the project folder, which will then be used to set all the other project information.
9
- # The project information will be stored in the instance as class variables.
10
-
11
- # Documentation Notes:
12
- # print each variable name and value after it is defined, so the user can easily reference the project's basic information using the self. class variables
13
- # example: print(f"self.project_file: {self.project_file}")
14
-
15
- class ProjectConfig:
16
- _instance = None
17
-
18
- def __new__(cls):
19
- if cls._instance is None:
20
- cls._instance = super(ProjectConfig, cls).__new__(cls)
21
- cls._instance.initialized = False
22
- return cls._instance
23
-
24
- def initialize(self, project_folder, hecras_exe_path):
25
- self.project_folder = Path(project_folder)
26
- print(f"self.project_folder: {self.project_folder}")
27
-
28
- self.project_file = find_hecras_project_file(self.project_folder)
29
- print(f"self.project_file: {self.project_file}")
30
-
31
- if self.project_file is None:
32
- raise ValueError(f"No HEC-RAS project file found in {self.project_folder}")
33
-
34
- self.project_name = self.project_file.stem
35
- print(f"self.project_name: {self.project_name}")
36
-
37
- self.hecras_exe_path = hecras_exe_path
38
- print(f"self.hecras_exe_path: {self.hecras_exe_path}")
39
-
40
- self._load_project_data()
41
- self.initialized = True
42
- print(f"self.initialized: {self.initialized}")
43
-
44
- def _load_project_data(self):
45
- data = load_project_data(self.project_file)
46
- self.ras_plan_entries = data['ras_plan_entries']
47
- print(f"self.ras_plan_entries: {self.ras_plan_entries}")
48
-
49
- self.ras_flow_entries = data['ras_flow_entries']
50
- print(f"self.ras_flow_entries: {self.ras_flow_entries}")
51
-
52
- self.ras_unsteady_entries = data['ras_unsteady_entries']
53
- print(f"self.ras_unsteady_entries: {self.ras_unsteady_entries}")
54
-
55
- self.ras_geom_entries = data['ras_geom_entries']
56
- print(f"self.ras_geom_entries: {self.ras_geom_entries}")
57
-
58
- @property
59
- def is_initialized(self):
60
- return self.initialized
61
-
62
- def check_initialized(self):
63
- if not self.initialized:
64
- raise RuntimeError("Project not initialized. Call init_ras_project() first.")
@@ -1,174 +0,0 @@
1
- from .project_config import ProjectConfig
2
- from pathlib import Path
3
-
4
- def init_ras_project(ras_project_folder, hecras_version_or_path):
5
- """
6
- Initialize a HEC-RAS project.
7
-
8
- This function sets up a HEC-RAS project by validating the project folder,
9
- determining the HEC-RAS executable path, and initializing the project configuration.
10
-
11
- Args:
12
- ras_project_folder (str): Path to the HEC-RAS project folder.
13
- hecras_version_or_path (str): Either the HEC-RAS version number or the full path to the HEC-RAS executable.
14
-
15
- Returns:
16
- ProjectConfig: An initialized ProjectConfig object containing the project configuration.
17
-
18
- Raises:
19
- FileNotFoundError: If the specified RAS project folder does not exist.
20
-
21
- Note:
22
- This function prints a confirmation message upon successful initialization.
23
-
24
- Future Development Roadmap:
25
-
26
- 1. Load critical keys and values from the project files into the project config
27
- Implemented:
28
- - Project Name
29
- - Project Folder
30
- - Lists of plan, flow, unsteady, and geometry files
31
- - HEC-RAS Executable Path
32
-
33
- Not Implemented:
34
- - Units
35
- - Coordinate System
36
- - rasmap file path (replace prj file path extension with ".rasmap" and add it to the project config)
37
- - Current Plan
38
- - Description (including checks to see if it is a valid string and within the default max length)
39
- - DSS Start Date=01JAN1999 (note format is MMDDYYY)
40
- - DSS Start Time=1200 (note format is HHMM)
41
- - DSS End Date=04JAN1999 (note format is MMDDYYY)
42
- - DSS End Time=1200 (note format is HHMM)
43
- - DSS File=dss
44
- - DSS File=Bald_Eagle_Creek.dss
45
-
46
- Other not implemented:
47
-
48
- 2. Load critical keys and lists of string values from the plan files into the project config
49
- - Plan Title
50
- - Plan Shortid
51
- - Simulation Date
52
- - Geometry File
53
- - Flow File (may not be present) - if present, the plan is a 1D steady plan
54
- - Unsteady File (may not be present) - if present, the plan is a 1D or 2D unsteady plan
55
- - UNET D2 Name (may not be present) - if present, the plan is a 2D plan
56
- - Type (1D Steady, 1D Unsteady, 1D/2D, or 2D)
57
- - UNET 1D Methodology
58
-
59
- 3. Load critical keys and strings from the unsteady flow files into the project config
60
- - Flow Title
61
- - Pandas Dataframe for any Boundary Conditions present and whether they are defined in the file or whether they use a DSS file input
62
- - One dataframe for all unsteady flow files, with each Boundary Location in each file having its own row
63
- - For each unsteady flow filereturn an appended dataframe with each boundary condition and it's "Boundary Name", "Interval", "DSS Path", "Flow Hydrograph Slope", and whether Use DSS is True or False
64
- - Need to incorporate knowledge from the excel methods we used for setting boundary conditions
65
-
66
- 4. Load critical keys and strings from the steady flow files into the project config
67
- - Flow Title
68
- - Since steady models are not as commonly used, this is a low priority integration (external contributions are welcome)
69
-
70
-
71
- 5. Load critical keys and values from the rasmap file into the project config
72
- - rasmap_projection_string
73
- - Version #Ex: <Version>2.0.0</Version>
74
- - RASProjectionFilename Filename=".\Terrain\Projection.prj"
75
-
76
- - List of ras terrains as pandas dataframes
77
- - for each, list of tiff files and order
78
- - flag whether terrain mods exist
79
-
80
- - List of Infiltration hdf files as pandas dataframes
81
- - Mapping of infiltration layers to geometries
82
-
83
- - List of land cover hdf files as pandas dataframes
84
- - Mapping of land cover to geometries
85
-
86
- - List of all Mannings N layers, hdf files and mapping to geometries as pandas dataframes
87
-
88
- 6. Create a list of all valid hdf plan files are present in the project folder, and flag whether they contain a completed simulation
89
-
90
- This roadmap for the project_init function will provide the basic information needed to support most basic hec-ras automation workflows.
91
-
92
- Remember, this project init might be called multiple times. Every time, it should clear any previously created datafrarmes and variables and replace them. It is important that revisions can be made, init be re-run, and information is current and reflects the current state of the project.
93
-
94
-
95
-
96
- """
97
- # Check if the provided paths exist
98
- if not Path(ras_project_folder).exists():
99
- raise FileNotFoundError(f"The specified RAS project folder does not exist: {ras_project_folder}, Please check the path and try again.")
100
-
101
- hecras_exe_path = get_hecras_exe_path(hecras_version_or_path)
102
-
103
- config = ProjectConfig()
104
- config.initialize(ras_project_folder, hecras_exe_path)
105
- print(f"HEC-RAS project initialized: {config.project_name}")
106
- return config
107
-
108
-
109
- def get_hecras_exe_path(hecras_version_or_path):
110
- """
111
- Determine the HEC-RAS executable path based on the input.
112
-
113
- Args:
114
- hecras_version_or_path (str): Either a version number or a full path to the HEC-RAS executable.
115
-
116
- Returns:
117
- str: The full path to the HEC-RAS executable.
118
-
119
- Raises:
120
- ValueError: If the input is neither a valid version number nor a valid file path.
121
- FileNotFoundError: If the executable file does not exist at the specified or constructed path.
122
- """
123
-
124
-
125
- # hecras_exe_path should be changed to hecras_version_or_path
126
- # Based on whether a full path is provided, or a version number is provided, the path will be set accordingly
127
- # By default, HEC-RAS is installed in the Program Files (x86) folder
128
- # For example: hecras_exe_path = r"C:\Program Files (x86)\HEC\HEC-RAS\6.5\Ras.exe" for version 6.5
129
- # an f string to build the path based on the version number
130
- # hecras_exe_path = f"C:\Program Files (x86)\HEC\HEC-RAS\{hecras_version}\Ras.exe"
131
- # where hecras_version is one of the following:
132
-
133
- # List of HEC-RAS version numbers
134
- ras_version_numbers = [
135
- "6.5", "6.4.1", "6.3.1", "6.3", "6.2", "6.1", "6.0",
136
- "5.0.7", "5.0.6", "5.0.5", "5.0.4", "5.0.3", "5.0.1", "5.0",
137
- "4.1", "4.0", "3.1.3", "3.1.2", "3.1.1", "3.0", "2.2"
138
- ]
139
-
140
- hecras_path = Path(hecras_version_or_path)
141
-
142
- # Check if the input is a full path
143
- if hecras_path.is_file() and hecras_path.suffix.lower() == '.exe':
144
- return str(hecras_path)
145
-
146
- # Check if the input is a version number
147
- if hecras_version_or_path in ras_version_numbers:
148
- default_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{hecras_version_or_path}/Ras.exe")
149
- if default_path.is_file():
150
- return str(default_path)
151
- else:
152
- raise FileNotFoundError(f"HEC-RAS executable not found at the expected path: {default_path}")
153
-
154
- # Check if it's a newer version
155
- try:
156
- version_float = float(hecras_version_or_path)
157
- if version_float > max(float(v) for v in ras_version_numbers):
158
- newer_version_path = Path(f"C:/Program Files (x86)/HEC/HEC-RAS/{hecras_version_or_path}/Ras.exe")
159
- if newer_version_path.is_file():
160
- return str(newer_version_path)
161
- else:
162
- raise FileNotFoundError(f"Newer version of HEC-RAS was specified. Check the version number or pass the full Ras.exe path as the function argument instead of the version number. The script looked for the executable at: {newer_version_path}")
163
- except ValueError:
164
- pass # Not a valid float, so not a version number
165
-
166
- raise ValueError(f"Invalid HEC-RAS version or path: {hecras_version_or_path}. "
167
- f"Please provide a valid version number from {ras_version_numbers} "
168
- "or a full path to the HEC-RAS executable.")
169
- # Return the validated HEC-RAS executable path
170
- return hecras_exe_path
171
-
172
-
173
-
174
-