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 |
|---|---|
|
The model’s CRS string (EPSG code, PROJ string, or WKT). |
|
Set the CRS. Accepts |
Coordinates#
Method |
Action / returns |
|---|---|
|
Node point coordinates (returns |
|
Bulk read / write of every node’s coordinates. Returns a tuple
of two |
|
Link “centroid” / annotation point. |
|
Subcatchment centroid. |
|
Rain gage location. |
Link vertices (polylines)#
Method |
Action / returns |
|---|---|
|
Number of polyline vertices on link |
|
Tuple |
|
Replace the vertex list; |
Subcatchment polygons#
Method |
Action / returns |
|---|---|
|
Number of vertices on the subcatchment polygon. |
|
Tuple |
|
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#
x, y = spatial.get_node_coords_bulk() # two ndarrays, shape (n_nodes,)
Re-project node coordinates with pyproj#
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)
x, y = spatial.get_node_coords_bulk() # detach implicitly via assignment
lon, lat = tx.transform(x, y)
spatial.set_node_coords_bulk(lon, lat)
spatial.set_crs(dst_crs)
Walk every link vertex#
for i in range(links.count()):
n = spatial.get_link_vertex_count(i)
if n == 0:
continue
xs, ys = spatial.get_link_vertices(i)
print(f"{links.get_id(i):<12} has {n} vertices (incl. endpoints)")
for x, y in zip(xs, ys):
print(f" ({x:.2f}, {y:.2f})")
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
xs, ys = spatial.get_subcatch_polygon(i)
ring = list(zip(xs.tolist(), ys.tolist()))
ring_closed = 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 |
|---|---|
|
Tuple |
|
Same shapes; updates every node coordinate at once. |
|
Tuple |
For per-link or per-subcatchment polylines there is no project-wide bulk surface — the vertex counts vary per object, so you must walk them.
EngineState requirements & exceptions#
Method group |
Required state |
Notes |
|---|---|---|
read accessors |
|
n/a |
setters (CRS, coords, vertices) |
|
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#
Hot start — CRS captured in / restored from hot-start files.
Nodes, Links, Subcatchments — the objects whose geometry this class manages.