Spatial (CRS, coordinates, geometry)#

Note

Engine: OpenSWMM 6 — refactored. Documents openswmm.engine.Spatial. This is unique to the v6 engine.

The Spatial class manages the geometric / cartographic metadata of the model:

  • CRS — Coordinate Reference System (EPSG code or WKT string).

  • Coordinates — point coordinates for nodes, links, gages, and subcatchment centroids.

  • Geometry — link vertices (polylines) and subcatchment polygons.

Reference: openswmm_spatial.h.


Class signature#

class Spatial:
    def __init__(self, solver: Solver) -> None: ...

Key methods#

CRS#

Method

Action / returns

get_crs()

The model’s CRS string (EPSG code, PROJ string, or WKT).

set_crs(crs)()

Set the CRS. Accepts "EPSG:4326", full WKT, etc.

Coordinates#

Method

Action / returns

get_node_coord(idx)() / set_node_coord(idx, x, y)()

Node point coordinates.

get_node_coords_bulk() / set_node_coords_bulk(...)()

Bulk read / write of every node’s (x, y).

get_link_coord(idx)() / set_link_coord(idx, x, y)()

Link “centroid” / annotation point.

get_subcatch_coord(idx)() / set_subcatch_coord(idx, x, y)()

Subcatchment centroid.

get_gage_coord(idx)() / set_gage_coord(idx, x, y)()

Rain gage location.

Subcatchment polygons#

Method

Action / returns

get_subcatch_polygon_count(idx)()

Number of vertices on the subcatchment polygon.

get_subcatch_polygon(idx)()

All polygon (x, y) coordinates.

set_subcatch_polygon(idx, ...)()

Replace the polygon.


End-to-end example#

from openswmm.engine import Solver, Spatial, Nodes

with Solver("urban.inp", "urban.rpt", "urban.out") as s:
    spatial = Spatial(s)
    nodes = Nodes(s)

    print("CRS:", spatial.get_crs() or "<none>")

    # Print every junction's coordinates
    for i in range(nodes.count()):
        x, y = spatial.get_node_coord(i)
        print(f"  {nodes.get_id(i):<12}  ({x:.2f}, {y:.2f})")

Common recipes#

Set a CRS at edit time#

s.open()
spatial.set_crs("EPSG:6433")           # Idaho West (US Survey Feet)
s.initialize()

Bulk-read every node coordinate into NumPy#

coords = spatial.get_node_coords_bulk()    # ndarray, shape (n_nodes, 2)
# → coords[:, 0] = x, coords[:, 1] = y

Re-project node coordinates with pyproj#

import numpy as np
from pyproj import Transformer

src_crs = spatial.get_crs() or "EPSG:6433"
dst_crs = "EPSG:4326"
tx = Transformer.from_crs(src_crs, dst_crs, always_xy=True)

coords = spatial.get_node_coords_bulk().copy()      # detach from scratch
lon, lat = tx.transform(coords[:, 0], coords[:, 1])
new = np.column_stack([lon, lat])

spatial.set_node_coords_bulk(new)
spatial.set_crs(dst_crs)

Export every subcatchment polygon to GeoJSON#

import json

features = []
for i in range(sc.count()):
    n = spatial.get_subcatch_polygon_count(i)
    if n < 3:
        continue
    ring = spatial.get_subcatch_polygon(i)
    ring_closed = list(ring) + [ring[0]]
    features.append({
        "type": "Feature",
        "id":   sc.get_id(i),
        "geometry": {"type": "Polygon", "coordinates": [ring_closed]},
        "properties": {"id": sc.get_id(i)},
    })

with open("subcatchments.geojson", "w") as f:
    json.dump({"type": "FeatureCollection", "features": features}, f)

Bulk arrays#

Method

Shape

get_node_coords_bulk()

np.ndarray[float64], shape (n_nodes, 2).

set_node_coords_bulk(arr)()

Same shape; updates every node coordinate at once.

For per-link or per-subcatchment polygons there is no bulk surface — the vertex counts vary per object, so you must walk them.


EngineState requirements & exceptions#

Method group

Required state

Notes

read accessors

OPENED or later

n/a

setters (CRS, coords, vertices)

OPENED

Geometry is metadata; mid-run changes do not affect routing.

Common EngineError codes:

  • INVALID_INDEX — out-of-range index.

  • INVALID_TYPE — too few vertices for a polygon (≥ 3 required).


See also#