floodmodeller-api 0.5.1__py3-none-any.whl → 0.5.2__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.
Files changed (86) hide show
  1. floodmodeller_api/__init__.py +10 -0
  2. floodmodeller_api/_base.py +29 -20
  3. floodmodeller_api/backup.py +12 -10
  4. floodmodeller_api/dat.py +162 -91
  5. floodmodeller_api/diff.py +1 -1
  6. floodmodeller_api/hydrology_plus/hydrology_plus_export.py +1 -1
  7. floodmodeller_api/ied.py +2 -4
  8. floodmodeller_api/ief.py +29 -17
  9. floodmodeller_api/ief_flags.py +1 -1
  10. floodmodeller_api/inp.py +4 -6
  11. floodmodeller_api/logs/lf.py +18 -12
  12. floodmodeller_api/logs/lf_helpers.py +2 -2
  13. floodmodeller_api/logs/lf_params.py +1 -5
  14. floodmodeller_api/mapping.py +9 -2
  15. floodmodeller_api/test/test_conveyance.py +9 -4
  16. floodmodeller_api/test/test_dat.py +166 -18
  17. floodmodeller_api/test/test_data/EX18_DAT_expected.json +164 -144
  18. floodmodeller_api/test/test_data/EX3_DAT_expected.json +6 -2
  19. floodmodeller_api/test/test_data/EX6_DAT_expected.json +12 -46
  20. floodmodeller_api/test/test_data/encoding_test_cp1252.dat +1081 -0
  21. floodmodeller_api/test/test_data/encoding_test_utf8.dat +1081 -0
  22. floodmodeller_api/test/test_data/integrated_bridge/AR_NoSP_NoBl_2O_NO_OneFRC.ied +33 -0
  23. floodmodeller_api/test/test_data/integrated_bridge/AR_vSP_25pc_1O.ied +32 -0
  24. floodmodeller_api/test/test_data/integrated_bridge/PL_vSP_25pc_1O.ied +34 -0
  25. floodmodeller_api/test/test_data/integrated_bridge/SBTwoFRCsStaggered.IED +32 -0
  26. floodmodeller_api/test/test_data/integrated_bridge/US_NoSP_NoBl_OR_RN.ied +28 -0
  27. floodmodeller_api/test/test_data/integrated_bridge/US_SP_NoBl_OR_frc_PT2-5_RN.ied +34 -0
  28. floodmodeller_api/test/test_data/integrated_bridge/US_fSP_NoBl_1O.ied +30 -0
  29. floodmodeller_api/test/test_data/integrated_bridge/US_nSP_NoBl_1O.ied +49 -0
  30. floodmodeller_api/test/test_data/integrated_bridge/US_vSP_NoBl_2O_Para.ied +35 -0
  31. floodmodeller_api/test/test_data/integrated_bridge.dat +40 -0
  32. floodmodeller_api/test/test_data/network.ied +2 -2
  33. floodmodeller_api/test/test_data/network_dat_expected.json +141 -243
  34. floodmodeller_api/test/test_data/network_ied_expected.json +2 -2
  35. floodmodeller_api/test/test_data/network_with_comments.ied +2 -2
  36. floodmodeller_api/test/test_ied.py +1 -1
  37. floodmodeller_api/test/test_ief.py +10 -2
  38. floodmodeller_api/test/test_integrated_bridge.py +159 -0
  39. floodmodeller_api/test/test_json.py +9 -3
  40. floodmodeller_api/test/test_logs_lf.py +45 -24
  41. floodmodeller_api/test/test_river.py +1 -1
  42. floodmodeller_api/test/test_toolbox_structure_log.py +0 -1
  43. floodmodeller_api/test/test_xml2d.py +5 -5
  44. floodmodeller_api/to_from_json.py +1 -1
  45. floodmodeller_api/tool.py +3 -5
  46. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +1 -1
  47. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +12 -8
  48. floodmodeller_api/units/__init__.py +15 -0
  49. floodmodeller_api/units/_base.py +73 -10
  50. floodmodeller_api/units/_helpers.py +343 -0
  51. floodmodeller_api/units/boundaries.py +59 -71
  52. floodmodeller_api/units/comment.py +1 -1
  53. floodmodeller_api/units/conduits.py +57 -54
  54. floodmodeller_api/units/connectors.py +112 -0
  55. floodmodeller_api/units/controls.py +107 -0
  56. floodmodeller_api/units/iic.py +2 -9
  57. floodmodeller_api/units/losses.py +42 -42
  58. floodmodeller_api/units/sections.py +40 -43
  59. floodmodeller_api/units/structures.py +360 -530
  60. floodmodeller_api/units/units.py +25 -26
  61. floodmodeller_api/units/unsupported.py +5 -7
  62. floodmodeller_api/units/variables.py +2 -2
  63. floodmodeller_api/urban1d/_base.py +7 -8
  64. floodmodeller_api/urban1d/conduits.py +11 -21
  65. floodmodeller_api/urban1d/general_parameters.py +1 -1
  66. floodmodeller_api/urban1d/junctions.py +7 -11
  67. floodmodeller_api/urban1d/losses.py +13 -17
  68. floodmodeller_api/urban1d/outfalls.py +16 -21
  69. floodmodeller_api/urban1d/raingauges.py +3 -9
  70. floodmodeller_api/urban1d/subsections.py +3 -4
  71. floodmodeller_api/urban1d/xsections.py +11 -15
  72. floodmodeller_api/util.py +7 -4
  73. floodmodeller_api/validation/parameters.py +7 -3
  74. floodmodeller_api/validation/urban_parameters.py +1 -4
  75. floodmodeller_api/validation/validation.py +9 -4
  76. floodmodeller_api/version.py +1 -1
  77. floodmodeller_api/xml2d.py +9 -11
  78. floodmodeller_api/xml2d_template.py +1 -1
  79. floodmodeller_api/zz.py +7 -6
  80. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/LICENSE.txt +1 -1
  81. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/METADATA +11 -3
  82. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/RECORD +85 -70
  83. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/WHEEL +1 -1
  84. floodmodeller_api/units/helpers.py +0 -121
  85. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/entry_points.txt +0 -0
  86. {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  """
2
2
  Flood Modeller Python API
3
- Copyright (C) 2024 Jacobs U.K. Limited
3
+ Copyright (C) 2025 Jacobs U.K. Limited
4
4
 
5
5
  This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
6
  as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
@@ -14,20 +14,36 @@ If you have any query about this program or this License, please contact us at s
14
14
  address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
15
  """
16
16
 
17
+ from __future__ import annotations
18
+
19
+ import logging
20
+
17
21
  import pandas as pd
18
22
 
19
23
  from floodmodeller_api.validation import _validate_unit
20
24
 
21
25
  from ._base import Unit
22
- from .helpers import (
23
- _to_float,
24
- _to_int,
25
- _to_str,
26
+ from ._helpers import (
27
+ get_int,
26
28
  join_10_char,
27
- join_12_char_ljust,
28
29
  join_n_char_ljust,
30
+ read_bridge_cross_sections,
31
+ read_bridge_culvert_data,
32
+ read_bridge_opening_data,
33
+ read_bridge_pier_locations,
34
+ read_dataframe_from_lines,
35
+ read_spill_section_data,
36
+ read_superbridge_block_data,
37
+ read_superbridge_opening_data,
38
+ set_bridge_params,
39
+ set_pier_params,
29
40
  split_10_char,
30
41
  split_n_char,
42
+ to_float,
43
+ to_int,
44
+ to_str,
45
+ write_dataframe,
46
+ write_dataframes,
31
47
  )
32
48
 
33
49
 
@@ -124,235 +140,191 @@ class BRIDGE(Unit):
124
140
 
125
141
  _unit = "BRIDGE"
126
142
 
127
- def _read(self, br_block): # noqa: C901, PLR0912, PLR0915
143
+ # attributes set in a function (for mypy)
144
+ calibration_coefficient: float
145
+ skew: float
146
+ bridge_width_dual: float
147
+ bridge_dist_dual: float
148
+ total_pier_width: float
149
+ orifice_flow: bool
150
+ orifice_lower_transition_dist: float
151
+ orifice_upper_transition_dist: float
152
+ orifice_discharge_coefficient: float
153
+ specify_piers: bool
154
+ npiers: int
155
+ pier_use_calibration_coeff: bool
156
+ pier_calibration_coeff: float
157
+ pier_shape: str
158
+ soffit_shape: str
159
+ pier_faces: str
160
+
161
+ def _read(self, br_block: list[str]) -> None: # noqa: PLR0915
128
162
  """Function to read a given BRIDGE block and store data as class attributes"""
129
- self._subtype = br_block[1].split(" ")[0].strip()
163
+ self.comment = self._remove_unit_name(br_block[0])
164
+ self._subtype = self._get_first_word(br_block[1])
130
165
  # Extends label line to be correct length before splitting to pick up blank labels
131
166
  labels = split_n_char(f"{br_block[2]:<{4*self._label_len}}", self._label_len)
132
167
  self.name = labels[0]
133
168
  self.ds_label = labels[1]
134
169
  self.us_remote_label = labels[2]
135
170
  self.ds_remote_label = labels[3]
136
- self.comment = br_block[0].replace("BRIDGE", "").strip()
137
171
 
138
172
  # Read ARCH type unit
139
173
  if self.subtype == "ARCH":
140
- # Read Params
141
- params = split_10_char(f"{br_block[4]:<90}")
142
- self.calibration_coefficient = _to_float(params[0], 1.0)
143
- self.skew = _to_float(params[1])
144
- self.bridge_width_dual = _to_float(params[2])
145
- self.bridge_dist_dual = _to_float(params[3])
146
- if params[5] == "ORIFICE":
147
- self.orifice_flow = True
148
- else:
149
- self.orifice_flow = False
150
- self.orifice_lower_transition_dist = _to_float(params[6])
151
- self.orifice_upper_transition_dist = _to_float(params[7])
152
- self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
153
-
154
- # Read cross section data
155
- self.section_nrows = int(split_10_char(br_block[5])[0])
156
- data_list = []
157
- for row in br_block[6 : 6 + self.section_nrows]:
158
- row_split = split_10_char(f"{row:<50}")
159
- x = _to_float(row_split[0]) # chainage
160
- y = _to_float(row_split[1]) # elevation
161
- n = _to_float(row_split[2]) # Mannings
162
- embankment = row_split[4] # Embankment flag
163
- data_list.append([x, y, n, embankment])
164
- self.section_data = pd.DataFrame(
165
- data_list,
166
- columns=["X", "Y", "Mannings n", "Embankments"],
174
+ set_bridge_params(self, br_block[4], include_pier=False)
175
+
176
+ self.section_nrows, end_idx, self.section_data = read_dataframe_from_lines(
177
+ br_block,
178
+ 5,
179
+ read_bridge_cross_sections,
167
180
  )
168
181
 
169
- # Read bridge opening data
170
- self.opening_nrows = int(split_10_char(br_block[6 + self.section_nrows])[0])
171
- data_list = []
172
- for row in br_block[6 + self.section_nrows + 1 :]:
173
- row_split = split_10_char(f"{row:<40}")
174
- start = _to_float(row_split[0]) # Start (m)
175
- finish = _to_float(row_split[1]) # Finish (m)
176
- spring = _to_float(row_split[2]) # Springing Level
177
- soffit = _to_float(row_split[3]) # Soffit Level
178
- data_list.append([start, finish, spring, soffit])
179
- self.opening_data = pd.DataFrame(
180
- data_list,
181
- columns=["Start", "Finish", "Springing Level", "Soffit Level"],
182
+ self.opening_nrows, end_idx, self.opening_data = read_dataframe_from_lines(
183
+ br_block,
184
+ end_idx,
185
+ read_bridge_opening_data,
182
186
  )
183
187
 
184
188
  # Read USBPR type unit
185
189
  elif self.subtype == "USBPR1978":
186
- # Read Params
187
- params = split_10_char(f"{br_block[4]:<90}")
188
- self.calibration_coefficient = _to_float(params[0], 1.0)
189
- self.skew = _to_float(params[1])
190
- self.bridge_width_dual = _to_float(params[2])
191
- self.bridge_dist_dual = _to_float(params[3])
192
- self.total_pier_width = _to_float(params[4])
193
- if params[5] == "ORIFICE":
194
- self.orifice_flow = True
195
- else:
196
- self.orifice_flow = False
197
- self.orifice_lower_transition_dist = _to_float(params[6])
198
- self.orifice_upper_transition_dist = _to_float(params[7])
199
- self.orifice_discharge_coefficient = _to_float(params[8], 1.0)
190
+ set_bridge_params(self, br_block[4])
200
191
 
201
192
  self.abutment_type = split_10_char(br_block[5])[0]
202
193
  self.abutment_alignment = split_10_char(br_block[7])[0]
203
194
 
204
- pier_info = split_10_char(f"{br_block[6]:<40}")
205
- if int(pier_info[0]) > 0:
206
- self.specify_piers = True
207
- self.npiers = int(pier_info[0])
208
- if pier_info[1] == "COEF":
209
- self.pier_use_calibration_coeff = True
210
- self.pier_calibration_coeff = _to_float(pier_info[3])
211
- else:
212
- self.pier_use_calibration_coeff = False
213
- self.pier_shape = pier_info[1]
214
- self.pier_faces = pier_info[2]
215
- else:
216
- self.specify_piers = False
217
- self.soffit_shape = pier_info[1]
218
-
219
- # Read cross section data
220
- self.section_nrows = int(split_10_char(br_block[8])[0])
221
- data_list = []
222
- for row in br_block[9 : 9 + self.section_nrows]:
223
- row_split = split_10_char(f"{row:<50}")
224
- x = _to_float(row_split[0]) # chainage
225
- y = _to_float(row_split[1]) # elevation
226
- n = _to_float(row_split[2]) # Mannings
227
- embankment = row_split[4] # Embankment flag
228
- data_list.append([x, y, n, embankment])
229
- self.section_data = pd.DataFrame(
230
- data_list,
231
- columns=["X", "Y", "Mannings n", "Embankments"],
232
- )
195
+ set_pier_params(self, br_block[6])
233
196
 
234
- # Read bridge opening data
235
- self.opening_nrows = int(split_10_char(br_block[9 + self.section_nrows])[0])
236
- data_list = []
237
- start_row = 9 + self.section_nrows + 1
238
- end_row = start_row + self.opening_nrows
239
- for row in br_block[start_row:end_row]:
240
- row_split = split_10_char(f"{row:<40}")
241
- start = _to_float(row_split[0]) # Start (m)
242
- finish = _to_float(row_split[1]) # Finish (m)
243
- spring = _to_float(row_split[2]) # Springing Level
244
- soffit = _to_float(row_split[3]) # Soffit Level
245
- data_list.append([start, finish, spring, soffit])
246
- self.opening_data = pd.DataFrame(
247
- data_list,
248
- columns=["Start", "Finish", "Springing Level", "Soffit Level"],
197
+ self.section_nrows, end_idx, self.section_data = read_dataframe_from_lines(
198
+ br_block,
199
+ 8,
200
+ read_bridge_cross_sections,
249
201
  )
250
202
 
251
- # Read flood relief culvert data
252
- self.culvert_nrows = int(
253
- split_10_char(br_block[9 + self.section_nrows + self.opening_nrows + 1])[0],
203
+ self.opening_nrows, end_idx, self.opening_data = read_dataframe_from_lines(
204
+ br_block,
205
+ end_idx,
206
+ read_bridge_opening_data,
254
207
  )
255
- data_list = []
256
- start_row = 9 + self.section_nrows + self.opening_nrows + 2
257
- end_row = start_row + self.culvert_nrows
258
- for row in br_block[start_row:end_row]:
259
- row_split = split_10_char(f"{row:<60}")
260
- invert = _to_float(row_split[0]) # Invert
261
- soffit = _to_float(row_split[1]) # Soffit
262
- area = _to_float(row_split[2]) # Section Area
263
- cd_part = _to_float(row_split[3]) # Cd Part Full
264
- cd_full = _to_float(row_split[4]) # Cd Full
265
- drown = _to_float(row_split[5]) # Drowning Coefficient
266
- data_list.append([invert, soffit, area, cd_part, cd_full, drown])
267
- self.culvert_data = pd.DataFrame(
268
- data_list,
269
- columns=[
270
- "Invert",
271
- "Soffit",
272
- "Section Area",
273
- "Cd Part Full",
274
- "Cd Full",
275
- "Drowning Coefficient",
276
- ],
208
+
209
+ self.culvert_nrows, end_idx, self.culvert_data = read_dataframe_from_lines(
210
+ br_block,
211
+ end_idx,
212
+ read_bridge_culvert_data,
277
213
  )
278
214
 
279
215
  # Read Pierloss type bridge
280
216
  elif self.subtype == "PIERLOSS":
281
217
  # Read Params
282
- params = split_10_char(f"{br_block[4]:<50}")
283
- self.calibration_coefficient = _to_float(params[0], 1.0)
284
- if params[1] == "ORIFICE":
285
- self.orifice_flow = True
286
- else:
287
- self.orifice_flow = False
288
- self.orifice_lower_transition_dist = _to_float(params[3])
289
- self.orifice_upper_transition_dist = _to_float(params[4])
290
- self.orifice_discharge_coefficient = _to_float(params[2], 1.0)
218
+ pierloss_params = split_10_char(f"{br_block[4]:<50}")
219
+ self.calibration_coefficient = to_float(pierloss_params[0], 1.0)
220
+ self.orifice_flow = pierloss_params[1] == "ORIFICE"
221
+ self.orifice_discharge_coefficient = to_float(pierloss_params[2], 1.0)
222
+ self.orifice_lower_transition_dist = to_float(pierloss_params[3])
223
+ self.orifice_upper_transition_dist = to_float(pierloss_params[4])
224
+
291
225
  additional_params = split_10_char(f"{br_block[5]:<20}")
292
- self.pier_coefficient = _to_float(additional_params[0], 0.9)
293
- self.bridge_width = _to_float(additional_params[1])
294
-
295
- # Read US cross section data
296
- self.us_section_nrows = int(split_10_char(br_block[6])[0])
297
- data_list = []
298
- for row in br_block[7 : 7 + self.us_section_nrows]:
299
- row_split = split_10_char(f"{row:<60}")
300
- x = _to_float(row_split[0]) # chainage
301
- y = _to_float(row_split[1]) # elevation
302
- n = _to_float(row_split[2]) # Mannings
303
- embankment = row_split[4] # Embankment flag
304
- top_level = row_split[5] # Top Level (m)
305
- data_list.append([x, y, n, embankment, top_level])
306
- self.us_section_data = pd.DataFrame(
307
- data_list,
308
- columns=["X", "Y", "Mannings n", "Embankments", "Top Level"],
226
+ self.pier_coefficient = to_float(additional_params[0], 0.9)
227
+ self.bridge_width = to_float(additional_params[1])
228
+
229
+ self.us_section_nrows, end_idx, self.us_section_data = read_dataframe_from_lines(
230
+ br_block,
231
+ 6,
232
+ read_bridge_cross_sections,
233
+ include_top_level=True,
234
+ )
235
+
236
+ self.ds_section_nrows, end_idx, self.ds_section_data = read_dataframe_from_lines(
237
+ br_block,
238
+ end_idx,
239
+ read_bridge_cross_sections,
240
+ include_top_level=True,
241
+ )
242
+
243
+ self.pier_locs_nrows, end_idx, self.pier_locs_data = read_dataframe_from_lines(
244
+ br_block,
245
+ end_idx,
246
+ read_bridge_pier_locations,
309
247
  )
310
248
 
311
- # Read DS cross section data
312
- new_idx = 6 + 1 + self.us_section_nrows
313
- self.ds_section_nrows = int(split_10_char(br_block[new_idx])[0])
314
- data_list = []
315
- for row in br_block[new_idx + 1 : new_idx + 1 + self.ds_section_nrows]:
316
- row_split = split_10_char(f"{row:<60}")
317
- x = _to_float(row_split[0]) # chainage
318
- y = _to_float(row_split[1]) # elevation
319
- n = _to_float(row_split[2]) # Mannings
320
- embankment = row_split[4] # Embankment flag
321
- top_level = row_split[5] # Top Level (m)
322
- data_list.append([x, y, n, embankment, top_level])
323
- self.ds_section_data = pd.DataFrame(
324
- data_list,
325
- columns=["X", "Y", "Mannings n", "Embankments", "Top Level"],
249
+ elif self.subtype == "INTEGRATED":
250
+ self.revision = to_int(br_block[3])
251
+ self.bridge_name = br_block[4]
252
+ self.integrated_subtype = br_block[5].strip()
253
+ set_bridge_params(self, br_block[6])
254
+ self.abutment_type = to_int(br_block[7])
255
+ set_pier_params(self, br_block[8])
256
+ self.aligned = br_block[9].strip() == "ALIGNED"
257
+
258
+ end_idx = 10
259
+ self.section_nrows_list: list[int] = []
260
+ self.section_data_list: list[pd.DataFrame] = []
261
+ for _ in range(4):
262
+ nrows, end_idx, data = read_dataframe_from_lines(
263
+ br_block,
264
+ end_idx,
265
+ read_bridge_cross_sections,
266
+ include_panel_marker=True,
267
+ )
268
+ self.section_nrows_list.append(nrows)
269
+ self.section_data_list.append(data)
270
+
271
+ self.opening_type = br_block[end_idx]
272
+ end_idx += 1
273
+ self.opening_nrows = get_int(br_block[end_idx])
274
+ end_idx += 1
275
+ self.opening_nsubrows_list: list[int] = []
276
+ self.opening_data_list: list[pd.DataFrame] = []
277
+ for _ in range(self.opening_nrows):
278
+ nrows, end_idx, data = read_dataframe_from_lines(
279
+ br_block,
280
+ end_idx,
281
+ read_superbridge_opening_data,
282
+ )
283
+ self.opening_nsubrows_list.append(nrows)
284
+ self.opening_data_list.append(data)
285
+
286
+ self.culvert_nrows, end_idx, self.culvert_data = read_dataframe_from_lines(
287
+ br_block,
288
+ end_idx,
289
+ read_bridge_culvert_data,
326
290
  )
327
291
 
328
- # Read pier locations
329
- new_idx += 1 + self.ds_section_nrows
330
- self.pier_locs_nrows = int(split_10_char(br_block[new_idx])[0])
331
- data_list = []
332
- for row in br_block[new_idx + 1 : new_idx + 1 + self.pier_locs_nrows]:
333
- row_split = split_10_char(f"{row:<40}")
334
- l_x = _to_float(row_split[0]) # chainage
335
- l_top_level = _to_float(row_split[1]) # Top Level (m)
336
- r_x = _to_float(row_split[2]) # chainage
337
- r_top_level = _to_float(row_split[3]) # Top Level (m)
338
- data_list.append([l_x, l_top_level, r_x, r_top_level])
339
- self.pier_locs_data = pd.DataFrame(
340
- data_list,
341
- columns=["Left X", "Left Top Level", "Right X", "Right Top Level"],
292
+ spill_params = split_10_char(f"{br_block[end_idx]:<30}")
293
+ self.weir_coefficient = to_float(spill_params[1])
294
+ self.modular_limit = to_float(spill_params[2])
295
+ self.spill_nrows, end_idx, self.spill_data = read_dataframe_from_lines(
296
+ br_block,
297
+ end_idx,
298
+ read_spill_section_data,
299
+ )
300
+
301
+ self.block_comment = br_block[end_idx]
302
+ end_idx += 1
303
+ block_params = split_10_char(f"{br_block[end_idx]:<50}")
304
+ self.inlet_loss = to_float(block_params[1])
305
+ self.outlet_loss = to_float(block_params[2])
306
+ self.block_method = "USDEPTH" if (block_params[3] == "") else block_params[3]
307
+ self.override = block_params[4] == "OVERRIDE"
308
+ self.block_nrows, end_idx, self.block_data = read_dataframe_from_lines(
309
+ br_block,
310
+ end_idx,
311
+ read_superbridge_block_data,
342
312
  )
343
313
 
344
314
  else:
345
- # This else block is triggered for bridge subtypes which aren't yet supported, and just keeps the 'br_block' in it's raw state to write back.
346
- print(
347
- f'This Bridge sub-type: "{self.subtype}" is currently unsupported for reading/editing',
315
+ # This else block is triggered for bridge subtypes which aren't yet supported
316
+ # and just keeps the 'br_block' in its raw state to write back.
317
+ logging.warning(
318
+ "This Bridge sub-type: '%s' is currently unsupported for reading/editing",
319
+ self.subtype,
348
320
  )
349
321
  self._raw_block = br_block
350
322
  self.name = br_block[2][:12].strip()
351
323
 
352
- def _write(self): # noqa: C901, PLR0912, PLR0915
324
+ def _write(self) -> list[str]: # noqa: PLR0915
353
325
  """Function to write a valid BRIDGE block"""
354
326
  _validate_unit(self) # Function to check the params are valid for BRIDGE unit
355
- header = "BRIDGE " + self.comment
327
+ header = self._create_header()
356
328
  labels = join_n_char_ljust(
357
329
  self._label_len,
358
330
  self.name,
@@ -375,25 +347,14 @@ class BRIDGE(Unit):
375
347
  self.orifice_upper_transition_dist,
376
348
  self.orifice_discharge_coefficient,
377
349
  )
378
- self.section_nrows = len(self.section_data)
379
- br_block.extend(["MANNING", params, f"{self.section_nrows!s:>10}"])
380
-
381
- section_data = []
382
- for _, x, y, n, embankments in self.section_data.itertuples():
383
- # Adding extra 10 spaces before embankment flag
384
- row = join_10_char(x, y, n, "")
385
- row += embankments
386
- section_data.append(row)
350
+ br_block.extend(["MANNING", params])
387
351
 
352
+ self.section_nrows = len(self.section_data)
353
+ section_data = write_dataframe(self.section_nrows, self.section_data, empty=3)
388
354
  br_block.extend(section_data)
389
355
 
390
356
  self.opening_nrows = len(self.opening_data)
391
- br_block.append(f"{self.opening_nrows!s:>10}")
392
- opening_data = []
393
- for _, start, finish, spring, soffit in self.opening_data.itertuples():
394
- row = join_10_char(start, finish, spring, soffit)
395
- opening_data.append(row)
396
-
357
+ opening_data = write_dataframe(self.opening_nrows, self.opening_data)
397
358
  br_block.extend(opening_data)
398
359
 
399
360
  return br_block
@@ -413,13 +374,12 @@ class BRIDGE(Unit):
413
374
  )
414
375
  if self.specify_piers:
415
376
  if self.pier_use_calibration_coeff:
416
- pier_params = f'{self.npiers:>10}{"COEF":<10}{"":>10}{self.calibration_coefficient:>10.3f}'
377
+ pier_params = f'{self.npiers:>10}{"COEFF":<10}{"":>10}{self.pier_calibration_coeff:>10.3f}'
417
378
  else:
418
379
  pier_params = f"{self.npiers:>10}{self.pier_shape:<10}{self.pier_faces:<10}"
419
380
  else:
420
381
  pier_params = f"{0:>10}{self.soffit_shape}"
421
382
 
422
- self.section_nrows = len(self.section_data)
423
383
  br_block.extend(
424
384
  [
425
385
  "MANNING",
@@ -427,40 +387,19 @@ class BRIDGE(Unit):
427
387
  f"{self.abutment_type!s:>10}",
428
388
  pier_params,
429
389
  self.abutment_alignment,
430
- f"{self.section_nrows!s:>10}",
431
390
  ],
432
391
  )
433
392
 
434
- section_data = []
435
- for _, x, y, n, embankments in self.section_data.itertuples():
436
- # Adding extra 10 spaces before embankment flag
437
- row = join_10_char(x, y, n, "")
438
- row += embankments
439
- section_data.append(row)
393
+ self.section_nrows = len(self.section_data)
394
+ section_data = write_dataframe(self.section_nrows, self.section_data, empty=3)
440
395
  br_block.extend(section_data)
441
396
 
442
397
  self.opening_nrows = len(self.opening_data)
443
- br_block.append(f"{self.opening_nrows!s:>10}")
444
- opening_data = []
445
- for _, start, finish, spring, soffit in self.opening_data.itertuples():
446
- row = join_10_char(start, finish, spring, soffit)
447
- opening_data.append(row)
398
+ opening_data = write_dataframe(self.opening_nrows, self.opening_data)
448
399
  br_block.extend(opening_data)
449
400
 
450
401
  self.culvert_nrows = len(self.culvert_data)
451
- br_block.append(f"{self.culvert_nrows!s:>10}")
452
- culvert_data = []
453
- for (
454
- _,
455
- invert,
456
- soffit,
457
- area,
458
- cd_part,
459
- cd_full,
460
- drown,
461
- ) in self.culvert_data.itertuples():
462
- row = join_10_char(invert, soffit, area, cd_part, cd_full, drown)
463
- culvert_data.append(row)
402
+ culvert_data = write_dataframe(self.culvert_nrows, self.culvert_data)
464
403
  br_block.extend(culvert_data)
465
404
 
466
405
  return br_block
@@ -475,52 +414,105 @@ class BRIDGE(Unit):
475
414
  self.orifice_upper_transition_dist,
476
415
  )
477
416
  additional_params = join_10_char(self.pier_coefficient, self.bridge_width)
478
- self.us_section_nrows = len(self.us_section_data)
479
- br_block.extend(
480
- [
481
- "YARNELL",
482
- params,
483
- additional_params,
484
- f"{self.us_section_nrows!s:>10}",
485
- ],
486
- )
417
+ br_block.extend(["YARNELL", params, additional_params])
487
418
 
488
- us_section_data = []
489
- for _, x, y, n, embankments, top_level in self.us_section_data.itertuples():
490
- # Adding extra 10 spaces before embankment flag
491
- row = join_10_char(x, y, n, "")
492
- row += f"{embankments:<10}"
493
- row += join_10_char(top_level)
494
- us_section_data.append(row)
419
+ self.us_section_nrows = len(self.us_section_data)
420
+ us_section_data = write_dataframe(self.us_section_nrows, self.us_section_data, empty=3)
495
421
  br_block.extend(us_section_data)
496
422
 
497
423
  self.ds_section_nrows = len(self.ds_section_data)
498
- br_block.append(f"{self.ds_section_nrows!s:>10}")
499
- ds_section_data = []
500
- for _, x, y, n, embankments, top_level in self.ds_section_data.itertuples():
501
- # Adding extra 10 spaces before embankment flag
502
- row = join_10_char(x, y, n, "")
503
- row += f"{embankments:<10}"
504
- row += join_10_char(top_level)
505
- ds_section_data.append(row)
424
+ ds_section_data = write_dataframe(self.ds_section_nrows, self.ds_section_data, empty=3)
506
425
  br_block.extend(ds_section_data)
507
426
 
508
427
  self.pier_locs_nrows = len(self.pier_locs_data)
509
- br_block.append(f"{self.pier_locs_nrows!s:>10}")
510
- pier_locs_data = []
511
- for (
512
- _,
513
- l_x,
514
- l_top_level,
515
- r_x,
516
- r_top_level,
517
- ) in self.pier_locs_data.itertuples():
518
- row = join_10_char(l_x, l_top_level, r_x, r_top_level)
519
- pier_locs_data.append(row)
428
+ pier_locs_data = write_dataframe(self.pier_locs_nrows, self.pier_locs_data)
520
429
  br_block.extend(pier_locs_data)
521
430
 
522
431
  return br_block
523
432
 
433
+ if self.subtype == "INTEGRATED":
434
+ line_1_2 = br_block
435
+ line_3 = str(self.revision)
436
+ line_4 = self.bridge_name
437
+ line_5 = self.integrated_subtype
438
+ line_6 = join_10_char(
439
+ self.calibration_coefficient,
440
+ self.skew,
441
+ self.bridge_width_dual,
442
+ self.bridge_dist_dual,
443
+ self.total_pier_width,
444
+ "ORIFICE" if self.orifice_flow else "",
445
+ self.orifice_lower_transition_dist,
446
+ self.orifice_upper_transition_dist,
447
+ self.orifice_discharge_coefficient,
448
+ )
449
+ line_7 = str(self.abutment_type)
450
+ if self.specify_piers:
451
+ if self.pier_use_calibration_coeff:
452
+ line_8 = join_10_char(
453
+ self.npiers,
454
+ "COEFF",
455
+ "",
456
+ self.pier_calibration_coeff,
457
+ )
458
+ else:
459
+ line_8 = join_10_char(
460
+ self.npiers,
461
+ self.pier_shape,
462
+ self.pier_faces,
463
+ )
464
+ else:
465
+ line_8 = join_10_char(
466
+ 0,
467
+ self.soffit_shape,
468
+ )
469
+ line_9 = "ALIGNED" if self.aligned else ""
470
+ line_10_11_12_13 = write_dataframes(
471
+ None,
472
+ self.section_nrows_list,
473
+ self.section_data_list,
474
+ )
475
+ line_14 = self.opening_type
476
+ line_15 = write_dataframes(
477
+ self.opening_nrows,
478
+ self.opening_nsubrows_list,
479
+ self.opening_data_list,
480
+ )
481
+ line_16 = write_dataframe(self.culvert_nrows, self.culvert_data)
482
+ line_17 = write_dataframe(
483
+ join_10_char(self.spill_nrows, self.weir_coefficient, self.modular_limit),
484
+ self.spill_data,
485
+ )
486
+ line_18 = self.block_comment
487
+ line_19 = write_dataframe(
488
+ join_10_char(
489
+ self.block_nrows,
490
+ self.inlet_loss,
491
+ self.outlet_loss,
492
+ self.block_method,
493
+ "OVERRIDE" if self.override else "NOOVERRIDE",
494
+ ),
495
+ self.block_data,
496
+ )
497
+
498
+ return [
499
+ *line_1_2,
500
+ line_3,
501
+ line_4,
502
+ line_5, # type: ignore
503
+ line_6,
504
+ line_7,
505
+ line_8,
506
+ line_9,
507
+ *line_10_11_12_13,
508
+ line_14,
509
+ *line_15,
510
+ *line_16,
511
+ *line_17,
512
+ line_18,
513
+ *line_19,
514
+ ]
515
+
524
516
  return self._raw_block
525
517
 
526
518
 
@@ -594,41 +586,41 @@ class SLUICE(Unit):
594
586
 
595
587
  def _read(self, block):
596
588
  """Function to read a given SLUICE block and store data as class attributes"""
597
- self._subtype = block[1].split(" ")[0].strip()
589
+ self._subtype = self._get_first_word(block[1])
598
590
 
599
591
  # Extends label line to be correct length before splitting to pick up blank labels
600
592
  labels = split_n_char(f"{block[2]:<{3*self._label_len}}", self._label_len)
601
593
  self.name = labels[0]
602
594
  self.ds_label = labels[1]
603
595
  self.remote_label = labels[2]
604
- self.comment = block[0].replace("SLUICE", "").strip()
596
+ self.comment = self._remove_unit_name(block[0])
605
597
 
606
598
  # First parameter line
607
599
  params1 = split_10_char(f"{block[3]:<80}")
608
- self.weir_flow_coefficient = _to_float(params1[0], 1.0)
609
- self.under_gate_flow = _to_float(params1[1], 1.0)
610
- self.weir_breadth = _to_float(params1[2])
611
- self.crest_elevation = _to_float(params1[3])
612
- self.gate_height_or_chord = _to_float(params1[4])
613
- self.weir_length = _to_float(params1[5])
600
+ self.weir_flow_coefficient = to_float(params1[0], 1.0)
601
+ self.under_gate_flow = to_float(params1[1], 1.0)
602
+ self.weir_breadth = to_float(params1[2])
603
+ self.crest_elevation = to_float(params1[3])
604
+ self.gate_height_or_chord = to_float(params1[4])
605
+ self.weir_length = to_float(params1[5])
614
606
  if self.subtype == "RADIAL":
615
607
  self.use_degrees = params1[6] == "DEGREES"
616
608
  self.allow_free_flow_under = params1[7] == "FREESLUICE"
617
609
 
618
610
  # Second parameter line
619
611
  params2 = split_10_char(f"{block[4]:<70}")
620
- self.us_weir_height = _to_float(params2[0])
621
- self.ds_weir_height = _to_float(params2[1])
622
- self.bias_factor = _to_float(params2[2], 1.0)
623
- self.over_gate_flow = _to_float(params2[3], 1.0)
612
+ self.us_weir_height = to_float(params2[0])
613
+ self.ds_weir_height = to_float(params2[1])
614
+ self.bias_factor = to_float(params2[2], 1.0)
615
+ self.over_gate_flow = to_float(params2[3], 1.0)
624
616
  if self.subtype == "RADIAL":
625
- self.pivot_height = _to_float(params2[4], 0.7)
626
- self.gate_radius = _to_float(params2[5], 0.7)
617
+ self.pivot_height = to_float(params2[4], 0.7)
618
+ self.gate_radius = to_float(params2[5], 0.7)
627
619
  else:
628
620
  self.modular_limits = {
629
- "weir_flow": _to_float(params2[4]),
630
- "under_gate_flow": _to_float(params2[5], 1.0),
631
- "over_gate_flow": _to_float(params2[6], 1.0),
621
+ "weir_flow": to_float(params2[4]),
622
+ "under_gate_flow": to_float(params2[5], 1.0),
623
+ "over_gate_flow": to_float(params2[6], 1.0),
632
624
  }
633
625
 
634
626
  # Third parameter line
@@ -636,15 +628,15 @@ class SLUICE(Unit):
636
628
  self.ngates = int(params3[0]) # number of gates
637
629
  if self.subtype == "RADIAL":
638
630
  self.modular_limits = {
639
- "weir_flow": _to_float(params3[1]),
640
- "under_gate_flow": _to_float(params3[2], 1.0),
641
- "over_gate_flow": _to_float(params3[3], 1.0),
631
+ "weir_flow": to_float(params3[1]),
632
+ "under_gate_flow": to_float(params3[2], 1.0),
633
+ "over_gate_flow": to_float(params3[3], 1.0),
642
634
  }
643
- self.timeunit = _to_str(params3[4], "SECONDS", check_float=True)
644
- self.extendmethod = _to_str(params3[5], "EXTEND")
635
+ self.timeunit = to_str(params3[4], "SECONDS", check_float=True)
636
+ self.extendmethod = to_str(params3[5], "EXTEND")
645
637
  else:
646
- self.timeunit = _to_str(params3[1], "SECONDS", check_float=True)
647
- self.extendmethod = _to_str(params3[2], "EXTEND")
638
+ self.timeunit = to_str(params3[1], "SECONDS", check_float=True)
639
+ self.extendmethod = to_str(params3[2], "EXTEND")
648
640
 
649
641
  # Control lines
650
642
  self.control_method = block[6].split()[0].upper()
@@ -661,14 +653,15 @@ class SLUICE(Unit):
661
653
 
662
654
  else:
663
655
  self._raw_extra_lines = block[6:]
664
- print(
665
- f"Note: Sluice control using method: {self.control_method} is not currently supported.",
656
+ logging.warning(
657
+ "Note: Sluice control using method: '%s' is not currently supported.",
658
+ self.control_method,
666
659
  )
667
660
 
668
661
  def _write(self):
669
662
  """Function to write a valid SLUICE block"""
670
663
  _validate_unit(self) # Function to check the params are valid for CONDUIT unit
671
- header = "SLUICE " + self.comment
664
+ header = self._create_header()
672
665
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label, self.remote_label)
673
666
  block = [header, self.subtype, labels]
674
667
 
@@ -763,8 +756,8 @@ class SLUICE(Unit):
763
756
  data_list = []
764
757
  for row in block[gate_row + 2 : gate_row + 2 + nrows]:
765
758
  row_split = split_10_char(f"{row:<20}")
766
- x = _to_float(row_split[0]) # time
767
- y = _to_float(row_split[1]) # opening
759
+ x = to_float(row_split[0]) # time
760
+ y = to_float(row_split[1]) # opening
768
761
  data_list.append([x, y])
769
762
 
770
763
  gate_data = pd.DataFrame(data_list, columns=["Time", "Opening"])
@@ -785,9 +778,9 @@ class SLUICE(Unit):
785
778
  data_list = []
786
779
  for row in block[gate_row + 2 : gate_row + 2 + nrows]:
787
780
  row_split = split_10_char(f"{row:<30}")
788
- x = _to_float(row_split[0]) # time
781
+ x = to_float(row_split[0]) # time
789
782
  y = row_split[1] # mode
790
- z = _to_float(row_split[2]) # opening
783
+ z = to_float(row_split[2]) # opening
791
784
  data_list.append([x, y, z])
792
785
 
793
786
  gate_data = pd.DataFrame(data_list, columns=["Time", "Mode", "Opening"])
@@ -830,34 +823,34 @@ class ORIFICE(Unit):
830
823
 
831
824
  def _read(self, block):
832
825
  """Function to read a given ORIFICE block and store data as class attributes"""
833
- self._subtype = block[1].split(" ")[0].strip()
826
+ self._subtype = self._get_first_word(block[1])
834
827
  self.flapped = self.subtype == "FLAPPED"
835
828
 
836
829
  # Extends label line to be correct length before splitting to pick up blank labels
837
830
  labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
838
831
  self.name = labels[0]
839
832
  self.ds_label = labels[1]
840
- self.comment = block[0].replace("ORIFICE", "").strip()
833
+ self.comment = self._remove_unit_name(block[0])
841
834
 
842
835
  # First parameter line
843
836
  params1 = split_10_char(f"{block[3]:<60}")
844
- self.invert = _to_float(params1[0])
845
- self.soffit = _to_float(params1[1])
846
- self.bore_area = _to_float(params1[2])
847
- self.upstream_sill = _to_float(params1[3])
848
- self.downstream_sill = _to_float(params1[4])
849
- self.shape = _to_str(params1[5], "RECTANGLE")
837
+ self.invert = to_float(params1[0])
838
+ self.soffit = to_float(params1[1])
839
+ self.bore_area = to_float(params1[2])
840
+ self.upstream_sill = to_float(params1[3])
841
+ self.downstream_sill = to_float(params1[4])
842
+ self.shape = to_str(params1[5], "RECTANGLE")
850
843
 
851
844
  # Second parameter line
852
845
  params2 = split_10_char(block[4])
853
- self.weir_flow = _to_float(params2[0], 1.0)
854
- self.surcharged_flow = _to_float(params2[1], 1.0)
855
- self.modular_limit = _to_float(params2[2], 0.7)
846
+ self.weir_flow = to_float(params2[0], 1.0)
847
+ self.surcharged_flow = to_float(params2[1], 1.0)
848
+ self.modular_limit = to_float(params2[2], 0.7)
856
849
 
857
850
  def _write(self):
858
851
  """Function to write a valid ORIFICE block"""
859
852
  _validate_unit(self) # Function to check the params are valid for CONDUIT unit
860
- header = "ORIFICE " + self.comment
853
+ header = self._create_header()
861
854
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
862
855
 
863
856
  self._subtype = "FLAPPED" if self.flapped else "OPEN"
@@ -938,31 +931,20 @@ class SPILL(Unit):
938
931
  labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
939
932
  self.name = labels[0]
940
933
  self.ds_label = labels[1]
941
- self.comment = block[0].replace("SPILL", "").strip()
934
+ self.comment = self._remove_unit_name(block[0])
942
935
 
943
936
  # First parameter line
944
937
  params = split_10_char(block[2])
945
- self.weir_coefficient = _to_float(params[0], 1.2)
946
- self.modular_limit = _to_float(params[1], 0.9)
938
+ self.weir_coefficient = to_float(params[0], 1.2)
939
+ self.modular_limit = to_float(params[1], 0.9)
947
940
 
948
941
  # Spill section data
949
- data_list = []
950
- for row in block[4:]:
951
- row_split = split_10_char(f"{row:<40}")
952
- x = _to_float(row_split[0]) # chainage
953
- y = _to_float(row_split[1]) # elevation
954
- e = _to_float(row_split[2]) # easting
955
- n = _to_float(row_split[3]) # northing
956
-
957
- data_list.append([x, y, e, n])
958
-
959
- spill_data = pd.DataFrame(data_list, columns=["X", "Y", "Easting", "Northing"])
960
- self.data = spill_data
942
+ self.data = read_spill_section_data(block[4:])
961
943
 
962
944
  def _write(self):
963
945
  """Function to write a valid SPILL block"""
964
946
  _validate_unit(self) # Function to check the params are valid for CONDUIT unit
965
- header = "SPILL " + self.comment
947
+ header = self._create_header()
966
948
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
967
949
  block = [header, labels]
968
950
 
@@ -1030,25 +1012,25 @@ class RNWEIR(Unit):
1030
1012
  labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1031
1013
  self.name = labels[0]
1032
1014
  self.ds_label = labels[1]
1033
- self.comment = block[0].replace("RNWEIR", "").strip()
1015
+ self.comment = self._remove_unit_name(block[0])
1034
1016
 
1035
1017
  # First parameter line
1036
1018
  params1 = split_10_char(f"{block[2]:<50}")
1037
- self.velocity_coefficient = _to_float(params1[0])
1038
- self.weir_length = _to_float(params1[1])
1039
- self.weir_breadth = _to_float(params1[2])
1040
- self.weir_elevation = _to_float(params1[3])
1041
- self.modular_limit = _to_float(params1[4])
1019
+ self.velocity_coefficient = to_float(params1[0])
1020
+ self.weir_length = to_float(params1[1])
1021
+ self.weir_breadth = to_float(params1[2])
1022
+ self.weir_elevation = to_float(params1[3])
1023
+ self.modular_limit = to_float(params1[4])
1042
1024
 
1043
1025
  # Second parameter line
1044
1026
  params2 = split_10_char(f"{block[3]:<20}")
1045
- self.upstream_crest_height = _to_float(params2[0])
1046
- self.downstream_crest_height = _to_float(params2[1])
1027
+ self.upstream_crest_height = to_float(params2[0])
1028
+ self.downstream_crest_height = to_float(params2[1])
1047
1029
 
1048
1030
  def _write(self):
1049
1031
  """Function to write a valid RNWEIR block"""
1050
1032
  _validate_unit(self)
1051
- header = "RNWEIR " + self.comment
1033
+ header = self._create_header()
1052
1034
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1053
1035
  block = [header, labels]
1054
1036
 
@@ -1131,23 +1113,23 @@ class WEIR(Unit):
1131
1113
  labels = split_n_char(f"{block[1]:<{2*self._label_len}}", self._label_len)
1132
1114
  self.name = labels[0]
1133
1115
  self.ds_label = labels[1]
1134
- self.comment = block[0].replace("WEIR", "").strip()
1116
+ self.comment = self._remove_unit_name(block[0])
1135
1117
 
1136
1118
  # Exponent
1137
- self.exponent = _to_float(block[2].strip())
1119
+ self.exponent = to_float(block[2].strip())
1138
1120
 
1139
1121
  # Parameters line
1140
1122
  params = split_10_char(f"{block[3]:<50}")
1141
- self.discharge_coefficient = _to_float(params[0])
1142
- self.velocity_coefficient = _to_float(params[1])
1143
- self.weir_breadth = _to_float(params[2])
1144
- self.weir_elevation = _to_float(params[3])
1145
- self.modular_limit = _to_float(params[4])
1123
+ self.discharge_coefficient = to_float(params[0])
1124
+ self.velocity_coefficient = to_float(params[1])
1125
+ self.weir_breadth = to_float(params[2])
1126
+ self.weir_elevation = to_float(params[3])
1127
+ self.modular_limit = to_float(params[4])
1146
1128
 
1147
1129
  def _write(self):
1148
1130
  """Function to write a valid WEIR block"""
1149
1131
  _validate_unit(self)
1150
- header = "WEIR " + self.comment
1132
+ header = self._create_header()
1151
1133
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1152
1134
  block = [header, labels]
1153
1135
 
@@ -1225,24 +1207,24 @@ class CRUMP(Unit):
1225
1207
  self.ds_label = labels[1]
1226
1208
  self.us_remote_label = labels[2]
1227
1209
  self.ds_remote_label = labels[3]
1228
- self.comment = block[0].replace("CRUMP", "").strip()
1210
+ self.comment = self._remove_unit_name(block[0])
1229
1211
 
1230
1212
  # First parameter line
1231
1213
  params1 = split_10_char(f"{block[2]:<40}")
1232
- self.calibration_coefficient = _to_float(params1[0])
1233
- self.weir_breadth = _to_float(params1[1])
1234
- self.weir_elevation = _to_float(params1[2])
1235
- self.modular_limit = _to_float(params1[3])
1214
+ self.calibration_coefficient = to_float(params1[0])
1215
+ self.weir_breadth = to_float(params1[1])
1216
+ self.weir_elevation = to_float(params1[2])
1217
+ self.modular_limit = to_float(params1[3])
1236
1218
 
1237
1219
  # Second parameter line
1238
1220
  params2 = split_10_char(f"{block[3]:<20}")
1239
- self.upstream_crest_height = _to_float(params2[0])
1240
- self.downstream_crest_height = _to_float(params2[1])
1221
+ self.upstream_crest_height = to_float(params2[0])
1222
+ self.downstream_crest_height = to_float(params2[1])
1241
1223
 
1242
1224
  def _write(self):
1243
1225
  """Function to write a valid CRUMP block"""
1244
1226
  _validate_unit(self)
1245
- header = "CRUMP " + self.comment
1227
+ header = self._create_header()
1246
1228
  labels = join_n_char_ljust(
1247
1229
  self._label_len,
1248
1230
  self.name,
@@ -1334,30 +1316,30 @@ class FLAT_V_WEIR(Unit): # noqa: N801
1334
1316
  self.ds_label = labels[1]
1335
1317
  self.us_remote_label = labels[2]
1336
1318
  self.ds_remote_label = labels[3]
1337
- self.comment = block[0].replace("FLAT-V WEIR", "").strip()
1319
+ self.comment = self._remove_unit_name(block[0])
1338
1320
 
1339
1321
  # First parameter line
1340
1322
  params1 = split_10_char(f"{block[2]:<90}")
1341
- self.calibration_coefficient = _to_float(params1[0])
1342
- self.weir_breadth = _to_float(params1[1])
1343
- self.weir_elevation = _to_float(params1[2])
1344
- self.modular_limit = _to_float(params1[3])
1345
- self.v_slope = _to_float(params1[4])
1346
- self.side_slope = _to_float(params1[5])
1347
- self.ds_face_slope = _to_float(params1[6])
1348
- self.coriolis_coefficient = _to_float(params1[7])
1349
- self.bank_top_elevation = _to_float(params1[8])
1323
+ self.calibration_coefficient = to_float(params1[0])
1324
+ self.weir_breadth = to_float(params1[1])
1325
+ self.weir_elevation = to_float(params1[2])
1326
+ self.modular_limit = to_float(params1[3])
1327
+ self.v_slope = to_float(params1[4])
1328
+ self.side_slope = to_float(params1[5])
1329
+ self.ds_face_slope = to_float(params1[6])
1330
+ self.coriolis_coefficient = to_float(params1[7])
1331
+ self.bank_top_elevation = to_float(params1[8])
1350
1332
 
1351
1333
  # Second parameter line
1352
1334
  params2 = split_10_char(f"{block[3]:<20}")
1353
- self.upstream_crest_height = _to_float(params2[0])
1354
- self.downstream_crest_height = _to_float(params2[1])
1335
+ self.upstream_crest_height = to_float(params2[0])
1336
+ self.downstream_crest_height = to_float(params2[1])
1355
1337
 
1356
1338
  def _write(self):
1357
1339
  """Function to write a valid FLAT-V WEIR block"""
1358
1340
 
1359
1341
  _validate_unit(self)
1360
- header = "FLAT-V WEIR " + self.comment
1342
+ header = self._create_header()
1361
1343
  labels = join_n_char_ljust(
1362
1344
  self._label_len,
1363
1345
  self.name,
@@ -1427,158 +1409,6 @@ class FLAT_V_WEIR(Unit): # noqa: N801
1427
1409
  setattr(self, param, val)
1428
1410
 
1429
1411
 
1430
- class RESERVOIR(Unit): # NOT CURRENTLY IN USE
1431
- """Class to hold and process RESERVOIR unit type
1432
-
1433
- Args:
1434
- name (str, optional): Unit name.
1435
- comment (str, optional): Comment included in unit.
1436
- all_labels (str, optional): Unlimited number of labels - not including first label (name).
1437
- easting (float, optional): Easting coordinate of reservoir reference point (not used in computations).
1438
- northing (float, optional): Northing coordinate of reservoir reference point (not used in computations).
1439
- runoff_factor (float, optional): Rainfall runoff factor.
1440
- num_pairs (float, optional): Number of elevation/area pairs.
1441
- lat1 (str, optional): First lateral inflow label.
1442
- lat2 (str, optional): Second lateral inflow label.
1443
- lat3 (str, optional): Third lateral inflow label.
1444
- lat4 (str, optional): Fourth lateral inflow label.
1445
- data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
1446
- Columns are ``'Elevation','Plan Area'``
1447
-
1448
- Returns:
1449
- RESERVOIR: Flood Modeller RESERVOIR Unit class object"""
1450
-
1451
- _unit = "RESERVOIR"
1452
-
1453
- def _read(self, block):
1454
- """Function to read a given RESERVOIR WEIR block and store data as class attributes"""
1455
-
1456
- # Extends label line to be correct length before splitting to pick up blank labels
1457
- num_labels = len(block[1]) // self._label_len
1458
- labels = split_n_char(f"{block[1]:<{num_labels*self._label_len}}", self._label_len)
1459
- self.name = labels[0]
1460
- self.all_labels = labels[0 : len(labels)]
1461
- self.comment = block[0].replace("RESERVOIR", "").strip()
1462
-
1463
- # Option 1 (runs if comment == "#revision#1")
1464
- if self.comment == "#revision#1":
1465
- # Lateral inflow labels
1466
- lateral_labels = split_n_char(f"{block[2]:<{4*self._label_len}}", self._label_len)
1467
- self.lat1 = lateral_labels[0]
1468
- self.lat2 = lateral_labels[1]
1469
- self.lat3 = lateral_labels[2]
1470
- self.lat4 = lateral_labels[3]
1471
-
1472
- # Number of pairs of data
1473
- self.num_pairs = _to_int(block[3])
1474
-
1475
- # Reservoir section data
1476
- data_list = []
1477
- for row in block[4 : len(block) - 1]:
1478
- row_split = split_10_char(f"{row:<20}")
1479
- elevation = _to_float(row_split[0]) # elevation
1480
- plan_area = _to_float(row_split[1]) # plan area
1481
- data_list.append([elevation, plan_area])
1482
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1483
- self.data = reservoir_data
1484
-
1485
- # Coordinate data
1486
- coordinate_data = split_n_char(
1487
- f"{block[len(block)-1]:<{3*self._label_len}}",
1488
- self._label_len,
1489
- )
1490
- self.easting = _to_float(coordinate_data[0])
1491
- self.northing = _to_float(coordinate_data[1])
1492
- self.runoff_factor = _to_float(coordinate_data[2])
1493
- else: # Option 2 (runs if comment != "#revision#1")
1494
- # Number of pairs of data
1495
- self.num_pairs = _to_int(block[2])
1496
-
1497
- # Reservoir section data
1498
- data_list = []
1499
- for row in block[3:]:
1500
- row_split = split_10_char(f"{row:<20}")
1501
- elevation = _to_float(row_split[0]) # elevation
1502
- plan_area = _to_float(row_split[1]) # plan area
1503
- data_list.append([elevation, plan_area])
1504
- reservoir_data = pd.DataFrame(data_list, columns=["Elevation", "Plan Area"])
1505
- self.data = reservoir_data
1506
-
1507
- def _write(self):
1508
- """Function to write a valid RESERVOIR block"""
1509
-
1510
- _validate_unit(self)
1511
- header = "RESERVOIR " + self.comment
1512
- self.labels = " ".join(self.all_labels)
1513
- block = [header, self.labels]
1514
-
1515
- # Option 1 (runs if comment == "#revision#1")
1516
- if self.comment == "#revision#1":
1517
- # Lateral inflow labels
1518
- lat_labels = join_12_char_ljust(self.lat1, self.lat2, self.lat3, self.lat4)
1519
- block.append(lat_labels)
1520
-
1521
- # Number of pairs of data
1522
- block.append(join_12_char_ljust(self.num_pairs))
1523
-
1524
- # Reservoir section data
1525
- section_data = [
1526
- join_12_char_ljust(elevation, plan_area)
1527
- for _, elevation, plan_area, in self.data.itertuples()
1528
- ]
1529
- block.extend(section_data)
1530
-
1531
- # Coordinate data
1532
- coords = join_12_char_ljust(self.easting, self.northing, self.runoff_factor)
1533
- block.append(coords)
1534
- else: # Option 2 (runs if comment != "#revision#1")
1535
- # Number of pairs of data
1536
- block.append(join_12_char_ljust(self.num_pairs))
1537
-
1538
- # Reservoir section data
1539
- section_data = [
1540
- join_12_char_ljust(elevation, plan_area)
1541
- for _, elevation, plan_area, in self.data.itertuples()
1542
- ]
1543
- block.extend(section_data)
1544
-
1545
- return block
1546
-
1547
- def _create_from_blank( # noqa: PLR0913
1548
- self,
1549
- name="new_reservoir",
1550
- comment="",
1551
- easting=0.0,
1552
- northing=0.0,
1553
- runoff_factor=0.0,
1554
- num_pairs=1,
1555
- data=None,
1556
- lat1="",
1557
- lat2="",
1558
- lat3="",
1559
- lat4="",
1560
- ):
1561
- for param, val in {
1562
- "name": name,
1563
- "comment": comment,
1564
- "easting": easting,
1565
- "northing": northing,
1566
- "runoff_factor": runoff_factor,
1567
- "num_pairs": num_pairs,
1568
- "lat1": lat1,
1569
- "lat2": lat2,
1570
- "lat3": lat3,
1571
- "lat4": lat4,
1572
- }.items():
1573
- setattr(self, param, val)
1574
-
1575
- self.data = (
1576
- data
1577
- if isinstance(data, pd.DataFrame)
1578
- else pd.DataFrame([[0.0, 0.0]], columns=["Elevation", "Plan Area"])
1579
- )
1580
-
1581
-
1582
1412
  class OUTFALL(Unit):
1583
1413
  """Class to hold and process OUTFALL unit type
1584
1414
 
@@ -1606,34 +1436,34 @@ class OUTFALL(Unit):
1606
1436
 
1607
1437
  def _read(self, block):
1608
1438
  """Function to read a given OUTFALL block and store data as class attributes"""
1609
- self._subtype = block[1].split(" ")[0].strip()
1439
+ self._subtype = self._get_first_word(block[1])
1610
1440
  self.flapped = self.subtype == "FLAPPED"
1611
1441
 
1612
1442
  # Extends label line to be correct length before splitting to pick up blank labels
1613
1443
  labels = split_n_char(f"{block[2]:<{2*self._label_len}}", self._label_len)
1614
1444
  self.name = labels[0]
1615
1445
  self.ds_label = labels[1]
1616
- self.comment = block[0].replace("OUTFALL", "").strip()
1446
+ self.comment = self._remove_unit_name(block[0])
1617
1447
 
1618
1448
  # First parameter line
1619
1449
  params1 = split_10_char(f"{block[3]:<60}")
1620
- self.invert = _to_float(params1[0])
1621
- self.soffit = _to_float(params1[1])
1622
- self.bore_area = _to_float(params1[2])
1623
- self.upstream_sill = _to_float(params1[3])
1624
- self.downstream_sill = _to_float(params1[4])
1625
- self.shape = _to_str(params1[5], "RECTANGLE")
1450
+ self.invert = to_float(params1[0])
1451
+ self.soffit = to_float(params1[1])
1452
+ self.bore_area = to_float(params1[2])
1453
+ self.upstream_sill = to_float(params1[3])
1454
+ self.downstream_sill = to_float(params1[4])
1455
+ self.shape = to_str(params1[5], "RECTANGLE")
1626
1456
 
1627
1457
  # Second parameter line
1628
1458
  params2 = split_10_char(block[4])
1629
- self.weir_flow = _to_float(params2[0], 1.0)
1630
- self.surcharged_flow = _to_float(params2[1], 1.0)
1631
- self.modular_limit = _to_float(params2[2], 0.7)
1459
+ self.weir_flow = to_float(params2[0], 1.0)
1460
+ self.surcharged_flow = to_float(params2[1], 1.0)
1461
+ self.modular_limit = to_float(params2[2], 0.7)
1632
1462
 
1633
1463
  def _write(self):
1634
1464
  """Function to write a valid OUTFALL block"""
1635
1465
  _validate_unit(self) # Function to check the params are valid for CONDUIT unit
1636
- header = "OUTFALL " + self.comment
1466
+ header = self._create_header()
1637
1467
  labels = join_n_char_ljust(self._label_len, self.name, self.ds_label)
1638
1468
 
1639
1469
  self._subtype = "FLAPPED" if self.flapped else "OPEN"