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,14 +1,57 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import copy
|
|
1
4
|
import csv
|
|
5
|
+
import logging
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
2
7
|
|
|
3
8
|
from floodmodeller_api import DAT
|
|
4
9
|
|
|
10
|
+
if TYPE_CHECKING:
|
|
11
|
+
from floodmodeller_api._base import Unit
|
|
12
|
+
from floodmodeller_api.units import (
|
|
13
|
+
BRIDGE,
|
|
14
|
+
CONDUIT,
|
|
15
|
+
ORIFICE,
|
|
16
|
+
REPLICATE,
|
|
17
|
+
RNWEIR,
|
|
18
|
+
SLUICE,
|
|
19
|
+
SPILL,
|
|
20
|
+
WEIR,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def extract_attrs(object_input, keys):
|
|
25
|
+
"""Extract attributes and their values from a given object and return as a dict.
|
|
26
|
+
|
|
27
|
+
No handling for where attributes don't exist but can ignore for when this is an issue, shouldn't be an issue here
|
|
28
|
+
"""
|
|
29
|
+
return {key: getattr(object_input, key) for key in keys}
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def serialise_keys(old_dict):
|
|
33
|
+
"""Exchange tuple keys in dict for string keys.
|
|
34
|
+
|
|
35
|
+
Dictionary keys must be unique; but in FM the node label isn't always unique
|
|
36
|
+
however the (label,type) pair will always be unique; so we have to use that as our key.
|
|
37
|
+
json doesn't like tuples as keys so we have to convert them first if we need to serialise our dictionary output.
|
|
38
|
+
|
|
39
|
+
Replaces tuple keys with string versions of themselves, wrapped in ()
|
|
40
|
+
|
|
41
|
+
"""
|
|
42
|
+
return {f"({','.join(key)})": value for key, value in old_dict.items()}
|
|
43
|
+
|
|
5
44
|
|
|
6
45
|
class StructureLogBuilder:
|
|
7
|
-
def __init__(self, input_path, output_path) -> None:
|
|
46
|
+
def __init__(self, input_path: str | None = None, output_path: str | None = None) -> None:
|
|
8
47
|
self.dat_file_path = input_path
|
|
9
48
|
self.csv_output_path = output_path
|
|
49
|
+
self._conduit_chains: dict[str | None, list[str | None]] = {}
|
|
50
|
+
self._already_in_chain: set[str | None] = set()
|
|
51
|
+
self.unit_store: dict[(str, str)] = {}
|
|
52
|
+
self._replicate_mimics: dict[str | None, str | None] = {}
|
|
10
53
|
|
|
11
|
-
def _add_fields(self):
|
|
54
|
+
def _add_fields(self, writer) -> None:
|
|
12
55
|
field = [
|
|
13
56
|
"Unit Name",
|
|
14
57
|
"Unit Type",
|
|
@@ -19,237 +62,452 @@ class StructureLogBuilder:
|
|
|
19
62
|
"Weir Coefficient",
|
|
20
63
|
"Culvert Inlet/Outlet Loss",
|
|
21
64
|
]
|
|
22
|
-
|
|
65
|
+
writer.writerow(field)
|
|
23
66
|
|
|
24
|
-
def _conduit_data(self, conduit):
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
67
|
+
def _conduit_data(self, conduit: CONDUIT | REPLICATE) -> tuple[dict, Unit | None]:
|
|
68
|
+
conduit_data = {"length": conduit.dist_to_next}
|
|
69
|
+
# modified conduit crawler script
|
|
70
|
+
add_to_conduit_stack = None
|
|
71
|
+
|
|
72
|
+
# check if the previous node is an inlet
|
|
73
|
+
previous = self.dat.prev(conduit)
|
|
29
74
|
if hasattr(previous, "subtype") and previous.subtype == "INLET":
|
|
30
|
-
inlet = previous.ki
|
|
75
|
+
conduit_data["inlet"] = previous.ki
|
|
76
|
+
|
|
31
77
|
current_conduit = conduit
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
78
|
+
|
|
79
|
+
if current_conduit.name not in self._already_in_chain:
|
|
80
|
+
# if this conduit isn't part of a chain already, then it must be part of a new chain
|
|
81
|
+
total_length = 0
|
|
82
|
+
chain = []
|
|
83
|
+
while True:
|
|
84
|
+
self._already_in_chain.add(current_conduit.name)
|
|
85
|
+
chain.append(current_conduit.name)
|
|
86
|
+
|
|
87
|
+
if current_conduit._unit not in ("CONDUIT", "REPLICATE"):
|
|
88
|
+
# This occurs in cases where a conduit chain doesnt 'legally' end.
|
|
89
|
+
break
|
|
90
|
+
|
|
91
|
+
if current_conduit.dist_to_next == 0:
|
|
92
|
+
break
|
|
93
|
+
|
|
94
|
+
total_length += current_conduit.dist_to_next
|
|
95
|
+
current_conduit = self.dat.next(current_conduit)
|
|
96
|
+
|
|
97
|
+
self._conduit_chains[conduit.name] = chain.copy()
|
|
98
|
+
conduit_data["total_length"] = total_length
|
|
99
|
+
|
|
100
|
+
next_conduit = self.dat.next(conduit)
|
|
101
|
+
if next_conduit is not None:
|
|
102
|
+
if hasattr(next_conduit, "subtype") and next_conduit.subtype == "OUTLET":
|
|
103
|
+
conduit_data["outlet"] = next_conduit.loss_coefficient
|
|
104
|
+
|
|
105
|
+
if next_conduit._unit == "REPLICATE":
|
|
106
|
+
# for replicates, pass down the label of the unit it's copying from.
|
|
107
|
+
if current_conduit._unit == "REPLICATE":
|
|
108
|
+
self._replicate_mimics[next_conduit.name] = self._replicate_mimics[
|
|
109
|
+
current_conduit.name
|
|
110
|
+
]
|
|
111
|
+
else:
|
|
112
|
+
self._replicate_mimics[next_conduit.name] = current_conduit.name
|
|
113
|
+
|
|
114
|
+
# Replicates aren't in the DAT.structures dict so we need to add them manually.
|
|
115
|
+
add_to_conduit_stack = copy.deepcopy(next_conduit)
|
|
116
|
+
|
|
117
|
+
return conduit_data, add_to_conduit_stack
|
|
118
|
+
|
|
119
|
+
def _circular_data(self, conduit: CONDUIT) -> dict:
|
|
120
|
+
dimensions = extract_attrs(conduit, {"diameter", "invert"})
|
|
121
|
+
all_friction = [
|
|
53
122
|
conduit.friction_above_axis,
|
|
54
123
|
conduit.friction_below_axis,
|
|
55
124
|
]
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
125
|
+
friction_set = sorted(set(all_friction))
|
|
126
|
+
friction = {
|
|
127
|
+
"friction_eq": conduit.friction_eq,
|
|
128
|
+
"friction_set": friction_set,
|
|
129
|
+
"all_friction": all_friction,
|
|
130
|
+
}
|
|
61
131
|
|
|
62
|
-
return
|
|
132
|
+
return {"dimensions": dimensions, "friction": friction}
|
|
63
133
|
|
|
64
|
-
def _sprungarch_data(self, conduit
|
|
65
|
-
dimensions =
|
|
66
|
-
|
|
134
|
+
def _sprungarch_data(self, conduit: CONDUIT) -> dict:
|
|
135
|
+
dimensions = extract_attrs(
|
|
136
|
+
conduit,
|
|
137
|
+
{"width", "height_springing", "height_crown", "elevation_invert"},
|
|
138
|
+
)
|
|
139
|
+
# invert attribute is different for these, making homogenous
|
|
140
|
+
dimensions["invert"] = dimensions.pop("elevation_invert")
|
|
141
|
+
all_friction = [
|
|
67
142
|
conduit.friction_on_invert,
|
|
68
143
|
conduit.friction_on_soffit,
|
|
69
144
|
conduit.friction_on_walls,
|
|
70
145
|
]
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
146
|
+
friction_set = sorted(set(all_friction))
|
|
147
|
+
friction = {
|
|
148
|
+
"friction_eq": conduit.equation,
|
|
149
|
+
"friction_set": friction_set,
|
|
150
|
+
"all_friction": all_friction,
|
|
151
|
+
}
|
|
152
|
+
return {"dimensions": dimensions, "friction": friction}
|
|
76
153
|
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
dimensions = f"h: {conduit.height:.2f} x w: {conduit.width:.2f} x l: {length:.2f}"
|
|
81
|
-
all_mannings = [
|
|
154
|
+
def _rectangular_data(self, conduit):
|
|
155
|
+
dimensions = extract_attrs(conduit, {"width", "height", "invert"})
|
|
156
|
+
all_friction = [
|
|
82
157
|
conduit.friction_on_invert,
|
|
83
158
|
conduit.friction_on_soffit,
|
|
84
159
|
conduit.friction_on_walls,
|
|
85
160
|
]
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
161
|
+
friction_set = sorted(set(all_friction))
|
|
162
|
+
friction = {
|
|
163
|
+
"friction_eq": conduit.friction_eq,
|
|
164
|
+
"friction_set": friction_set,
|
|
165
|
+
"all_friction": all_friction,
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return {"dimensions": dimensions, "friction": friction}
|
|
91
169
|
|
|
92
|
-
|
|
170
|
+
def _section_data(self, conduit: CONDUIT) -> dict:
|
|
171
|
+
# Symmetrical conduits
|
|
172
|
+
# these have a lot of weirdness in terms of data validity and FM will rearrange data that it thinks is wrong
|
|
173
|
+
# so it's mostly worth just trusting the modeller here
|
|
93
174
|
|
|
94
|
-
def _section_data(self, conduit, length):
|
|
95
175
|
x_list = conduit.coords.x.tolist()
|
|
96
|
-
width = (max(x_list) - min(x_list)) * 2
|
|
97
176
|
y_list = conduit.coords.y.tolist()
|
|
177
|
+
all_friction = conduit.coords.cw_friction.tolist()
|
|
178
|
+
# want to serialise coords so converting out of dataframe
|
|
179
|
+
width = max(x_list) * 2
|
|
98
180
|
height = max(y_list) - min(y_list)
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
else:
|
|
107
|
-
friction = (
|
|
108
|
-
f"Colebrook-White: [min: {cw_frictions_set.pop()}, max: {cw_frictions_set.pop()}]"
|
|
109
|
-
)
|
|
181
|
+
elevation_invert = min(y_list)
|
|
182
|
+
dimensions = {
|
|
183
|
+
"width": width,
|
|
184
|
+
"height": height,
|
|
185
|
+
"invert": elevation_invert,
|
|
186
|
+
"section_data": {"x": x_list, "y": y_list},
|
|
187
|
+
}
|
|
110
188
|
|
|
111
|
-
|
|
189
|
+
all_friction = conduit.coords.cw_friction.tolist()
|
|
190
|
+
friction_set = sorted(set(all_friction))
|
|
191
|
+
friction = {
|
|
192
|
+
"friction_eq": "COLEBROOK-WHITE",
|
|
193
|
+
"friction_set": friction_set,
|
|
194
|
+
"all_friction": all_friction,
|
|
195
|
+
}
|
|
112
196
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
197
|
+
return {"dimensions": dimensions, "friction": friction}
|
|
198
|
+
|
|
199
|
+
def _sprung_data(self, conduit: CONDUIT) -> dict:
|
|
200
|
+
dimensions = extract_attrs(
|
|
201
|
+
conduit,
|
|
202
|
+
{"width", "height_springing", "height_crown", "elevation_invert"},
|
|
203
|
+
)
|
|
204
|
+
# invert attribute is different for these, making homogenous
|
|
205
|
+
dimensions["invert"] = dimensions.pop("elevation_invert")
|
|
206
|
+
all_friction = [
|
|
116
207
|
conduit.friction_on_invert,
|
|
117
208
|
conduit.friction_on_soffit,
|
|
118
209
|
conduit.friction_on_walls,
|
|
119
210
|
]
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
211
|
+
friction_set = sorted(set(all_friction))
|
|
212
|
+
friction = {
|
|
213
|
+
"friction_eq": conduit.equation,
|
|
214
|
+
"friction_set": friction_set,
|
|
215
|
+
"all_friction": all_friction,
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
return {"dimensions": dimensions, "friction": friction}
|
|
219
|
+
|
|
220
|
+
def _replicate_data(self, replicate: REPLICATE) -> dict:
|
|
221
|
+
dimensions = {
|
|
222
|
+
"bed_level_drop": replicate.bed_level_drop,
|
|
223
|
+
"mimic": self._replicate_mimics[replicate.name],
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return {"dimensions": dimensions}
|
|
227
|
+
|
|
228
|
+
def add_conduits(self):
|
|
229
|
+
conduit_stack = copy.deepcopy(list(self.dat.conduits.values()))
|
|
230
|
+
|
|
231
|
+
# this is a stack/while-loop because I want to add units to it as we go, to detail inline replicate units
|
|
232
|
+
while len(conduit_stack) > 0:
|
|
233
|
+
conduit = conduit_stack.pop(0)
|
|
234
|
+
self.unit_store[(conduit.name, conduit._unit)] = {
|
|
235
|
+
"name": conduit.name,
|
|
236
|
+
"type": conduit._unit,
|
|
237
|
+
"subtype": conduit.subtype,
|
|
238
|
+
"comment": conduit.comment,
|
|
239
|
+
}
|
|
240
|
+
if (conduit._unit, conduit.subtype) not in [
|
|
241
|
+
("CONDUIT", "CIRCULAR"),
|
|
242
|
+
("CONDUIT", "SPRUNGARCH"),
|
|
243
|
+
("CONDUIT", "RECTANGULAR"),
|
|
244
|
+
("CONDUIT", "SECTION"),
|
|
245
|
+
("CONDUIT", "SPRUNG"),
|
|
246
|
+
("REPLICATE", None),
|
|
136
247
|
]:
|
|
137
|
-
|
|
248
|
+
logging.warning(
|
|
249
|
+
"Conduit subtype: %s not currently supported in structure log",
|
|
250
|
+
conduit.subtype,
|
|
251
|
+
)
|
|
138
252
|
self._write(conduit.name, conduit._unit, conduit.subtype)
|
|
139
253
|
continue
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
254
|
+
conduit_dict, add_to_conduit_stack = self._conduit_data(conduit)
|
|
255
|
+
self.unit_store[(conduit.name, conduit._unit)]["conduit_data"] = conduit_dict
|
|
256
|
+
# now use individual functions to get friction and dimensional data in a way that is appropriate for each conduit type
|
|
257
|
+
match (conduit._unit, conduit.subtype):
|
|
258
|
+
case ("CONDUIT", "CIRCULAR"):
|
|
259
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._circular_data(conduit)
|
|
260
|
+
case ("CONDUIT", "SPRUNGARCH"):
|
|
261
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._sprungarch_data(conduit)
|
|
262
|
+
case ("CONDUIT", "RECTANGULAR"):
|
|
263
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._rectangular_data(conduit) # fmt: skip
|
|
264
|
+
case ("CONDUIT", "SECTION"):
|
|
265
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._section_data(conduit)
|
|
266
|
+
case ("CONDUIT", "SPRUNG"):
|
|
267
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._sprung_data(conduit)
|
|
268
|
+
case ("REPLICATE", None):
|
|
269
|
+
self.unit_store[(conduit.name, conduit._unit)] |= self._replicate_data(conduit)
|
|
270
|
+
case _:
|
|
271
|
+
pass
|
|
144
272
|
|
|
145
|
-
if
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
culvert_loss = self._culvert_loss_data(inlet, outlet)
|
|
149
|
-
friction = ""
|
|
150
|
-
dimensions = ""
|
|
151
|
-
if conduit.subtype == "CIRCULAR":
|
|
152
|
-
circular_data = self._circular_data(conduit, length)
|
|
153
|
-
friction = circular_data[0]
|
|
154
|
-
dimensions = circular_data[1]
|
|
155
|
-
elif conduit.subtype == "SPRUNGARCH":
|
|
156
|
-
sprungarch_data = self._sprungarch_data(conduit, length)
|
|
157
|
-
friction = sprungarch_data[0]
|
|
158
|
-
dimensions = sprungarch_data[1]
|
|
159
|
-
elif conduit.subtype == "RECTANGULAR":
|
|
160
|
-
rectangular_data = self._rectangular_data(conduit, length)
|
|
161
|
-
friction = rectangular_data[0]
|
|
162
|
-
dimensions = rectangular_data[1]
|
|
163
|
-
elif conduit.subtype == "SECTION":
|
|
164
|
-
section_data = self._section_data(conduit, length)
|
|
165
|
-
friction = section_data[0]
|
|
166
|
-
dimensions = section_data[1]
|
|
167
|
-
elif conduit.subtype == "SPRUNG":
|
|
168
|
-
sprung_data = self._sprung_data(conduit, length)
|
|
169
|
-
friction = sprung_data[0]
|
|
170
|
-
dimensions = sprung_data[1]
|
|
171
|
-
|
|
172
|
-
self._write(
|
|
173
|
-
conduit.name,
|
|
174
|
-
conduit._unit,
|
|
175
|
-
conduit.subtype,
|
|
176
|
-
conduit.comment,
|
|
177
|
-
friction,
|
|
178
|
-
dimensions,
|
|
179
|
-
"",
|
|
180
|
-
culvert_loss,
|
|
181
|
-
)
|
|
273
|
+
if add_to_conduit_stack is not None:
|
|
274
|
+
conduit_stack.insert(0, add_to_conduit_stack)
|
|
182
275
|
|
|
183
|
-
def _orifice_dimensions(self, structure):
|
|
276
|
+
def _orifice_dimensions(self, structure: ORIFICE) -> dict:
|
|
277
|
+
dimensions = {
|
|
278
|
+
"shape": structure.shape,
|
|
279
|
+
"invert": structure.invert,
|
|
280
|
+
"soffit": structure.soffit,
|
|
281
|
+
"bore_area": structure.bore_area,
|
|
282
|
+
}
|
|
283
|
+
dimensions["height"] = structure.soffit - structure.invert
|
|
184
284
|
if structure.shape == "RECTANGLE":
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
dimensions = f"h: {height:.2f} x w: {width:.2f}"
|
|
285
|
+
dimensions["width"] = structure.bore_area / dimensions["height"]
|
|
286
|
+
dimensions["bore_area"] = structure.bore_area
|
|
188
287
|
elif structure.shape == "CIRCULAR":
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
288
|
+
dimensions["width"] = dimensions["height"]
|
|
289
|
+
# calcuate bore area from given diameter
|
|
290
|
+
dimensions["bore_area"] = (dimensions["height"] ** 2) * (3.1415 * 0.25)
|
|
291
|
+
return {"dimensions": dimensions}
|
|
192
292
|
|
|
193
|
-
def _spill_data(self, structure):
|
|
194
|
-
elevation = min(structure.data.Y.tolist())
|
|
293
|
+
def _spill_data(self, structure: SPILL) -> dict:
|
|
195
294
|
x_list = structure.data.X.tolist()
|
|
196
|
-
|
|
197
|
-
dimensions =
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
295
|
+
y_list = structure.data.Y.tolist()
|
|
296
|
+
dimensions = {
|
|
297
|
+
"invert": min(y_list),
|
|
298
|
+
"width": max(x_list) - min(x_list),
|
|
299
|
+
"weir_coefficient": structure.weir_coefficient,
|
|
300
|
+
"section_data": {"x": x_list, "y": y_list},
|
|
301
|
+
}
|
|
302
|
+
return {"dimensions": dimensions}
|
|
303
|
+
|
|
304
|
+
def _bridge_data(self, structure: BRIDGE) -> dict:
|
|
305
|
+
section_df = structure.section_data
|
|
306
|
+
all_friction = section_df["Mannings n"].tolist()
|
|
307
|
+
friction_set = sorted(set(all_friction))
|
|
308
|
+
friction = {"friction_set": friction_set, "all_friction": all_friction}
|
|
309
|
+
|
|
310
|
+
orifice_data = extract_attrs(
|
|
311
|
+
structure,
|
|
312
|
+
{
|
|
313
|
+
"orifice_flow",
|
|
314
|
+
"orifice_lower_transition_dist",
|
|
315
|
+
"orifice_upper_transition_dist",
|
|
316
|
+
"orifice_discharge_coefficient",
|
|
317
|
+
},
|
|
318
|
+
)
|
|
319
|
+
orifice_data["transition_width"] = (
|
|
320
|
+
structure.orifice_upper_transition_dist + structure.orifice_lower_transition_dist
|
|
321
|
+
)
|
|
322
|
+
|
|
323
|
+
opening_data = []
|
|
324
|
+
# if it has openings, not sure why it wouldn't but worth checking.
|
|
325
|
+
if structure.opening_nrows > 0:
|
|
326
|
+
opening_df = structure.opening_data
|
|
327
|
+
# this isn't 'proper' for a df but there should be few rows and we're doing special stuff
|
|
328
|
+
for _, row in opening_df.iterrows():
|
|
329
|
+
opening = {}
|
|
330
|
+
opening["width"] = row["Finish"] - row["Start"]
|
|
331
|
+
|
|
332
|
+
temp_df = section_df.loc[
|
|
333
|
+
(row["Start"] <= section_df["X"]) & (section_df["X"] <= row["Finish"])
|
|
334
|
+
]
|
|
335
|
+
opening["bed_minimum"] = temp_df["Y"].min()
|
|
336
|
+
|
|
337
|
+
opening["soffit_level"] = row["Soffit Level"]
|
|
338
|
+
opening["opening_height"] = opening["soffit_level"] - opening["bed_minimum"]
|
|
339
|
+
|
|
340
|
+
opening_data.append(opening)
|
|
341
|
+
|
|
342
|
+
culvert_data = []
|
|
343
|
+
if hasattr(structure, "culvert_data") and structure.culvert_data.shape[0] > 1:
|
|
344
|
+
for _, row in structure.culvert_data.iterrows():
|
|
345
|
+
culvert = {
|
|
346
|
+
"invert": row["Invert"],
|
|
347
|
+
"soffit": row["Soffit"],
|
|
348
|
+
"bore_area": "Section Area",
|
|
349
|
+
}
|
|
350
|
+
culvert["height"] = culvert["soffit"] - culvert["invert"]
|
|
351
|
+
culvert["average_width"] = culvert["bore_area"] / culvert["height"]
|
|
352
|
+
culvert_data.append(culvert)
|
|
353
|
+
|
|
354
|
+
return {
|
|
355
|
+
"opening_data": opening_data,
|
|
356
|
+
"friction": friction,
|
|
357
|
+
"orifice_data": orifice_data,
|
|
358
|
+
"culvert_data": culvert_data,
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
def _sluice_data(self, structure: SLUICE) -> dict:
|
|
362
|
+
dimensions = extract_attrs(structure, {"crest_elevation", "weir_breadth", "weir_length"})
|
|
363
|
+
|
|
364
|
+
return {"dimensions": dimensions}
|
|
365
|
+
|
|
366
|
+
def _weir_data(self, structure: RNWEIR | WEIR) -> dict:
|
|
367
|
+
dimensions = extract_attrs(structure, {"weir_elevation", "weir_breadth"})
|
|
368
|
+
|
|
369
|
+
if structure._unit == "RNWEIR":
|
|
370
|
+
dimensions |= extract_attrs(
|
|
371
|
+
structure,
|
|
372
|
+
{"weir_length", "upstream_crest_height", "downstream_crest_height"},
|
|
373
|
+
)
|
|
374
|
+
|
|
375
|
+
return {"dimensions": dimensions}
|
|
376
|
+
|
|
377
|
+
def add_structures(self):
|
|
378
|
+
for structure in self.dat.structures.values():
|
|
379
|
+
self.unit_store[(structure.name, structure._unit)] = {
|
|
380
|
+
"name": structure.name,
|
|
381
|
+
"type": structure._unit,
|
|
382
|
+
"subtype": structure.subtype,
|
|
383
|
+
"comment": structure.comment,
|
|
384
|
+
}
|
|
218
385
|
if structure._unit == "ORIFICE":
|
|
219
|
-
|
|
386
|
+
self.unit_store[(structure.name, structure._unit)] |= self._orifice_dimensions(
|
|
387
|
+
structure,
|
|
388
|
+
)
|
|
220
389
|
elif structure._unit == "SPILL":
|
|
221
|
-
|
|
222
|
-
dimensions = spill_data[0]
|
|
223
|
-
weir_coefficient = spill_data[1]
|
|
390
|
+
self.unit_store[(structure.name, structure._unit)] |= self._spill_data(structure)
|
|
224
391
|
elif structure._unit == "SLUICE":
|
|
225
|
-
|
|
226
|
-
elif structure._unit
|
|
227
|
-
|
|
228
|
-
elif structure._unit == "WEIR":
|
|
229
|
-
dimensions = f"Crest Elevation: {structure.weir_elevation:.2f} x w: {structure.weir_breadth:.2f}"
|
|
392
|
+
self.unit_store[(structure.name, structure._unit)] |= self._sluice_data(structure)
|
|
393
|
+
elif structure._unit in {"WEIR", "RNWEIR"}:
|
|
394
|
+
self.unit_store[(structure.name, structure._unit)] |= self._weir_data(structure)
|
|
230
395
|
# Need weir coefficient (the velocity attribute??)
|
|
231
396
|
elif structure._unit == "BRIDGE":
|
|
232
|
-
|
|
233
|
-
friction = bridge_data[0]
|
|
234
|
-
dimensions = bridge_data[1]
|
|
397
|
+
self.unit_store[(structure.name, structure._unit)] |= self._bridge_data(structure)
|
|
235
398
|
else:
|
|
236
|
-
|
|
399
|
+
logging.warning(
|
|
400
|
+
"Structure: %s not currently supported in structure log",
|
|
401
|
+
structure._unit,
|
|
402
|
+
)
|
|
237
403
|
self._write(structure.name, structure._unit, structure.subtype)
|
|
238
404
|
continue
|
|
239
405
|
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
""
|
|
249
|
-
|
|
406
|
+
def _format_friction(self, unit_dict):
|
|
407
|
+
text = ""
|
|
408
|
+
|
|
409
|
+
if "friction" not in unit_dict:
|
|
410
|
+
return ""
|
|
411
|
+
|
|
412
|
+
try:
|
|
413
|
+
match unit_dict["friction"]["friction_eq"]:
|
|
414
|
+
case "MANNING":
|
|
415
|
+
text += "Mannings: "
|
|
416
|
+
case "COLEBROOK-WHITE":
|
|
417
|
+
text += "Colebrook-White: "
|
|
418
|
+
except KeyError:
|
|
419
|
+
text += "Mannings: "
|
|
420
|
+
|
|
421
|
+
friction_set = unit_dict["friction"]["friction_set"]
|
|
422
|
+
if len(friction_set) == 1:
|
|
423
|
+
text += f"{friction_set[0]:.3f}"
|
|
424
|
+
else:
|
|
425
|
+
text += f"[min: {friction_set[0]:.3f}, max: {friction_set[-1]:.3f}]"
|
|
426
|
+
|
|
427
|
+
return text
|
|
428
|
+
|
|
429
|
+
def _format_bridge_dimensions(self, unit_dict):
|
|
430
|
+
if len(unit_dict["opening_data"]) == 1:
|
|
431
|
+
opening = unit_dict["opening_data"][0]
|
|
432
|
+
height = opening["opening_height"]
|
|
433
|
+
width = opening["width"]
|
|
434
|
+
return f"h: {height:.2f} x w: {width:.2f}"
|
|
435
|
+
|
|
436
|
+
text = ""
|
|
437
|
+
|
|
438
|
+
for n, opening in enumerate(unit_dict["opening_data"]):
|
|
439
|
+
height = opening["opening_height"]
|
|
440
|
+
width = opening["width"]
|
|
441
|
+
|
|
442
|
+
text += f"Opening {n+1}: h: {height:.2f} x w: {width:.2f} "
|
|
443
|
+
|
|
444
|
+
return text.rstrip()
|
|
445
|
+
|
|
446
|
+
def _format_orifice_dimensions(self, unit_dict):
|
|
447
|
+
height = unit_dict["dimensions"]["height"]
|
|
448
|
+
width = unit_dict["dimensions"]["width"]
|
|
449
|
+
match unit_dict["dimensions"]["shape"]:
|
|
450
|
+
case "RECTANGLE":
|
|
451
|
+
return f"h: {height:.2f} x w: {width:.2f}"
|
|
452
|
+
case "CIRCULAR":
|
|
453
|
+
return f"dia: {width:.2f}"
|
|
454
|
+
|
|
455
|
+
def _format_spill_dimensions(self, unit_dict):
|
|
456
|
+
invert = unit_dict["dimensions"]["invert"]
|
|
457
|
+
width = unit_dict["dimensions"]["width"]
|
|
458
|
+
|
|
459
|
+
return f"Elevation: {invert:.2f} x w: {width:.2f}"
|
|
460
|
+
|
|
461
|
+
def _format_weir_dimensions(self, unit_dict):
|
|
462
|
+
text = ""
|
|
463
|
+
elevation = unit_dict["dimensions"]["weir_elevation"]
|
|
464
|
+
breadth = unit_dict["dimensions"]["weir_breadth"]
|
|
465
|
+
|
|
466
|
+
text += f"Crest Elevation: {elevation:.2f} x w: {breadth:.2f}"
|
|
467
|
+
|
|
468
|
+
if "weir_length" in unit_dict["dimensions"]:
|
|
469
|
+
length = unit_dict["dimensions"]["weir_length"]
|
|
470
|
+
text += f" x l: {length:.2f}"
|
|
471
|
+
return text
|
|
472
|
+
|
|
473
|
+
def _format_sluice_dimensions(self, unit_dict):
|
|
474
|
+
elevation = unit_dict["dimensions"]["crest_elevation"]
|
|
475
|
+
breadth = unit_dict["dimensions"]["weir_breadth"]
|
|
476
|
+
length = unit_dict["dimensions"]["weir_length"]
|
|
477
|
+
|
|
478
|
+
return f"Crest Elevation: {elevation:.2f} x w: {breadth:.2f} x l: {length:.2f}"
|
|
479
|
+
|
|
480
|
+
def _format_conduit_dimensions(self, unit_dict):
|
|
481
|
+
text = ""
|
|
482
|
+
culvert_loss = ""
|
|
483
|
+
match unit_dict["subtype"]:
|
|
484
|
+
case "CIRCULAR":
|
|
485
|
+
text += f'dia: {unit_dict["dimensions"]["diameter"]:.2f} x l: {unit_dict["conduit_data"]["length"]:.2f}'
|
|
486
|
+
case "SPRUNGARCH" | "SPRUNG":
|
|
487
|
+
text += f'(Springing: {unit_dict["dimensions"]["height_springing"]:.2f}, Crown: {unit_dict["dimensions"]["height_crown"]:.2f}) x w: {unit_dict["dimensions"]["width"]:.2f} x l: {unit_dict["conduit_data"]["length"]:.2f}'
|
|
488
|
+
case "RECTANGULAR":
|
|
489
|
+
text += f'h: {unit_dict["dimensions"]["height"]:.2f} x w: {unit_dict["dimensions"]["width"]:.2f} x l: {unit_dict["conduit_data"]["length"]:.2f}'
|
|
490
|
+
case "SECTION":
|
|
491
|
+
text += f'h: {unit_dict["dimensions"]["height"]:.2f} x w: {unit_dict["dimensions"]["width"]:.2f} x l: {unit_dict["conduit_data"]["length"]:.2f}'
|
|
492
|
+
case _:
|
|
493
|
+
return "", ""
|
|
494
|
+
|
|
495
|
+
if "total_length" in unit_dict["conduit_data"]:
|
|
496
|
+
text += f' (Total conduit length: {unit_dict["conduit_data"]["total_length"]:.2f})'
|
|
497
|
+
|
|
498
|
+
if "inlet" in unit_dict["conduit_data"]:
|
|
499
|
+
culvert_loss += f'Ki: {unit_dict["conduit_data"]["inlet"]}, '
|
|
500
|
+
|
|
501
|
+
if "outlet" in unit_dict["conduit_data"]:
|
|
502
|
+
culvert_loss += f'Ko: {unit_dict["conduit_data"]["outlet"]}, '
|
|
503
|
+
|
|
504
|
+
culvert_loss = culvert_loss.rstrip(", ")
|
|
505
|
+
|
|
506
|
+
return text, culvert_loss
|
|
250
507
|
|
|
251
508
|
def _write( # noqa: PLR0913
|
|
252
509
|
self,
|
|
510
|
+
writer,
|
|
253
511
|
name,
|
|
254
512
|
unit,
|
|
255
513
|
subtype,
|
|
@@ -259,7 +517,7 @@ class StructureLogBuilder:
|
|
|
259
517
|
weir_coefficient="",
|
|
260
518
|
culvert_loss="",
|
|
261
519
|
):
|
|
262
|
-
|
|
520
|
+
writer.writerow(
|
|
263
521
|
[
|
|
264
522
|
name,
|
|
265
523
|
unit,
|
|
@@ -272,16 +530,64 @@ class StructureLogBuilder:
|
|
|
272
530
|
],
|
|
273
531
|
)
|
|
274
532
|
|
|
275
|
-
def
|
|
276
|
-
|
|
277
|
-
|
|
533
|
+
def write_csv_output(self, file):
|
|
534
|
+
"""
|
|
535
|
+
Take the current state of the instance (unit_store etc) and write it to the specified output file.
|
|
536
|
+
"""
|
|
278
537
|
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
538
|
+
writer = csv.writer(file)
|
|
539
|
+
|
|
540
|
+
self._add_fields(writer)
|
|
541
|
+
|
|
542
|
+
for unit_dict in self.unit_store.values():
|
|
543
|
+
name = unit_dict["name"]
|
|
544
|
+
unit_type = unit_dict["type"]
|
|
545
|
+
subtype = unit_dict["subtype"]
|
|
546
|
+
comment = unit_dict["comment"]
|
|
282
547
|
|
|
283
|
-
self.
|
|
548
|
+
friction = self._format_friction(unit_dict)
|
|
284
549
|
|
|
285
|
-
|
|
550
|
+
culvert_loss = ""
|
|
286
551
|
|
|
287
|
-
|
|
552
|
+
match unit_type:
|
|
553
|
+
case "BRIDGE":
|
|
554
|
+
dimensions = self._format_bridge_dimensions(unit_dict)
|
|
555
|
+
case "ORIFICE":
|
|
556
|
+
dimensions = self._format_orifice_dimensions(unit_dict)
|
|
557
|
+
case "WEIR" | "RNWEIR":
|
|
558
|
+
dimensions = self._format_weir_dimensions(unit_dict)
|
|
559
|
+
case "SLUICE":
|
|
560
|
+
dimensions = self._format_sluice_dimensions(unit_dict)
|
|
561
|
+
case "SPILL":
|
|
562
|
+
dimensions = self._format_spill_dimensions(unit_dict)
|
|
563
|
+
case "CONDUIT":
|
|
564
|
+
dimensions, culvert_loss = self._format_conduit_dimensions(unit_dict)
|
|
565
|
+
case _:
|
|
566
|
+
dimensions = ""
|
|
567
|
+
|
|
568
|
+
try:
|
|
569
|
+
weir_coefficient = unit_dict["dimensions"]["weir_coefficient"]
|
|
570
|
+
except KeyError:
|
|
571
|
+
weir_coefficient = ""
|
|
572
|
+
|
|
573
|
+
self._write(
|
|
574
|
+
writer,
|
|
575
|
+
name,
|
|
576
|
+
unit_type,
|
|
577
|
+
subtype,
|
|
578
|
+
comment,
|
|
579
|
+
friction,
|
|
580
|
+
dimensions,
|
|
581
|
+
weir_coefficient,
|
|
582
|
+
culvert_loss,
|
|
583
|
+
)
|
|
584
|
+
|
|
585
|
+
def create(self):
|
|
586
|
+
"""
|
|
587
|
+
When using the toolbox wrapper or commandline entry point, this is the entrypoint to the structure logger code
|
|
588
|
+
"""
|
|
589
|
+
self.dat = DAT(self.dat_file_path)
|
|
590
|
+
self.add_conduits()
|
|
591
|
+
self.add_structures()
|
|
592
|
+
with open(self.csv_output_path, "w", newline="") as file:
|
|
593
|
+
self.write_csv_output(file)
|