Monday, December 30, 2024

SWMMIO profiler.py Summary

Below is an extended summary of the code that describes how it constructs a SWMM model profile plot in Python, focusing on node/link geometry, profile configuration, and plot labeling:


Overview

This code supplies a set of matplotlib-based utilities for generating a profile plot (a side-view cut) of a SWMM model network, including nodes (manholes), links (conduits, weirs, orifices, pumps, outlets), ground elevation, and an optional hydraulic grade line (HGL). The profile is built from a path (a sequential chain of upstream-to-downstream elements) and rendered onto a given matplotlib axis (ax).

Core functionalities:

  1. Profile Building
    • build_profile_plot(ax, model, path_selection) – the central function that orchestrates retrieving geometry from a SWMM model, placing each node/link at the correct horizontal position, and returning a config dictionary (profile_config) for subsequent use.
  2. Node & Link Drawing
    • _add_node_plot(...) and _add_link_plot(...) – low-level routines to plot rectangles/trapezoids for manholes or link cross-sections (differentiating between conduits, weirs, orifices, pumps, etc.).
  3. Ground Line & HGL
    • _add_ground_plot(...) – draws the ground-level profile (brown dashed line) across the entire path.
    • add_hgl_plot(...) – overlays a line that represents the computed or observed water surface elevation at each node (or between nodes).
  4. Labeling
    • add_node_labels_plot(...) – attaches text labels/arrows for each node in the profile.
    • add_link_labels_plot(...) – attaches text labels/arrows for each link in the profile.

The net result is a layered side-view diagram of how nodes, links, ground line, and HGL align along a chosen path in a SWMM model.


Key Components

1. build_profile_plot(ax, model, path_selection)

Purpose:

  • Assembles the entire profile by iterating over a list of (us_node, ds_node, link_name) tuples, effectively describing a chain from upstream to downstream.
  • Produces a dictionary with details on each node/link’s placement, enabling further annotation or plotting.

Process:

  1. Initialization

    • Retrieves nodes = model.nodes.dataframe and links = model.links.dataframe.
    • Declares profile_config with empty lists for “nodes” and “links,” plus “path_selection.”
    • Sets up arrays for ground_levels['x'] & ground_levels['level'].
  2. Iterating over Path

    • For each tuple (us_node, ds_node, link_id) in path_selection:
      • Plots the first node if ind == 0, calling _add_node_plot(...) and storing the node geometry in profile_config["nodes"].
      • Accumulates a “rolling x-position” (rolling_x_pos) that simulates the horizontal distance for each link, dependent on link length or a default link length (if it’s a weir, orifice, pump, outlet).
      • Calls _add_node_plot(...) again for the downstream node and _add_link_plot(...) for the link geometry between them.
        • Each node gets appended to profile_config['nodes'], each link to profile_config['links'].
    • After finishing the loop, _add_ground_plot(...) is used to draw a dashed line indicating ground elevation.
  3. Return

    • A profile_config dictionary with:
      • nodes: A list of dicts containing node IDs, rolling x-positions, invert elevations.
      • links: A list of dicts with link IDs, midpoints, link type.
      • path_selection: Echo of the input path for reference.

2. _add_node_plot(ax, x, model, node_name, link_set, ... )

Purpose:

  • Draws a rectangular manhole (or node structure) at position x on the x-axis, with the correct invert elevation and max depth.

Details:

  • Extracts invert_el from nodes.loc[node_name].
  • Determines the node’s depth from model’s input data (model.inp.junctions, model.inp.outfalls, model.inp.storage), defaulting to MaxDepth.
  • Plots a rectangle with corners [(x−width, invert_el), (x+width, invert_el+depth), ...] in black lines.
  • Optionally draws a gradient fill on the sides for a 3D effect.

Returns:

  • A dictionary of {'x': [ul_x, ur_x], 'level': [ul_y, ur_y]}, used by _add_ground_plot to incorporate node’s top edge as part of ground level, if needed.

3. _add_link_plot(ax, us_x_position, ds_x_position, model, link_set, ...)

Purpose:

  • Draws a link cross-section between the upstream node at us_x_position and downstream node at ds_x_position, considering the link type (conduit, weir, orifice, pump, outlet).

Process:

  1. Determine link type from links.loc[link_id].Type.
  2. For CONDUIT:
    • Use links.loc[link_id].Length (already accounted for in the x offset) plus geometry fields like Geom1 for diameter/height, InOffset, OutOffset for vertical offsets.
  3. For ORIFICE, WEIR, PUMP, OUTLET:
    • Uses specialized default lengths (like DEFAULT_WEIR_LENGTH) or a single crest height for orifices/weirs.
    • Draw polygons or lines to represent the side profile.
  4. Returns a dict with {'x': [...], 'bottom': [...], 'link_type': link_type, 'mid_x': [...], 'mid_y': [...]}, enabling the main function or labeling routines to know how to position text or the HGL line.

4. _add_ground_plot(ax, ground_levels)

Purpose:

  • Joins the ground-level points computed from each node or link in a dashed brown line ('--', 'brown').
  • The ground_levels['x'] and ground_levels['level'] data are accumulated during node draws in build_profile_plot.

5. add_hgl_plot(ax, profile_config, hgl=None, depth=None, color='b', label="HGL")

Purpose:

  • Overlays a line representing the hydraulic grade line (HGL) or water surface.
  • Two usage patterns:
    1. hgl – a dictionary/Series mapping node IDs to HGL elevations, or
    2. depth – a dictionary/Series mapping node IDs to water depth, which is then added to the node invert.
  • The function loops through each node in profile_config['nodes'], builds an array of HGL values, and does a single ax.plot(...).

Weir Link Special Handling:

  • If a link is type “WEIR,” it tries to insert a midpoint to reflect any possible “step” between upstream and downstream side. This ensures the HGL transitions properly if weirs create abrupt changes.

6. add_node_labels_plot(ax, model, profile_config, ...) and add_link_labels_plot(ax, model, profile_config, ...)

Purpose:

  • Adds textual labels for each node or link above/below the structure.
  • Each label is anchored with an arrow (arrowprops=dict(...)) from the label text down to the structure.
  • Offers optional stagger so that labels alternate to avoid collisions.

Implementation details:

  • For nodes:
    • Extract invert + depth = top of node.
    • Place an annotation arrow from the node top to a label above the highest node in the plot.
  • For links:
    • Use the link’s midpoint or bottom to place the arrow.
    • Place the text below the entire group if needed (to avoid crowding).

Typical Usage Flow

  1. Call build_profile_plot(ax, model, path_selection).
    • Returns profile_config with node/link geometry.
    • Draws the profile lines for manholes, conduits, ground, etc.
  2. Optionally overlay:
    • add_hgl_plot to show the water surface profile.
    • add_node_labels_plot or add_link_labels_plot to label items.
  3. Render or save the figure:
    import matplotlib.pyplot as plt
    
    fig, ax = plt.subplots()
    pcfg = build_profile_plot(ax, my_model, my_path)
    add_hgl_plot(ax, pcfg, hgl=node_water_surface_dict)
    add_node_labels_plot(ax, my_model, pcfg)
    add_link_labels_plot(ax, my_model, pcfg)
    plt.show()
    

Constants and Defaults

  • MH_WIDTH = 10: Horizontal half-width for manholes when drawing a node profile.
  • DEFAULT_ORIFICE_LENGTH, DEFAULT_PUMP_LENGTH, DEFAULT_WEIR_LENGTH, DEFAULT_OUTLET_LENGTH: The “visual” length used for these special link types if the model doesn’t have a length (or if a simplified approach is desired).

Error Handling

  • error.InvalidDataTypes – raised if add_hgl_plot(...) sees neither hgl nor depth in the correct form.
  • The code also uses model.inp references (like model.inp.junctions) to check how to interpret node depth or offset. If data is missing, it might cause an exception or result in an incomplete profile.

Conclusion

This code forms a profile-plot toolkit for SWMM or SWMM-like hydraulic models:

  1. build_profile_plot systematically places nodes and links horizontally, respecting lengths and offsets.
  2. Node/Link subroutines handle the geometry difference among CONDUIT, WEIR, ORIFICE, PUMP, OUTLET.
  3. Ground line and HGL lines add topographical and hydraulic context.
  4. Labeling routines annotate each node/link, making the final plot self-explanatory.

Together, these help engineers or researchers visualize how water flows and see the relative elevations of nodes, the shape/length of links, and changes in water surface (HGL) across a chosen path in the SWMM network.

SWMMIO swmm_graphics.py Summary

 Below is an extended summary of this code, detailing how it generates both static images and interactive web maps for SWMM model elements (nodes, conduits, parcels) and what each function contributes to that workflow.


Overview

This code comprises two primary functionalities:

  1. draw_model – Generates a static image (PNG) of a SWMM model’s geometry (nodes, links, optional parcels) with optional basemap integration.
  2. create_map – Produces an interactive web map (HTML/GeoJSON) reflecting the model’s network, centering on the model’s bounding box or centroid.

By combining methods for coordinate transformation, PIL-based drawing, and HTML/JavaScript templating, the module allows users to visualize SWMM models in a flexible way—either as a static, annotated image or an interactive map in a browser.


1. draw_model Function

Purpose

draw_model orchestrates the creation of a PNG image representing SWMM model data, optionally including:

  • Nodes: Drawn as circles using a separate draw_node utility.
  • Conduits (Links): Drawn as lines with draw_conduit.
  • Parcels: (Optional) drawn as polygons (e.g., for flood-risk areas).
  • Basemap layers: If configured, it calls _draw_basemap to overlay additional shapefile-based features (streets, polygons).

Key Arguments

  • model: A swmmio.Model object (optional). If provided, the code automatically retrieves model.nodes() and model.links().
  • nodes, conduits: Pandas DataFrames containing geometry columns (e.g., coords). These can be passed directly if a model object is not used.
  • parcels: An optional DataFrame for polygon data. Each row needs a draw_coords or coordinate info.
  • title and annotation: Strings to annotate the final image with a title (top-left) and extra details (bottom-left).
  • file_path: If provided, the final PNG is saved to disk; if omitted, the PIL Image object is returned.
  • bbox: Custom bounding box for clipping. If omitted, the bounding box is derived from the data extents.
  • px_width: Desired output image width in pixels (default 2048). Internally, the function doubles this for anti-aliasing.

Workflow

  1. Data Gathering:

    • If model is provided, calls model.nodes() and model.links() for geometry.
    • Otherwise, uses the nodes and conduits DataFrames directly.
  2. Coordinate Transform:

    • Calls px_to_irl_coords to convert real-world or model coordinates into pixel coordinates.
    • Yields a bounding box and scale ratio for consistent rendering.
  3. Creating the Image:

    • Makes a PIL Image of the appropriate dimension and background color (white).
    • Obtains a Draw handle (draw = ImageDraw.Draw(img)).
  4. Basemap Layers (optional):

    • If config.include_basemap is True, calls _draw_basemap to load shapefiles from config.basemap_options and draw them in the background (e.g., roads, property boundaries).
  5. Drawing:

    • Parcels: If provided, draws each polygon using the draw_coords plus a fill color from r.draw_color.
    • Conduits: Each row is passed to draw_conduit(row, draw).
    • Nodes: Each row is passed to draw_node(row, draw).
  6. Annotation:

    • If title is given, calls annotate_title.
    • If annotation is given, calls annotate_details.
    • Always calls annotate_timestamp to add a timestamp in the top-right corner.
  7. Saving or Returning the Image:

    • If file_path is specified, calls save_image(img, file_path) (which can do an antialias thumbnail and optionally open the file).
    • Otherwise, returns the img object directly—useful in Jupyter notebooks or for further processing in memory.

Typical Use

my_image = draw_model(
    model=my_swmm_model,
    title="Example Model",
    annotation="Beta version",
    file_path="example_model.png",
    bbox=((2691647, 221073), (2702592, 227171)),
    px_width=2048
)

2. create_map Function

Purpose

Creates an interactive web-based map (HTML + GeoJSON) displaying the SWMM model’s geometry. This approach uses a map template (e.g., from BETTER_BASEMAP_PATH) where placeholders are replaced with:

  • The model’s centroid and bounding box, to position and bound the map.
  • The model’s links.geojson and nodes.geojson data (assuming model has .geojson properties).

Arguments

  • model: A swmmio.Model object that presumably includes .nodes.geojson and .links.geojson.
  • filename: Destination HTML file path. If not provided, the function returns the generated HTML as a string.
  • basemap: A path to an HTML template with placeholders.

Workflow

  1. CRS Check: If model.crs is known, tries to transform the model coordinates to EPSG:4326 for web mapping.
  2. Compute centroid and bounding box from the model’s inp.coordinates (via centroid_and_bbox_from_coords).
  3. Read the basemap template file line by line.
  4. Insert relevant data:
    • conduits = <geojson dumps of model.links.geojson>
    • nodes = <geojson dumps of model.nodes.geojson>
    • map center from the centroid [lng, lat].
    • fitBounds calls from the bounding box.
  5. Write the final HTML to the filename.
    • If filename is None, the result is returned as a string.

Typical Use

html_content = create_map(model=my_swmm_model, filename=None)

# This returns the HTML as a string, which can be displayed in a Jupyter notebook via IFrame or written to file manually

Supporting Elements

_draw_basemap(draw, img, bbox, px_width, shift_ratio)

  • Called internally by draw_model if config.include_basemap is True.
  • Iterates over features in config.basemap_options['features'], reads shapefiles, transforms them to pixel coords, and draws lines or polygons.
  • If columns like ST_NAME exist, it calls annotate_streets to place text.

import statements

  • PIL (Image, ImageDraw) for static image creation.
  • swmmio.graphics.config and swmmio.defs.constants for color definitions and user preferences (like white background).
  • px_to_irl_coords, save_image from swmmio.graphics.utils: handle geometry transformations and final image saving.
  • draw_node, draw_conduit from swmmio.graphics.drawing: actual shape-drawing functions for SWMM nodes/links.
  • spatial modules for shapefile reading (spatial.read_shapefile) and coordinate conversions.
  • centroid_and_bbox_from_coords for bounding box logic.

Why These Functions Are Useful

  1. Versatile Visualization:

    • draw_model suits quick static diagrams for reports, screenshots, or docs.
    • create_map suits interactive exploration—useful for large networks or checking node/link attributes in detail.
  2. Easy Integration:

    • Because the code references typical SWMM data structures (from model.nodes(), model.links()), it plugs directly into swmmio’s data pipeline.
  3. Customizable:

    • By adjusting parameters like px_width, bbox, or passing in your own DataFrames, you can refine your map’s resolution and coverage.
    • The code can also incorporate external shapefiles for background context (streets, administrative boundaries) via _draw_basemap.

Example End-to-End Usage

  1. Create/Load a SWMM Model:
    from swmmio import Model
    my_model = Model("example_model.inp")
    
  2. Generate a Static PNG:
    draw_model(
        model=my_model,
        title="My SWMM Model Visualization",
        annotation="Preliminary results",
        file_path="my_model_viz.png",
        px_width=2048
    )
    
  3. Generate an Interactive Map:
    map_html = create_map(my_model, filename="my_model_map.html", auto_open=True)
    # This writes an HTML file that references the model's geojson data.
    # If auto_open=True is implemented, it might automatically open in a web browser (depending on OS).
    

Conclusion

Through draw_model and create_map, this code seamlessly visualizes SWMM networks in static or interactive formats:

  • draw_model: Renders a color-coded, labeled 2D PNG from a SWMM model or user-supplied DataFrames.
  • create_map: Creates a Leaflet-style HTML page embedding the model as geojson for dynamic panning/zooming.

These functionalities enhance the analysis workflow by allowing engineers, researchers, and stakeholders to quickly see how a model’s nodes, links, or external shapefile-based layers align, and to share results in either a simple image or an interactive, browsable map.

SWMMIO utils.py Summary

 Below is an extended summary of this code. It explains how each function assists in rendering or transforming geospatial data—particularly SWMM or model elements—to a 2D image (e.g., for plotting or map-making). The code heavily depends on PIL (Pillow) for image manipulation and pandas for DataFrame handling.


Overview

These utility/helper functions are primarily about:

  1. Coordinate transformations (real-world to pixel space).
  2. Bounding box manipulations for clipping or placing circles/polygons on an image.
  3. Calculations like distances between points, midpoints, angles, and bounding boxes.
  4. Saving rendered images with optional resizing, plus an auto-open feature.

They complement other modules that do the actual drawing of SWMM objects (nodes, conduits, parcels) by giving the tools to convert geospatial or real-world coordinates to the pixel domain, ensuring everything lines up properly on a final image canvas.


Primary Functions

1. save_image(img, img_path, antialias=True, auto_open=False)

  • Purpose: Saves a PIL Image object to disk.
  • Key Parameters:
    • img: The PIL.Image object to save.
    • img_path: File path (including extension) for saving.
    • antialias (bool): If True, shrinks the image (50% thumbnail) using a high-quality resampling filter (LANCZOS).
    • auto_open (bool): If True, attempts to open the resulting file via the OS default program (os.startfile on Windows).
  • Usage Example:
    save_image(my_image, "output.png", antialias=True, auto_open=True)
    

2. px_to_irl_coords(df, px_width=4096.0, bbox=None, shift_ratio=None)

  • Purpose: Transforms a DataFrame’s coords column (which stores real-world or model coordinates) into pixel coordinates for drawing onto an image of width px_width.
  • Parameters:
    • df: A pandas DataFrame that must include a coords column. Each row’s coords is a list of (x, y) tuples.
    • px_width: Target image width (in pixels).
    • bbox: Optional bounding box ([(xmin, ymin), (xmax, ymax)]). If not provided, it’s computed from min and max of the coordinates.
    • shift_ratio: If provided, it forces a custom scale factor. Otherwise, it’s derived from px_width / width_of_real_coords.
  • Returns:
    1. Modified df with a new column named draw_coords storing scaled pixel coordinates.
    2. The bounding box used (bbox).
    3. Calculated image height in pixels.
    4. Calculated image width in pixels.
    5. The final shift_ratio.
  • Key Steps:
    1. Possibly clip the DataFrame rows to the bounding box (using clip_to_box).
    2. Compute a scale factor if shift_ratio not specified.
    3. For each row, shift & scale original coords from the real bounding box to pixel space.

3. circle_bbox(coordinates, radius=5)

  • Purpose: Returns a PIL-friendly bounding box for an ellipse or circle.
  • Parameter:
    • coordinates: (x, y) center point in pixel space.
    • radius: Circle radius in pixels.
  • Returns: A 4-tuple (x_min, y_min, x_max, y_max) that PIL uses for draw.ellipse(...).

4. clip_to_box(df, bbox)

  • Purpose: Filters rows whose coords do not intersect the bounding box.
  • Implementation:
    • For each row’s coordinate set, checks if any point is inside bbox. If none are inside, the row is excluded.
  • Used to ensure only features within the bounding box are drawn.

5. angle_bw_points(xy1, xy2)

  • Purpose: Calculates an angle (in degrees) suitable for rotating text or shapes such that it aligns with a line from xy1 to xy2.
  • Implementation:
    • Basic trigonometry with atan(dx/dy), adjusting the angle to transform to typical image coordinates.
  • Used in label or street annotation to orient text along a line.

6. midpoint(xy1, xy2)

  • Purpose: Returns the midpoint (x, y) between two coordinates (xy1, xy2).
  • Usage: Often used for placing text or icons along a segment.

7. point_in_box(bbox, point)

  • Purpose: Quick test if point is within bounding box bbox = [(xmin, ymin), (xmax, ymax)].
  • Returns: True if (x, y) is inside or on edges of that box.

8. length_bw_coords(upstreamXY, downstreamXY)

  • Purpose: Returns Euclidean distance (straight-line) between two points (x1, y1) and (x2, y2).
  • Used: Checking the length of lines, e.g., for deciding if some label or circle is too large for a short line segment.

9. rotate_coord_about_point(xy, radians, origin=(0, 0))

  • Purpose: Rotates a coordinate (xy) around a specified origin by a given angle in radians.
  • Implementation:
    adjusted_x = x - origin_x
    adjusted_y = y - origin_y
    # apply rotation
    qx = origin_x + cos_rad * adjusted_x + sin_rad * adjusted_y
    qy = origin_y - sin_rad * adjusted_x + cos_rad * adjusted_y
    
  • Usage: Potentially used when rotating polygons, nodes, or text anchor points around a pivot.

Typical Workflow

  1. Load model or real-world coordinates into a DataFrame with a coords column.
  2. Determine bounding box for the region of interest.
  3. Convert from real-world coordinates to pixel space with px_to_irl_coords(...).
  4. Initialize a PIL image (e.g., Image.new(...)).
  5. Draw shapes using the transformed draw_coords for each element. Possibly use circle_bbox(...) for nodes, lines for conduits, etc.
  6. (Optional) Clip data to bounding box with clip_to_box(...).
  7. Save the resulting image with save_image(...), specifying size or antialiasing settings.

Integration & Benefits

  • Integration: These functions complement other modules that define color gradients or shapes for SWMM nodes/links.
  • Scalability: By separating transformations (px_to_irl_coords) from actual drawing logic, the code remains modular—one part ensures correct coordinate mapping, the other draws specific objects.
  • Automated: The bounding box logic and scale factor calculations streamline the process of generating consistent maps, ensuring that model elements fill the image neatly.
  • Clarity: Reusable geometry functions (like midpoint, angle_bw_points, length_bw_coords) keep the code DRY and maintainable.

Conclusion

Overall, these are geometry and utility functions supporting 2D image creation from geospatial or SWMM-like data:

  • Converts coordinate sets to pixel-based coordinates for rendering.
  • Clips data to a bounding box so only relevant features are drawn.
  • Enables easy circle bounding boxes and line geometry checks.
  • Provides a standard method (save_image) to finalize and optionally open the resultant map.

Hence, they form the foundation for constructing dynamic or static visualizations of model networks, flood extents, or any data that can be mapped to x-y coordinates and displayed in a 2D image.

SWMMIO drawing.py Summary

 Below is an extended summary of this code, describing its primary functions, purpose, and how it integrates data about SWMM models (or similarly structured geospatial data) with Python’s PIL library for visualization.


Overview

This code provides a set of utility functions for graphically rendering elements of a SWMM-like model (nodes, conduits, parcels, etc.) on a static image. It leverages PIL (Python Imaging Library) to:

  1. Draw shapes (e.g., circles for nodes, lines/polygons for conduits, parcels).
  2. Apply coloring schemes (e.g., flooding risk gradients, capacity usage).
  3. Annotate images with text (titles, timestamps, details).

These functions appear especially useful for diagnosing or visualizing simulation results, such as hours flooded, maximum flow, or changes in subcatchment flooding.


Key Functional Areas

  1. Size & Color Calculation for Nodes/Conduits/Parcels

    • node_draw_size(node) and node_draw_color(node):
      • Calculate a node’s radius and fill color based on attributes like “HoursFlooded.”
      • Example: If a node floods for more than a threshold, it’s drawn larger and in red.
    • conduit_draw_size(conduit) and conduit_draw_color(conduit):
      • Return draw size (line thickness) and color for a conduit based on flow capacity or “MaxQPerc.”
      • Example: Overstressed conduits might be rendered thicker and shift from grey to red.
    • parcel_draw_color(parcel, style='risk'):
      • For polygons representing parcels, color them according to risk levels or differences between scenarios (e.g., new_flooding = purple, decreased_flooding = lightblue).
  2. Drawing with PIL

    • draw_node(node, draw) and draw_conduit(conduit, draw):
      • Use the node/conduit’s computed color/size to draw circles/lines on a PIL.ImageDraw object.
      • Coordinates are taken from node.draw_coords or conduit.draw_coords.
    • draw_parcel_risk(parcel, draw) or draw_parcel_risk_delta(parcel, draw):
      • Fill polygons for subcatchments or parcels with specific colors to indicate risk/delta changes.
  3. Annotating Streets & Text

    • annotate_streets(df, img, text_col):
      • For each row in a DataFrame, draws a street name text along a line, rotated according to the angle between two points.
      • Uses ImageFont.truetype with a user-defined font path (FONT_PATH).
    • annotate_title(title, draw), annotate_timestamp(draw), annotate_details(txt, draw):
      • Writes a map title, date/time stamp, or additional text onto the image at specified positions.
  4. Color Gradients

    • gradient_grey_red(x, xmin, xmax):
      • Converts a numeric value into an RGB color between grey and red, depending on how x compares to [xmin, xmax].
    • gradient_color_red(x, xmin, xmax, startCol=lightgrey):
      • Similar approach, but from a start color (e.g., lightgrey) to red.
    • The gradient formulas increment or decrement each RGB channel to create a color scale.
    • These gradient functions are used in node/conduit drawing or parcel coloring, to reflect data like flow or hours flooded.
  5. Miscellaneous Utilities

    • circle_bbox(center, radius): Returns bounding box coords for a circle, given center coords and radius.
    • length_bw_coords(pt1, pt2): Computes distance between two points.
    • angle_bw_points(pt1, pt2): Returns the angle in degrees between two points, used for text rotation.
    • midpoint(pt1, pt2): Returns the midpoint of two coordinates.
    • line_size(q, exp=1): Quick function for scaling a numeric flow q into a line width by raising it to some exponent.

Usage Flow

A typical workflow might look like:

  1. Precompute or store in each node/parcel row the relevant attributes (e.g., HoursFlooded, MaxQ) along with drawing coordinates (node.draw_coords).
  2. Call draw_node(node, draw) or draw_conduit(conduit, draw) on a PIL.ImageDraw instance:
    • This automatically fetches the color and size from node_draw_color(node) and node_draw_size(node).
  3. Optionally annotate the image with street names (annotate_streets) or add a title/timestamp at the top corner.
  4. Save or show the final PIL image after all shapes are drawn.

Integration

  • swmmio.defs.constants: The code references color constants (red, purple, lightblue, lightgreen, black, lightgrey, grey).
  • FONT_PATH: A user-specified or default file path to a TrueType font, needed by ImageFont.truetype.
  • swmmio.graphics.utils: Additional geometry or drawing utilities (like circle_bbox, angle_bw_points).
  • The code ultimately depends on PIL (from PIL import Image, ImageDraw, ImageFont, ImageOps) to manipulate raster images.

Benefits & Rationale

  1. Automated Visualization:

    • Simplifies drawing SWMM-based or geospatial data onto images, using meaningful color scales for flow, flood hours, or infiltration.
    • Great for debugging (e.g., seeing which conduits are near capacity) or for final reporting.
  2. Flexible:

    • Because color sizes/gradients are derived from numeric columns (MaxQPerc, HoursFlooded, etc.), the script can be easily extended to new metrics (like water quality data).
  3. PIL-based:

    • The code is library-agnostic in the sense that it doesn’t rely on more complex plotting frameworks (like matplotlib).
    • A plain ImageDraw approach can be scaled for simpler or more custom rendering.

Conclusion

In summary, this code is a lightweight but comprehensive set of drawing and annotation routines for SWMM or other hydrologic/hydraulic data. It ties together numeric data (like flooding hours or conduit capacity usage) with visual cues (line thickness, color gradients) in a PIL image. By doing so, it enables automated map creation or quick debugging plots for model elements—nodes, conduits, parcels—complete with text annotations and dynamic color scaling.

PYSWMM lidunits.py Summary

 Below is an extended summary of the code, describing its structure, primary functionalities, and how each class interacts with SWMM’s LID (Low Impact Development) data.


Purpose and Context

This code defines Python classes (Surface, Pavement, Storage, Soil, and WaterBalance) that provide a user-friendly interface to SWMM’s LID components. Each class focuses on a specific layer (or conceptual grouping) of a Low Impact Development unit—such as bioretention cells, permeable pavements, or green roofs—and exposes numeric properties (inflows, outflows, evaporations, infiltration rates, etc.) by querying PySWMM’s low-level toolkit functions (getLidUResult and getLidUFluxRates).

By wrapping SWMM’s C-based toolkit calls into clear, Pythonic property getters, modelers can easily monitor real-time hydraulic processes within each LID layer.


Structure and Key Components

1. _flux_rate Helper Function

def _flux_rate(model, subcatchment, lid_index, layer):
    return model.getLidUFluxRates(subcatchment, lid_index, layer)
  • A private utility that retrieves net inflow/outflow (flux) for a particular LID unit layer.
  • Supports surface, soil, storage, and pavement layers as enumerated by LidLayers.

2. Surface Class

  • Represents the surface layer of an LID unit (e.g., ponded water on top of a biocell or permeable pavement).
  • Properties:
    • depth: Current depth of water on the surface.
    • inflow: Rate of precipitation + runon flowing into this LID unit.
    • infiltration: Rate at which water is infiltrating from the surface layer downward.
    • evaporation: Water lost to evaporation from the surface.
    • outflow: Overflow or surface water leaving the LID.
    • flux_rate: Net flux (inflow - outflow) from the surface layer in the previous time step.

3. Pavement Class

  • Focuses on the porous pavement layer in certain LID designs.
  • Properties:
    • depth: Depth of water within the pavement voids.
    • evaporation: Pavement water lost via evaporation.
    • percolation: Water percolating out of the pavement layer down into the underlying layer.
    • flux_rate: Net flux from the pavement layer.

4. Storage Class

  • Applies to the storage layer, which might represent a storage zone underneath surface layers (e.g., gravel storage in permeable pavement or an underdrain storage chamber).
  • Properties:
    • depth: Current depth of water in the storage layer.
    • inflow: Water entering this storage layer from above.
    • exfiltration: Water exfiltrating into native soil.
    • evaporation: Water lost via evaporation from the storage zone.
    • drain: Flow leaving through an underdrain or outlet.
    • flux_rate: Net inflow-outflow rate from the storage layer.

5. Soil Class

  • Represents the soil layer for LIDs such as bioretention cells or green roofs.
  • Properties:
    • moisture: The current soil moisture content.
    • evaporation: Evaporation from soil.
    • percolation: Percolation (downward flow) from the soil layer.
    • flux_rate: Net flux of water into/out of the soil layer.

6. WaterBalance Class

  • Provides overall LID water balance metrics, representing totals across the entire LID unit.
  • Properties:
    • inflow: Total inflow received by the LID (surface and subsurface combined).
    • evaporation: Total evaporation from all layers.
    • infiltration: Total infiltration to the native soil.
    • surface_flow: Total surface runoff leaving the LID.
    • drain_flow: Flow exiting the LID through an underdrain.
    • initial_volume, final_volume: Stored water volume at the start and end of a time step.

Typical Usage

  1. Accessing LID Data

    • These classes are generally instantiated within a broader LIDUnit context in PySWMM. For example:
      surf = Surface(model, lidunit_object)
      current_depth = surf.depth
      
    • Each class needs references to the SWMM model object (model) and the specific LID unit and subcatchment (lidunit).
  2. Retrieving Real-Time Values

    • Within a simulation loop, a user might query Surface infiltration or Soil moisture at each time step to make control decisions or track performance.
      for step in sim:
          print(surf.infiltration, soil.moisture)
      
  3. Analyzing End-of-Simulation Results

    • Users may also retrieve final state or total inflow/outflow totals after running the simulation, aiding in post-processing or reporting.

Key Advantages

  1. High-Level Abstraction:

    • By naming each property (surface.inflow, pavement.depth) rather than relying on numeric indices, the code is more readable and user-friendly.
  2. Modular LID Layers:

    • Splitting each layer (Surface, Pavement, Storage, Soil) into its own class clarifies the distinct processes involved and simplifies maintenance.
  3. Consistent Access Patterns:

    • All classes follow a similar pattern: @property getters that call getLidUResult or _flux_rate under the hood, ensuring a uniform interface across different layers.
  4. Real-Time or Post-Run Data:

    • The properties can be queried on-the-fly during a time-stepped simulation or after the simulation completes for a detailed water balance assessment.

Conclusion

In summary, these classes (Surface, Pavement, Storage, Soil, and WaterBalance) encapsulate SWMM’s LID layer data into convenient, Python-based APIs. By doing so, they empower engineers and researchers to more easily inspect and manage the complex hydrologic and hydraulic processes occurring within a Low Impact Development practice during a SWMM simulation.

PYSWMM links.py Summary

Below is an extended summary of this code, highlighting its main classes, functionality, and how each piece fits into the broader PySWMM architecture:


Overview

This module defines Python classes (Links, Link, Conduit, and Pump) that provide a user-friendly interface to SWMM5’s link objects. By leveraging PySWMM’s underlying toolkit (primarily through self._model references), it enables modelers to access and manipulate hydraulic and water-quality data for conduits, pumps, orifices, weirs, and other link types during a SWMM simulation.

Key capabilities include:

  1. Iteration over all links in a SWMM model.
  2. Easy retrieval of key link parameters (e.g., offsets, flow limit, seepage rate).
  3. On-the-fly manipulation of specific link properties (initial flow, target setting, etc.).
  4. Access to runtime results (flow depth, volume, water quality) at each simulation timestep.
  5. Specialized subclasses for conduits and pumps, enabling detailed statistics and custom methods.

Core Classes

1. Links

  • Purpose: Acts as a container and iterator for all links in an open SWMM model.
  • Initialization:
    links = Links(sim)
    
    Here, sim is an active Simulation object. The constructor verifies that the SWMM model is loaded (model._model.fileLoaded) before proceeding.
  • Iteration:
    • Supports Python’s iteration protocol (__iter__, __next__), allowing developers to loop over links:
      for link in Links(sim):
          print(link.linkid)
      
    • Internally, each iteration retrieves a new Link instance using its index-based ID from the underlying SWMM model.
  • Lookup:
    • Provides __getitem__ to retrieve a Link object directly by ID:
      c1c2 = links['C1:C2']
      
    • If the retrieved link is identified as a conduit or pump, it is automatically cast to the appropriate subclass (Conduit or Pump).
  • Membership:
    • Implements __contains__ to check if a link ID exists in the model:
      "C1:C2" in links  # returns True or False
      

2. Link

  • Purpose: Represents an individual SWMM link. This base class applies to all link types (conduit, pump, orifice, weir, outlet).
  • Initialization:
    c1c2 = Link(model, 'C1:C2')
    
    • Verifies the model is open and that the given ID is valid.
  • Properties & Methods:
    1. Link Identification
      • linkid: Returns the link’s SWMM ID (e.g., "C1:C2").
      • connections: Returns a tuple (inlet_node_id, outlet_node_id).
    2. Link Type Checks
      • is_conduit(), is_pump(), is_orifice(), is_weir(), is_outlet(): Each returns a boolean indicating the link’s specific type.
    3. Parameter Accessors (Getters/Setters)
      • Structural Parameters:
        • inlet_offset, outlet_offset
        • initial_flow (link’s startup flow), flow_limit (maximum flow rate)
        • inlet_head_loss, outlet_head_loss, average_head_loss, seepage_rate
      • Each property retrieves or updates SWMM’s internal data via self._model.getLinkParam or self._model.setLinkParam.
    4. Runtime Results
      • flow, depth, volume, froude, ups_xsection_area, ds_xsection_area
      • current_setting (real-time setting for a controlled link)
      • target_setting (allows the user to change the setting programmatically via self._model.setLinkSetting(linkid, value)).
    5. Water Quality
      • pollut_quality: Dictionary of pollutant concentrations currently in the link.
        • Can be set by passing a tuple (pollutant_ID, pollutant_value) to the property.
      • total_loading: Summed pollutant loading in the link.
      • reactor_quality: Pollutant concentration in the “mixed reactor” portion of the link (also a dictionary).
  • Usage:
    with Simulation('my_model.inp') as sim:
        link_obj = Links(sim)['C1:C2']
        link_obj.flow_limit = 10.0       # Set max flow
        link_obj.target_setting = 0.75   # Adjust link setting mid-simulation
    

3. Conduit (Subclass of Link)

  • Purpose: Specialized link class for conduits, inheriting from Link.
  • Primary Feature:
    • conduit_statistics: Provides a dictionary of extended hydraulic metrics (peak flow/velocity/depth, surcharging times, capacity-limited fraction, flow class durations, and more).
    • These statistics are cumulative over the entire simulation run and are accessed after the simulation loop completes:
      with Simulation('my_model.inp') as sim:
          c1c2 = Links(sim)['C1:C2']
          for step in sim:
              pass
          stats = c1c2.conduit_statistics
          print(stats['peak_flow'])  # e.g., 2.134 cfs
      

4. Pump (Subclass of Link)

  • Purpose: Specialized link class for pumps.
  • Primary Feature:
    • pump_statistics: Cumulative usage data for the pump, including utilization time, min/avg/max flow rates, total volume pumped, energy consumed, and how frequently the pump operates off-curve.
    • Accessed similarly to the conduit stats:
      with Simulation('my_pump_model.inp') as sim:
          pump_link = Links(sim)['Pump1']
          for step in sim:
              pass
          stats = pump_link.pump_statistics
          print(stats['energy_consumed'], stats['total_volume'])
      

Typical Workflow

  1. Open a SWMM simulation:
    from pyswmm import Simulation, Links
    
    with Simulation('network.inp') as sim:
        # ...
    
  2. Instantiate the Links collection:
    links_collection = Links(sim)
    
  3. Iterate or access individual links:
    # Directly by ID
    mylink = links_collection['LinkID123']
    
    # Or by iteration
    for link in links_collection:
        print(link.linkid)
        if link.is_pump():
            print(link.flow)
    
  4. Manipulate or retrieve parameters:
    mylink.flow_limit = 15.0
    print(mylink.seepage_rate)
    
  5. Run or step through the simulation:
    for step in sim:
        # Retrieve real-time results
        current_flow = mylink.flow
        if current_flow > 10.0:
            mylink.target_setting = 0.5  # Partially close a weir or orifice
    
  6. Obtain summary statistics after the simulation ends:
    if mylink.is_conduit():
        stats = mylink.conduit_statistics
        print("Peak flow was:", stats["peak_flow"])
    elif mylink.is_pump():
        stats = mylink.pump_statistics
        print("Pump total volume pumped:", stats["total_volume"])
    

Key Advantages

  1. Pythonic Access: Modelers can reference link properties (offsets, flows, pollutant concentrations) through intuitive property getters/setters rather than manually parsing SWMM data files.
  2. Class Inheritance: The Conduit and Pump subclasses handle specialized metrics that don’t apply to all link types (e.g., pump energy consumption).
  3. Runtime Control: Because these properties can be changed during a time-stepped simulation, users can implement sophisticated real-time control strategies (e.g., adjusting a pump setting when a flow threshold is exceeded).
  4. Robust Error-Handling: Each method checks that the model is loaded. If it isn’t, a PYSWMMException is raised, preventing inadvertent calls on an uninitialized state.

Conclusion

By providing an object-oriented, user-friendly abstraction over SWMM’s link objects, this module simplifies both reading and writing of key simulation parameters. The Links container and its subclasses (Conduit, Pump) significantly reduce the complexity of stormwater modeling workflows, allowing for powerful, Pythonic real-time control and post-processing of SWMM simulations.

PYSWMM output.py Summary

 Below is an extended summary of this code, which focuses on reading and accessing data from a SWMM output (.out) file via a Python interface. It describes the purpose, structure, and usage of each class, along with relevant methods and decorators.


Overview

This module provides a robust way to interact with SWMM’s output binary files in Python. It allows users to:

  1. Open or close SWMM output binary files.
  2. Query time-series data for subcatchments, nodes, links, and the overall system.
  3. Retrieve, filter, and present simulation results as Python dictionaries keyed by timestamps.
  4. Easily integrate with pandas or other data-processing tools, thanks to dictionary-based outputs.

Key elements include:

  • Output class: Core functionality for opening and querying the binary file.
  • output_open_handler decorator: Ensures an output file is opened before any function runs.
  • Classes like SubcatchSeries, NodeSeries, LinkSeries, and SystemSeries: High-level objects that map SWMM attributes (rainfall, flow rate, infiltration, etc.) into time-indexed Python dictionaries.

Core Functionality

1. Output Class

Purpose: Manages a SWMM output (.out) file, including opening/closing it, reading metadata (like start time, end time, number of periods), and fetching results in various formats.

Key Methods and Properties:

  1. File Handling

    • open(): Initializes an internal handle to the output file. Loads critical info such as start date/time, reporting interval, total periods, etc.
    • close(): Closes the file and cleans up the internal handle.
    • __enter__ / __exit__: Implements Python’s context-manager protocol (with statement), ensuring that the file automatically opens and closes.
  2. Project/Simulation Metadata

    • project_size: Returns counts for subcatchments, nodes, links, system objects, and pollutants.
    • start, end, report, period: Start date/time, end date/time, reporting interval (in seconds), and number of reporting periods.
    • version: Returns the SWMM engine version used to produce this output file.
    • units: Shows the user’s unit system (US or SI), flow units (CFS, CMS, etc.), and pollutant units.
  3. Model Element Dictionaries

    • subcatchments, nodes, links, pollutants: Each returns a dict mapping element IDs (e.g., “J1”, “C2”, “S1”) to integer indices. These indices are needed to make calls into the underlying SWMM output toolkit.
  4. Time Management

    • times: A list of datetime objects for each reporting period in the simulation.
    • Internally, times are generated by starting at self.start and incrementing in steps of self.report seconds.
  5. Retrieving Data

    • subcatch_series, node_series, link_series, system_series: Time-series retrieval for a single object (subcatch/node/link) or the entire system.
      • Returns a dictionary of { datetime: value }.
    • subcatch_attribute, node_attribute, link_attribute: Snapshots of a single attribute for all subcatchments/nodes/links at a single time index.
    • subcatch_result, node_result, link_result: Returns all attributes for a single subcatchment/node/link at a single time index.
    • system_result: Returns a dictionary of system-wide attributes (e.g., total runoff, infiltration) at a single time index.
  6. Validation Helpers

    • verify_index(...): Ensures a user-provided ID or index is valid for a given subcatchment/node/link dictionary.
    • verify_time(...): Converts a user-supplied datetime or integer index into a valid time-step index. Raises exceptions if the time/index is out of range.

2. output_open_handler Decorator

  • Ensures that before any method runs (e.g., subcatch_series, node_attribute), the output file is open.
  • If self.loaded is False, it calls self.open() to establish a handle.
  • This decorator simplifies code by allowing users to call methods directly without worrying about manually opening the output file first.

3. High-Level Series Classes

  1. SubcatchSeries

    • Usage: SubcatchSeries(out_handle)[‘S1’].rainfall
    • Each property (e.g., .rainfall, .snow_depth) yields a time-series dictionary for the specified subcatchment.
    • Internally calls Output.subcatch_series(...).
  2. NodeSeries

    • Usage: NodeSeries(out_handle)[‘J1’].invert_depth
    • Maps node attributes like INVERT_DEPTH, HYDRAULIC_HEAD, etc. to time-series dictionaries.
  3. LinkSeries

    • Usage: LinkSeries(out_handle)[‘C1:C2’].flow_rate
    • Provides link attributes such as FLOW_RATE, FLOW_DEPTH, FLOW_VELOCITY.
  4. SystemSeries

    • Usage: SystemSeries(out_handle).runoff_flow
    • No element ID needed (the entire system is a single entity).
    • Returns time-series data for system-level attributes (e.g., total rainfall, total inflow, outflow).

Common Patterns:

  • Each of these series classes inherits from OutAttributeBase, which implements:
    • A dynamic __getattr__ that interprets attribute names (e.g., .flow_rate) and calls the underlying PySWMM methods.
    • A _series_type(...) helper method that routes to Output methods like .subcatch_series, .node_series, etc.

4. Example Usage

from pyswmm import Output, NodeSeries

# 1) Open a SWMM output file via the context manager
with Output("my_model.out") as out:
    # 2) Access metadata
    print("Start Time:", out.start)
    print("End Time:", out.end)
    print("Number of Nodes:", len(out.nodes))

    # 3) Retrieve time-series data for a specific node
    node_data = NodeSeries(out)["J1"].total_inflow
    for time, inflow_value in node_data.items():
        print(time, inflow_value)

    # 4) Retrieve a single time snapshot for all nodes
    snapshot_16h = out.node_attribute(
        attribute=shared_enum.NodeAttribute.INVERT_DEPTH,
        time_index=datetime(2020, 1, 1, 16, 0)
    )
    print(snapshot_16h)  # {"J1": 10.2, "J2": 12.3, ...}

Benefits and Applications

  1. Simplicity: Users can retrieve SWMM results using Python dictionaries keyed by datetime, making integration with other data libraries (like pandas or matplotlib) straightforward.

  2. Flexibility:

    • Retrieve results by time (snapshot across many objects) or by object (time-series for a single subcatchment/node/link).
    • Retrieve single attributes or all attributes at once.
  3. Error Handling:

    • Clear exceptions (OutputException) if an ID or time-step is invalid.
    • Decorators ensuring the file is open before data retrieval.
  4. Separation of Concerns:

    • Output handles low-level file reading and indexing.
    • SubcatchSeries, NodeSeries, LinkSeries, and SystemSeries provide higher-level, user-friendly interfaces to time-series data.

Conclusion

This code presents a comprehensive Python interface for processing SWMM output files. By abstracting the SWMM toolkit’s C-based calls into intuitive classes and decorators, it gives modelers and data scientists convenient access to simulation results at different levels (subcatchment, node, link, or entire system). Thanks to dictionary-based returns keyed by timestamps, it integrates naturally with Python’s ecosystem for data analysis and visualization, ultimately streamlining the post-processing workflow for stormwater and hydrological modeling projects.

PYSWMM nodes.py Summary

 Below is an extended summary of the code, highlighting its structure, purpose, and how each component interacts with SWMM’s node objects in a Pythonic way:


Overview

This module defines classes (Nodes, Node, Outfall, Storage) that offer a Pythonic interface to SWMM (Storm Water Management Model) node objects. By leveraging PySWMM’s underlying toolkit (through self._model references), these classes enable users to easily query and modify various node parameters—such as invert elevations, initial depths, pollutant levels, and real-time hydraulic results (inflow, outflow, water depth)—during a SWMM simulation.

In addition to the base Node class, there are specialized subclasses:

  • Outfall for handling outfall-specific parameters and statistics
  • Storage for extended storage node statistics

This modular design makes it straightforward for modelers to handle general node properties while also supporting custom behaviors unique to storage nodes and outfalls.


Core Classes

1. Nodes

  • Purpose: Manages a collection (iterator) of all node objects within an open SWMM model.
  • Initialization:
    nodes = Nodes(simulation_instance)
    
    • Ensures that the model (simulation_instance._model) is loaded. If not, it raises a PYSWMMException.
  • Iteration:
    • Supports Python’s iteration protocol (__iter__, __next__), allowing you to loop over nodes:
      for node in Nodes(sim):
          print(node.nodeid)
      
    • Each iteration returns a Node object (or one of its subclasses, such as Outfall or Storage), depending on the node’s SWMM type.
  • Lookup:
    • The __getitem__ method lets you directly retrieve a specific node by ID:
      j1 = nodes['J1']
      print(j1.depth)
      
    • The correct subclass is assigned automatically if the node is an outfall or storage node.
  • Membership:
    • The __contains__ method checks whether a given node ID exists in the model:
      "J1" in nodes  # returns True or False
      
  • Length:
    • len(nodes) returns the total number of nodes in the model.

2. Node

  • Purpose: Represents an individual SWMM node (junction, outfall, divider, or storage). This is the base class for all node types except for specialized functionality.

  • Initialization:

    j1 = Node(model_object, "J1")
    
    • Ensures the model is loaded and the provided ID is valid for a node.
  • Common Parameters (exposed as properties):

    1. Geometry and Configuration
      • invert_elevation, full_depth, surcharge_depth, ponding_area, initial_depth
        j1.invert_elevation = 20.0
        current_invert = j1.invert_elevation
        
    2. Runtime Hydraulic Results (retrieved during or after simulation stepping):
      • total_inflow, total_outflow, losses, volume, flooding, depth, head, lateral_inflow, hydraulic_retention_time
        print(j1.depth, j1.total_inflow)
        
    3. Pollutant Quality
      • pollut_quality: Current concentration in the node’s water volume
      • inflow_quality: Quality of inflow entering the node
      • reactor_quality: Quality within the “mixed reactor” portion of the node
        print(j1.pollut_quality)
        j1.pollut_quality = ('pollut_name', 50.0)  # sets pollutant concentration
        
    4. Helpers and Others
      • nodeid: The SWMM ID of the node
      • generated_inflow(...): Directly sets an inflow rate into the node for real-time control or scenario testing.
      • cumulative_inflow: Returns the total inflow volume over the simulation for continuity checks.
  • Node Type Checks:

    • is_junction(), is_outfall(), is_storage(), is_divider() each returns a boolean indicating the node’s type based on SWMM’s internal classification.
  • Statistics:

    • The statistics property returns a dictionary of rolling/cumulative node statistics (e.g., average depth, max flooding rate, total surcharge duration).

3. Outfall (Subclass of Node)

  • Purpose: Specializes the Node class for outfalls.
  • Primary Features:
    1. outfall_statistics
      • Dictionary of outfall-specific metrics (e.g., average flowrate, peak flowrate, total pollutant loading).
    2. outfall_stage(stage)
      • Overrides the outfall water surface elevation (head) at runtime, allowing a user to simulate tidal backwater conditions or other external water levels dynamically.

Example:

with Simulation('network.inp') as sim:
    outfall_node = Nodes(sim)['OF1']  # This will be an Outfall object
    for step in sim:
        if step > 2.0:
            outfall_node.outfall_stage(5.0)  # Setting stage to 5.0
    stats = outfall_node.outfall_statistics

4. Storage (Subclass of Node)

  • Purpose: Specializes the Node class for storage nodes, such as detention basins or tanks.
  • Primary Feature:
    • storage_statistics: A dictionary of rolling/cumulative metrics relevant to storage units (e.g., initial volume, average volume, evaporation losses, exfiltration losses, maximum volume date).

Example:

with Simulation('storage_model.inp') as sim:
    storage_node = Nodes(sim)['Tank1']  # This will be a Storage object
    for step in sim:
        pass
    stats = storage_node.storage_statistics
    print("Max volume:", stats["max_volume"])

Typical Workflow

  1. Open a SWMM simulation:
    from pyswmm import Simulation, Nodes
    
    with Simulation('my_network.inp') as sim:
        # ...
    
  2. Instantiate the Nodes collection:
    nodes = Nodes(sim)
    
  3. Lookup or iterate over nodes:
    # Directly by ID
    j1 = nodes['J1']
    print(j1.depth, j1.head)
    
    # Or by iteration
    for node in nodes:
        print(node.nodeid, node.depth, node.is_outfall())
    
  4. Modify node parameters (e.g., invert elevation):
    j1.invert_elevation = 25.5
    j1.initial_depth = 2.0
    
  5. Run or step through the simulation:
    for step in sim:
        inflow = j1.total_inflow
        # Possibly add some external inflow if conditions are met
        if inflow < 1.0:
            j1.generated_inflow(1.2)
    
  6. Retrieve final statistics:
    print(j1.statistics)  # e.g., peak flooding, max depth date
    
  7. Outfall and Storage:
    • If the node is an outfall or storage node, you can access specialized stats after the simulation:
      if j1.is_outfall():
          print(j1.outfall_statistics)
      
      if j1.is_storage():
          print(j1.storage_statistics)
      

Key Advantages

  1. User-Friendly Access:

    • Node parameters are accessible through Python properties rather than opaque function calls or manual data parsing, reducing coding errors and improving readability.
  2. On-the-Fly Control:

    • The Node.generated_inflow(...) and Outfall.outfall_stage(...) methods allow real-time manipulation of node boundary conditions, enabling advanced control logic and scenario testing.
  3. Subclass Specialization:

    • Outfall and Storage classes surface relevant statistics that do not apply to generic nodes, making the interface more organized and clear.
  4. Seamless Integration with SWMM:

    • All reads and writes are internally routed through PySWMM’s low-level C-toolkit calls, ensuring direct and efficient communication with the SWMM engine.

Conclusion

By defining Nodes, Node, Outfall, and Storage, this module simplifies how modelers and developers interact with SWMM nodes. It abstracts away lower-level API details and provides a clean, Pythonic syntax for reading and modifying both static and dynamic node attributes (inflow/outflow, depth, pollutant concentrations). This design makes PySWMM especially well-suited for tasks like real-time simulation control, data analysis, scenario-based optimization, and educational demonstrations of hydrodynamic modeling concepts.

PYSWMM lidcontrols.py Summary

 Below is an extended summary of this module, highlighting its purpose, class structure, and how it facilitates the management of Low Impact Development (LID) usage data for SWMM subcatchments:


Purpose and Context

This module provides a high-level, Pythonic interface to the LID usage section of a SWMM model. In particular, it helps users discover which subcatchments have LIDs, how many LID units exist per subcatchment, and the parameters and real-time results of each LID unit. By building on PySWMM’s low-level API (toolkitapi), these classes enable both pre-simulation configuration and dynamic, mid-simulation manipulation of LID settings (for those parameters allowed to be changed at runtime).

Key ideas:

  1. One LidGroups object corresponds to all subcatchments in the model.
  2. Each LidGroup object is associated with one subcatchment but may contain multiple LID units.
  3. A LidUnit represents a single LID instance (e.g., “Permeable Pavement #1”) in that subcatchment, exposing both configuration parameters (like area, drain node) and runtime results (like drain flow or evaporation).

Class-by-Class Overview

1. LidGroups

  • Role: Acts as a top-level container for LID usage across all subcatchments in the SWMM model.
  • Initialization:
    lid_groups = LidGroups(simulation)
    
    • Checks if the SWMM model is open; raises PYSWMMException otherwise.
  • Key Behaviors:
    1. Iteration (__iter__, __next__):
      • Iterates over each subcatchment in the model by ID, returning a LidGroup object.
      • Uses __len__ to report the total number of subcatchments.
    2. Lookup (__getitem__):
      • lid_groups[subcatchment_id] yields the LidGroup for that specific subcatchment, if it exists.
    3. Contains (__contains__):
      • Enables expressions like "S1" in lid_groups to check if subcatchment “S1” is valid.

Example:

for group in LidGroups(sim):
    print(group)  # Prints each subcatchment ID that has LID usage defined

2. LidGroup

  • Role: Represents all LID units associated with a single subcatchment.
  • Initialization:
    LidGroup(model, "SubcatchmentID")
    
    • Ensures that the subcatchment exists and the model is open.
  • Key Behaviors:
    1. Iteration / Lookup (__iter__, __next__, __getitem__):
      • Iterates through each LID unit index (0, 1, 2, …) in the subcatchment, yielding LidUnit objects.
      • lid_group[i] returns the i-th LID unit in that subcatchment.
      • len(lid_group) reveals how many LIDs are defined in this subcatchment.
    2. Aggregated Results:
      • pervious_area: Total pervious area associated with these LID units.
      • flow_to_pervious: Flow directed from the LID(s) to pervious surfaces.
      • old_drain_flow, new_drain_flow: The previous and current drain flows, summed across all LID units in this subcatchment.
  • Typical Use:
    lid_group_S1 = LidGroups(sim)["S1"]
    print(len(lid_group_S1))  # e.g., number of LID units in subcatchment S1
    
    for lid_unit in lid_group_S1:
        print(lid_unit.index, lid_unit.unit_area)
    

3. LidUnit

  • Role: Encapsulates a single LID instance (e.g., one green roof, one bioretention cell) within a subcatchment.
  • Initialization:
    LidUnit(model, subcatchment_id, lid_index)
    
    • lid_index is the 0-based index for a particular LID usage record in that subcatchment’s [LID_USAGE] section.
  • Sub-Components:
    • surface, pavement, soil, storage: Layer-specific objects (defined in pyswmm.lidunits) that manage the physical parameters of each layer (thickness, porosity, infiltration, etc.).
    • water_balance: Provides aggregated infiltration, evaporation, drain flow, etc.
  • Key Properties:
    1. LID Setup:
      • unit_area, full_width, initial_saturation
      • from_impervious, from_pervious: Fraction of runoff from impervious/pervious areas that is treated by this LID.
    2. Routing and Drainage:
      • drain_subcatchment, drain_node: Where underdrain flow is directed (other subcatchment or a node).
      • to_pervious: Whether outflow is sent to a pervious area (1) or not (0).
    3. Runtime Results:
      • dry_time: Time since last rainfall (in seconds).
      • old_drain_flow, new_drain_flow: Drain flow amounts in previous vs. current time step.
      • evaporation, native_infiltration: Real-time rates for evaporation and infiltration.
    4. IDs & Indices:
      • index: The ID for the LID Control (matching ObjectType.LID) in the overall model.
      • lid_control: Returns the textual ID of the LID control used (if index is known).

Typical Workflow:

lid_group_s1 = LidGroups(sim)['S1']
lid_unit0 = lid_group_s1[0]

# Check or adjust parameters before/after simulation
lid_unit0.unit_area = 200  # Set area in square feet (or model units)
lid_unit0.drain_node = "J5"  # Route underdrain flow to node J5

# During the simulation
for step_idx, step_time in enumerate(sim):
    if step_idx == 100:
        # Switch drain node mid-run
        lid_unit0.drain_node = "J6"

Typical Use Cases

  1. Pre-Run Configuration:
    • Adjusting the LID area, fraction of impervious/pervious inflow, or drain routing node to experiment with different LID designs.
  2. Dynamic Control:
    • During a SWMM simulation, changing drain_node or other runtime-adjustable parameters to test how real-time management might affect system performance.
  3. Data Extraction:
    • After or during the run, retrieving LID results like new_drain_flow or evaporation helps measure performance, infiltration capacity, or water balance.

Key Takeaways

  1. Hierarchical Design
    • LidGroups > LidGroup > LidUnit mirrors SWMM’s data organization:
      • Model has multiple subcatchments (each subcatchment can have LID usage)
      • Each subcatchment’s LID usage can have multiple LID units.
  2. Property Access
    • Clear “getter” and “setter” properties for each LID parameter.
    • Some parameters (e.g., drain_subcatchment, drain_node) can be altered during simulation. Others (e.g., unit_area) must typically be set before starting the simulation.
  3. Integration with pyswmm.lidunits
    • A LidUnit automatically includes references to sub-layer classes (Surface, Pavement, Soil, Storage, WaterBalance), which further expands the user’s control over LID components.

Final Notes

  • Error Handling:
    • Raises PYSWMMException if the model is not open or if an invalid subcatchment/LID unit index is requested.
  • Scalability:
    • By iterating over LidGroups and each LidGroup, this design gracefully handles large models with many subcatchments and multiple LID units per subcatchment.
  • Real-Time Flexibility:
    • The code supports dynamic adjustments to certain LID parameters mid-simulation, enabling advanced scenario testing and real-time control strategies in PySWMM.

Overall, these classes significantly streamline the process of configuring and analyzing SWMM’s LID usage in Python, bridging the gap between low-level toolkit functions and high-level environmental modeling needs.

PYSWMM lidgroups.py Summary

 Below is an extended summary of this code, focusing on its structure, overarching purpose, and how each class interacts with SWMM’s LID (Low Impact Development) units at the subcatchment level.


Overview

This module manages LID (Low Impact Development) usage across subcatchments within a SWMM model. It introduces several Pythonic classes (LidGroups, LidGroup, LidUnit) that encapsulate SWMM’s underlying LID usage data, allowing users to retrieve, iterate over, and dynamically adjust LID parameters during a simulation. This higher-level interface is part of PySWMM and relies on lower-level calls to the SWMM toolkit.

Key Capabilities:

  1. Iterate Over LID Usage: Provide loops and lookups to access all subcatchments with defined LIDs.
  2. Access/Modify LID Units: Change certain LID parameters (e.g., area, drain node) before or even during a simulation.
  3. Retrieve Real-Time Results: Get time-varying flow components (e.g., drain flow, infiltration, evaporation) from each LID unit during or after a simulation.

Core Classes

1. LidGroups

  • Purpose: Acts as a container/iterator for all subcatchment-level LID groups. Since every subcatchment can define one or more LID units, each subcatchment effectively “has” one LidGroup instance.
  • Initialization:
    lid_groups = LidGroups(sim)
    
    • Ensures that the SWMM model is open (fileLoaded == True).
  • Iteration (__iter__, __next__, __getitem__):
    • Each iteration yields a LidGroup object representing the LID usage in a single subcatchment.
    • len(lid_groups) returns the total number of subcatchments, which is also the number of lid groups (even if some subcatchments have 0 LID units).
    • lid_groups[some_subcatch_id] returns a LidGroup for that subcatchment.
  • Example:
    for lid_group in LidGroups(sim):
        print(lid_group)  # Prints the subcatchment name
    

2. LidGroup

  • Purpose: Represents all LID units defined within a specific subcatchment.
  • Initialization:
    lid_group = LidGroup(model, "SubcatchID")
    
    • Checks that the subcatchment ID is valid in the SWMM model.
  • Iteration:
    • Each iteration yields a LidUnit object.
    • len(lid_group) returns how many LID units are defined on this subcatchment.
    • lid_group[i] retrieves the LidUnit at index i.
  • LID Group-Level Results:
    • pervious_area: Amount of pervious area within this subcatchment influenced by LID(s).
    • flow_to_pervious: How much LID outflow is sent to pervious surfaces.
    • old_drain_flow, new_drain_flow: Drain flow from the previous/current simulation step, aggregated for all LID units in this subcatchment.
  • Example:
    lid_group_j1_j3 = LidGroups(sim)["J1_J3_qqqqqqq"]
    lid_unit0 = lid_group_j1_j3[0]  # Access the first LID unit in subcatchment "J1_J3_qqqqqqq"
    

3. LidUnit

  • Purpose: Encapsulates a single LID usage instance within a subcatchment. For example, if a subcatchment has two “permeable pavement” LIDs, you can differentiate them via their indexes in the LidGroup.
  • Initialization:
    lid_unit = LidUnit(model, "SubcatchID", lid_index)
    
    • lid_index is the 0-based index referencing a particular LID usage record in SWMM.
  • Sub-Components / Layers:
    • surface, pavement, soil, storage: Each of these returns a specialized class (Surface, Pavement, Soil, Storage) that manages layer-specific parameters (e.g., thickness, porosity, infiltration rates).
    • water_balance: Summarizes infiltration, evaporation, drain flow, etc.
  • Parameters (via @property):
    • Physical Properties
      • unit_area, full_width
      • initial_saturation: how saturated the LID’s layers are at simulation start
      • from_impervious, from_pervious: fraction of drainage area from impervious/pervious surfaces directed to this LID unit
    • Connections
      • index, number, to_pervious: references to how many units are replicated, whether outflow is sent to a pervious area, etc.
      • drain_subcatchment, drain_node: IDs or indices specifying where the LID’s underdrain flow is routed (another subcatchment or a node).
    • Runtime Results
      • dry_time: time since last rainfall event (seconds)
      • old_drain_flow, new_drain_flow: drain flows at previous and current timestep.
      • evaporation, native_infiltration: real-time infiltration and evaporation rates.
  • Modifiability:
    • Some parameters can only be changed before a simulation (e.g., unit area, number of replicate units).
    • Others (like drain_node) can be changed during a simulation, enabling real-time control.

Typical Usage Examples

A. Iterating Through All LID Groups and Units

from pyswmm import Simulation, LidGroups

with Simulation('lid_model.inp') as sim:
    for lid_group in LidGroups(sim):
        print("Subcatchment with LID usage:", lid_group)

        for lid_unit in lid_group:
            print("  LID Unit Index:", lid_unit.index)
            print("  LID Area:", lid_unit.unit_area)

B. Changing Drain Node Mid-Simulation

from pyswmm import Simulation, LidGroups

with Simulation('lid_model.inp') as sim:
    lid_group_s1 = LidGroups(sim)['S1']
    lid_unit0 = lid_group_s1[0]

    for step_index, step_time in enumerate(sim):
        # After 50 simulation steps, reroute drain flow to node "J04"
        if step_index == 50:
            lid_unit0.drain_node = "J04"

C. Retrieving LID Performance Data

from pyswmm import Simulation, LidGroups

with Simulation('lid_model.inp') as sim:
    lid_group_s1 = LidGroups(sim)['S1']
    lid_unit0 = lid_group_s1[0]

    for step in sim:
        # Access water balance layer
        wb = lid_unit0.water_balance
        print("Inflow:", wb.inflow)
        print("Drain Flow:", wb.drain_flow)

Architectural Notes

  1. Integration with PySWMM:

    • The classes in this file build on top of the lower-level toolkitapi and the pyswmm.swmm5 interface.
    • They rely on numeric SWMM parameters, object indices, and enumerations for LID usage (LidUParams, LidUOptions, etc.).
  2. Error Handling:

    • Custom exceptions (PYSWMMException) are raised if invalid IDs or indexes are used, or if the SWMM model is not open.
  3. Consistency and Scalability:

    • Each subcatchment is associated with exactly one LidGroup, even if that group has zero or multiple LID units.
    • The iteration pattern (__iter__, __next__) is consistent with Python best practices, allowing convenient loops over items.
  4. Runtime Flexibility:

    • Because certain LID parameters can be adjusted mid-simulation, PySWMM opens the door to advanced real-time control scenarios, such as changing how water is routed or adjusting infiltration settings based on ongoing conditions.

Conclusion

This code provides a high-level, object-oriented interface for managing and querying SWMM’s LID usage data at both subcatchment and individual LID levels. By encapsulating complex details into easy-to-use classes and properties, PySWMM empowers users to:

  • Enumerate and manage LID groups across all subcatchments,
  • Modify LID unit parameters (e.g., drain nodes, area, infiltration) before or during a run,
  • Examine real-time or post-run data (flow rates, infiltration, evaporation) for each LID unit.

This design greatly simplifies typical LID modeling and real-time control workflows, making sophisticated water management strategies more accessible to practitioners and researchers alike.

AI Prompt for Generating a SWMM5 inp file with Rules

 Below is an example prompt you can use (in ChatGPT or any advanced language model that understands SWMM5 syntax) to generate a syntactical...