swmmx Documentation

Importing Data

Python reserves the word import, so swmmx exposes imports as m.import_csv and m.import_gis.

m.import_csv.node.junction("junctions.csv")
m.import_csv.node.outfall("outfalls.csv")
m.import_csv.link.conduit("conduits.csv")
m.import_csv.hydrology.subcatchment("subcatchments.csv")

m.import_gis.node.junction("junctions.shp")
m.import_gis.link.conduit("pipes.geojson")
m.import_gis.hydrology.subcatchment("subcatchments.gpkg", layer="subcatchments")

Field Mapping

result = m.import_csv.link.conduit(
    "pipes.csv",
    field_map={
        "id": "PipeID",
        "from_node": "FromNode",
        "to_node": "ToNode",
        "length": "Length",
        "roughness": "ManningN",
        "diameter": "Diameter",
    },
)

print(result.summary())
print(result.to_frame())

Options

OptionMeaning
mode="add" | "update" | "upsert"Choose whether rows add new objects, update existing objects, or do both.
dry_run=TrueValidate rows without modifying the model.
on_missing_required"error" or "skip".
on_unknown_fields"ignore", "warn", or "error".
on_error"raise", "skip", or "collect".

Group-level shortcuts dispatch nodes and links by a type column: m.import_csv.node("nodes.csv", default_type="junction") and m.import_csv.link("links.csv", default_type="conduit").

Complete Import Reference

Rendered from 16_all_import_export_functions.ipynb.

swmmx import and export reference

This notebook is a practical reference for the swmmx import and export APIs introduced in version 0.0.33 and strengthened for exported-file round trips in version 0.0.36.

The import API intentionally uses m.import_csv and m.import_gis instead of m.import, because import is a reserved Python keyword.

m.import_csv.<category>.<element_type>(file_path, field_map=None, **options)
m.import_gis.<category>.<element_type>(file_path, field_map=None, **options)

The design mirrors the rest of the package: m.get..., m.set..., m.add..., m.remove..., m.export..., and now m.import_csv... / m.import_gis....

Code cell 1
from pathlib import Path
import pandas as pd

from swmmx import swmm

WORK = Path("output/import_export_demo")
WORK.mkdir(parents=True, exist_ok=True)

m = swmm(new="SI")
print("Created a new SI model")

1. Import managers and endpoint shape

The new managers are attached directly to every model object:

m.import_csv
m.import_gis

Element-level imports are explicit:

m.import_csv.node.junction("junctions.csv")
m.import_csv.link.conduit("conduits.csv")
m.import_gis.hydrology.subcatchment("subcatchments.shp")

Group-level shortcuts are available where practical:

m.import_csv.node("nodes.csv")
m.import_csv.link("links.csv")
m.import_gis.node("nodes.shp")
m.import_gis.link("links.shp")

Group-level imports use a type field to dispatch each row. If the type field is missing, pass default_type="junction" for nodes or default_type="conduit" for links.

Code cell 2
# Quick discovery in an interactive session:
print("CSV categories:", [name for name in dir(m.import_csv) if not name.startswith("_")])
print("Node CSV endpoints:", [name for name in dir(m.import_csv.node) if not name.startswith("_")])
print("Link CSV endpoints:", [name for name in dir(m.import_csv.link) if not name.startswith("_")])

2. Core CSV endpoints

CategoryEndpoint examplesNotes
Nodem.import_csv.node.junction(...), .outfall(...), .flow_divider(...), .storage_unit(...)Junction/outfall imports use existing add/update machinery where available; divider/storage rows are written through the same internal SWMM sections.
Linkm.import_csv.link.conduit(...), .pump(...), .orifice(...), .weir(...), .outlet(...)Conduits use cross-section data such as shape, diameter/geometry_1, barrels, losses, and optional vertices.
Hydrologym.import_csv.hydrology.rain_gage(...), .subcatchment(...)Subcatchments can include centroid coordinates and optional polygon geometry.
Timem.import_csv.time.time_series(...), .time_pattern(...)Time series and curves support multiple rows per object ID.
Datam.import_csv.curve.curve(...)Curve points are grouped by curve ID.
Coordinatesm.import_csv.coordinate.node_coordinates(...), .link_vertices(...), .polygons(...), .labels(...)Spatial helper tables can update geometry without adding hydraulic objects.

3. Import options

All import functions return an ImportResult and accept a consistent option set.

OptionValuesMeaning
mode"add", "update", "upsert"Add only, update only, or add missing/update existing.
field_mapdict or NoneExplicit mapping from canonical swmmx field names to source columns. Explicit mappings win over aliases.
on_missing_required"error", "skip"Raise for missing required fields or skip invalid rows and record issues.
on_unknown_fields"ignore", "warn", "error"Control how extra source columns are handled.
on_error"raise", "skip", "collect"Stop at first row error, skip failed rows, or collect row errors and continue.
dry_runTrue, FalseValidate and summarize without modifying the model.
encodingstringCSV text encoding, default "utf-8".
delimiterstring or NonePassed to pandas.read_csv as sep= when provided.
default_typestring or NoneUsed by group-level imports when the type field is absent.
overwrite_geometryTrue, FalseReplace existing coordinates/vertices/polygons on update/upsert.

4. Field matching rules

The importer normalizes column names before matching:

  • lowercase
  • remove spaces, underscores, hyphens, periods, brackets, parentheses, and slashes
  • convert % to percent
  • remove simple punctuation

Examples:

Source columnNormalizedCan match
Node IDnodeidid
node_idnodeidid
Invert Elev.invertelevinvert_elevation
Max Depthmaxdepthmax_depth
% Impervpercentimpervimpervious_percent
Eastingeastingx
Northingnorthingy

Matching priority:

1. Explicit field_map 2. Exact normalized canonical field name 3. Alias catalog match 4. Conservative obvious matching only

If two source columns match the same target, the importer raises an ambiguous-field error unless field_map resolves it.

5. ImportResult

Every import returns an ImportResult object with counts, field matches, ignored columns, and issues.

Useful attributes and methods:

result.ok
result.has_warnings
result.has_errors
result.to_dict()
result.to_frame()
result.summary()
Code cell 3
# A tiny CSV with common alias names.
junctions_csv = WORK / "junctions_aliases.csv"
pd.DataFrame(
    {
        "Node ID": ["J1", "J2"],
        "Easting": [0.0, 100.0],
        "Northing": [0.0, 0.0],
        "Invert Elev.": [10.0, 9.5],
        "Max Depth": [3.0, 2.5],
        "Tag": ["main", "main"],
    }
).to_csv(junctions_csv, index=False)

result = m.import_csv.node.junction(junctions_csv)
print(result.summary())
print(result.field_matches)
result.to_frame()

6. CSV junction import with explicit field_map

Use field_map when your file uses project-specific names or when a column would otherwise be ambiguous.

Code cell 4
junctions_map_csv = WORK / "junctions_field_map.csv"
pd.DataFrame(
    {
        "NodeName": ["J3"],
        "CoordEast": [200.0],
        "CoordNorth": [0.0],
        "Invert": [9.0],
        "Depth": [2.0],
    }
).to_csv(junctions_map_csv, index=False)

result = m.import_csv.node.junction(
    junctions_map_csv,
    field_map={
        "id": "NodeName",              # required object ID
        "x": "CoordEast",              # required map x-coordinate
        "y": "CoordNorth",             # required map y-coordinate
        "invert_elevation": "Invert",  # optional junction invert elevation
        "max_depth": "Depth",          # optional junction maximum depth
    },
)
print(result.summary())

7. CSV outfall import

Outfalls require id, x, and y; optional fields include invert_elevation, type, fixed_stage, tidal_curve, time_series, tide_gate, route_to, and tag.

Code cell 5
outfalls_csv = WORK / "outfalls.csv"
pd.DataFrame(
    {
        "ID": ["OUT1"],
        "X": [300.0],
        "Y": [0.0],
        "Invert": [8.5],
        "Type": ["FREE"],
    }
).to_csv(outfalls_csv, index=False)

result = m.import_csv.node.outfall(outfalls_csv)
print(result.summary())

8. CSV conduit import

Conduits require id, from_node, to_node, length, and roughness. A circular conduit may provide diameter, which the importer maps into cross-section geometry.

Common aliases include PipeID -> id, US_Node -> from_node, DS_Node -> to_node, ManningN -> roughness, and Dia/Diameter -> diameter.

Code cell 6
conduits_csv = WORK / "conduits.csv"
pd.DataFrame(
    {
        "PipeID": ["C1", "C2", "C3"],
        "US_Node": ["J1", "J2", "J3"],
        "DS_Node": ["J2", "J3", "OUT1"],
        "Length_m": [100.0, 100.0, 100.0],
        "ManningN": [0.013, 0.014, 0.015],
        "Diameter": [1.0, 1.0, 1.2],
    }
).to_csv(conduits_csv, index=False)

result = m.import_csv.link.conduit(
    conduits_csv,
    field_map={
        "id": "PipeID",
        "from_node": "US_Node",
        "to_node": "DS_Node",
        "length": "Length_m",
        "roughness": "ManningN",
        "diameter": "Diameter",
    },
)
print(result.summary())

9. Group-level node import

m.import_csv.node(...) reads a type field and dispatches rows to:

  • junction
  • outfall
  • flow_divider
  • storage_unit

If type is missing, use default_type="junction" or another valid node type.

Code cell 7
group_nodes_csv = WORK / "group_nodes.csv"
pd.DataFrame(
    {
        "id": ["J4", "OUT2"],
        "type": ["junction", "outfall"],
        "x": [400.0, 500.0],
        "y": [0.0, 0.0],
        "invert_elevation": [8.0, 7.8],
        "max_depth": [2.0, None],
    }
).to_csv(group_nodes_csv, index=False)

result = m.import_csv.node(group_nodes_csv)
print(result.summary())

10. Group-level link import

m.import_csv.link(...) reads a type field and dispatches rows to:

  • conduit
  • pump
  • orifice
  • weir
  • outlet

If type is missing, use default_type="conduit" or another valid link type.

Code cell 8
group_links_csv = WORK / "group_links.csv"
pd.DataFrame(
    {
        "id": ["C4"],
        "type": ["conduit"],
        "from_node": ["J4"],
        "to_node": ["OUT2"],
        "length": [100.0],
        "roughness": [0.013],
        "diameter": [1.0],
    }
).to_csv(group_links_csv, index=False)

result = m.import_csv.link(group_links_csv)
print(result.summary())

11. Dry run

dry_run=True validates, matches fields, and returns an ImportResult without modifying the model.

Code cell 9
dry_run_csv = WORK / "dry_run_junction.csv"
pd.DataFrame({"id": ["J_DRY"], "x": [999.0], "y": [999.0]}).to_csv(dry_run_csv, index=False)

before = list(m.get.node.id())
result = m.import_csv.node.junction(dry_run_csv, dry_run=True)
after = list(m.get.node.id())

print(result.summary())
print("Model changed?", before != after)

12. Add, update, and upsert modes

  • mode="add": add new objects only; fail if an ID already exists.
  • mode="update": update existing objects only; fail if an ID does not exist.
  • mode="upsert": update existing objects and add missing objects.
Code cell 10
upsert_csv = WORK / "junctions_upsert.csv"
pd.DataFrame(
    {
        "id": ["J1", "J_NEW"],
        "x": [0.0, 600.0],
        "y": [0.0, 0.0],
        "invert_elevation": [11.0, 7.0],
        "max_depth": [4.0, 2.0],
    }
).to_csv(upsert_csv, index=False)

result = m.import_csv.node.junction(upsert_csv, mode="upsert")
print(result.summary())

13. Missing fields, unknown fields, and row errors

Use strict behavior during data QA and more forgiving behavior during exploratory cleaning.

Code cell 11
bad_csv = WORK / "bad_junctions.csv"
pd.DataFrame(
    {
        "id": ["BAD1", "BAD2"],
        "x": [0.0, 1.0],
        # y is intentionally missing
        "notes": ["missing y", "missing y"],
    }
).to_csv(bad_csv, index=False)

result = m.import_csv.node.junction(
    bad_csv,
    on_missing_required="skip",
    on_unknown_fields="warn",
    on_error="collect",
)

print(result.summary())
result.to_frame()

14. CSV time series import

Time series rows are grouped by id. Use either a single datetime column or separate date + time columns.

Code cell 12
time_series_csv = WORK / "rain_timeseries.csv"
pd.DataFrame(
    {
        "id": ["TS1", "TS1", "TS1"],
        "datetime": ["2026-01-01 00:00", "2026-01-01 00:05", "2026-01-01 00:10"],
        "value": [0.0, 1.5, 0.0],
    }
).to_csv(time_series_csv, index=False)

result = m.import_csv.time.time_series(time_series_csv)
print(result.summary())

15. CSV curve import

Curve rows are grouped by id and imported as ordered (x, y) points.

Code cell 13
curve_csv = WORK / "curves.csv"
pd.DataFrame(
    {
        "id": ["CURVE1", "CURVE1", "CURVE1"],
        "x": [0.0, 1.0, 2.0],
        "y": [0.0, 0.8, 1.0],
        "type": ["STORAGE", "STORAGE", "STORAGE"],
    }
).to_csv(curve_csv, index=False)

result = m.import_csv.curve.curve(curve_csv)
print(result.summary())

16. CSV coordinate helper imports

Coordinate helpers can update spatial records directly:

m.import_csv.coordinate.node_coordinates("node_coordinates.csv")
m.import_csv.coordinate.link_vertices("link_vertices.csv")
m.import_csv.coordinate.polygons("polygons.csv")
m.import_csv.coordinate.labels("labels.csv")

Useful canonical fields:

  • node coordinates: id, x, y
  • link vertices: id, x, y with one row per vertex
  • polygons: id, x, y with one row per polygon vertex
  • labels: id, x, y, optional text/label fields depending on the model section
Code cell 14
vertices_csv = WORK / "link_vertices.csv"
pd.DataFrame(
    {
        "id": ["C1", "C1"],
        "x": [25.0, 75.0],
        "y": [10.0, 10.0],
    }
).to_csv(vertices_csv, index=False)

result = m.import_csv.coordinate.link_vertices(vertices_csv, mode="upsert")
print(result.summary())

17. GIS import overview

GIS import has the same endpoint style as CSV import:

m.import_gis.node.junction("junctions.shp")
m.import_gis.node.outfall("outfalls.gpkg", layer="outfalls")
m.import_gis.link.conduit("pipes.geojson")
m.import_gis.hydrology.subcatchment("subcatchments.shp")

GIS import lazily imports optional dependencies. Install them only if you need spatial import/export:

pip install geopandas shapely

GIS-specific options:

OptionMeaning
layer=NoneRead a specific layer from GeoPackage or other multi-layer data.
use_geometry=TrueUse geometry to fill coordinates, vertices, polygons, and optional lengths.
geometry_field="geometry"Name of geometry column.
compute_length_from_geometry=FalseFor line geometry, compute length if no length field is available.
polygon_to_centroid=TrueFor polygons, use centroid as x/y when explicit coordinates are absent.
`crs_action="warn""ignore"
Code cell 15
# GIS examples are shown but not executed here because geopandas/shapely are optional.
# Uncomment after installing geopandas and shapely and after preparing source files.

# result = m.import_gis.node.junction(
#     "junctions.shp",
#     field_map={"id": "NodeID"},  # Point geometry supplies x/y
# )
# print(result.summary())

# result = m.import_gis.link.conduit(
#     "pipes.geojson",
#     field_map={
#         "id": "PipeID",
#         "from_node": "US_Node",
#         "to_node": "DS_Node",
#         "roughness": "ManningN",
#         "diameter": "Diameter",
#     },
#     compute_length_from_geometry=True,  # LineString geometry can supply vertices and length
# )
# print(result.summary())

# result = m.import_gis.hydrology.subcatchment(
#     "subcatchments.gpkg",
#     layer="subcatchments",
#     polygon_to_centroid=True,  # Polygon geometry supplies polygon and centroid x/y
# )
# print(result.summary())

18. GIS geometry behavior

Geometry typeImport behavior
PointSupplies x and y for nodes, rain gages, and labels when columns are absent.
LineStringSupplies link vertices; can compute length when compute_length_from_geometry=True.
PolygonSupplies subcatchment polygon; centroid can supply x and y.
MultiLineStringUses the longest line conservatively and records a warning.
MultiPolygonUses the largest polygon conservatively and records a warning.

If your GIS file has trustworthy attribute fields for x, y, length, or polygon vertices, those fields can still be used through normal alias matching or field_map.