easysewer 0.0.1__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.
- easysewer/Area.py +294 -0
- easysewer/Curve.py +119 -0
- easysewer/Link.py +294 -0
- easysewer/Node.py +346 -0
- easysewer/Options.py +373 -0
- easysewer/OutputAPI.py +179 -0
- easysewer/Rain.py +209 -0
- easysewer/SolverAPI.py +154 -0
- easysewer/UDM.py +126 -0
- easysewer/__init__.py +7 -0
- easysewer/libs/linux/libswmm5.so +0 -0
- easysewer/libs/linux/swmm-output.so +0 -0
- easysewer/libs/win/swmm-output.dll +0 -0
- easysewer/libs/win/swmm5.dll +0 -0
- easysewer/utils.py +97 -0
- easysewer-0.0.1.dist-info/METADATA +16 -0
- easysewer-0.0.1.dist-info/RECORD +19 -0
- easysewer-0.0.1.dist-info/WHEEL +5 -0
- easysewer-0.0.1.dist-info/top_level.txt +1 -0
easysewer/Link.py
ADDED
@@ -0,0 +1,294 @@
|
|
1
|
+
"""
|
2
|
+
Link Management Module
|
3
|
+
|
4
|
+
This module implements various types of conduits and channels that connect nodes
|
5
|
+
in the drainage network. Supports different cross-section types and hydraulic
|
6
|
+
characteristics.
|
7
|
+
"""
|
8
|
+
from .utils import *
|
9
|
+
|
10
|
+
|
11
|
+
class Vertices:
|
12
|
+
"""
|
13
|
+
Container for link vertex coordinates.
|
14
|
+
|
15
|
+
Stores the geometry of a link's path between its endpoints.
|
16
|
+
|
17
|
+
Attributes:
|
18
|
+
link_name (str): Name of the associated link
|
19
|
+
x (list): X-coordinates of vertices
|
20
|
+
y (list): Y-coordinates of vertices
|
21
|
+
"""
|
22
|
+
def __init__(self):
|
23
|
+
self.link_name = None
|
24
|
+
self.x = []
|
25
|
+
self.y = []
|
26
|
+
|
27
|
+
|
28
|
+
class Link:
|
29
|
+
"""
|
30
|
+
Base class for all hydraulic links.
|
31
|
+
|
32
|
+
Represents a connection between two nodes in the drainage network.
|
33
|
+
|
34
|
+
Attributes:
|
35
|
+
name (str): Unique identifier for the link
|
36
|
+
vertices (Vertices): Geometric path of the link
|
37
|
+
"""
|
38
|
+
def __init__(self):
|
39
|
+
self.name = ''
|
40
|
+
self.vertices = Vertices()
|
41
|
+
|
42
|
+
def __repr__(self):
|
43
|
+
return f'Link<{self.name}>'
|
44
|
+
|
45
|
+
|
46
|
+
class Conduit(Link):
|
47
|
+
"""
|
48
|
+
Base class for conduit-type links.
|
49
|
+
|
50
|
+
Represents closed conduits or open channels with specific hydraulic properties.
|
51
|
+
|
52
|
+
Attributes:
|
53
|
+
upstream_node (str): Name of upstream node
|
54
|
+
downstream_node (str): Name of downstream node
|
55
|
+
length (float): Conduit length
|
56
|
+
roughness (float): Manning's roughness coefficient
|
57
|
+
upstream_offset (float): Offset at upstream end
|
58
|
+
downstream_offset (float): Offset at downstream end
|
59
|
+
initial_flow (float): Initial flow rate
|
60
|
+
maximum_flow (float): Maximum allowed flow rate
|
61
|
+
"""
|
62
|
+
def __init__(self):
|
63
|
+
Link.__init__(self)
|
64
|
+
self.upstream_node = ''
|
65
|
+
self.downstream_node = ''
|
66
|
+
self.length = 0.0
|
67
|
+
self.roughness = 0.0
|
68
|
+
self.upstream_offset = 0.0
|
69
|
+
self.downstream_offset = 0.0
|
70
|
+
# optional variable
|
71
|
+
self.initial_flow = 0
|
72
|
+
self.maximum_flow = 0 # means no limit
|
73
|
+
|
74
|
+
|
75
|
+
class ConduitCircle(Conduit):
|
76
|
+
def __init__(self):
|
77
|
+
Conduit.__init__(self)
|
78
|
+
self.barrels_number = 1
|
79
|
+
self.height = 0.0
|
80
|
+
|
81
|
+
|
82
|
+
class ConduitFilledCircle(Conduit):
|
83
|
+
def __init__(self):
|
84
|
+
Conduit.__init__(self)
|
85
|
+
self.barrels_number = 1
|
86
|
+
self.height = 0.0
|
87
|
+
self.filled = 0.0
|
88
|
+
|
89
|
+
|
90
|
+
class ConduitRectangleOpen(Conduit):
|
91
|
+
def __init__(self):
|
92
|
+
Conduit.__init__(self)
|
93
|
+
self.barrels_number = 1
|
94
|
+
self.height = 0.0
|
95
|
+
self.width = 0.0
|
96
|
+
|
97
|
+
|
98
|
+
class ConduitCustom(Conduit):
|
99
|
+
def __init__(self):
|
100
|
+
Conduit.__init__(self)
|
101
|
+
self.barrels_number = 1
|
102
|
+
self.height = 0.0
|
103
|
+
self.curve = ''
|
104
|
+
|
105
|
+
|
106
|
+
class LinkList:
|
107
|
+
def __init__(self):
|
108
|
+
self.data = []
|
109
|
+
|
110
|
+
def __repr__(self):
|
111
|
+
return f'{len(self.data)} Links'
|
112
|
+
|
113
|
+
def __len__(self):
|
114
|
+
return len(self.data)
|
115
|
+
|
116
|
+
def __getitem__(self, key):
|
117
|
+
if isinstance(key, int):
|
118
|
+
return self.data[key]
|
119
|
+
elif isinstance(key, str):
|
120
|
+
for item in self.data:
|
121
|
+
if item.name == key:
|
122
|
+
return item
|
123
|
+
raise KeyError(f"No item found with name '{key}'")
|
124
|
+
else:
|
125
|
+
raise TypeError("Key must be an integer or a string")
|
126
|
+
|
127
|
+
def __iter__(self):
|
128
|
+
return iter(self.data)
|
129
|
+
|
130
|
+
def __contains__(self, item):
|
131
|
+
return item in self.data
|
132
|
+
|
133
|
+
def add_link(self, link_type, link_information):
|
134
|
+
def execute(func1, func2):
|
135
|
+
def inner():
|
136
|
+
new_link = func1()
|
137
|
+
# basic information of conduit
|
138
|
+
new_link.name = link_information['name']
|
139
|
+
new_link.upstream_node = link_information['upstream_node']
|
140
|
+
new_link.downstream_node = link_information['downstream_node']
|
141
|
+
new_link.length = link_information['length']
|
142
|
+
new_link.roughness = link_information['roughness']
|
143
|
+
new_link.upstream_offset = link_information['upstream_offset']
|
144
|
+
new_link.downstream_offset = link_information['downstream_offset']
|
145
|
+
if 'initial_flow' in link_information:
|
146
|
+
new_link.initial_flow = link_information['initial_flow']
|
147
|
+
if 'maximum_flow' in link_information:
|
148
|
+
new_link.maximum_flow = link_information['maximum_flow']
|
149
|
+
# specific information of different conduit type
|
150
|
+
func2(new_link)
|
151
|
+
# add new link to link list
|
152
|
+
self.data.append(new_link)
|
153
|
+
return 0
|
154
|
+
|
155
|
+
return inner
|
156
|
+
|
157
|
+
match link_type:
|
158
|
+
case 'conduit_circle' | 'ConduitCircle':
|
159
|
+
def conduit_circle(new_link):
|
160
|
+
new_link.height = link_information['height']
|
161
|
+
if 'barrels_number' in link_information:
|
162
|
+
new_link.barrels_number = link_information['barrels_number']
|
163
|
+
|
164
|
+
return execute(ConduitCircle, conduit_circle)()
|
165
|
+
|
166
|
+
case 'conduit_filled_circle' | 'ConduitFilledCircle':
|
167
|
+
|
168
|
+
def conduit_filled_circle(new_link):
|
169
|
+
new_link.height = link_information['height']
|
170
|
+
new_link.filled = link_information['filled']
|
171
|
+
if 'barrels_number' in link_information:
|
172
|
+
new_link.barrels_number = link_information['barrels_number']
|
173
|
+
|
174
|
+
return execute(ConduitFilledCircle, conduit_filled_circle)()
|
175
|
+
|
176
|
+
case 'conduit_rectangle_open' | 'ConduitRectangleOpen':
|
177
|
+
def conduit_rectangle_open(new_link):
|
178
|
+
new_link.height = link_information['height']
|
179
|
+
new_link.width = link_information['width']
|
180
|
+
if 'barrels_number' in link_information:
|
181
|
+
new_link.barrels_number = link_information['barrels_number']
|
182
|
+
|
183
|
+
return execute(ConduitRectangleOpen, conduit_rectangle_open)()
|
184
|
+
|
185
|
+
case 'conduit_custom' | 'ConduitCustom':
|
186
|
+
def conduit_custom(new_link):
|
187
|
+
new_link.height = link_information['height']
|
188
|
+
new_link.curve = link_information['curve']
|
189
|
+
if 'barrels_number' in link_information:
|
190
|
+
new_link.barrels_number = link_information['barrels_number']
|
191
|
+
|
192
|
+
return execute(ConduitCustom, conduit_custom)()
|
193
|
+
|
194
|
+
case _:
|
195
|
+
raise TypeError(f"Unknown link type, failed to add {link_information['name']}")
|
196
|
+
|
197
|
+
def read_from_swmm_inp(self, filename):
|
198
|
+
conduit_contents = get_swmm_inp_content(filename, '[CONDUITS]')
|
199
|
+
# fill in default values
|
200
|
+
for index, line in enumerate(conduit_contents):
|
201
|
+
if len(line.split()) == 7:
|
202
|
+
conduit_contents[index] += ' 0 0'
|
203
|
+
elif len(line.split()) == 8:
|
204
|
+
conduit_contents[index] += ' 0'
|
205
|
+
x_section = get_swmm_inp_content(filename, '[XSECTIONS]')
|
206
|
+
content = combine_swmm_inp_contents(conduit_contents, x_section)
|
207
|
+
for line in content:
|
208
|
+
pair = line.split()
|
209
|
+
dic = {'name': pair[0], 'upstream_node': pair[1], 'downstream_node': pair[2], 'length': float(pair[3]),
|
210
|
+
'roughness': float(pair[4]), 'upstream_offset': float(pair[5]), 'downstream_offset': float(pair[6]),
|
211
|
+
'initial_flow': float(pair[7]), 'maximum_flow': float(pair[8])}
|
212
|
+
|
213
|
+
match pair[9]:
|
214
|
+
case 'CIRCULAR':
|
215
|
+
dic['height'] = float(pair[10])
|
216
|
+
# optional variable: Barrels
|
217
|
+
if len(pair) >= 15:
|
218
|
+
dic['barrels_number'] = int(pair[14])
|
219
|
+
self.add_link('conduit_circle', dic)
|
220
|
+
|
221
|
+
case 'FILLED_CIRCULAR':
|
222
|
+
dic['height'] = float(pair[10])
|
223
|
+
dic['filled'] = float(pair[11])
|
224
|
+
# optional variable: Barrels
|
225
|
+
if len(pair) >= 15:
|
226
|
+
dic['barrels_number'] = int(pair[14])
|
227
|
+
self.add_link('conduit_filled_circle', dic)
|
228
|
+
|
229
|
+
case 'RECT_OPEN':
|
230
|
+
dic['height'] = float(pair[10])
|
231
|
+
dic['width'] = float(pair[11])
|
232
|
+
# optional variable: Barrels
|
233
|
+
if len(pair) >= 15:
|
234
|
+
dic['barrels_number'] = int(pair[14])
|
235
|
+
self.add_link('conduit_rectangle_open', dic)
|
236
|
+
|
237
|
+
case 'CUSTOM':
|
238
|
+
dic['height'] = float(pair[10])
|
239
|
+
dic['curve'] = pair[11]
|
240
|
+
# optional variable: Barrels
|
241
|
+
if len(pair) >= 13:
|
242
|
+
dic['barrels_number'] = int(pair[-1])
|
243
|
+
self.add_link('conduit_custom', dic)
|
244
|
+
#
|
245
|
+
vertices_contents = get_swmm_inp_content(filename, '[VERTICES]')
|
246
|
+
for line in vertices_contents:
|
247
|
+
pair = line.split()
|
248
|
+
for link in self.data:
|
249
|
+
if link.name == pair[0]:
|
250
|
+
link.vertices.x.append(float(pair[1]))
|
251
|
+
link.vertices.y.append(float(pair[2]))
|
252
|
+
link.vertices.link_name = pair[0]
|
253
|
+
return 0
|
254
|
+
|
255
|
+
def write_to_swmm_inp(self, filename):
|
256
|
+
with open(filename, 'a', encoding='utf-8') as f:
|
257
|
+
f.write('\n\n[CONDUITS]\n')
|
258
|
+
f.write(
|
259
|
+
';;Name Upstream Downstream Length Roughness Up-offset Down-offset Init_flow Max_flow\n')
|
260
|
+
for link in self.data:
|
261
|
+
f.write(
|
262
|
+
f'{link.name:30} {link.upstream_node:8} {link.downstream_node:8} {link.length:8.2f} {link.roughness:8.3f} {link.upstream_offset:8.3f} {link.downstream_offset:8.3f} {link.initial_flow:8.2f} {link.maximum_flow:8.2f}\n')
|
263
|
+
#
|
264
|
+
f.write('\n\n[XSECTIONS]\n')
|
265
|
+
f.write(
|
266
|
+
';;Name Shape Geom1 Geom2 Geom3 Geom4 Barrels (Culvert)\n')
|
267
|
+
for link in self.data:
|
268
|
+
zero = 0
|
269
|
+
if isinstance(link, ConduitCircle):
|
270
|
+
f.write(
|
271
|
+
f'{link.name:30} CIRCULAR {link.height:8.2f} {zero:8} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
272
|
+
if isinstance(link, ConduitFilledCircle):
|
273
|
+
f.write(
|
274
|
+
f'{link.name:30} FILLED_CIRCULAR {link.height:8.2f} {link.filled:8.2f} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
275
|
+
if isinstance(link, ConduitRectangleOpen):
|
276
|
+
f.write(
|
277
|
+
f'{link.name:30} RECT_OPEN {link.height:8.2f} {link.width:8.2f} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
278
|
+
if isinstance(link, ConduitCustom):
|
279
|
+
f.write(
|
280
|
+
f'{link.name:30} CUSTOM {link.height:8.2f} {link.curve:8} 0 0 {link.barrels_number:8}\n')
|
281
|
+
#
|
282
|
+
f.write('\n\n[VERTICES]\n')
|
283
|
+
f.write(';;Link X-Coord Y-Coord\n')
|
284
|
+
for link in self.data:
|
285
|
+
if link.vertices.link_name is not None:
|
286
|
+
for xi, yi in zip(link.vertices.x, link.vertices.y):
|
287
|
+
f.write(f'{link.vertices.link_name} {xi} {yi}\n')
|
288
|
+
return 0
|
289
|
+
|
290
|
+
def index_of(self, link_name):
|
291
|
+
for index, item in enumerate(self.data):
|
292
|
+
if item.name == link_name:
|
293
|
+
return index
|
294
|
+
raise ValueError(f"No item found with name '{link_name}'")
|
easysewer/Node.py
ADDED
@@ -0,0 +1,346 @@
|
|
1
|
+
"""
|
2
|
+
Node Management Module
|
3
|
+
|
4
|
+
This module implements various types of nodes used in urban drainage networks including:
|
5
|
+
- Basic nodes (junctions)
|
6
|
+
- Outfall nodes with different boundary conditions
|
7
|
+
- Support for node properties like elevation, coordinates, and flow characteristics
|
8
|
+
"""
|
9
|
+
from .utils import *
|
10
|
+
|
11
|
+
|
12
|
+
class Node:
|
13
|
+
"""
|
14
|
+
Base class for all node types in the drainage network.
|
15
|
+
|
16
|
+
Represents a point element in the drainage network with basic properties
|
17
|
+
like location and elevation.
|
18
|
+
|
19
|
+
Attributes:
|
20
|
+
name (str): Unique identifier for the node
|
21
|
+
coordinate (list): [x, y] coordinates of the node
|
22
|
+
elevation (float): Node invert elevation
|
23
|
+
"""
|
24
|
+
def __init__(self):
|
25
|
+
self.name = ''
|
26
|
+
self.coordinate = [0.0, 0.0]
|
27
|
+
self.elevation = 0
|
28
|
+
|
29
|
+
def __repr__(self):
|
30
|
+
return f'Node<{self.name}>'
|
31
|
+
|
32
|
+
|
33
|
+
class Junction(Node):
|
34
|
+
"""
|
35
|
+
Junction node type for connecting conduits.
|
36
|
+
|
37
|
+
Represents intersection points in the drainage network where flows combine
|
38
|
+
or split. Includes properties for depth and ponding characteristics.
|
39
|
+
|
40
|
+
Attributes:
|
41
|
+
maximum_depth (float): Maximum water depth at junction
|
42
|
+
initial_depth (float): Initial water depth at start of simulation
|
43
|
+
overload_depth (float): Depth above which overflows occur
|
44
|
+
surface_ponding_area (float): Area available for surface ponding
|
45
|
+
dwf_base_value (float): Base dry weather flow value
|
46
|
+
dwf_patterns (list): Time patterns for dry weather flow
|
47
|
+
inflow (dict): Inflow characteristics and time series
|
48
|
+
"""
|
49
|
+
def __init__(self):
|
50
|
+
Node.__init__(self)
|
51
|
+
self.maximum_depth = 0
|
52
|
+
self.initial_depth = 0
|
53
|
+
self.overload_depth = 0
|
54
|
+
self.surface_ponding_area = 0
|
55
|
+
#
|
56
|
+
# dry weather flow
|
57
|
+
self.dwf_base_value = 0
|
58
|
+
self.dwf_patterns = []
|
59
|
+
#
|
60
|
+
# inflow
|
61
|
+
self.inflow = None
|
62
|
+
|
63
|
+
|
64
|
+
class Outfall(Node):
|
65
|
+
"""
|
66
|
+
Base class for outfall nodes.
|
67
|
+
|
68
|
+
Represents points where water leaves the drainage system. Supports various
|
69
|
+
boundary condition types through derived classes.
|
70
|
+
|
71
|
+
Attributes:
|
72
|
+
flap_gate (bool): Whether backflow prevention is present
|
73
|
+
route_to (str): Routing destination for diverted flow
|
74
|
+
"""
|
75
|
+
def __init__(self):
|
76
|
+
Node.__init__(self)
|
77
|
+
self.flap_gate = False
|
78
|
+
self.route_to = ''
|
79
|
+
|
80
|
+
|
81
|
+
class OutfallFree(Outfall):
|
82
|
+
def __init__(self):
|
83
|
+
Outfall.__init__(self)
|
84
|
+
|
85
|
+
|
86
|
+
class OutfallNormal(Outfall):
|
87
|
+
def __init__(self):
|
88
|
+
Outfall.__init__(self)
|
89
|
+
|
90
|
+
|
91
|
+
class OutfallFixed(Outfall):
|
92
|
+
def __init__(self):
|
93
|
+
Outfall.__init__(self)
|
94
|
+
self.stage = 0.0
|
95
|
+
|
96
|
+
|
97
|
+
class OutfallTidal(Outfall):
|
98
|
+
def __init__(self):
|
99
|
+
Outfall.__init__(self)
|
100
|
+
self.tidal = ''
|
101
|
+
|
102
|
+
|
103
|
+
class OutfallTimeseries(Outfall):
|
104
|
+
def __init__(self):
|
105
|
+
Outfall.__init__(self)
|
106
|
+
self.time_series = ''
|
107
|
+
|
108
|
+
|
109
|
+
class NodeList:
|
110
|
+
def __init__(self):
|
111
|
+
self.data = []
|
112
|
+
|
113
|
+
def __repr__(self):
|
114
|
+
return f'{len(self.data)} Nodes'
|
115
|
+
|
116
|
+
def __len__(self):
|
117
|
+
return len(self.data)
|
118
|
+
|
119
|
+
def __getitem__(self, key):
|
120
|
+
if isinstance(key, int):
|
121
|
+
return self.data[key]
|
122
|
+
elif isinstance(key, str):
|
123
|
+
for item in self.data:
|
124
|
+
if item.name == key:
|
125
|
+
return item
|
126
|
+
raise KeyError(f"No item found with name '{key}'")
|
127
|
+
else:
|
128
|
+
raise TypeError("Key must be an integer or a string")
|
129
|
+
|
130
|
+
def __iter__(self):
|
131
|
+
return iter(self.data)
|
132
|
+
|
133
|
+
def __contains__(self, item):
|
134
|
+
return item in self.data
|
135
|
+
|
136
|
+
def add_node(self, node_type, node_information):
|
137
|
+
def execute(func1, func2):
|
138
|
+
def inner():
|
139
|
+
# new an object according to node_type
|
140
|
+
new_node = func1()
|
141
|
+
# add essential information
|
142
|
+
if 'name' in node_information:
|
143
|
+
new_node.name = node_information['name']
|
144
|
+
else: # if it can not find name, raise error
|
145
|
+
# print('Unknown Node: Can not recognize node name')
|
146
|
+
return -1
|
147
|
+
if 'coordinate' in node_information:
|
148
|
+
new_node.coordinate = node_information['coordinate']
|
149
|
+
if 'elevation' in node_information:
|
150
|
+
new_node.elevation = node_information['elevation']
|
151
|
+
# for Outfalls
|
152
|
+
if 'flap_gate' in node_information:
|
153
|
+
new_node.flap_gate = True if node_information['flap_gate'] == 'YES' else False
|
154
|
+
if 'route_to' in node_information:
|
155
|
+
new_node.route_to = node_information['route_to']
|
156
|
+
# add node_type related information
|
157
|
+
func2(new_node)
|
158
|
+
# update node_list
|
159
|
+
self.data.append(new_node)
|
160
|
+
return 0
|
161
|
+
|
162
|
+
return inner
|
163
|
+
|
164
|
+
match node_type:
|
165
|
+
case 'junction' | 'Junction':
|
166
|
+
def junction_type(new_node):
|
167
|
+
if 'maximum_depth' in node_information:
|
168
|
+
new_node.maximum_depth = node_information['maximum_depth']
|
169
|
+
if 'initial_depth' in node_information:
|
170
|
+
new_node.initial_depth = node_information['initial_depth']
|
171
|
+
if 'overload_depth' in node_information:
|
172
|
+
new_node.overload_depth = node_information['overload_depth']
|
173
|
+
if 'surface_ponding_area' in node_information:
|
174
|
+
new_node.surface_ponding_area = node_information['surface_ponding_area']
|
175
|
+
if 'dwf_base_value' in node_information:
|
176
|
+
new_node.dwf_base_value = node_information['dwf_base_value']
|
177
|
+
if 'dwf_patterns' in node_information:
|
178
|
+
new_node.dwf_patterns = node_information['dwf_patterns']
|
179
|
+
|
180
|
+
return execute(Junction, junction_type)()
|
181
|
+
|
182
|
+
case 'outfall_free' | 'OutfallFree':
|
183
|
+
def outfall_free_type(_):
|
184
|
+
pass
|
185
|
+
|
186
|
+
return execute(OutfallFree, outfall_free_type)()
|
187
|
+
|
188
|
+
case 'outfall_normal' | 'OutfallNormal':
|
189
|
+
def outfall_normal_type(_):
|
190
|
+
pass
|
191
|
+
|
192
|
+
return execute(OutfallNormal, outfall_normal_type)()
|
193
|
+
|
194
|
+
case 'outfall_fixed' | 'OutfallFixed':
|
195
|
+
def outfall_fixed_type(new_node):
|
196
|
+
if 'stage' in node_information:
|
197
|
+
new_node.stage = node_information['stage']
|
198
|
+
|
199
|
+
return execute(OutfallFixed, outfall_fixed_type)()
|
200
|
+
|
201
|
+
case 'outfall_tidal' | 'OutfallTidal':
|
202
|
+
def outfall_tidal_type(new_node):
|
203
|
+
if 'tidal' in node_information:
|
204
|
+
new_node.tidal = node_information['tidal']
|
205
|
+
|
206
|
+
return execute(OutfallTidal, outfall_tidal_type)()
|
207
|
+
|
208
|
+
case 'outfall_time_series' | 'OutfallTimeseries':
|
209
|
+
def outfall_time_series_type(new_node):
|
210
|
+
if 'time_series' in node_information:
|
211
|
+
new_node.time_series = node_information['time_series']
|
212
|
+
|
213
|
+
return execute(OutfallTimeseries, outfall_time_series_type)()
|
214
|
+
|
215
|
+
case _:
|
216
|
+
raise TypeError(f"Unknown node type, failed to add {node_information['name']}")
|
217
|
+
|
218
|
+
def read_from_swmm_inp(self, filename):
|
219
|
+
junction_contents = get_swmm_inp_content(filename, '[JUNCTIONS]')
|
220
|
+
coordinates = get_swmm_inp_content(filename, '[COORDINATES]')
|
221
|
+
outfall_contents = get_swmm_inp_content(filename, '[OUTFALLS]')
|
222
|
+
dwf_contents = get_swmm_inp_content(filename, '[DWF]')
|
223
|
+
inflow_contents = get_swmm_inp_content(filename, '[INFLOWS]')
|
224
|
+
|
225
|
+
# coordinate list
|
226
|
+
coordinates_dic = {}
|
227
|
+
for line in coordinates:
|
228
|
+
keys = line.split()
|
229
|
+
coordinates_dic[keys[0]] = [float(keys[1]), float(keys[2])]
|
230
|
+
# process junctions
|
231
|
+
for line in junction_contents:
|
232
|
+
pair = line.split()
|
233
|
+
dic = {'name': pair[0], 'coordinate': [0.0, 0.0], 'elevation': float(pair[1]),
|
234
|
+
'maximum_depth': float(pair[2]), 'initial_depth': float(pair[3]),
|
235
|
+
'overload_depth': float(pair[4]), 'surface_ponding_area': float(pair[5])}
|
236
|
+
dic['coordinate'] = coordinates_dic[dic['name']]
|
237
|
+
self.add_node('junction', dic)
|
238
|
+
# process outfalls
|
239
|
+
for line in outfall_contents:
|
240
|
+
pair = line.split()
|
241
|
+
dic = {'name': pair[0], 'coordinate': [0.0, 0.0], 'elevation': float(pair[1])}
|
242
|
+
dic['coordinate'] = coordinates_dic[dic['name']]
|
243
|
+
#
|
244
|
+
if pair[-1] == 'YES':
|
245
|
+
dic['flap_gate'] = 'YES'
|
246
|
+
elif pair[-1] == 'NO':
|
247
|
+
dic['flap_gate'] = 'NO'
|
248
|
+
else:
|
249
|
+
dic['flap_gate'] = pair[-2]
|
250
|
+
dic['route_to'] = pair[-1]
|
251
|
+
#
|
252
|
+
match pair[2]:
|
253
|
+
case 'FREE':
|
254
|
+
self.add_node('outfall_free', dic)
|
255
|
+
case 'NORMAL':
|
256
|
+
self.add_node('outfall_normal', dic)
|
257
|
+
case 'FIXED':
|
258
|
+
dic['stage'] = float(pair[2])
|
259
|
+
self.add_node('outfall_fixed', dic)
|
260
|
+
case 'TIDAL':
|
261
|
+
dic['tidal'] = float(pair[2])
|
262
|
+
self.add_node('outfall_tidal', dic)
|
263
|
+
case 'TIMESERIES':
|
264
|
+
dic['time_series'] = float(pair[2])
|
265
|
+
self.add_node('outfall_time_series', dic)
|
266
|
+
case _:
|
267
|
+
pass
|
268
|
+
# process DWF
|
269
|
+
for line in dwf_contents:
|
270
|
+
pair = line.split()
|
271
|
+
for node in self.data:
|
272
|
+
if node.name == pair[0]:
|
273
|
+
node.dwf_base_value = pair[2]
|
274
|
+
for pattern in pair[3::]:
|
275
|
+
node.dwf_patterns.append(pattern)
|
276
|
+
# process inflow
|
277
|
+
for line in inflow_contents:
|
278
|
+
pair = line.split()
|
279
|
+
if pair[1] != 'FLOW':
|
280
|
+
raise Exception('Unsupported inflow type, only FLOW is accepted.')
|
281
|
+
for node in self.data:
|
282
|
+
if node.name == pair[0]:
|
283
|
+
result = {'time_series': pair[2], 'type': pair[3], 'm_factor': float(pair[4]),
|
284
|
+
's_factor': float(pair[5]), 'baseline': float(pair[6]), 'pattern': pair[7]}
|
285
|
+
node.inflow = result
|
286
|
+
return 0
|
287
|
+
|
288
|
+
def write_to_swmm_inp(self, filename):
|
289
|
+
with open(filename, 'a', encoding='utf-8') as f:
|
290
|
+
f.write('\n\n[JUNCTIONS]\n')
|
291
|
+
f.write(';;Name Elevation MaxDepth InitDepth SurDepth Ponding\n')
|
292
|
+
for node in self.data:
|
293
|
+
if isinstance(node, Junction):
|
294
|
+
f.write(
|
295
|
+
f'{node.name:8} {node.elevation:8.3f} {node.maximum_depth:8.3f} {node.initial_depth:8.3f} {node.overload_depth:8.3f} {node.surface_ponding_area:8.3f}\n')
|
296
|
+
#
|
297
|
+
f.write('\n\n[OUTFALLS]\n')
|
298
|
+
f.write(';;Name Elevation Type // Gated RouteTo\n')
|
299
|
+
for node in self.data:
|
300
|
+
if isinstance(node, OutfallFree):
|
301
|
+
msg = 'YES' if node.flap_gate else 'NO'
|
302
|
+
f.write(f'{node.name:8} {node.elevation:8.3f} FREE {msg:8} {node.route_to}\n')
|
303
|
+
if isinstance(node, OutfallNormal):
|
304
|
+
msg = 'YES' if node.flap_gate else 'NO'
|
305
|
+
f.write(f'{node.name:8} {node.elevation:8.3f} NORMAL {msg:8} {node.route_to}\n')
|
306
|
+
if isinstance(node, OutfallFixed):
|
307
|
+
msg = 'YES' if node.flap_gate else 'NO'
|
308
|
+
f.write(
|
309
|
+
f'{node.name:8} {node.elevation:8.3f} FIXED {node.stage:8} {msg} {node.route_to}\n')
|
310
|
+
if isinstance(node, OutfallTidal):
|
311
|
+
msg = 'YES' if node.flap_gate else 'NO'
|
312
|
+
f.write(
|
313
|
+
f'{node.name:8} {node.elevation:8.3f} TIDAL {node.tidal:8} {msg} {node.route_to}\n')
|
314
|
+
if isinstance(node, OutfallTimeseries):
|
315
|
+
msg = 'YES' if node.flap_gate else 'NO'
|
316
|
+
f.write(
|
317
|
+
f'{node.name:8} {node.elevation:8.3f} TIMESERIES {node.time_series:8} {msg} {node.route_to}\n')
|
318
|
+
#
|
319
|
+
f.write('\n\n[COORDINATES]\n')
|
320
|
+
f.write(';;Name X-Coord Y-Coord\n')
|
321
|
+
for node in self.data:
|
322
|
+
f.write(f'{node.name:8} {node.coordinate[0]:8.2f} {node.coordinate[1]:8.2f}\n')
|
323
|
+
#
|
324
|
+
f.write('\n\n[DWF]\n')
|
325
|
+
f.write(';;Node Constituent Baseline Patterns \n')
|
326
|
+
for node in self.data:
|
327
|
+
if isinstance(node, Junction):
|
328
|
+
if node.dwf_base_value != 0:
|
329
|
+
string = ' '.join(node.dwf_patterns)
|
330
|
+
f.write(f'{node.name} FLOW {node.dwf_base_value} {string}\n')
|
331
|
+
#
|
332
|
+
f.write('\n\n[INFLOWS]\n')
|
333
|
+
f.write(';;Node Constituent Time Series Type Mfactor Sfactor Baseline Pattern\n')
|
334
|
+
for node in self.data:
|
335
|
+
if isinstance(node, Junction):
|
336
|
+
if node.inflow is not None:
|
337
|
+
res = [str(i) for i in list(node.inflow.values())]
|
338
|
+
res = ' '.join(res)
|
339
|
+
f.write(f'{node.name} FLOW {res} \n')
|
340
|
+
return 0
|
341
|
+
|
342
|
+
def index_of(self, node_name):
|
343
|
+
for index, item in enumerate(self.data):
|
344
|
+
if item.name == node_name:
|
345
|
+
return index
|
346
|
+
raise ValueError(f"No item found with name '{node_name}'")
|