floodmodeller-api 0.5.0.post1__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.
- floodmodeller_api/__init__.py +11 -1
- floodmodeller_api/_base.py +55 -36
- floodmodeller_api/backup.py +15 -12
- floodmodeller_api/dat.py +191 -121
- floodmodeller_api/diff.py +4 -4
- floodmodeller_api/hydrology_plus/hydrology_plus_export.py +15 -14
- floodmodeller_api/ied.py +8 -10
- floodmodeller_api/ief.py +56 -42
- floodmodeller_api/ief_flags.py +1 -1
- floodmodeller_api/inp.py +7 -10
- floodmodeller_api/logs/lf.py +25 -26
- floodmodeller_api/logs/lf_helpers.py +20 -20
- floodmodeller_api/logs/lf_params.py +1 -5
- floodmodeller_api/mapping.py +11 -2
- floodmodeller_api/test/__init__.py +2 -2
- floodmodeller_api/test/conftest.py +2 -3
- floodmodeller_api/test/test_backup.py +2 -2
- floodmodeller_api/test/test_conveyance.py +13 -7
- floodmodeller_api/test/test_dat.py +168 -20
- floodmodeller_api/test/test_data/EX18_DAT_expected.json +164 -144
- floodmodeller_api/test/test_data/EX3_DAT_expected.json +6 -2
- floodmodeller_api/test/test_data/EX6_DAT_expected.json +12 -46
- floodmodeller_api/test/test_data/encoding_test_cp1252.dat +1081 -0
- floodmodeller_api/test/test_data/encoding_test_utf8.dat +1081 -0
- floodmodeller_api/test/test_data/integrated_bridge/AR_NoSP_NoBl_2O_NO_OneFRC.ied +33 -0
- floodmodeller_api/test/test_data/integrated_bridge/AR_vSP_25pc_1O.ied +32 -0
- floodmodeller_api/test/test_data/integrated_bridge/PL_vSP_25pc_1O.ied +34 -0
- floodmodeller_api/test/test_data/integrated_bridge/SBTwoFRCsStaggered.IED +32 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_NoSP_NoBl_OR_RN.ied +28 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_SP_NoBl_OR_frc_PT2-5_RN.ied +34 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_fSP_NoBl_1O.ied +30 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_nSP_NoBl_1O.ied +49 -0
- floodmodeller_api/test/test_data/integrated_bridge/US_vSP_NoBl_2O_Para.ied +35 -0
- floodmodeller_api/test/test_data/integrated_bridge.dat +40 -0
- floodmodeller_api/test/test_data/network.ied +2 -2
- floodmodeller_api/test/test_data/network_dat_expected.json +141 -243
- floodmodeller_api/test/test_data/network_ied_expected.json +2 -2
- floodmodeller_api/test/test_data/network_with_comments.ied +2 -2
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX17_expected.json +69 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.csv +20 -0
- floodmodeller_api/test/test_data/structure_logs/EX18_expected.json +292 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.csv +4 -0
- floodmodeller_api/test/test_data/structure_logs/EX6_expected.json +35 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_flow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_fr.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_stage.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_state.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzn_velocity.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_left_fp_mode.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_link_inflow.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_max.csv +87 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_h.csv +182 -0
- floodmodeller_api/test/test_data/tabular_csv_outputs/network_zzx_right_fp_mode.csv +182 -0
- floodmodeller_api/test/test_flowtimeprofile.py +2 -2
- floodmodeller_api/test/test_hydrology_plus_export.py +4 -2
- floodmodeller_api/test/test_ied.py +3 -3
- floodmodeller_api/test/test_ief.py +12 -4
- floodmodeller_api/test/test_inp.py +2 -2
- floodmodeller_api/test/test_integrated_bridge.py +159 -0
- floodmodeller_api/test/test_json.py +14 -13
- floodmodeller_api/test/test_logs_lf.py +50 -29
- floodmodeller_api/test/test_read_file.py +1 -0
- floodmodeller_api/test/test_river.py +12 -12
- floodmodeller_api/test/test_tool.py +8 -5
- floodmodeller_api/test/test_toolbox_structure_log.py +148 -158
- floodmodeller_api/test/test_xml2d.py +14 -16
- floodmodeller_api/test/test_zz.py +143 -0
- floodmodeller_api/to_from_json.py +9 -9
- floodmodeller_api/tool.py +15 -11
- floodmodeller_api/toolbox/example_tool.py +5 -1
- floodmodeller_api/toolbox/model_build/add_siltation_definition.py +13 -9
- floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +500 -194
- floodmodeller_api/toolbox/model_build/structure_log_definition.py +5 -1
- floodmodeller_api/units/__init__.py +15 -0
- floodmodeller_api/units/_base.py +87 -20
- floodmodeller_api/units/_helpers.py +343 -0
- floodmodeller_api/units/boundaries.py +59 -71
- floodmodeller_api/units/comment.py +1 -1
- floodmodeller_api/units/conduits.py +57 -54
- floodmodeller_api/units/connectors.py +112 -0
- floodmodeller_api/units/controls.py +107 -0
- floodmodeller_api/units/conveyance.py +1 -1
- floodmodeller_api/units/iic.py +2 -9
- floodmodeller_api/units/losses.py +44 -45
- floodmodeller_api/units/sections.py +52 -51
- floodmodeller_api/units/structures.py +361 -531
- floodmodeller_api/units/units.py +27 -26
- floodmodeller_api/units/unsupported.py +5 -7
- floodmodeller_api/units/variables.py +2 -2
- floodmodeller_api/urban1d/_base.py +13 -17
- floodmodeller_api/urban1d/conduits.py +11 -21
- floodmodeller_api/urban1d/general_parameters.py +1 -1
- floodmodeller_api/urban1d/junctions.py +7 -11
- floodmodeller_api/urban1d/losses.py +13 -17
- floodmodeller_api/urban1d/outfalls.py +18 -22
- floodmodeller_api/urban1d/raingauges.py +5 -10
- floodmodeller_api/urban1d/subsections.py +5 -4
- floodmodeller_api/urban1d/xsections.py +14 -17
- floodmodeller_api/util.py +23 -6
- floodmodeller_api/validation/parameters.py +7 -3
- floodmodeller_api/validation/urban_parameters.py +1 -4
- floodmodeller_api/validation/validation.py +11 -5
- floodmodeller_api/version.py +1 -1
- floodmodeller_api/xml2d.py +27 -31
- floodmodeller_api/xml2d_template.py +1 -1
- floodmodeller_api/zz.py +539 -0
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/LICENSE.txt +1 -1
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/METADATA +30 -16
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/RECORD +116 -83
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/WHEEL +1 -1
- floodmodeller_api/test/test_zzn.py +0 -36
- floodmodeller_api/units/helpers.py +0 -123
- floodmodeller_api/zzn.py +0 -414
- /floodmodeller_api/test/test_data/{network_from_tabularCSV.csv → tabular_csv_outputs/network_zzn_max.csv} +0 -0
- {floodmodeller_api-0.5.0.post1.dist-info → floodmodeller_api-0.5.2.dist-info}/entry_points.txt +0 -0
- {floodmodeller_api-0.5.0.post1.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)
|
|
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.
|
|
@@ -20,14 +20,13 @@ from floodmodeller_api.validation import _validate_unit
|
|
|
20
20
|
from floodmodeller_api.validation.parameters import parameter_options
|
|
21
21
|
|
|
22
22
|
from ._base import Unit
|
|
23
|
-
from .
|
|
24
|
-
_to_data_list,
|
|
25
|
-
_to_float,
|
|
26
|
-
_to_int,
|
|
27
|
-
_to_str,
|
|
23
|
+
from ._helpers import (
|
|
28
24
|
join_10_char,
|
|
29
25
|
join_n_char_ljust,
|
|
30
26
|
split_10_char,
|
|
27
|
+
to_data_list,
|
|
28
|
+
to_float,
|
|
29
|
+
to_str,
|
|
31
30
|
)
|
|
32
31
|
|
|
33
32
|
|
|
@@ -82,13 +81,6 @@ class QTBDY(Unit):
|
|
|
82
81
|
}.items():
|
|
83
82
|
setattr(self, param, val)
|
|
84
83
|
|
|
85
|
-
# AL Since this is most likely used when building a model,
|
|
86
|
-
# AL it would be nice to have a "name generator" to create
|
|
87
|
-
# AL a unique name with each call (ie new_qtbdy_12345 then new_qtbdy_02508)
|
|
88
|
-
# JP Yes this is a good idea, although I'm not sure how it would be best implemented
|
|
89
|
-
# since any two instances of the class being initialised would be unaware of each other?
|
|
90
|
-
# There is always the option to pass a name when constrcuting the class which may be better
|
|
91
|
-
|
|
92
84
|
self.data = (
|
|
93
85
|
data
|
|
94
86
|
if isinstance(data, pd.Series)
|
|
@@ -98,21 +90,21 @@ class QTBDY(Unit):
|
|
|
98
90
|
def _read(self, qtbdy_block):
|
|
99
91
|
"""Function to read a given QTBDY block and store data as class attributes"""
|
|
100
92
|
self.name = qtbdy_block[1][: self._label_len].strip()
|
|
101
|
-
self.comment = qtbdy_block[0]
|
|
93
|
+
self.comment = self._remove_unit_name(qtbdy_block[0])
|
|
102
94
|
qtbdy_params = split_10_char(f"{qtbdy_block[2]:<90}")
|
|
103
95
|
self.nrows = int(qtbdy_params[0])
|
|
104
|
-
self.timeoffset =
|
|
105
|
-
self._something =
|
|
106
|
-
self.timeunit =
|
|
107
|
-
self.extendmethod =
|
|
108
|
-
self.interpmethod =
|
|
109
|
-
self.flowmultiplier =
|
|
110
|
-
self.minflow =
|
|
111
|
-
self.allow_override =
|
|
96
|
+
self.timeoffset = to_float(qtbdy_params[1])
|
|
97
|
+
self._something = to_float(qtbdy_params[2])
|
|
98
|
+
self.timeunit = to_str(qtbdy_params[3], "HOURS", check_float=True)
|
|
99
|
+
self.extendmethod = to_str(qtbdy_params[4], "EXTEND")
|
|
100
|
+
self.interpmethod = to_str(qtbdy_params[5], "LINEAR")
|
|
101
|
+
self.flowmultiplier = to_float(qtbdy_params[6])
|
|
102
|
+
self.minflow = to_float(qtbdy_params[7])
|
|
103
|
+
self.allow_override = to_str(qtbdy_params[8], "OVERRIDE") # ''/OVERRIDE or NOOVERRIDE
|
|
112
104
|
data_list = (
|
|
113
|
-
|
|
105
|
+
to_data_list(qtbdy_block[3:], date_col=1)
|
|
114
106
|
if self.timeunit == "DATES"
|
|
115
|
-
else
|
|
107
|
+
else to_data_list(qtbdy_block[3:])
|
|
116
108
|
)
|
|
117
109
|
|
|
118
110
|
self.data = pd.DataFrame(data_list, columns=["Flow", "Time"])
|
|
@@ -122,7 +114,7 @@ class QTBDY(Unit):
|
|
|
122
114
|
def _write(self):
|
|
123
115
|
"""Function to write a valid QTBDY block"""
|
|
124
116
|
_validate_unit(self) # Function to check the params are valid for QTBDY
|
|
125
|
-
header =
|
|
117
|
+
header = self._create_header()
|
|
126
118
|
name = self.name[: self._label_len]
|
|
127
119
|
self.nrows = len(self.data)
|
|
128
120
|
|
|
@@ -195,18 +187,18 @@ class HTBDY(Unit):
|
|
|
195
187
|
def _read(self, htbdy_block):
|
|
196
188
|
"""Function to read a given HTBDY block and store data as class attributes"""
|
|
197
189
|
self.name = htbdy_block[1][: self._label_len].strip()
|
|
198
|
-
self.comment = htbdy_block[0]
|
|
190
|
+
self.comment = self._remove_unit_name(htbdy_block[0])
|
|
199
191
|
htbdy_params = split_10_char(f"{htbdy_block[2]:<50}")
|
|
200
192
|
self.nrows = int(htbdy_params[0])
|
|
201
|
-
self._something =
|
|
202
|
-
self.timeunit =
|
|
203
|
-
self.extendmethod =
|
|
204
|
-
self.interpmethod =
|
|
193
|
+
self._something = to_str(htbdy_params[1], "")
|
|
194
|
+
self.timeunit = to_str(htbdy_params[2], "HOURS", check_float=True)
|
|
195
|
+
self.extendmethod = to_str(htbdy_params[3], "EXTEND")
|
|
196
|
+
self.interpmethod = to_str(htbdy_params[4], "LINEAR")
|
|
205
197
|
|
|
206
198
|
data_list = (
|
|
207
|
-
|
|
199
|
+
to_data_list(htbdy_block[3:], date_col=1)
|
|
208
200
|
if self.timeunit == "DATES"
|
|
209
|
-
else
|
|
201
|
+
else to_data_list(htbdy_block[3:])
|
|
210
202
|
)
|
|
211
203
|
|
|
212
204
|
self.data = pd.DataFrame(data_list, columns=["Stage", "Time"])
|
|
@@ -221,7 +213,7 @@ class HTBDY(Unit):
|
|
|
221
213
|
def _write(self):
|
|
222
214
|
"""Function to write a valid HTBDY block"""
|
|
223
215
|
_validate_unit(self) # Function to check the params are valid for HTBDY
|
|
224
|
-
header =
|
|
216
|
+
header = self._create_header()
|
|
225
217
|
name = self.name
|
|
226
218
|
self.nrows = len(self.data)
|
|
227
219
|
|
|
@@ -273,12 +265,12 @@ class QHBDY(Unit):
|
|
|
273
265
|
def _read(self, qhbdy_block):
|
|
274
266
|
"""Function to read a given QHBDY block and store data as class attributes"""
|
|
275
267
|
self.name = qhbdy_block[1][: self._label_len].strip()
|
|
276
|
-
self.comment = qhbdy_block[0]
|
|
268
|
+
self.comment = self._remove_unit_name(qhbdy_block[0])
|
|
277
269
|
qhbdy_params = split_10_char(f"{qhbdy_block[2]:<30}")
|
|
278
270
|
self.nrows = int(qhbdy_params[0])
|
|
279
|
-
self.interpmethod =
|
|
271
|
+
self.interpmethod = to_str(qhbdy_params[2], "LINEAR")
|
|
280
272
|
|
|
281
|
-
data_list =
|
|
273
|
+
data_list = to_data_list(qhbdy_block[3:])
|
|
282
274
|
|
|
283
275
|
self.data = pd.DataFrame(data_list, columns=["Flow", "Stage"])
|
|
284
276
|
self.data = self.data.set_index("Stage")
|
|
@@ -287,7 +279,7 @@ class QHBDY(Unit):
|
|
|
287
279
|
def _write(self):
|
|
288
280
|
"""Function to write a valid QHBDY block"""
|
|
289
281
|
_validate_unit(self) # Function to check the params are valid for QHBDY
|
|
290
|
-
header =
|
|
282
|
+
header = self._create_header()
|
|
291
283
|
name = self.name
|
|
292
284
|
self.nrows = len(self.data)
|
|
293
285
|
|
|
@@ -348,43 +340,40 @@ class REFHBDY(Unit):
|
|
|
348
340
|
"""Function to read a given REFHBDY block and store data as class attributes"""
|
|
349
341
|
# line 1 & 2
|
|
350
342
|
# Extract comment and revision number
|
|
351
|
-
|
|
352
|
-
self._revision = _to_int(b[0], 1)
|
|
353
|
-
self.comment = b[1:].strip()
|
|
343
|
+
self._revision, self.comment = self._get_revision_and_comment(refhbdy_block[0])
|
|
354
344
|
self.name = refhbdy_block[1][: self._label_len].strip()
|
|
355
345
|
|
|
356
346
|
# line 3
|
|
357
347
|
refhbdy_params1 = split_10_char(refhbdy_block[2])
|
|
358
|
-
|
|
359
|
-
self._something = _to_float(refhbdy_params1[0])
|
|
348
|
+
self._unknown_param_1 = to_float(refhbdy_params1[0])
|
|
360
349
|
self.easting = int(float(refhbdy_params1[1]))
|
|
361
350
|
self.northing = int(float(refhbdy_params1[2]))
|
|
362
351
|
|
|
363
352
|
# line 4
|
|
364
353
|
refhbdy_opts = split_10_char(f"{refhbdy_block[3]:<90}")
|
|
365
|
-
self.time_delay =
|
|
354
|
+
self.time_delay = to_float(refhbdy_opts[0])
|
|
366
355
|
# SD / timestep must be odd interval
|
|
367
|
-
self.timestep =
|
|
356
|
+
self.timestep = to_float(refhbdy_opts[1])
|
|
368
357
|
# '' : Full hydrograph, 'pfonly' : peak flow, 'bfonly' : baseflow only
|
|
369
358
|
self.sim_type = refhbdy_opts[2]
|
|
370
|
-
self.scale_method =
|
|
371
|
-
self.scale_value =
|
|
372
|
-
self.boundary_type =
|
|
373
|
-
self.scale_type =
|
|
374
|
-
self.minflow =
|
|
359
|
+
self.scale_method = to_str(refhbdy_opts[3], "SCALEFACT") # PEAKVALUE or SCALEFACT
|
|
360
|
+
self.scale_value = to_float(refhbdy_opts[4], 1.0)
|
|
361
|
+
self.boundary_type = to_str(refhbdy_opts[5], "HYDROGRAPH") # HYDROGRAPH or HYETOGRAPH
|
|
362
|
+
self.scale_type = to_str(refhbdy_opts[6], "FULL") # FULL or RUNOFF
|
|
363
|
+
self.minflow = to_float(refhbdy_opts[7])
|
|
375
364
|
self.allow_override = refhbdy_opts[8] # ''/OVERRIDE or NOOVERRIDE
|
|
376
365
|
|
|
377
366
|
# line 5
|
|
378
367
|
refhbdy_params2 = split_10_char(f"{refhbdy_block[4]:<60}")
|
|
379
|
-
self.area =
|
|
368
|
+
self.area = to_float(refhbdy_params2[0])
|
|
380
369
|
try:
|
|
381
370
|
# Maintain SAAR as integer if already is, else use float
|
|
382
371
|
self.saar = int(refhbdy_params2[1])
|
|
383
372
|
except ValueError:
|
|
384
373
|
self.saar = float(refhbdy_params2[1])
|
|
385
|
-
self.urbext =
|
|
386
|
-
self.season =
|
|
387
|
-
self.calc_source =
|
|
374
|
+
self.urbext = to_float(refhbdy_params2[2])
|
|
375
|
+
self.season = to_str(refhbdy_params2[3], "DEFAULT") # DEFAULT, SUMMER or WINTER
|
|
376
|
+
self.calc_source = to_str(refhbdy_params2[4], "DLL") # DLL or REPORT
|
|
388
377
|
self.use_urban_subdivisions = refhbdy_params2[5] != ""
|
|
389
378
|
if self.use_urban_subdivisions:
|
|
390
379
|
# Just keeping this raw for now as unlikely to be used.
|
|
@@ -399,37 +388,36 @@ class REFHBDY(Unit):
|
|
|
399
388
|
|
|
400
389
|
# line 6
|
|
401
390
|
rainfall_params1 = split_10_char(rainfall_params1)
|
|
402
|
-
self.storm_area =
|
|
403
|
-
self.storm_duration =
|
|
404
|
-
|
|
405
|
-
self._something2 = _to_float(rainfall_params1[2])
|
|
391
|
+
self.storm_area = to_float(rainfall_params1[0])
|
|
392
|
+
self.storm_duration = to_float(rainfall_params1[1])
|
|
393
|
+
self._unknown_param_2 = to_float(rainfall_params1[2])
|
|
406
394
|
|
|
407
395
|
# line 7
|
|
408
396
|
self.rainfall_comment = rainfall_params2[20:]
|
|
409
397
|
rainfall_params2 = split_10_char(rainfall_params2[:20])
|
|
410
398
|
self.arf_method = rainfall_params2[1]
|
|
411
|
-
self.
|
|
399
|
+
self._unknown_param_3 = rainfall_params2[0]
|
|
412
400
|
|
|
413
401
|
# line 8
|
|
414
402
|
rainfall_params3 = split_10_char(rainfall_params3)
|
|
415
|
-
self.observed_rainfall_depth =
|
|
416
|
-
self.return_period =
|
|
417
|
-
self.arf =
|
|
418
|
-
self.ddf_c =
|
|
419
|
-
self.ddf_d1 =
|
|
420
|
-
self.ddf_d2 =
|
|
421
|
-
self.ddf_d3 =
|
|
422
|
-
self.ddf_e =
|
|
423
|
-
self.ddf_f =
|
|
403
|
+
self.observed_rainfall_depth = to_float(rainfall_params3[0])
|
|
404
|
+
self.return_period = to_float(rainfall_params3[1])
|
|
405
|
+
self.arf = to_float(rainfall_params3[2])
|
|
406
|
+
self.ddf_c = to_float(rainfall_params3[3])
|
|
407
|
+
self.ddf_d1 = to_float(rainfall_params3[4])
|
|
408
|
+
self.ddf_d2 = to_float(rainfall_params3[5])
|
|
409
|
+
self.ddf_d3 = to_float(rainfall_params3[6])
|
|
410
|
+
self.ddf_e = to_float(rainfall_params3[7])
|
|
411
|
+
self.ddf_f = to_float(rainfall_params3[8])
|
|
424
412
|
|
|
425
413
|
def _write(self):
|
|
426
414
|
"""Function to write a valid REFHBDY block"""
|
|
427
415
|
_validate_unit(self) # Function to check the params are valid for QTBDY
|
|
428
|
-
header =
|
|
416
|
+
header = self._create_header(include_revision=True)
|
|
429
417
|
name = self.name[: self._label_len]
|
|
430
418
|
|
|
431
419
|
refhbdy_block = [header, name]
|
|
432
|
-
line3 = join_10_char(self.
|
|
420
|
+
line3 = join_10_char(self._unknown_param_1, self.easting, self.northing)
|
|
433
421
|
self.sim_type = (
|
|
434
422
|
"" if self.sim_type.upper() == "FULL" else self.sim_type
|
|
435
423
|
) # Allow 'full' as an option
|
|
@@ -454,8 +442,8 @@ class REFHBDY(Unit):
|
|
|
454
442
|
if self.use_urban_subdivisions:
|
|
455
443
|
refhbdy_block.extend(self._urban_refh_data)
|
|
456
444
|
|
|
457
|
-
line6 = join_10_char(self.storm_area, self.storm_duration, self.
|
|
458
|
-
line7 = join_10_char(self.
|
|
445
|
+
line6 = join_10_char(self.storm_area, self.storm_duration, self._unknown_param_2)
|
|
446
|
+
line7 = join_10_char(self._unknown_param_3, self.arf_method) + self.rainfall_comment
|
|
459
447
|
line8 = join_10_char(
|
|
460
448
|
self.observed_rainfall_depth,
|
|
461
449
|
self.return_period,
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Flood Modeller Python API
|
|
3
|
-
Copyright (C)
|
|
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.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
Flood Modeller Python API
|
|
3
|
-
Copyright (C)
|
|
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,19 +14,21 @@ 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
|
+
import logging
|
|
18
|
+
|
|
17
19
|
import pandas as pd
|
|
18
20
|
|
|
19
21
|
from floodmodeller_api.validation import _validate_unit
|
|
20
22
|
|
|
21
23
|
from ._base import Unit
|
|
22
|
-
from .
|
|
23
|
-
_to_float,
|
|
24
|
-
_to_int,
|
|
25
|
-
_to_str,
|
|
24
|
+
from ._helpers import (
|
|
26
25
|
join_10_char,
|
|
27
26
|
join_n_char_ljust,
|
|
28
27
|
split_10_char,
|
|
29
28
|
split_n_char,
|
|
29
|
+
to_float,
|
|
30
|
+
to_int,
|
|
31
|
+
to_str,
|
|
30
32
|
)
|
|
31
33
|
|
|
32
34
|
|
|
@@ -179,93 +181,94 @@ class CONDUIT(Unit):
|
|
|
179
181
|
|
|
180
182
|
def _read(self, c_block): # noqa: PLR0915
|
|
181
183
|
"""Function to read a given CONDUIT block and store data as class attributes"""
|
|
182
|
-
self._subtype = c_block[1]
|
|
184
|
+
self._subtype = self._get_first_word(c_block[1])
|
|
183
185
|
# Extends label line to be correct length before splitting to pick up blank labels
|
|
184
186
|
labels = split_n_char(f"{c_block[2]:<{2*self._label_len}}", self._label_len)
|
|
185
187
|
self.name = labels[0]
|
|
186
188
|
self.spill = labels[1]
|
|
187
|
-
self.comment = c_block[0]
|
|
189
|
+
self.comment = self._remove_unit_name(c_block[0])
|
|
188
190
|
|
|
189
191
|
# Read CIRCULAR type unit
|
|
190
192
|
if self._subtype == "CIRCULAR":
|
|
191
193
|
# Read Params
|
|
192
|
-
self.dist_to_next =
|
|
194
|
+
self.dist_to_next = to_float(split_10_char(c_block[3])[0])
|
|
193
195
|
self.friction_eq = c_block[4].strip()
|
|
194
196
|
params = split_10_char(f"{c_block[5]:<80}")
|
|
195
|
-
self.invert =
|
|
196
|
-
self.diameter =
|
|
197
|
-
self.use_bottom_slot =
|
|
198
|
-
self.bottom_slot_dist =
|
|
199
|
-
self.bottom_slot_depth =
|
|
200
|
-
self.use_top_slot =
|
|
201
|
-
self.top_slot_dist =
|
|
202
|
-
self.top_slot_depth =
|
|
197
|
+
self.invert = to_float(params[0])
|
|
198
|
+
self.diameter = to_float(params[1])
|
|
199
|
+
self.use_bottom_slot = to_str(params[2], "GLOBAL")
|
|
200
|
+
self.bottom_slot_dist = to_float(params[3])
|
|
201
|
+
self.bottom_slot_depth = to_float(params[4])
|
|
202
|
+
self.use_top_slot = to_str(params[5], "GLOBAL")
|
|
203
|
+
self.top_slot_dist = to_float(params[6])
|
|
204
|
+
self.top_slot_depth = to_float(params[7])
|
|
203
205
|
friction_params = split_10_char(f"{c_block[6]:<20}")
|
|
204
|
-
self.friction_below_axis =
|
|
205
|
-
self.friction_above_axis =
|
|
206
|
+
self.friction_below_axis = to_float(friction_params[0])
|
|
207
|
+
self.friction_above_axis = to_float(friction_params[1])
|
|
206
208
|
|
|
207
209
|
elif self._subtype == "RECTANGULAR":
|
|
208
210
|
# Read Params
|
|
209
|
-
self.dist_to_next =
|
|
211
|
+
self.dist_to_next = to_float(split_10_char(c_block[3])[0])
|
|
210
212
|
self.friction_eq = c_block[4].strip()
|
|
211
213
|
params = split_10_char(f"{c_block[5]:<90}")
|
|
212
|
-
self.invert =
|
|
213
|
-
self.width =
|
|
214
|
-
self.height =
|
|
215
|
-
self.use_bottom_slot =
|
|
216
|
-
self.bottom_slot_dist =
|
|
217
|
-
self.bottom_slot_depth =
|
|
218
|
-
self.use_top_slot =
|
|
219
|
-
self.top_slot_dist =
|
|
220
|
-
self.top_slot_depth =
|
|
214
|
+
self.invert = to_float(params[0])
|
|
215
|
+
self.width = to_float(params[1])
|
|
216
|
+
self.height = to_float(params[2])
|
|
217
|
+
self.use_bottom_slot = to_str(params[3], "GLOBAL")
|
|
218
|
+
self.bottom_slot_dist = to_float(params[4])
|
|
219
|
+
self.bottom_slot_depth = to_float(params[5])
|
|
220
|
+
self.use_top_slot = to_str(params[6], "GLOBAL")
|
|
221
|
+
self.top_slot_dist = to_float(params[7])
|
|
222
|
+
self.top_slot_depth = to_float(params[8])
|
|
221
223
|
friction_params = split_10_char(f"{c_block[6]:<30}")
|
|
222
|
-
self.friction_on_invert =
|
|
223
|
-
self.friction_on_walls =
|
|
224
|
-
self.friction_on_soffit =
|
|
224
|
+
self.friction_on_invert = to_float(friction_params[0])
|
|
225
|
+
self.friction_on_walls = to_float(friction_params[1])
|
|
226
|
+
self.friction_on_soffit = to_float(friction_params[2])
|
|
225
227
|
|
|
226
228
|
elif self._subtype in ("SPRUNG", "SPRUNGARCH"):
|
|
227
|
-
self.dist_to_next =
|
|
228
|
-
self.equation =
|
|
229
|
+
self.dist_to_next = to_float(split_10_char(c_block[3])[0])
|
|
230
|
+
self.equation = to_str(c_block[4], "MANNING")
|
|
229
231
|
params = split_10_char(f"{c_block[5]:<100}")
|
|
230
|
-
self.elevation_invert =
|
|
231
|
-
self.width =
|
|
232
|
-
self.height_springing =
|
|
233
|
-
self.height_crown =
|
|
234
|
-
self.use_bottom_slot =
|
|
235
|
-
self.bottom_slot_dist =
|
|
236
|
-
self.bottom_slot_depth =
|
|
237
|
-
self.use_top_slot =
|
|
238
|
-
self.top_slot_dist =
|
|
239
|
-
self.top_slot_depth =
|
|
232
|
+
self.elevation_invert = to_float(params[0])
|
|
233
|
+
self.width = to_float(params[1])
|
|
234
|
+
self.height_springing = to_float(params[2])
|
|
235
|
+
self.height_crown = to_float(params[3])
|
|
236
|
+
self.use_bottom_slot = to_str(params[4], "GLOBAL")
|
|
237
|
+
self.bottom_slot_dist = to_float(params[5])
|
|
238
|
+
self.bottom_slot_depth = to_float(params[6])
|
|
239
|
+
self.use_top_slot = to_str(params[7], "GLOBAL")
|
|
240
|
+
self.top_slot_dist = to_float(params[8])
|
|
241
|
+
self.top_slot_depth = to_float(params[9])
|
|
240
242
|
friction_params = split_10_char(f"{c_block[6]:<30}")
|
|
241
|
-
self.friction_on_invert =
|
|
242
|
-
self.friction_on_walls =
|
|
243
|
-
self.friction_on_soffit =
|
|
243
|
+
self.friction_on_invert = to_float(friction_params[0])
|
|
244
|
+
self.friction_on_walls = to_float(friction_params[1])
|
|
245
|
+
self.friction_on_soffit = to_float(friction_params[2])
|
|
244
246
|
|
|
245
247
|
elif self._subtype == "SECTION":
|
|
246
|
-
self.dist_to_next =
|
|
247
|
-
end_index = 5 +
|
|
248
|
+
self.dist_to_next = to_float(split_10_char(c_block[3])[0])
|
|
249
|
+
end_index = 5 + to_int(c_block[4])
|
|
248
250
|
x = []
|
|
249
251
|
y = []
|
|
250
252
|
friction = []
|
|
251
253
|
for i in range(5, end_index):
|
|
252
254
|
row_data = split_10_char(f"{c_block[i]:<30}")
|
|
253
|
-
x.append(
|
|
254
|
-
y.append(
|
|
255
|
-
friction.append(
|
|
255
|
+
x.append(to_float(row_data[0]))
|
|
256
|
+
y.append(to_float(row_data[1]))
|
|
257
|
+
friction.append(to_float(row_data[2]))
|
|
256
258
|
self.coords = pd.DataFrame({"x": x, "y": y, "cw_friction": friction})
|
|
257
259
|
|
|
258
260
|
else:
|
|
259
261
|
# This else block is triggered for conduit subtypes which aren't yet supported, and just keeps the '_block' in it's raw state to write back.
|
|
260
|
-
|
|
261
|
-
|
|
262
|
+
logging.warning(
|
|
263
|
+
"This Conduit sub-type: '%s' is currently unsupported for reading/editing",
|
|
264
|
+
self._subtype,
|
|
262
265
|
)
|
|
263
266
|
self._raw_block = c_block
|
|
264
267
|
|
|
265
268
|
def _write(self):
|
|
266
269
|
"""Function to write a valid CONDUIT block"""
|
|
267
270
|
_validate_unit(self) # Function to check the params are valid for CONDUIT unit
|
|
268
|
-
header =
|
|
271
|
+
header = self._create_header()
|
|
269
272
|
labels = join_n_char_ljust(self._label_len, self.name, self.spill)
|
|
270
273
|
c_block = [header, self._subtype, labels]
|
|
271
274
|
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from floodmodeller_api.validation import _validate_unit
|
|
6
|
+
|
|
7
|
+
from ._base import Unit
|
|
8
|
+
from ._helpers import (
|
|
9
|
+
join_12_char_ljust,
|
|
10
|
+
read_lateral_data,
|
|
11
|
+
split_12_char,
|
|
12
|
+
to_int,
|
|
13
|
+
write_dataframe,
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
if TYPE_CHECKING:
|
|
17
|
+
import pandas as pd
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class JUNCTION(Unit):
|
|
21
|
+
"""Class to hold and process JUNCTION unit type
|
|
22
|
+
|
|
23
|
+
Args:
|
|
24
|
+
comment (str, optional): Comment included in unit.
|
|
25
|
+
subtype (str, optional): Defines the type of junction unit (*Should not be changed*)
|
|
26
|
+
labels (str, optional): Unlimited number of labels, the first of which is the name.
|
|
27
|
+
|
|
28
|
+
Returns:
|
|
29
|
+
JUNCTION: Flood Modeller JUNCTION Unit class object"""
|
|
30
|
+
|
|
31
|
+
_unit = "JUNCTION"
|
|
32
|
+
|
|
33
|
+
def _read(self, block: list[str]) -> None:
|
|
34
|
+
self.comment = self._remove_unit_name(block[0])
|
|
35
|
+
self._subtype = self._get_first_word(block[1])
|
|
36
|
+
self.labels = split_12_char(block[2])
|
|
37
|
+
|
|
38
|
+
self.name = self.labels[0]
|
|
39
|
+
|
|
40
|
+
def _write(self) -> list[str]:
|
|
41
|
+
_validate_unit(self)
|
|
42
|
+
return [
|
|
43
|
+
self._create_header(),
|
|
44
|
+
self.subtype, # type: ignore
|
|
45
|
+
join_12_char_ljust(*self.labels).rstrip(),
|
|
46
|
+
]
|
|
47
|
+
|
|
48
|
+
def _create_from_blank(
|
|
49
|
+
self,
|
|
50
|
+
comment: str = "",
|
|
51
|
+
subtype: str = "OPEN",
|
|
52
|
+
labels: list[str] | None = None,
|
|
53
|
+
) -> None:
|
|
54
|
+
self.comment = comment
|
|
55
|
+
self._subtype = subtype
|
|
56
|
+
self.labels = labels if labels is not None else []
|
|
57
|
+
self.name = self.labels[0]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
class LATERAL(Unit):
|
|
61
|
+
"""Class to hold and process LATERAL unit type
|
|
62
|
+
|
|
63
|
+
Args:
|
|
64
|
+
name (str, optional): Unit name.
|
|
65
|
+
comment (str, optional): Comment included in unit.
|
|
66
|
+
subtype (str, optional): Defines the type of lateral unit (*Should not be changed*)
|
|
67
|
+
weight_factor (str, optional): Corresponding weight factors or user-defined area for
|
|
68
|
+
each receiving unit
|
|
69
|
+
data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
|
|
70
|
+
Columns are ``'Node Label', 'Custom Weight Factor', 'Use Weight Factor'``
|
|
71
|
+
|
|
72
|
+
Returns:
|
|
73
|
+
LATERAL: Flood Modeller LATERAL Unit class object"""
|
|
74
|
+
|
|
75
|
+
_unit = "LATERAL"
|
|
76
|
+
|
|
77
|
+
def _read(self, block: list[str]) -> None:
|
|
78
|
+
self.comment = self._remove_unit_name(block[0])
|
|
79
|
+
self.name = block[1]
|
|
80
|
+
self.weight_factor = block[2]
|
|
81
|
+
self.no_units = to_int(block[3])
|
|
82
|
+
self.data = read_lateral_data(block[4:])
|
|
83
|
+
self.labels = list(self.data["Node Label"])
|
|
84
|
+
|
|
85
|
+
def _write(self) -> list[str]:
|
|
86
|
+
_validate_unit(self)
|
|
87
|
+
return [
|
|
88
|
+
self._create_header(),
|
|
89
|
+
self.name, # type: ignore
|
|
90
|
+
self.weight_factor,
|
|
91
|
+
*write_dataframe(self.no_units, self.data, n=12),
|
|
92
|
+
]
|
|
93
|
+
|
|
94
|
+
def _create_from_blank(
|
|
95
|
+
self,
|
|
96
|
+
name: str = "new_junction",
|
|
97
|
+
comment: str = "",
|
|
98
|
+
subtype: str = "OPEN",
|
|
99
|
+
weight_factor: str = "REACH",
|
|
100
|
+
data: pd.DataFrame | None = None,
|
|
101
|
+
) -> None:
|
|
102
|
+
self.name = name
|
|
103
|
+
self.comment = comment
|
|
104
|
+
self._subtype = subtype
|
|
105
|
+
self.weight_factor = weight_factor
|
|
106
|
+
|
|
107
|
+
self.data = self._enforce_dataframe(
|
|
108
|
+
data,
|
|
109
|
+
("Node Label", "Custom Weight Factor", "Use Weight Factor"),
|
|
110
|
+
)
|
|
111
|
+
self.no_units = len(self.data)
|
|
112
|
+
self.labels = list(self.data["Node Label"])
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from floodmodeller_api.validation import _validate_unit
|
|
6
|
+
|
|
7
|
+
from ._base import Unit
|
|
8
|
+
from ._helpers import (
|
|
9
|
+
join_10_char,
|
|
10
|
+
join_12_char_ljust,
|
|
11
|
+
read_reservoir_data,
|
|
12
|
+
split_10_char,
|
|
13
|
+
split_12_char,
|
|
14
|
+
to_float,
|
|
15
|
+
to_int,
|
|
16
|
+
write_dataframe,
|
|
17
|
+
)
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
import pandas as pd
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class RESERVOIR(Unit):
|
|
24
|
+
"""Class to hold and process RESERVOIR unit type
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
name (str, optional): Unit name.
|
|
28
|
+
comment (str, optional): Comment included in unit.
|
|
29
|
+
labels (str, optional): Unlimited number of labels - not including first label (name).
|
|
30
|
+
easting (float, optional): Easting coordinate of reservoir reference point (not used in computations).
|
|
31
|
+
northing (float, optional): Northing coordinate of reservoir reference point (not used in computations).
|
|
32
|
+
runoff_factor (float, optional): Rainfall runoff factor.
|
|
33
|
+
lateral_inflow_labels (list[str], optional): Lateral inflow labels (up to 4).
|
|
34
|
+
data (pandas.DataFrame): Dataframe object containing all the reservoir section data.
|
|
35
|
+
Columns are ``'Elevation','Plan Area'``
|
|
36
|
+
|
|
37
|
+
Returns:
|
|
38
|
+
RESERVOIR: Flood Modeller RESERVOIR Unit class object"""
|
|
39
|
+
|
|
40
|
+
_unit = "RESERVOIR"
|
|
41
|
+
|
|
42
|
+
def _read(self, block: list[str]) -> None:
|
|
43
|
+
self._revision, self.comment = self._get_revision_and_comment(block[0])
|
|
44
|
+
|
|
45
|
+
self.labels = split_12_char(block[1])
|
|
46
|
+
self.name = self.labels[0]
|
|
47
|
+
|
|
48
|
+
if self._revision == 1:
|
|
49
|
+
self.lateral_inflow_labels = split_12_char(block[2])
|
|
50
|
+
idx = 3
|
|
51
|
+
|
|
52
|
+
lines = split_10_char(f"{block[-1]:<30}")
|
|
53
|
+
self.easting = to_float(lines[0])
|
|
54
|
+
self.northing = to_float(lines[1])
|
|
55
|
+
self.runoff = to_float(lines[2])
|
|
56
|
+
else:
|
|
57
|
+
idx = 2
|
|
58
|
+
|
|
59
|
+
self.no_rows = to_int(block[idx])
|
|
60
|
+
start_idx = idx + 1
|
|
61
|
+
end_idx = start_idx + self.no_rows
|
|
62
|
+
self.data = read_reservoir_data(block[start_idx:end_idx])
|
|
63
|
+
|
|
64
|
+
def _write(self) -> list[str]:
|
|
65
|
+
_validate_unit(self)
|
|
66
|
+
rev1_a = (
|
|
67
|
+
[join_12_char_ljust(*self.lateral_inflow_labels).rstrip()]
|
|
68
|
+
if self._revision == 1
|
|
69
|
+
else []
|
|
70
|
+
)
|
|
71
|
+
rev1_b = (
|
|
72
|
+
[join_10_char(self.easting, self.northing, self.runoff)] if self._revision == 1 else []
|
|
73
|
+
)
|
|
74
|
+
return [
|
|
75
|
+
self._create_header(include_revision=self._revision is not None),
|
|
76
|
+
join_12_char_ljust(*self.labels).rstrip(),
|
|
77
|
+
*rev1_a,
|
|
78
|
+
*write_dataframe(self.no_rows, self.data),
|
|
79
|
+
*rev1_b,
|
|
80
|
+
]
|
|
81
|
+
|
|
82
|
+
def _create_from_blank( # noqa: PLR0913 (need that many)
|
|
83
|
+
self,
|
|
84
|
+
name: str = "new_reservoir",
|
|
85
|
+
comment: str = "",
|
|
86
|
+
subtype: str = "OPEN",
|
|
87
|
+
labels: list[str] | None = None,
|
|
88
|
+
easting: float = 0.0,
|
|
89
|
+
northing: float = 0.0,
|
|
90
|
+
runoff: float = 0.0,
|
|
91
|
+
lateral_inflow_labels: list[str] | None = None,
|
|
92
|
+
data: pd.DataFrame | None = None,
|
|
93
|
+
) -> None:
|
|
94
|
+
self.easting = easting
|
|
95
|
+
self.northing = northing
|
|
96
|
+
self.runoff = runoff
|
|
97
|
+
self.name = name
|
|
98
|
+
self.comment = comment
|
|
99
|
+
self._subtype = subtype
|
|
100
|
+
self._revision = 1
|
|
101
|
+
self.labels = labels if labels is not None else []
|
|
102
|
+
self.lateral_inflow_labels = (
|
|
103
|
+
lateral_inflow_labels if lateral_inflow_labels is not None else []
|
|
104
|
+
)
|
|
105
|
+
|
|
106
|
+
self.data = self._enforce_dataframe(data, ("Elevation", "Plan Area"))
|
|
107
|
+
self.no_rows = len(self.data)
|