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
| Option | Meaning |
|---|---|
mode="add" | "update" | "upsert" | Choose whether rows add new objects, update existing objects, or do both. |
dry_run=True | Validate 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....
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.
# 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
| Category | Endpoint examples | Notes |
|---|---|---|
| Node | m.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. |
| Link | m.import_csv.link.conduit(...), .pump(...), .orifice(...), .weir(...), .outlet(...) | Conduits use cross-section data such as shape, diameter/geometry_1, barrels, losses, and optional vertices. |
| Hydrology | m.import_csv.hydrology.rain_gage(...), .subcatchment(...) | Subcatchments can include centroid coordinates and optional polygon geometry. |
| Time | m.import_csv.time.time_series(...), .time_pattern(...) | Time series and curves support multiple rows per object ID. |
| Data | m.import_csv.curve.curve(...) | Curve points are grouped by curve ID. |
| Coordinates | m.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.
| Option | Values | Meaning |
|---|---|---|
mode | "add", "update", "upsert" | Add only, update only, or add missing/update existing. |
field_map | dict or None | Explicit 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_run | True, False | Validate and summarize without modifying the model. |
encoding | string | CSV text encoding, default "utf-8". |
delimiter | string or None | Passed to pandas.read_csv as sep= when provided. |
default_type | string or None | Used by group-level imports when the type field is absent. |
overwrite_geometry | True, False | Replace 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
%topercent - remove simple punctuation
Examples:
| Source column | Normalized | Can match |
|---|---|---|
Node ID | nodeid | id |
node_id | nodeid | id |
Invert Elev. | invertelev | invert_elevation |
Max Depth | maxdepth | max_depth |
% Imperv | percentimperv | impervious_percent |
Easting | easting | x |
Northing | northing | y |
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()
# 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.
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.
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.
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:
junctionoutfallflow_dividerstorage_unit
If type is missing, use default_type="junction" or another valid node type.
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:
conduitpumporificeweiroutlet
If type is missing, use default_type="conduit" or another valid link type.
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.
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.
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.
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.
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.
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,ywith one row per vertex - polygons:
id,x,ywith one row per polygon vertex - labels:
id,x,y, optionaltext/label fields depending on the model section
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:
| Option | Meaning |
|---|---|
layer=None | Read a specific layer from GeoPackage or other multi-layer data. |
use_geometry=True | Use geometry to fill coordinates, vertices, polygons, and optional lengths. |
geometry_field="geometry" | Name of geometry column. |
compute_length_from_geometry=False | For line geometry, compute length if no length field is available. |
polygon_to_centroid=True | For polygons, use centroid as x/y when explicit coordinates are absent. |
| `crs_action="warn" | "ignore" |
# 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 type | Import behavior |
|---|---|
Point | Supplies x and y for nodes, rain gages, and labels when columns are absent. |
LineString | Supplies link vertices; can compute length when compute_length_from_geometry=True. |
Polygon | Supplies subcatchment polygon; centroid can supply x and y. |
MultiLineString | Uses the longest line conservatively and records a warning. |
MultiPolygon | Uses 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.