floodmodeller-api 0.4.2.post1__py3-none-any.whl → 0.4.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (178) hide show
  1. floodmodeller_api/__init__.py +8 -9
  2. floodmodeller_api/_base.py +169 -176
  3. floodmodeller_api/backup.py +273 -273
  4. floodmodeller_api/dat.py +889 -831
  5. floodmodeller_api/diff.py +136 -119
  6. floodmodeller_api/ied.py +302 -306
  7. floodmodeller_api/ief.py +553 -637
  8. floodmodeller_api/ief_flags.py +253 -253
  9. floodmodeller_api/inp.py +260 -266
  10. floodmodeller_api/libs/libifcoremd.dll +0 -0
  11. floodmodeller_api/libs/libifcoremt.so.5 +0 -0
  12. floodmodeller_api/libs/libifport.so.5 +0 -0
  13. floodmodeller_api/{libmmd.dll → libs/libimf.so} +0 -0
  14. floodmodeller_api/libs/libintlc.so.5 +0 -0
  15. floodmodeller_api/libs/libmmd.dll +0 -0
  16. floodmodeller_api/libs/libsvml.so +0 -0
  17. floodmodeller_api/libs/libzzn_read.so +0 -0
  18. floodmodeller_api/libs/zzn_read.dll +0 -0
  19. floodmodeller_api/logs/__init__.py +2 -2
  20. floodmodeller_api/logs/lf.py +364 -312
  21. floodmodeller_api/logs/lf_helpers.py +354 -352
  22. floodmodeller_api/logs/lf_params.py +643 -529
  23. floodmodeller_api/mapping.py +84 -0
  24. floodmodeller_api/test/__init__.py +4 -4
  25. floodmodeller_api/test/conftest.py +16 -8
  26. floodmodeller_api/test/test_backup.py +117 -117
  27. floodmodeller_api/test/test_conveyance.py +107 -0
  28. floodmodeller_api/test/test_dat.py +222 -92
  29. floodmodeller_api/test/test_data/All Units 4_6.DAT +1081 -1081
  30. floodmodeller_api/test/test_data/All Units 4_6.feb +1081 -1081
  31. floodmodeller_api/test/test_data/BRIDGE.DAT +926 -926
  32. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.dat +36 -36
  33. floodmodeller_api/test/test_data/Culvert_Inlet_Outlet.feb +36 -36
  34. floodmodeller_api/test/test_data/DamBreakADI.xml +52 -52
  35. floodmodeller_api/test/test_data/DamBreakFAST.xml +58 -58
  36. floodmodeller_api/test/test_data/DamBreakFAST_dy.xml +53 -53
  37. floodmodeller_api/test/test_data/DamBreakTVD.xml +55 -55
  38. floodmodeller_api/test/test_data/DefenceBreach.xml +53 -53
  39. floodmodeller_api/test/test_data/DefenceBreachFAST.xml +60 -60
  40. floodmodeller_api/test/test_data/DefenceBreachFAST_dy.xml +55 -55
  41. floodmodeller_api/test/test_data/Domain1+2_QH.xml +76 -76
  42. floodmodeller_api/test/test_data/Domain1_H.xml +41 -41
  43. floodmodeller_api/test/test_data/Domain1_Q.xml +41 -41
  44. floodmodeller_api/test/test_data/Domain1_Q_FAST.xml +48 -48
  45. floodmodeller_api/test/test_data/Domain1_Q_FAST_dy.xml +48 -48
  46. floodmodeller_api/test/test_data/Domain1_Q_xml_expected.json +263 -0
  47. floodmodeller_api/test/test_data/Domain1_W.xml +41 -41
  48. floodmodeller_api/test/test_data/EX1.DAT +321 -321
  49. floodmodeller_api/test/test_data/EX1.ext +107 -107
  50. floodmodeller_api/test/test_data/EX1.feb +320 -320
  51. floodmodeller_api/test/test_data/EX1.gxy +107 -107
  52. floodmodeller_api/test/test_data/EX17.DAT +421 -422
  53. floodmodeller_api/test/test_data/EX17.ext +213 -213
  54. floodmodeller_api/test/test_data/EX17.feb +422 -422
  55. floodmodeller_api/test/test_data/EX18.DAT +375 -375
  56. floodmodeller_api/test/test_data/EX18_DAT_expected.json +3876 -0
  57. floodmodeller_api/test/test_data/EX2.DAT +302 -302
  58. floodmodeller_api/test/test_data/EX3.DAT +926 -926
  59. floodmodeller_api/test/test_data/EX3_DAT_expected.json +16235 -0
  60. floodmodeller_api/test/test_data/EX3_IEF_expected.json +61 -0
  61. floodmodeller_api/test/test_data/EX6.DAT +2084 -2084
  62. floodmodeller_api/test/test_data/EX6.ext +532 -532
  63. floodmodeller_api/test/test_data/EX6.feb +2084 -2084
  64. floodmodeller_api/test/test_data/EX6_DAT_expected.json +31647 -0
  65. floodmodeller_api/test/test_data/Event Data Example.DAT +336 -336
  66. floodmodeller_api/test/test_data/Event Data Example.ext +107 -107
  67. floodmodeller_api/test/test_data/Event Data Example.feb +336 -336
  68. floodmodeller_api/test/test_data/Linked1D2D.xml +52 -52
  69. floodmodeller_api/test/test_data/Linked1D2DFAST.xml +53 -53
  70. floodmodeller_api/test/test_data/Linked1D2DFAST_dy.xml +48 -48
  71. floodmodeller_api/test/test_data/Linked1D2D_xml_expected.json +313 -0
  72. floodmodeller_api/test/test_data/blockage.dat +50 -50
  73. floodmodeller_api/test/test_data/blockage.ext +45 -45
  74. floodmodeller_api/test/test_data/blockage.feb +9 -9
  75. floodmodeller_api/test/test_data/blockage.gxy +71 -71
  76. floodmodeller_api/test/test_data/conveyance_test.dat +165 -0
  77. floodmodeller_api/test/test_data/conveyance_test.feb +116 -0
  78. floodmodeller_api/test/test_data/conveyance_test.gxy +85 -0
  79. floodmodeller_api/test/test_data/defaultUnits.dat +127 -127
  80. floodmodeller_api/test/test_data/defaultUnits.ext +45 -45
  81. floodmodeller_api/test/test_data/defaultUnits.feb +9 -9
  82. floodmodeller_api/test/test_data/defaultUnits.fmpx +58 -58
  83. floodmodeller_api/test/test_data/defaultUnits.gxy +85 -85
  84. floodmodeller_api/test/test_data/ex3.ief +20 -20
  85. floodmodeller_api/test/test_data/ex3.lf1 +2800 -2800
  86. floodmodeller_api/test/test_data/ex4.DAT +1374 -1374
  87. floodmodeller_api/test/test_data/ex4_changed.DAT +1374 -1374
  88. floodmodeller_api/test/test_data/example1.inp +329 -329
  89. floodmodeller_api/test/test_data/example2.inp +158 -158
  90. floodmodeller_api/test/test_data/example3.inp +297 -297
  91. floodmodeller_api/test/test_data/example4.inp +388 -388
  92. floodmodeller_api/test/test_data/example5.inp +147 -147
  93. floodmodeller_api/test/test_data/example6.inp +154 -154
  94. floodmodeller_api/test/test_data/expected_conveyance.csv +60 -0
  95. floodmodeller_api/test/test_data/jump.dat +176 -176
  96. floodmodeller_api/test/test_data/network.dat +1374 -1374
  97. floodmodeller_api/test/test_data/network.ext +45 -45
  98. floodmodeller_api/test/test_data/network.exy +1 -1
  99. floodmodeller_api/test/test_data/network.feb +45 -45
  100. floodmodeller_api/test/test_data/network.ied +45 -45
  101. floodmodeller_api/test/test_data/network.ief +20 -20
  102. floodmodeller_api/test/test_data/network.inp +147 -147
  103. floodmodeller_api/test/test_data/network.pxy +57 -57
  104. floodmodeller_api/test/test_data/network.zzd +122 -122
  105. floodmodeller_api/test/test_data/network_dat_expected.json +21837 -0
  106. floodmodeller_api/test/test_data/network_from_tabularCSV.csv +87 -87
  107. floodmodeller_api/test/test_data/network_ied_expected.json +287 -0
  108. floodmodeller_api/test/test_data/rnweir.dat +9 -9
  109. floodmodeller_api/test/test_data/rnweir.ext +45 -45
  110. floodmodeller_api/test/test_data/rnweir.feb +9 -9
  111. floodmodeller_api/test/test_data/rnweir.gxy +45 -45
  112. floodmodeller_api/test/test_data/rnweir_default.dat +74 -74
  113. floodmodeller_api/test/test_data/rnweir_default.ext +45 -45
  114. floodmodeller_api/test/test_data/rnweir_default.feb +9 -9
  115. floodmodeller_api/test/test_data/rnweir_default.fmpx +58 -58
  116. floodmodeller_api/test/test_data/rnweir_default.gxy +53 -53
  117. floodmodeller_api/test/test_data/unit checks.dat +16 -16
  118. floodmodeller_api/test/test_ied.py +29 -29
  119. floodmodeller_api/test/test_ief.py +136 -24
  120. floodmodeller_api/test/test_inp.py +47 -48
  121. floodmodeller_api/test/test_json.py +114 -0
  122. floodmodeller_api/test/test_logs_lf.py +102 -51
  123. floodmodeller_api/test/test_tool.py +165 -152
  124. floodmodeller_api/test/test_toolbox_structure_log.py +234 -239
  125. floodmodeller_api/test/test_xml2d.py +151 -156
  126. floodmodeller_api/test/test_zzn.py +36 -34
  127. floodmodeller_api/to_from_json.py +230 -0
  128. floodmodeller_api/tool.py +332 -329
  129. floodmodeller_api/toolbox/__init__.py +5 -5
  130. floodmodeller_api/toolbox/example_tool.py +45 -45
  131. floodmodeller_api/toolbox/model_build/__init__.py +2 -2
  132. floodmodeller_api/toolbox/model_build/add_siltation_definition.py +100 -98
  133. floodmodeller_api/toolbox/model_build/structure_log/__init__.py +1 -1
  134. floodmodeller_api/toolbox/model_build/structure_log/structure_log.py +287 -289
  135. floodmodeller_api/toolbox/model_build/structure_log_definition.py +76 -76
  136. floodmodeller_api/units/__init__.py +10 -10
  137. floodmodeller_api/units/_base.py +214 -212
  138. floodmodeller_api/units/boundaries.py +467 -467
  139. floodmodeller_api/units/comment.py +52 -55
  140. floodmodeller_api/units/conduits.py +382 -402
  141. floodmodeller_api/units/conveyance.py +301 -0
  142. floodmodeller_api/units/helpers.py +123 -131
  143. floodmodeller_api/units/iic.py +107 -101
  144. floodmodeller_api/units/losses.py +305 -306
  145. floodmodeller_api/units/sections.py +465 -446
  146. floodmodeller_api/units/structures.py +1690 -1683
  147. floodmodeller_api/units/units.py +93 -104
  148. floodmodeller_api/units/unsupported.py +44 -44
  149. floodmodeller_api/units/variables.py +87 -89
  150. floodmodeller_api/urban1d/__init__.py +11 -11
  151. floodmodeller_api/urban1d/_base.py +188 -179
  152. floodmodeller_api/urban1d/conduits.py +93 -85
  153. floodmodeller_api/urban1d/general_parameters.py +58 -58
  154. floodmodeller_api/urban1d/junctions.py +81 -79
  155. floodmodeller_api/urban1d/losses.py +81 -74
  156. floodmodeller_api/urban1d/outfalls.py +114 -110
  157. floodmodeller_api/urban1d/raingauges.py +111 -111
  158. floodmodeller_api/urban1d/subsections.py +92 -98
  159. floodmodeller_api/urban1d/xsections.py +147 -144
  160. floodmodeller_api/util.py +119 -21
  161. floodmodeller_api/validation/parameters.py +660 -660
  162. floodmodeller_api/validation/urban_parameters.py +388 -404
  163. floodmodeller_api/validation/validation.py +110 -108
  164. floodmodeller_api/version.py +1 -1
  165. floodmodeller_api/xml2d.py +632 -673
  166. floodmodeller_api/xml2d_template.py +37 -37
  167. floodmodeller_api/zzn.py +414 -363
  168. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/LICENSE.txt +13 -13
  169. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/METADATA +85 -82
  170. floodmodeller_api-0.4.4.dist-info/RECORD +185 -0
  171. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/WHEEL +1 -1
  172. floodmodeller_api/libifcoremd.dll +0 -0
  173. floodmodeller_api/test/test_data/EX3.bmp +0 -0
  174. floodmodeller_api/test/test_data/test_output.csv +0 -87
  175. floodmodeller_api/zzn_read.dll +0 -0
  176. floodmodeller_api-0.4.2.post1.dist-info/RECORD +0 -164
  177. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/entry_points.txt +0 -0
  178. {floodmodeller_api-0.4.2.post1.dist-info → floodmodeller_api-0.4.4.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,301 @@
1
+ from __future__ import annotations
2
+
3
+ from functools import lru_cache
4
+
5
+ import numpy as np
6
+ import pandas as pd
7
+ from shapely import LineString, MultiLineString, Polygon, intersection
8
+
9
+ MINIMUM_PERIMETER_THRESHOLD = 1e-8
10
+
11
+
12
+ def calculate_cross_section_conveyance(
13
+ x: np.ndarray,
14
+ y: np.ndarray,
15
+ n: np.ndarray,
16
+ rpl: np.ndarray,
17
+ panel_markers: np.ndarray,
18
+ ) -> pd.Series:
19
+ """
20
+ Calculate the conveyance of a cross-section by summing the conveyance
21
+ across all panels defined by panel markers.
22
+
23
+ Args:
24
+ x (np.ndarray): The x-coordinates of the cross-section.
25
+ y (np.ndarray): The y-coordinates of the cross-section.
26
+ n (np.ndarray): Manning's n values for each segment.
27
+ rpl (np.ndarray): Relative Path Length values for each segment.
28
+ panel_markers (np.ndarray): Boolean array indicating the start of each panel.
29
+
30
+ Returns:
31
+ pd.Series: A pandas Series containing the conveyance values indexed by water levels.
32
+
33
+ Example:
34
+ .. code-block:: python
35
+
36
+ x = np.array([0, 1, 2, 3, 4])
37
+ y = np.array([1, 2, 1, 2, 1])
38
+ n = np.array([0.03, 0.03, 0.03, 0.03, 0.03])
39
+ rpl = np.array([1., 1., 1., 1., 1.])
40
+ panel_markers = np.array([True, False, True, False, True])
41
+ result = calculate_cross_section_conveyance(x, y, n, rpl, panel_markers)
42
+ print(result)
43
+ """
44
+ # Create a set of water levels to calculate conveyance at,
45
+ # currently using 50mm minimum increments plus WLs at every data point
46
+ wls = insert_intermediate_wls(np.unique(y), threshold=0.05)
47
+
48
+ # Panel markers are forced true to the bounds to make the process work
49
+ panel_markers = np.array([True, *panel_markers[1:-1], True])
50
+ panel_indices = np.where(panel_markers)[0]
51
+ conveyance_by_panel = []
52
+ for panel_start, panel_end in zip(panel_indices[:-1], panel_indices[1:] + 1):
53
+ panel_x = x[panel_start:panel_end]
54
+ panel_y = y[panel_start:panel_end]
55
+ panel_n = n[panel_start:panel_end]
56
+ # RPL value is only valid for the start of a panel, and set to 1 if it's zero
57
+ panel_rpl = (
58
+ 1.0
59
+ if (panel_start == 0 and not panel_markers[0]) or rpl[panel_start] == 0
60
+ else float(rpl[panel_start])
61
+ )
62
+ conveyance_by_panel.append(
63
+ calculate_conveyance_by_panel(panel_x, panel_y, panel_n, panel_rpl, wls),
64
+ )
65
+
66
+ # Sum conveyance across panels
67
+ conveyance_values = [sum(values) for values in zip(*conveyance_by_panel)]
68
+
69
+ return pd.Series(data=conveyance_values, index=wls)
70
+
71
+
72
+ def calculate_conveyance_by_panel(
73
+ x: np.ndarray,
74
+ y: np.ndarray,
75
+ n: np.ndarray,
76
+ rpl: float,
77
+ wls: np.ndarray,
78
+ ) -> list[float]:
79
+ """
80
+ Calculate the conveyance for a single panel of a cross-section at specified water levels.
81
+
82
+ Args:
83
+ x (np.ndarray): The x-coordinates of the panel.
84
+ y (np.ndarray): The y-coordinates of the panel.
85
+ n (np.ndarray): Manning's n values for each segment in the panel.
86
+ rpl (float): Relative Path Length for each segment in the panel.
87
+ wls (np.ndarray): The water levels at which to calculate conveyance.
88
+
89
+ Returns:
90
+ list[float]: A list of conveyance values for each water level.
91
+ """
92
+
93
+ max_y = np.max(wls) + 1
94
+ min_y = np.min(wls) - 1
95
+
96
+ # insert additional start/end points to represent the glass wall sides
97
+ x = np.array([x[0], *x, x[-1]])
98
+ y = np.array([max_y, *y, max_y])
99
+ n = np.array([0, *n, 0])
100
+
101
+ # Define a polygon for the channel including artificial sides and top
102
+ channel_polygon = Polygon(zip(x, y))
103
+ start, end = x[0] - 0.1, x[-1] + 0.1 # Useful points enclosing the x bounds with small buffer
104
+
105
+ # Define linestring geometries representing glass walls, so they can be subtracted later
106
+ glass_walls = (
107
+ LineString(zip([x[0], x[1]], [y[0], y[1]])), # left
108
+ LineString(zip([x[-2], x[-1]], [y[-2], y[-1]])), # right
109
+ )
110
+
111
+ # Remove glass wall sections from coords
112
+ x, y, n = x[1:-1], y[1:-1], n[1:-1]
113
+
114
+ conveyance_values = []
115
+ for wl in wls:
116
+ if wl <= np.min(y):
117
+ # no channel capacity (essentially zero depth) so no need to calculate
118
+ conveyance_values.append(0.0)
119
+ continue
120
+
121
+ # Some geometries to represent the channel at a given water level
122
+ water_surface = Polygon(zip([start, start, end, end], [wl, min_y, min_y, wl]))
123
+ water_plane = intersection(channel_polygon, LineString(zip([start, end], [wl, wl])))
124
+ wetted_polygon = intersection(channel_polygon, water_surface)
125
+
126
+ multiple_parts = wetted_polygon.geom_type in ["GeometryCollection", "MultiPolygon"]
127
+ parts = wetted_polygon.geoms if multiple_parts else [wetted_polygon]
128
+
129
+ conveyance = 0.0
130
+
131
+ # 'parts' here refers to when a water level results in 2 separate channel sections,
132
+ # e.g. where the cross section has a 'peak' part way through
133
+ for part in parts:
134
+ conveyance += calculate_conveyance_part(part, water_plane, glass_walls, x, n, rpl)
135
+ conveyance_values.append(conveyance)
136
+
137
+ return conveyance_values
138
+
139
+
140
+ def calculate_conveyance_part( # noqa: PLR0913
141
+ wetted_polygon: Polygon,
142
+ water_plane: LineString,
143
+ glass_walls: tuple[LineString, LineString],
144
+ x: np.ndarray,
145
+ n: np.ndarray,
146
+ rpl: float,
147
+ ) -> float:
148
+ """
149
+ Calculate the conveyance for a part of the wetted area.
150
+
151
+ Args:
152
+ wetted_polygon (Polygon): The polygon representing the wetted area.
153
+ water_plane (LineString): The line representing the water plane.
154
+ glass_wall_left (LineString): The left boundary of the channel.
155
+ glass_wall_right (LineString): The right boundary of the channel.
156
+ x (np.ndarray): 1D array of channel chainage
157
+ n (np.ndarray): 1D array of channel mannings
158
+ rpl (float): Relative path length of panel
159
+
160
+ Returns:
161
+ float: The conveyance value for the wetted part.
162
+ """
163
+ water_plane_clip: LineString = intersection(water_plane, wetted_polygon)
164
+ glass_wall_left_clip: LineString = intersection(glass_walls[0], wetted_polygon)
165
+ glass_wall_right_clip: LineString = intersection(glass_walls[1], wetted_polygon)
166
+
167
+ # wetted perimeter should only account for actual section of channel, so we need to remove any
168
+ # length related to the water surface and any glass walls due to panel
169
+ perimeter_loss = (
170
+ water_plane_clip.length + glass_wall_left_clip.length + glass_wall_right_clip.length
171
+ )
172
+
173
+ wetted_perimeter = wetted_polygon.boundary.length - perimeter_loss
174
+ if wetted_perimeter < MINIMUM_PERIMETER_THRESHOLD:
175
+ # Would occur if water level is above lowest point on section, but intersects a near-zero
176
+ # perimeter, e.g. touching the bottom of an elevated side channel
177
+ return 0.0
178
+
179
+ area = wetted_polygon.area
180
+
181
+ wetted_polyline: LineString = (
182
+ wetted_polygon.exterior.difference(water_plane_clip)
183
+ .difference(glass_wall_left_clip)
184
+ .difference(glass_wall_right_clip)
185
+ )
186
+ weighted_mannings = calculate_weighted_mannings(x, n, rpl, wetted_polyline)
187
+
188
+ # apply conveyance equation
189
+ return (area ** (5 / 3) / wetted_perimeter ** (2 / 3)) * (wetted_perimeter / weighted_mannings)
190
+
191
+
192
+ def insert_intermediate_wls(arr: np.ndarray, threshold: float):
193
+ """
194
+ Insert intermediate water levels into an array based on a threshold.
195
+
196
+ Args:
197
+ arr (np.ndarray): The array of original water levels.
198
+ threshold (float): The maximum allowed gap between water levels.
199
+
200
+ Returns:
201
+ np.ndarray: The array with intermediate water levels inserted.
202
+ """
203
+ # Calculate gaps between consecutive elements
204
+ gaps = np.diff(arr)
205
+
206
+ # Calculate the number of points needed for each gap
207
+ num_points = (gaps // threshold).astype(int)
208
+
209
+ # Prepare lists to hold the new points and results
210
+ new_points = []
211
+
212
+ for i, start in enumerate(arr[:-1]):
213
+ end = arr[i + 1]
214
+ if num_points[i] > 0:
215
+ points = np.linspace(start, end, num_points[i] + 2)[1:-1]
216
+ new_points.extend(points)
217
+ new_points.append(end)
218
+
219
+ # Combine the original starting point with the new points
220
+ return np.array([arr[0]] + new_points)
221
+
222
+
223
+ def calculate_weighted_mannings(
224
+ x: np.ndarray,
225
+ n: np.ndarray,
226
+ rpl: float,
227
+ wetted_polyline: LineString,
228
+ ) -> float:
229
+ """Calculate the weighted Manning's n value for a wetted polyline."""
230
+
231
+ # We want the polyline to be split into each individual segment
232
+ segments = line_to_segments(wetted_polyline)
233
+ weighted_mannings = 0
234
+ for segment in segments:
235
+ mannings_value = get_mannings_by_segment_x_coords(
236
+ x,
237
+ n,
238
+ segment.coords[0][0],
239
+ segment.coords[1][0],
240
+ )
241
+ weighted_mannings += mannings_value * segment.length * np.sqrt(rpl)
242
+
243
+ return weighted_mannings
244
+
245
+
246
+ def line_to_segments(line: LineString | MultiLineString) -> list[LineString]:
247
+ """Convert a LineString or MultiLineString into a list of LineString segments."""
248
+ if isinstance(line, LineString):
249
+ segments = []
250
+ for start, end in zip(line.coords[:-1], line.coords[1:]):
251
+ points = sorted([start, end], key=lambda x: x[0])
252
+ segments.append(LineString(points))
253
+ return segments
254
+ if isinstance(line, MultiLineString):
255
+ segments = []
256
+ for linestring in line.geoms:
257
+ segments.extend(line_to_segments(linestring))
258
+ return segments
259
+ raise TypeError("Input must be a LineString or MultiLineString")
260
+
261
+
262
+ def get_mannings_by_segment_x_coords(
263
+ x: np.ndarray,
264
+ n: np.ndarray,
265
+ start_x: float,
266
+ end_x: float,
267
+ ) -> float:
268
+ """Get the Manning's n or RPL value for a segment based on its start x-coordinate."""
269
+
270
+ # This method doesn't handle cases where we have multiple manning's values at a vertical section
271
+ # and will always just take the first at any verticle, but it is probably quite rare for this
272
+ # not to be the case
273
+ if start_x == end_x:
274
+ # Vertical segment take first x match
275
+ index = np.searchsorted(x, start_x) - (start_x not in x)
276
+ else:
277
+ # Otherwise non-vertical segment, take last match
278
+ index = np.searchsorted(x, start_x, side="right") - 1
279
+
280
+ return n[index]
281
+
282
+
283
+ @lru_cache
284
+ def calculate_cross_section_conveyance_chached(
285
+ x: tuple[float],
286
+ y: tuple[float],
287
+ n: tuple[float],
288
+ rpl: tuple[float],
289
+ panel_markers: tuple[float],
290
+ ) -> pd.Series:
291
+ """Dummy function to allow for caching of the conveyance function as numpy arrays are not
292
+ hashable
293
+ """
294
+
295
+ return calculate_cross_section_conveyance(
296
+ np.array(x),
297
+ np.array(y),
298
+ np.array(n),
299
+ np.array(rpl),
300
+ np.array(panel_markers),
301
+ )
@@ -1,131 +1,123 @@
1
- """
2
- Flood Modeller Python API
3
- Copyright (C) 2023 Jacobs U.K. Limited
4
-
5
- This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
- as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
-
8
- This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
- of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
-
11
- You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
-
13
- If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
- address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
- """
16
-
17
- from typing import List, Optional
18
-
19
- # Helper Functions
20
-
21
-
22
- def split_10_char(line):
23
- return [line[i : i + 10].strip() for i in range(0, len(line), 10)]
24
-
25
-
26
- def split_12_char(line):
27
- return [line[i : i + 12].strip() for i in range(0, len(line), 12)]
28
-
29
-
30
- def split_n_char(line, n):
31
- return [line[i : i + n].strip() for i in range(0, len(line), n)]
32
-
33
-
34
- def join_10_char(*itms, dp=3):
35
- """Joins a set of values with a 10 character buffer and right-justified"""
36
- string = ""
37
- for itm in itms:
38
- if isinstance(itm, float):
39
- # save to 3 dp
40
- if len(f"{itm:.{dp}f}") > 10:
41
- # Use scientific notation if number greater than 10 characters
42
- itm = f"{itm:.{dp}e}"
43
- else:
44
- itm = f"{itm:.{dp}f}"
45
- itm = str(itm)
46
- itm = itm[:10]
47
- string += f"{itm:>10}"
48
- return string
49
-
50
-
51
- def join_12_char_ljust(*itms, dp=3):
52
- """Joins a set of values with a 12 character buffer and left-justified"""
53
- string = ""
54
- for itm in itms:
55
- if isinstance(itm, float):
56
- # save to 3 dp
57
- if len(f"{itm:.{dp}f}") > 10:
58
- # Use scientific notation if number greater than 10 characters
59
- itm = f"{itm:.{dp}e}"
60
- else:
61
- itm = f"{itm:.{dp}f}"
62
- itm = str(itm)
63
- itm = itm[:12]
64
- string += f"{itm:<12}"
65
- return string
66
-
67
-
68
- def join_n_char_ljust(n, *itms, dp=3):
69
- """Joins a set of values with a n character buffer and left-justified"""
70
- string = ""
71
- for itm in itms:
72
- if isinstance(itm, float):
73
- # save to 3 dp
74
- if len(f"{itm:.{dp}f}") > 10:
75
- # Use scientific notation if number greater than 10 characters
76
- itm = f"{itm:.{dp}e}"
77
- else:
78
- itm = f"{itm:.{dp}f}"
79
- itm = str(itm)
80
- itm = itm[:n]
81
- string += f"{itm:<{n}}"
82
- return string
83
-
84
-
85
- def _to_float(itm, default=0.0):
86
- try:
87
- return float(itm)
88
- except ValueError:
89
- return default
90
-
91
-
92
- def _to_int(itm, default=0):
93
- try:
94
- return int(itm)
95
- except ValueError:
96
- return default
97
-
98
-
99
- def _to_str(itm, default, check_float=False):
100
- if check_float:
101
- try:
102
- return float(itm)
103
- except ValueError:
104
- pass
105
- if itm == "":
106
- return default
107
- return itm
108
-
109
-
110
- def _to_data_list(block: List[str], num_cols: Optional[int] = None, date_col: Optional[int] = None):
111
- if num_cols is not None:
112
- num_cols += 1 if date_col is not None else 0
113
- data_list = []
114
- for row in block:
115
- row_split = split_10_char(row) if num_cols is None else split_10_char(row)[:num_cols]
116
- if date_col is not None:
117
- date_time = " ".join(row_split[date_col : date_col + 2])
118
- row_split = [
119
- _to_float(itm)
120
- for idx, itm in enumerate(row_split)
121
- if idx not in (date_col, date_col + 1)
122
- ]
123
- row_split.insert(date_col, date_time)
124
- else:
125
- row_split = [_to_float(itm) for itm in row_split]
126
-
127
- row_list = []
128
- for var in row_split:
129
- row_list.append(var)
130
- data_list.append(row_list)
131
- return data_list
1
+ """
2
+ Flood Modeller Python API
3
+ Copyright (C) 2024 Jacobs U.K. Limited
4
+
5
+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License
6
+ as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version.
7
+
8
+ This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
9
+ of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details.
10
+
11
+ You should have received a copy of the GNU General Public License along with this program. If not, see https://www.gnu.org/licenses/.
12
+
13
+ If you have any query about this program or this License, please contact us at support@floodmodeller.com or write to the following
14
+ address: Jacobs UK Limited, Flood Modeller, Cottons Centre, Cottons Lane, London, SE1 2QG, United Kingdom.
15
+ """
16
+
17
+ from __future__ import annotations
18
+
19
+ # Helper Functions
20
+ NOTATION_THRESHOLD = 10
21
+
22
+
23
+ def split_10_char(line):
24
+ return [line[i : i + 10].strip() for i in range(0, len(line), 10)]
25
+
26
+
27
+ def split_12_char(line):
28
+ return [line[i : i + 12].strip() for i in range(0, len(line), 12)]
29
+
30
+
31
+ def split_n_char(line, n):
32
+ return [line[i : i + n].strip() for i in range(0, len(line), n)]
33
+
34
+
35
+ def join_10_char(*itms, dp=3):
36
+ """Joins a set of values with a 10 character buffer and right-justified"""
37
+ string = ""
38
+ for itm in itms:
39
+ if isinstance(itm, float):
40
+ # save to 3 dp
41
+ # Use scientific notation if number greater than NOTATION_THRESHOLD characters
42
+ itm = f"{itm:.{dp}e}" if len(f"{itm:.{dp}f}") > NOTATION_THRESHOLD else f"{itm:.{dp}f}"
43
+ itm = str(itm)
44
+ itm = itm[:10]
45
+ string += f"{itm:>10}"
46
+ return string
47
+
48
+
49
+ def join_12_char_ljust(*itms, dp=3):
50
+ """Joins a set of values with a 12 character buffer and left-justified"""
51
+ string = ""
52
+ for itm in itms:
53
+ if isinstance(itm, float):
54
+ # save to 3 dp
55
+ # Use scientific notation if number greater than 10 characters
56
+ itm = f"{itm:.{dp}e}" if len(f"{itm:.{dp}f}") > NOTATION_THRESHOLD else f"{itm:.{dp}f}"
57
+ itm = str(itm)
58
+ itm = itm[:12]
59
+ string += f"{itm:<12}"
60
+ return string
61
+
62
+
63
+ def join_n_char_ljust(n, *itms, dp=3):
64
+ """Joins a set of values with a n character buffer and left-justified"""
65
+ string = ""
66
+ for itm in itms:
67
+ if isinstance(itm, float):
68
+ # save to 3 dp
69
+ # Use scientific notation if number greater than 10 characters
70
+ itm = f"{itm:.{dp}e}" if len(f"{itm:.{dp}f}") > NOTATION_THRESHOLD else f"{itm:.{dp}f}"
71
+ itm = str(itm)
72
+ itm = itm[:n]
73
+ string += f"{itm:<{n}}"
74
+ return string
75
+
76
+
77
+ def _to_float(itm, default=0.0):
78
+ try:
79
+ return float(itm)
80
+ except ValueError:
81
+ return default
82
+
83
+
84
+ def _to_int(itm, default=0):
85
+ try:
86
+ return int(itm)
87
+ except ValueError:
88
+ return default
89
+
90
+
91
+ def _to_str(itm, default, check_float=False):
92
+ if check_float:
93
+ try:
94
+ return float(itm)
95
+ except ValueError:
96
+ pass
97
+ if itm == "":
98
+ return default
99
+ return itm
100
+
101
+
102
+ def _to_data_list(block: list[str], num_cols: int | None = None, date_col: int | None = None):
103
+ if num_cols is not None:
104
+ num_cols += 1 if date_col is not None else 0
105
+ data_list = []
106
+ for row in block:
107
+ row_split = split_10_char(row) if num_cols is None else split_10_char(row)[:num_cols]
108
+ if date_col is not None:
109
+ date_time = " ".join(row_split[date_col : date_col + 2])
110
+ row_split = [
111
+ _to_float(itm)
112
+ for idx, itm in enumerate(row_split)
113
+ if idx not in (date_col, date_col + 1)
114
+ ]
115
+ row_split.insert(date_col, date_time)
116
+ else:
117
+ row_split = [_to_float(itm) for itm in row_split]
118
+
119
+ row_list = []
120
+ for var in row_split:
121
+ row_list.append(var)
122
+ data_list.append(row_list)
123
+ return data_list