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.
- floodmodeller_api/__init__.py +10 -0
- floodmodeller_api/_base.py +29 -20
- floodmodeller_api/backup.py +12 -10
- floodmodeller_api/dat.py +162 -91
- floodmodeller_api/diff.py +1 -1
- floodmodeller_api/hydrology_plus/hydrology_plus_export.py +1 -1
- floodmodeller_api/ied.py +2 -4
- floodmodeller_api/ief.py +29 -17
- floodmodeller_api/ief_flags.py +1 -1
- floodmodeller_api/inp.py +4 -6
- floodmodeller_api/logs/lf.py +18 -12
- floodmodeller_api/logs/lf_helpers.py +2 -2
- floodmodeller_api/logs/lf_params.py +1 -5
- floodmodeller_api/mapping.py +9 -2
- floodmodeller_api/test/test_conveyance.py +9 -4
- floodmodeller_api/test/test_dat.py +166 -18
- 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_ied.py +1 -1
- floodmodeller_api/test/test_ief.py +10 -2
- floodmodeller_api/test/test_integrated_bridge.py +159 -0
- floodmodeller_api/test/test_json.py +9 -3
- floodmodeller_api/test/test_logs_lf.py +45 -24
- floodmodeller_api/test/test_river.py +1 -1
- floodmodeller_api/test/test_toolbox_structure_log.py +0 -1
- floodmodeller_api/test/test_xml2d.py +5 -5
- floodmodeller_api/to_from_json.py +1 -1
- floodmodeller_api/tool.py +3 -5
- floodmodeller_api/toolbox/model_build/add_siltation_definition.py +1 -1
- floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +12 -8
- floodmodeller_api/units/__init__.py +15 -0
- floodmodeller_api/units/_base.py +73 -10
- 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/iic.py +2 -9
- floodmodeller_api/units/losses.py +42 -42
- floodmodeller_api/units/sections.py +40 -43
- floodmodeller_api/units/structures.py +360 -530
- floodmodeller_api/units/units.py +25 -26
- floodmodeller_api/units/unsupported.py +5 -7
- floodmodeller_api/units/variables.py +2 -2
- floodmodeller_api/urban1d/_base.py +7 -8
- 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 +16 -21
- floodmodeller_api/urban1d/raingauges.py +3 -9
- floodmodeller_api/urban1d/subsections.py +3 -4
- floodmodeller_api/urban1d/xsections.py +11 -15
- floodmodeller_api/util.py +7 -4
- floodmodeller_api/validation/parameters.py +7 -3
- floodmodeller_api/validation/urban_parameters.py +1 -4
- floodmodeller_api/validation/validation.py +9 -4
- floodmodeller_api/version.py +1 -1
- floodmodeller_api/xml2d.py +9 -11
- floodmodeller_api/xml2d_template.py +1 -1
- floodmodeller_api/zz.py +7 -6
- {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/LICENSE.txt +1 -1
- {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/METADATA +11 -3
- {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/RECORD +85 -70
- {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/WHEEL +1 -1
- floodmodeller_api/units/helpers.py +0 -121
- {floodmodeller_api-0.5.1.dist-info → floodmodeller_api-0.5.2.dist-info}/entry_points.txt +0 -0
- {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)
|
|
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)
|
floodmodeller_api/units/iic.py
CHANGED
|
@@ -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.
|
|
@@ -18,7 +18,7 @@ import pandas as pd
|
|
|
18
18
|
|
|
19
19
|
from ..diff import check_item_with_dataframe_equal
|
|
20
20
|
from ..to_from_json import Jsonable
|
|
21
|
-
from .
|
|
21
|
+
from ._helpers import join_10_char, split_10_char
|
|
22
22
|
|
|
23
23
|
# Initial Conditions Class
|
|
24
24
|
|
|
@@ -65,15 +65,8 @@ class IIC(Jsonable):
|
|
|
65
65
|
float(z),
|
|
66
66
|
],
|
|
67
67
|
)
|
|
68
|
-
# AL is this storing the values as strings?
|
|
69
68
|
self.data = pd.DataFrame(data_list, columns=header)
|
|
70
|
-
# JP Yes
|
|
71
|
-
# AL If it does, would it worth making it store the values instead?
|
|
72
|
-
# JP Yes I'll do that, only downside is that the updated values may not match notation
|
|
73
|
-
# of original even if no changes. (i.e 2.0 -> 2.00 or 2. -> 2.00)
|
|
74
69
|
|
|
75
|
-
# AL Is this only to transform the table of data into a string-like array?
|
|
76
|
-
# JP Yes it just transforms the dataframe back into valid DAT format
|
|
77
70
|
def _write(self):
|
|
78
71
|
ic_block = [
|
|
79
72
|
"INITIAL CONDITIONS",
|
|
@@ -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,20 +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_data_list,
|
|
24
|
-
_to_float,
|
|
25
|
-
_to_int,
|
|
26
|
-
_to_str,
|
|
24
|
+
from ._helpers import (
|
|
27
25
|
join_10_char,
|
|
28
26
|
join_n_char_ljust,
|
|
29
27
|
split_10_char,
|
|
30
28
|
split_n_char,
|
|
29
|
+
to_data_list,
|
|
30
|
+
to_float,
|
|
31
|
+
to_str,
|
|
31
32
|
)
|
|
32
33
|
|
|
33
34
|
|
|
@@ -76,8 +77,8 @@ class CULVERT(Unit):
|
|
|
76
77
|
"""Function to read a given CULVERT block and store data as class attributes"""
|
|
77
78
|
|
|
78
79
|
# Extract common attributes
|
|
79
|
-
self._subtype = block[1]
|
|
80
|
-
self.comment = block[0]
|
|
80
|
+
self._subtype = self._get_first_word(block[1])
|
|
81
|
+
self.comment = self._remove_unit_name(block[0])
|
|
81
82
|
labels = split_n_char(f"{block[2]:<{4*self._label_len}}", self._label_len)
|
|
82
83
|
self.name = labels[0]
|
|
83
84
|
self.ds_label = labels[1]
|
|
@@ -91,44 +92,43 @@ class CULVERT(Unit):
|
|
|
91
92
|
|
|
92
93
|
# Read first set of general parameters
|
|
93
94
|
params = split_10_char(f"{block[3]:<60}")
|
|
94
|
-
self.k =
|
|
95
|
-
self.m =
|
|
96
|
-
self.c =
|
|
97
|
-
self.y =
|
|
98
|
-
self.ki =
|
|
99
|
-
self.type_code =
|
|
95
|
+
self.k = to_float(params[0], 0.0)
|
|
96
|
+
self.m = to_float(params[1], 0.0)
|
|
97
|
+
self.c = to_float(params[2], 0.0)
|
|
98
|
+
self.y = to_float(params[3], 0.0)
|
|
99
|
+
self.ki = to_float(params[4], 0.0)
|
|
100
|
+
self.type_code = to_str(params[5], "A")
|
|
100
101
|
|
|
101
102
|
# Read trash screen and remaining general parameters
|
|
102
103
|
params1 = split_10_char(f"{block[4]:<70}")
|
|
103
|
-
self.screen_width =
|
|
104
|
-
self.bar_proportion =
|
|
105
|
-
self.debris_proportion =
|
|
106
|
-
self.loss_coefficient =
|
|
107
|
-
self.reverse_flow_mode =
|
|
108
|
-
self.headloss_type =
|
|
109
|
-
self.max_screen_height =
|
|
104
|
+
self.screen_width = to_float(params1[0], 0.0)
|
|
105
|
+
self.bar_proportion = to_float(params1[1], 0.0)
|
|
106
|
+
self.debris_proportion = to_float(params1[2], 0.0)
|
|
107
|
+
self.loss_coefficient = to_float(params1[3], 0.0)
|
|
108
|
+
self.reverse_flow_mode = to_str(params1[4], "CALCULATED", check_float=True)
|
|
109
|
+
self.headloss_type = to_str(params1[5], "TOTAL")
|
|
110
|
+
self.max_screen_height = to_float(params1[6], 0.0)
|
|
110
111
|
|
|
111
112
|
elif self.subtype == "OUTLET":
|
|
112
113
|
params = split_10_char(f"{block[3]:<30}")
|
|
113
|
-
self.loss_coefficient =
|
|
114
|
-
self.reverse_flow_mode =
|
|
115
|
-
self.headloss_type =
|
|
114
|
+
self.loss_coefficient = to_float(params[0], 1.0)
|
|
115
|
+
self.reverse_flow_mode = to_str(params[1], "CALCULATED")
|
|
116
|
+
self.headloss_type = to_str(params[2], "TOTAL")
|
|
116
117
|
|
|
117
118
|
else:
|
|
118
|
-
# This else block is triggered for culvert subtypes which aren't yet supported, and just keeps the '_block' in
|
|
119
|
-
|
|
120
|
-
|
|
119
|
+
# This else block is triggered for culvert subtypes which aren't yet supported, and just keeps the '_block' in its raw state to write back.
|
|
120
|
+
logging.warning(
|
|
121
|
+
"This Culvert sub-type: '%s' is currently unsupported for reading/editing",
|
|
122
|
+
self.subtype,
|
|
121
123
|
)
|
|
122
124
|
self._raw_block = block
|
|
123
125
|
|
|
124
|
-
# TODO: Create from blank. Not supported currently as CULVERT has multiple subtypes
|
|
125
|
-
|
|
126
126
|
def _write(self):
|
|
127
127
|
"""Function to write a valid CULVERT block"""
|
|
128
128
|
|
|
129
129
|
_validate_unit(self)
|
|
130
130
|
|
|
131
|
-
header =
|
|
131
|
+
header = self._create_header()
|
|
132
132
|
labels = join_n_char_ljust(
|
|
133
133
|
self._label_len,
|
|
134
134
|
self.name,
|
|
@@ -192,9 +192,7 @@ class BLOCKAGE(Unit):
|
|
|
192
192
|
"""Function to read a given BLOCKAGE block and store data as class attributes"""
|
|
193
193
|
|
|
194
194
|
# Extract comment and revision number
|
|
195
|
-
|
|
196
|
-
self._revision = _to_int(b[0], 1)
|
|
197
|
-
self.comment = b[1:].strip()
|
|
195
|
+
self._revision, self.comment = self._get_revision_and_comment(block[0])
|
|
198
196
|
|
|
199
197
|
# Extract labels
|
|
200
198
|
labels = split_n_char(f"{block[1]:<{5*self._label_len}}", self._label_len)
|
|
@@ -206,25 +204,27 @@ class BLOCKAGE(Unit):
|
|
|
206
204
|
|
|
207
205
|
# Extract inlet and outlet loss coefficients
|
|
208
206
|
params = split_10_char(f"{block[2]:<20}")
|
|
209
|
-
self.inlet_loss =
|
|
210
|
-
self.outlet_loss =
|
|
207
|
+
self.inlet_loss = to_float(params[0], 1.5)
|
|
208
|
+
self.outlet_loss = to_float(params[1], 1.0)
|
|
211
209
|
|
|
212
210
|
# Extract blockage timeseries parameters
|
|
213
211
|
params1 = split_10_char(f"{block[3]:<40}")
|
|
214
212
|
self.nrows = int(params1[0])
|
|
215
|
-
self.timeoffset =
|
|
213
|
+
self.timeoffset = to_float(params1[1])
|
|
216
214
|
|
|
217
|
-
self.timeunit =
|
|
215
|
+
self.timeunit = to_str(params1[2], "HOURS", check_float=True)
|
|
218
216
|
if self.timeunit == "DATE":
|
|
219
|
-
self.timeunit = "DATES"
|
|
217
|
+
self.timeunit = "DATES"
|
|
218
|
+
# Parameter value updated to 'DATES' for consistency with other unit types.
|
|
219
|
+
# 'DATE' and 'DATES' both accepted for blockage unit ONLY
|
|
220
220
|
|
|
221
|
-
self.extendmethod =
|
|
221
|
+
self.extendmethod = to_str(params1[3], "NOEXTEND")
|
|
222
222
|
|
|
223
223
|
# Extract blockage to timeseries
|
|
224
224
|
data_list = (
|
|
225
|
-
|
|
225
|
+
to_data_list(block[4:], num_cols=2, date_col=0)
|
|
226
226
|
if self.timeunit == "DATES"
|
|
227
|
-
else
|
|
227
|
+
else to_data_list(block[4:], num_cols=2)
|
|
228
228
|
) # Enforced two columns as Flood Modeller saves old parameters when using DATES (also to avoid extra 'HOURS' bug)
|
|
229
229
|
|
|
230
230
|
self.data = pd.DataFrame(data_list, columns=["Time", "Blockage"])
|
|
@@ -241,7 +241,7 @@ class BLOCKAGE(Unit):
|
|
|
241
241
|
msg = f"Parameter error with {self!r} - blockage percentage must be between 0 and 1"
|
|
242
242
|
raise ValueError(msg)
|
|
243
243
|
|
|
244
|
-
header =
|
|
244
|
+
header = self._create_header(include_revision=True)
|
|
245
245
|
labels = join_n_char_ljust(
|
|
246
246
|
self._label_len,
|
|
247
247
|
self.name,
|