easysewer 0.0.2__py3-none-any.whl → 0.0.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.
- easysewer/Area.py +617 -159
- easysewer/Link.py +461 -151
- easysewer/ModelAPI.py +200 -0
- easysewer/Node.py +971 -190
- easysewer/Options.py +1 -1
- easysewer/Rain.py +46 -17
- easysewer/UDM.py +47 -22
- easysewer/__init__.py +1 -1
- {easysewer-0.0.2.dist-info → easysewer-0.0.4.dist-info}/METADATA +1 -1
- easysewer-0.0.4.dist-info/RECORD +20 -0
- {easysewer-0.0.2.dist-info → easysewer-0.0.4.dist-info}/WHEEL +1 -1
- easysewer-0.0.2.dist-info/RECORD +0 -19
- {easysewer-0.0.2.dist-info → easysewer-0.0.4.dist-info}/top_level.txt +0 -0
easysewer/Link.py
CHANGED
@@ -73,6 +73,16 @@ class Conduit(Link):
|
|
73
73
|
|
74
74
|
|
75
75
|
class ConduitCircle(Conduit):
|
76
|
+
"""
|
77
|
+
Circular conduit type.
|
78
|
+
|
79
|
+
Represents a conduit with circular cross-section.
|
80
|
+
|
81
|
+
Attributes:
|
82
|
+
Inherits all attributes from Conduit class
|
83
|
+
barrels_number (int): Number of identical barrels (pipes)
|
84
|
+
height (float): Diameter of the circular conduit
|
85
|
+
"""
|
76
86
|
def __init__(self):
|
77
87
|
Conduit.__init__(self)
|
78
88
|
self.barrels_number = 1
|
@@ -80,6 +90,17 @@ class ConduitCircle(Conduit):
|
|
80
90
|
|
81
91
|
|
82
92
|
class ConduitFilledCircle(Conduit):
|
93
|
+
"""
|
94
|
+
Partially filled circular conduit type.
|
95
|
+
|
96
|
+
Represents a circular conduit with sediment or partial filling.
|
97
|
+
|
98
|
+
Attributes:
|
99
|
+
Inherits all attributes from Conduit class
|
100
|
+
barrels_number (int): Number of identical barrels (pipes)
|
101
|
+
height (float): Diameter of the circular conduit
|
102
|
+
filled (float): Height of filling/sediment from bottom
|
103
|
+
"""
|
83
104
|
def __init__(self):
|
84
105
|
Conduit.__init__(self)
|
85
106
|
self.barrels_number = 1
|
@@ -88,6 +109,17 @@ class ConduitFilledCircle(Conduit):
|
|
88
109
|
|
89
110
|
|
90
111
|
class ConduitRectangleOpen(Conduit):
|
112
|
+
"""
|
113
|
+
Open rectangular conduit type.
|
114
|
+
|
115
|
+
Represents an open channel with rectangular cross-section.
|
116
|
+
|
117
|
+
Attributes:
|
118
|
+
Inherits all attributes from Conduit class
|
119
|
+
barrels_number (int): Number of identical barrels (channels)
|
120
|
+
height (float): Height of the rectangular channel
|
121
|
+
width (float): Width of the rectangular channel
|
122
|
+
"""
|
91
123
|
def __init__(self):
|
92
124
|
Conduit.__init__(self)
|
93
125
|
self.barrels_number = 1
|
@@ -96,6 +128,17 @@ class ConduitRectangleOpen(Conduit):
|
|
96
128
|
|
97
129
|
|
98
130
|
class ConduitCustom(Conduit):
|
131
|
+
"""
|
132
|
+
Custom conduit type.
|
133
|
+
|
134
|
+
Represents a conduit with custom-defined cross-section based on a curve.
|
135
|
+
|
136
|
+
Attributes:
|
137
|
+
Inherits all attributes from Conduit class
|
138
|
+
barrels_number (int): Number of identical barrels
|
139
|
+
height (float): Maximum height of the custom cross-section
|
140
|
+
curve (str): Name of the curve defining the custom cross-section
|
141
|
+
"""
|
99
142
|
def __init__(self):
|
100
143
|
Conduit.__init__(self)
|
101
144
|
self.barrels_number = 1
|
@@ -104,16 +147,50 @@ class ConduitCustom(Conduit):
|
|
104
147
|
|
105
148
|
|
106
149
|
class LinkList:
|
150
|
+
"""
|
151
|
+
A collection class for managing links in a drainage network.
|
152
|
+
|
153
|
+
This class provides storage and management for various types of links (conduits),
|
154
|
+
with methods for adding, accessing, and processing links.
|
155
|
+
|
156
|
+
Attributes:
|
157
|
+
data (list): List containing all link objects
|
158
|
+
"""
|
107
159
|
def __init__(self):
|
108
160
|
self.data = []
|
109
161
|
|
110
162
|
def __repr__(self):
|
163
|
+
"""
|
164
|
+
Returns a string representation of the LinkList.
|
165
|
+
|
166
|
+
Returns:
|
167
|
+
str: A string showing the count of links in the list
|
168
|
+
"""
|
111
169
|
return f'{len(self.data)} Links'
|
112
170
|
|
113
171
|
def __len__(self):
|
172
|
+
"""
|
173
|
+
Returns the number of links in the list.
|
174
|
+
|
175
|
+
Returns:
|
176
|
+
int: Number of links in the list
|
177
|
+
"""
|
114
178
|
return len(self.data)
|
115
179
|
|
116
180
|
def __getitem__(self, key):
|
181
|
+
"""
|
182
|
+
Gets a link by index or name.
|
183
|
+
|
184
|
+
Args:
|
185
|
+
key (int|str): Index or name of link to retrieve
|
186
|
+
|
187
|
+
Returns:
|
188
|
+
Link: The requested link
|
189
|
+
|
190
|
+
Raises:
|
191
|
+
KeyError: If link name not found
|
192
|
+
TypeError: If key is not int or str
|
193
|
+
"""
|
117
194
|
if isinstance(key, int):
|
118
195
|
return self.data[key]
|
119
196
|
elif isinstance(key, str):
|
@@ -125,170 +202,403 @@ class LinkList:
|
|
125
202
|
raise TypeError("Key must be an integer or a string")
|
126
203
|
|
127
204
|
def __iter__(self):
|
205
|
+
"""
|
206
|
+
Returns an iterator for the link list.
|
207
|
+
|
208
|
+
Returns:
|
209
|
+
iterator: Iterator for the links
|
210
|
+
"""
|
128
211
|
return iter(self.data)
|
129
212
|
|
130
213
|
def __contains__(self, item):
|
214
|
+
"""
|
215
|
+
Checks if a link exists in the list.
|
216
|
+
|
217
|
+
Args:
|
218
|
+
item: Link to check for
|
219
|
+
|
220
|
+
Returns:
|
221
|
+
bool: True if link exists in list
|
222
|
+
"""
|
131
223
|
return item in self.data
|
132
224
|
|
133
|
-
def add_link(self, link_type, link_information):
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
153
|
-
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
|
161
|
-
|
162
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
179
|
-
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
225
|
+
def add_link(self, link_type, link_information=None):
|
226
|
+
"""
|
227
|
+
Add a new link to the data structure based on its type and information.
|
228
|
+
Generates default values for missing essential attributes.
|
229
|
+
|
230
|
+
Args:
|
231
|
+
link_type (str): Type of link to add (e.g., 'conduit_circle', 'conduit_filled_circle')
|
232
|
+
link_information (dict, optional): Dictionary containing link attributes
|
233
|
+
Defaults to empty dict if None
|
234
|
+
|
235
|
+
Returns:
|
236
|
+
Link: The newly created link object
|
237
|
+
|
238
|
+
Raises:
|
239
|
+
TypeError: If link_type is not recognized
|
240
|
+
ValueError: If a link with the same name already exists
|
241
|
+
or if required attributes are missing
|
242
|
+
|
243
|
+
Example:
|
244
|
+
>>> links = LinkList()
|
245
|
+
>>> links.add_link('conduit_circle', {'name': 'C1', 'upstream_node': 'J1',
|
246
|
+
... 'downstream_node': 'J2', 'height': 0.5})
|
247
|
+
<Link C1>
|
248
|
+
"""
|
249
|
+
# Initialize link_information dict if not provided
|
250
|
+
if link_information is None:
|
251
|
+
link_information = {}
|
252
|
+
|
253
|
+
# Normalize link type: lowercase and handle both formats
|
254
|
+
normalized_type = link_type.lower().replace('_', '')
|
255
|
+
if normalized_type.startswith('conduit'):
|
256
|
+
normalized_type = normalized_type
|
257
|
+
else:
|
258
|
+
normalized_type = 'conduit' + normalized_type
|
259
|
+
|
260
|
+
# Check if a name is provided and if it already exists in the collection
|
261
|
+
if 'name' in link_information:
|
262
|
+
requested_name = link_information['name']
|
263
|
+
if any(link.name == requested_name for link in self.data):
|
264
|
+
raise ValueError(f"Link with name '{requested_name}' already exists")
|
265
|
+
|
266
|
+
# Define attribute hierarchy based on class inheritance
|
267
|
+
# Level 1: Common attributes for all Link types with defaults
|
268
|
+
link_base_attrs = {
|
269
|
+
'name': lambda link_type, info: info.get('name', self._generate_default_name(link_type))
|
270
|
+
}
|
271
|
+
|
272
|
+
# Level 2: Attributes for all Conduit types with defaults
|
273
|
+
conduit_base_attrs = {
|
274
|
+
'upstream_node': lambda _, info: info.get('upstream_node', ''),
|
275
|
+
'downstream_node': lambda _, info: info.get('downstream_node', ''),
|
276
|
+
'length': lambda _, info: info.get('length', 4.29),
|
277
|
+
'roughness': lambda _, info: info.get('roughness', 0.013), # Default Manning's n
|
278
|
+
'upstream_offset': lambda _, info: info.get('upstream_offset', 0.0),
|
279
|
+
'downstream_offset': lambda _, info: info.get('downstream_offset', 0.0),
|
280
|
+
'initial_flow': lambda _, info: info.get('initial_flow', 0.0),
|
281
|
+
'maximum_flow': lambda _, info: info.get('maximum_flow', 0.0)
|
282
|
+
}
|
283
|
+
|
284
|
+
# Level 3: Specific attributes for conduit subtypes with defaults
|
285
|
+
conduit_specific_attrs = {
|
286
|
+
'conduitcircle': {
|
287
|
+
'barrels_number': lambda _, info: info.get('barrels_number', 1),
|
288
|
+
'height': lambda _, info: info.get('height', 0.3)
|
289
|
+
},
|
290
|
+
'conduitfilledcircle': {
|
291
|
+
'barrels_number': lambda _, info: info.get('barrels_number', 1),
|
292
|
+
'height': lambda _, info: info.get('height', 0.3),
|
293
|
+
'filled': lambda _, info: info.get('filled', 0.0)
|
294
|
+
},
|
295
|
+
'conduitrectangleopen': {
|
296
|
+
'barrels_number': lambda _, info: info.get('barrels_number', 1),
|
297
|
+
'height': lambda _, info: info.get('height', 2.0),
|
298
|
+
'width': lambda _, info: info.get('width', 1.0)
|
299
|
+
},
|
300
|
+
'conduitcustom': {
|
301
|
+
'barrels_number': lambda _, info: info.get('barrels_number', 1),
|
302
|
+
'height': lambda _, info: info.get('height', 1.0),
|
303
|
+
'curve': lambda _, info: info.get('curve', '')
|
304
|
+
}
|
305
|
+
}
|
306
|
+
|
307
|
+
# Define link type configurations
|
308
|
+
link_types = {
|
309
|
+
'conduitcircle': {
|
310
|
+
'class': ConduitCircle,
|
311
|
+
'attrs': {**link_base_attrs, **conduit_base_attrs, **conduit_specific_attrs['conduitcircle']}
|
312
|
+
},
|
313
|
+
'conduitfilledcircle': {
|
314
|
+
'class': ConduitFilledCircle,
|
315
|
+
'attrs': {**link_base_attrs, **conduit_base_attrs, **conduit_specific_attrs['conduitfilledcircle']}
|
316
|
+
},
|
317
|
+
'conduitrectangleopen': {
|
318
|
+
'class': ConduitRectangleOpen,
|
319
|
+
'attrs': {**link_base_attrs, **conduit_base_attrs, **conduit_specific_attrs['conduitrectangleopen']}
|
320
|
+
},
|
321
|
+
'conduitcustom': {
|
322
|
+
'class': ConduitCustom,
|
323
|
+
'attrs': {**link_base_attrs, **conduit_base_attrs, **conduit_specific_attrs['conduitcustom']}
|
324
|
+
}
|
325
|
+
}
|
326
|
+
|
327
|
+
# Check if normalized link type exists
|
328
|
+
if normalized_type not in link_types:
|
329
|
+
raise TypeError(
|
330
|
+
f"Unknown link type '{link_type}', failed to add {link_information.get('name', 'unnamed link')}"
|
331
|
+
)
|
332
|
+
|
333
|
+
# Get link configuration
|
334
|
+
link_config = link_types[normalized_type]
|
335
|
+
link_class = link_config['class']
|
336
|
+
attrs = link_config['attrs']
|
337
|
+
|
338
|
+
# Create new link
|
339
|
+
new_link = link_class()
|
340
|
+
|
341
|
+
# Set all applicable attributes according to hierarchy, using default generators
|
342
|
+
for attr, default_generator in attrs.items():
|
343
|
+
value = default_generator(normalized_type, link_information)
|
344
|
+
setattr(new_link, attr, value)
|
345
|
+
|
346
|
+
# Check if the generated default name is unique (when name wasn't explicitly provided)
|
347
|
+
if 'name' not in link_information and any(link.name == new_link.name for link in self.data):
|
348
|
+
raise ValueError(f"Generated default name '{new_link.name}' already exists")
|
349
|
+
|
350
|
+
# Add link to data structure
|
351
|
+
self.data.append(new_link)
|
352
|
+
|
353
|
+
return new_link # Return the created link for immediate use if needed
|
354
|
+
|
355
|
+
def _generate_default_name(self, link_type):
|
356
|
+
"""
|
357
|
+
Generate a default name for a link based on its type and existing links count.
|
358
|
+
|
359
|
+
Args:
|
360
|
+
link_type (str): Type of link (e.g. 'conduit_circle', 'conduit_custom')
|
361
|
+
|
362
|
+
Returns:
|
363
|
+
str: Generated name in format 'TYPE##' where TYPE is first 3 letters of link type
|
364
|
+
and ## is sequential number
|
365
|
+
"""
|
366
|
+
# Extract the specific type after 'conduit_'
|
367
|
+
if '_' in link_type:
|
368
|
+
specific_type = link_type.split('_')[1]
|
369
|
+
else:
|
370
|
+
specific_type = link_type.replace('conduit', '')
|
371
|
+
|
372
|
+
# Get first 3 letters of the specific type
|
373
|
+
prefix = specific_type[:3].upper() # First 3 letters of link type
|
374
|
+
|
375
|
+
# Get count of links with the same type prefix
|
376
|
+
existing_count = sum(1 for link in self.data if hasattr(link, 'name') and
|
377
|
+
link.name and link.name.startswith(prefix))
|
378
|
+
|
379
|
+
# Generate name with next number
|
380
|
+
return f"{prefix}{existing_count + 1}"
|
196
381
|
|
197
382
|
def read_from_swmm_inp(self, filename):
|
198
|
-
|
199
|
-
|
383
|
+
"""
|
384
|
+
Read link data from a SWMM input file.
|
385
|
+
|
386
|
+
Processes the following sections from SWMM input file:
|
387
|
+
- [CONDUITS]
|
388
|
+
- [XSECTIONS]
|
389
|
+
- [VERTICES]
|
390
|
+
|
391
|
+
Args:
|
392
|
+
filename (str): Path to the SWMM input file
|
393
|
+
|
394
|
+
Returns:
|
395
|
+
int: 0 if successful
|
396
|
+
|
397
|
+
Raises:
|
398
|
+
FileNotFoundError: If the input file doesn't exist
|
399
|
+
ValueError: If required sections are missing or data is malformed
|
400
|
+
Exception: For other processing errors
|
401
|
+
"""
|
402
|
+
try:
|
403
|
+
# Read all required sections
|
404
|
+
conduit_contents = get_swmm_inp_content(filename, '[CONDUITS]')
|
405
|
+
x_section_contents = get_swmm_inp_content(filename, '[XSECTIONS]')
|
406
|
+
vertices_contents = get_swmm_inp_content(filename, '[VERTICES]')
|
407
|
+
|
408
|
+
# Process conduits and cross-sections
|
409
|
+
self._process_conduits_and_xsections(conduit_contents, x_section_contents)
|
410
|
+
|
411
|
+
# Process vertices
|
412
|
+
self._process_vertices(vertices_contents)
|
413
|
+
|
414
|
+
return 0
|
415
|
+
except Exception as e:
|
416
|
+
# Re-raise with more context
|
417
|
+
raise type(e)(f"Error reading SWMM input file: {str(e)}")
|
418
|
+
|
419
|
+
def _process_conduits_and_xsections(self, conduit_contents, x_section_contents):
|
420
|
+
"""
|
421
|
+
Process conduit and cross-section data from SWMM input file.
|
422
|
+
|
423
|
+
Args:
|
424
|
+
conduit_contents (list): Lines from the [CONDUITS] section
|
425
|
+
x_section_contents (list): Lines from the [XSECTIONS] section
|
426
|
+
"""
|
427
|
+
# Fill in default values for conduit contents
|
200
428
|
for index, line in enumerate(conduit_contents):
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
209
|
-
|
210
|
-
|
211
|
-
|
212
|
-
|
213
|
-
|
214
|
-
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
if len(
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
429
|
+
parts = line.split()
|
430
|
+
if len(parts) == 7:
|
431
|
+
conduit_contents[index] += ' 0 0' # Add default initial_flow and maximum_flow
|
432
|
+
elif len(parts) == 8:
|
433
|
+
conduit_contents[index] += ' 0' # Add default maximum_flow
|
434
|
+
|
435
|
+
# Combine conduit and cross-section data
|
436
|
+
combined_content = combine_swmm_inp_contents(conduit_contents, x_section_contents)
|
437
|
+
|
438
|
+
# Process each combined line
|
439
|
+
for line in combined_content:
|
440
|
+
try:
|
441
|
+
parts = line.split()
|
442
|
+
if len(parts) < 10: # Need at least basic conduit info + shape
|
443
|
+
continue
|
444
|
+
|
445
|
+
# Create basic conduit dictionary
|
446
|
+
conduit_data = {
|
447
|
+
'name': parts[0],
|
448
|
+
'upstream_node': parts[1],
|
449
|
+
'downstream_node': parts[2],
|
450
|
+
'length': float(parts[3]),
|
451
|
+
'roughness': float(parts[4]),
|
452
|
+
'upstream_offset': float(parts[5]),
|
453
|
+
'downstream_offset': float(parts[6]),
|
454
|
+
'initial_flow': float(parts[7]),
|
455
|
+
'maximum_flow': float(parts[8])
|
456
|
+
}
|
457
|
+
|
458
|
+
# Process based on cross-section shape
|
459
|
+
shape = parts[9]
|
460
|
+
if shape == 'CIRCULAR':
|
461
|
+
if len(parts) > 10:
|
462
|
+
conduit_data['height'] = float(parts[10])
|
463
|
+
if len(parts) >= 15:
|
464
|
+
conduit_data['barrels_number'] = int(parts[14])
|
465
|
+
self.add_link('conduit_circle', conduit_data)
|
466
|
+
|
467
|
+
elif shape == 'FILLED_CIRCULAR':
|
468
|
+
if len(parts) > 11:
|
469
|
+
conduit_data['height'] = float(parts[10])
|
470
|
+
conduit_data['filled'] = float(parts[11])
|
471
|
+
if len(parts) >= 15:
|
472
|
+
conduit_data['barrels_number'] = int(parts[14])
|
473
|
+
self.add_link('conduit_filled_circle', conduit_data)
|
474
|
+
|
475
|
+
elif shape == 'RECT_OPEN':
|
476
|
+
if len(parts) > 11:
|
477
|
+
conduit_data['height'] = float(parts[10])
|
478
|
+
conduit_data['width'] = float(parts[11])
|
479
|
+
if len(parts) >= 15:
|
480
|
+
conduit_data['barrels_number'] = int(parts[14])
|
481
|
+
self.add_link('conduit_rectangle_open', conduit_data)
|
482
|
+
|
483
|
+
elif shape == 'CUSTOM':
|
484
|
+
if len(parts) > 11:
|
485
|
+
conduit_data['height'] = float(parts[10])
|
486
|
+
conduit_data['curve'] = parts[11]
|
487
|
+
if len(parts) >= 13:
|
488
|
+
conduit_data['barrels_number'] = int(parts[-1])
|
489
|
+
self.add_link('conduit_custom', conduit_data)
|
490
|
+
except (ValueError, IndexError) as e:
|
491
|
+
# Log error but continue processing other conduits
|
492
|
+
print(f"Warning: Error processing conduit in line '{line}': {str(e)}")
|
493
|
+
|
494
|
+
def _process_vertices(self, vertices_contents):
|
495
|
+
"""
|
496
|
+
Process vertex data from SWMM input file.
|
497
|
+
|
498
|
+
Args:
|
499
|
+
vertices_contents (list): Lines from the [VERTICES] section
|
500
|
+
"""
|
246
501
|
for line in vertices_contents:
|
247
|
-
|
248
|
-
|
249
|
-
if link
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
502
|
+
try:
|
503
|
+
parts = line.split()
|
504
|
+
if len(parts) < 3: # Need at least link name, x, y
|
505
|
+
continue
|
506
|
+
|
507
|
+
link_name = parts[0]
|
508
|
+
x_coord = float(parts[1])
|
509
|
+
y_coord = float(parts[2])
|
510
|
+
|
511
|
+
# Find the link and add vertex
|
512
|
+
for link in self.data:
|
513
|
+
if link.name == link_name:
|
514
|
+
link.vertices.x.append(x_coord)
|
515
|
+
link.vertices.y.append(y_coord)
|
516
|
+
link.vertices.link_name = link_name
|
517
|
+
break
|
518
|
+
except (ValueError, IndexError) as e:
|
519
|
+
# Log error but continue processing other vertices
|
520
|
+
print(f"Warning: Error processing vertex in line '{line}': {str(e)}")
|
254
521
|
|
255
522
|
def write_to_swmm_inp(self, filename):
|
256
|
-
|
257
|
-
|
258
|
-
|
259
|
-
|
260
|
-
|
523
|
+
"""
|
524
|
+
Write link data to a SWMM input file.
|
525
|
+
|
526
|
+
Writes the following sections to the SWMM input file:
|
527
|
+
- [CONDUITS]
|
528
|
+
- [XSECTIONS]
|
529
|
+
- [VERTICES]
|
530
|
+
|
531
|
+
Args:
|
532
|
+
filename (str): Path to the SWMM input file to write to
|
533
|
+
|
534
|
+
Returns:
|
535
|
+
int: 0 if successful
|
536
|
+
|
537
|
+
Raises:
|
538
|
+
IOError: If the file cannot be opened or written to
|
539
|
+
"""
|
540
|
+
try:
|
541
|
+
with open(filename, 'a', encoding='utf-8') as f:
|
542
|
+
# Write CONDUITS section
|
543
|
+
f.write('\n\n[CONDUITS]\n')
|
261
544
|
f.write(
|
262
|
-
|
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):
|
545
|
+
';;Name Upstream Downstream Length Roughness Up-offset Down-offset Init_flow Max_flow\n')
|
546
|
+
for link in self.data:
|
273
547
|
f.write(
|
274
|
-
f'{link.name:30}
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
289
|
-
|
290
|
-
|
548
|
+
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')
|
549
|
+
|
550
|
+
# Write XSECTIONS section
|
551
|
+
f.write('\n\n[XSECTIONS]\n')
|
552
|
+
f.write(
|
553
|
+
';;Name Shape Geom1 Geom2 Geom3 Geom4 Barrels (Culvert)\n')
|
554
|
+
for link in self.data:
|
555
|
+
zero = 0
|
556
|
+
if isinstance(link, ConduitCircle):
|
557
|
+
f.write(
|
558
|
+
f'{link.name:30} CIRCULAR {link.height:8.2f} {zero:8} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
559
|
+
elif isinstance(link, ConduitFilledCircle):
|
560
|
+
f.write(
|
561
|
+
f'{link.name:30} FILLED_CIRCULAR {link.height:8.2f} {link.filled:8.2f} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
562
|
+
elif isinstance(link, ConduitRectangleOpen):
|
563
|
+
f.write(
|
564
|
+
f'{link.name:30} RECT_OPEN {link.height:8.2f} {link.width:8.2f} {zero:8} {zero:8} {link.barrels_number:8}\n')
|
565
|
+
elif isinstance(link, ConduitCustom):
|
566
|
+
f.write(
|
567
|
+
f'{link.name:30} CUSTOM {link.height:8.2f} {link.curve:8} 0 0 {link.barrels_number:8}\n')
|
568
|
+
|
569
|
+
# Write VERTICES section
|
570
|
+
f.write('\n\n[VERTICES]\n')
|
571
|
+
f.write(';;Link X-Coord Y-Coord\n')
|
572
|
+
for link in self.data:
|
573
|
+
if link.vertices.link_name is not None:
|
574
|
+
for xi, yi in zip(link.vertices.x, link.vertices.y):
|
575
|
+
f.write(f'{link.vertices.link_name} {xi} {yi}\n')
|
576
|
+
return 0
|
577
|
+
except IOError as e:
|
578
|
+
raise IOError(f"Error writing to SWMM input file: {str(e)}")
|
579
|
+
|
580
|
+
def index_of(self, link_name, return_link=False):
|
581
|
+
"""
|
582
|
+
Find the index of a link by name.
|
583
|
+
|
584
|
+
Args:
|
585
|
+
link_name (str): Name of the link to find
|
586
|
+
return_link (bool, optional): If True, returns the link object instead of index
|
587
|
+
Defaults to False
|
588
|
+
|
589
|
+
Returns:
|
590
|
+
int or Link: Index of the link in the data list, or the link object if return_link is True
|
591
|
+
|
592
|
+
Raises:
|
593
|
+
ValueError: If no link with the given name is found
|
594
|
+
|
595
|
+
Example:
|
596
|
+
>>> links = LinkList()
|
597
|
+
>>> links.add_link('conduit_circle', {'name': 'C1'})
|
598
|
+
>>> links.index_of('C1')
|
599
|
+
0
|
600
|
+
"""
|
291
601
|
for index, item in enumerate(self.data):
|
292
602
|
if item.name == link_name:
|
293
|
-
return index
|
603
|
+
return item if return_link else index
|
294
604
|
raise ValueError(f"No item found with name '{link_name}'")
|