19. GIS: shapefiles, GeoJSON, GeoPackage, QGIS, coordinate systems and DEMs

The SWMM editor reads and writes the everyday GIS formats itself: ESRI shapefiles, GeoJSON, OGC GeoPackage, .prj coordinate systems, GeoTIFF and ESRI ASCII elevation grids. It also hands a model to QGIS and takes the edits back (§19.8). There is no GDAL, PROJ or other GIS library underneath; every reader and writer is written from the public specification named at the top of its source file under swmm/src/gis/, and every formula in the projection code is cited to Snyder (1987) or Karney (2011). That makes the reach deliberately narrower than a desktop GIS, and this chapter says exactly where the edges are.

What the GIS tools do:

All five are on the File menu while a model is open, and a shapefile, GeoJSON file, GeoTIFF or ASCII grid dropped on the window (or opened from the command line) with the SWMM workspace active opens the matching dialog.

19.1 Coordinate systems: what is supported

A coordinate reference system (CRS) is read from a .prj beside a shapefile or ASCII grid (OGC WKT1 or the ESRI dialect ArcGIS writes), from a GeoJSON crs member, from a GeoTIFF's GeoKeys, or typed as an EPSG code.

Projections. Transverse Mercator (Krüger series, after Karney 2011 — accurate to well under a millimetre across a UTM zone), Lambert Conformal Conic (one and two standard parallels), Albers Equal Area, Mercator, Web Mercator (EPSG:3857), Polar Stereographic (variant B) and Hotine Oblique Mercator (State Plane Alaska zone 1), plus plain geographic longitude/latitude. The formulas are from Snyder, Map Projections — A Working Manual, USGS Professional Paper 1395 (1987).

EPSG codes built in. 4326 (WGS 84), 4269 (NAD83), 4267 (NAD27, recognised only), 3857, every UTM zone on WGS 84 (326xx north, 327xx south) and NAD83 (269xx), 5070 (CONUS Albers), 3031/3413/3995 (polar), and every State Plane 1983 zone in metres (26929–26998, 32100–32161, and 2205 for Kentucky North) and, where the registry has one, in US survey or international feet (2222–2289, 2965, 2966). A .prj with no authority code (the ESRI form) is matched to a UTM or State Plane zone by its parameters, so it still gets its code.

The State Plane table. Zone constants (projection, origin, standard parallels, scale, false easting and northing) are the defining values in NOAA Manual NOS NGS 5, State Plane Coordinate System of 1983 (James E. Stem, 1990), Appendix A. On 2026-09-18 every one of the 122 zones, and every foot-unit code, was checked against PROJ 9.5.1: each projects a test point to within 1 cm of the EPSG definition, and each foot code carries the right foot. The one disagreement was the registry's own: EPSG 26979 (Kentucky North) is deprecated because it put both standard parallels at 37°58′, which misplaces points by metres. AquaSWMM uses the current code 2205, with Stem's parallels 37°58′ and 38°58′, and reads 26979 as 2205.

Units. Metres, US survey feet (1200/3937 m) and international feet (0.3048 m) are told apart. A US-survey-foot system on a model in feet is not quite 1:1 — lengths computed from it are multiplied by 1.000002 — which is correct and far below anything a drainage model notices.

Datums — read this.

Not supported. WKT2 (PROJCRS[…]), compound and vertical systems, datum grid shifts, and any projection not in the list above; each is refused with a message naming what it found.

19.2 The model CRS

The model's coordinate system is kept in a sidecar file beside the model, <model>.crs (for site.inp, site.crs) — never inside the .inp, because the EPA engine refuses a section it does not know and the .inp must stay a file every SWMM tool reads. The sidecar is plain text: comment lines start with #, an EPSG:nnnn line gives the code when there is one, and the rest is the WKT. Either half is enough to read it back. It is re-read whenever the model's path changes; setting a CRS on a model that has never been saved holds it until the first save, then writes the sidecar.

File → Model CRS… (also the Model CRS… button in the GIS section of the Layers tab) opens the Model CRS dialog:

With a CRS set, the Layers tab's GIS section shows Cursor lat/lon: the map cursor's latitude and longitude, updated as you move the mouse (WGS 84/NAD83, five decimals, about a metre). A model with no CRS works exactly as before; only the tools that need to know the unit or to reproject ask for one.

19.3 Import GIS Layer

File → Import GIS Layer… asks for a .shp, .geojson, .json or .gpkg file and opens the Import GIS Layer dialog. For a GeoPackage holding more than one feature table, a GeoPackage layer list picks the table. For the model's own nodes, links, subcatchments and gages tables, it also picks the matching target. The header shows the file, the feature count, the geometry type, the field count, the layer's CRS (or "not declared", or "not recognised" with the start of the text) and the model's.

What is read. Shapefiles per the ESRI Shapefile Technical Description (1998): Point, MultiPoint, PolyLine and Polygon, with their Z and M variants (the Z and M values are dropped); MultiPatch is refused. Attributes come from the .dbf (dBASE III/IV: character, numeric, float, logical and date fields), decoded as UTF-8 when the .cpg or the language driver says so, Windows-1252 for the ANSI drivers, else Latin-1. The .shx is optional on read. GeoJSON per RFC 7946: FeatureCollection, Feature or a bare geometry; a GeometryCollection contributes its first member. A GeoJSON file with a crs member naming an EPSG code (the pre-2016 convention many tools still write) is in that system; without one, coordinates inside ±180/±90 are taken as WGS 84 and anything else as having no CRS.

Import as. The targets the layer's geometry can become: points → Junction, Outfall, Storage Unit, Divider or Rain Gage; lines → Conduit; polygons → Subcatchment. The first is chosen for you. Changing it rebuilds the mapping table.

Name from / Name prefix. The field that names each object. A field called name, id, label, objectid or fid (any case) is picked automatically. "(numbered)" names them from the prefix (J1, J2, … for junctions, C conduits, S subcatchments, R gages, O outfalls, ST storage, D dividers). Names are made legal and unique: spaces become underscores (Basin A → Basin_A), a blank or null name is numbered, and a name already in the model or used earlier in the same import counts up to the next free one (MH-1 → MH-3 when MH-1 and MH-2 are taken).

Update objects that already exist (matched by name). A feature whose name is already an object of the same kind updates that object instead of adding a renamed copy. The update:

A node or link keeps its type: a feature naming an outfall updates the outfall, with the columns an outfall has, even when importing as junctions. Unchanged values and positions produce no edit, so re-importing an untouched export changes nothing. Features with new names are added as usual. This option starts on for a GeoPackage and off otherwise.

CRS options.

Conduit options.

Subcatchment options.

Keep as a reference layer keeps the layer on the map after the import (see §19.4).

Field mapping. One row per SWMM column the target has — for example JUNCTIONS Elevation, MaxDepth, InitDepth, SurDepth, Aponded; for conduits CONDUITS Length, Roughness, InOffset, OutOffset, InitFlow, MaxFlow and XSECTIONS Shape, Geom1–Geom4, Barrels; for subcatchments SUBCATCHMENTS RainGage, Outlet, Area, PctImperv, Width, PctSlope, CurbLen and SUBAREAS NImperv, NPerv, SImperv, SPerv, PctZero. Each row's source is:

Rules:

Area and length units. With nothing mapped to them, a subcatchment's Area and a conduit's Length come from the geometry:

Preview builds the import without touching the model and shows what it will create ("3 junction(s)", "2 conduit(s) + 1 junction(s) at conduit ends, 1 skipped"), any notes, and the first skipped features with the reason (no geometry, wrong geometry type, a coordinate that is not a number, a line with fewer than two points, no node within the tolerance, both ends on one node). Import applies it as one undo step labelled "import GIS layer" — conduits, the junctions created at their ends and every mapped field together — selects everything it created, and reports the counts in the status bar and the Layers tab. Cancel closes without a change.

19.4 Reference layers

A layer kept with Keep as a reference layer is drawn under the network in the model's coordinates (reprojected when the import reprojected). The GIS section of the Layers tab lists each one with a visibility checkbox, a colour, a label field (a field's values drawn beside each feature, or "no labels"), a Remove button, and its feature count and source file. The list is the drawing order: the layer at the top is drawn over the ones beneath it, and a newly imported layer goes on top. Up and Down move a layer one place. Every reference layer is drawn above the backdrop image and below the model's own objects. Reference layers are a view setting: they are not written to the .inp and are not kept between sessions.

19.5 DEMs: Import DEM

File → Import DEM… reads a digital elevation model and draws it under the map; the status bar reports how it was stored, its CRS and its elevation range. One DEM is loaded at a time; loading another replaces it.

GeoTIFF (TIFF 6.0 with the GeoTIFF 1.1 keys, OGC 19-008r4):

Not supported, each with its own message: BigTIFF (rewrite as classic TIFF, e.g. gdal_translate -co BIGTIFF=NO), JPEG and CCITT compression, separate-plane multi-band images, rotated or sheared transformations, non-square cells and sub-byte samples.

ESRI ASCII grid (.asc): the ncols/nrows/xllcorner/yllcorner (or xllcenter/yllcenter)/cellsize/NODATA_value header, square cells only, and its CRS from a .prj of the same name when there is one.

Drawing. A DEM in a different CRS from the model's is drawn at its reprojected corners (fine for the extents a drainage model covers); a DEM with no CRS, or with a datum that cannot be transformed, is drawn as if its coordinates were the model's. Grids larger than 2048 cells a side are thinned for display only; sampling always reads the full grid. In the GIS section of the Layers tab:

No-data cells are transparent.

19.6 Set Ground From DEM

File → Set Ground From DEM… (enabled once a DEM is loaded) sets each node's MaxDepth so that its rim is on the ground: MaxDepth = DEM ground − invert Elevation. The DEM is sampled at the node by bilinear interpolation between cell centres (the nearest cell at the grid's edge and beside no-data), after transforming the node's position into the DEM's CRS when the two differ.

The Set Ground From DEM dialog:

The DEM is not written into the model; only the depths are.

19.7 Export GIS Layers

File → Export GIS Layers… opens the Export GIS Layers dialog. The model becomes up to four layers — nodes (points), links (lines through their vertices), subcatchments (polygons from their outlines) and rain gages (points) — each object with its defining columns as attributes plus Name, Kind and Section; links also carry their cross-section (Shape, Geom1–Geom4, Barrels) and subcatchments their subareas. A column that is numeric in every row is written as a number, otherwise as text. Objects without a position (a subcatchment with no outline, a gage with no symbol) are written with no geometry.

Export… writes and reports the files; Close closes the dialog. The older File → Export → GeoJSON (map units, no CRS)… of §13.4 is still there and unchanged.

19.8 QGIS: Open in QGIS and Update From QGIS

File → Open in QGIS writes two files into a folder beside the model, <model>_qgis\:

It then starts QGIS on the project. QGIS is found in this order:

  1. the AQUASWMM_QGIS environment variable (the path of qgis-bin.exe);
  2. the newest Program Files\QGIS * install;
  3. C:\OSGeo4W;
  4. qgis on the PATH elsewhere.

If QGIS is not found, the files are still written and the status line says where. The project names the GeoPackage by a relative path, so the folder can be moved or sent as a whole. A model never saved uses a temporary folder. Opening again rewrites both files. Close the project in QGIS first, since it holds the GeoPackage open. While QGIS has it open, Open in QGIS stops with "in use (close it in QGIS first)" and leaves the file as it was.

In QGIS, edit the layers as usual:

Then save the layer edits (Toggle Editing, then Save).

File → Update From QGIS reads that GeoPackage back as one undo step. It is enabled once the file exists. Each table is imported with Update objects that already exist on:

The status line counts what changed and what was added per table. Deleting a feature in QGIS does not delete the object: delete it in the editor.

Update From QGIS takes seconds even for large models. With 20,000 each of junctions, conduits and subcatchments, every node moved, every roughness changed and every outline shifted comes back in about 2 seconds.

The files are read and written by the editor itself (GeoPackage 1.3 per OGC 12-128r18 on SQLite; QGIS 3 project XML). Neither QGIS nor GDAL needs to be installed to write or read them. Column names are unique ignoring case, as SQLite compares them. A clash, or a column named fid or geom, takes _2, _3 and so on. A number SQLite cannot hold (not a number, infinite) is written empty.

Automated tests run this against QGIS 3.44 LTR and QGIS 3.24:

19.9 Tutorial: a network from GIS layers, grounded on a DEM

The files are in the source tree under swmm/tests/fixtures/gis/: three manholes and two pipes as shapefiles in NC State Plane feet (EPSG:2264), two catchments as GeoJSON, and small GeoTIFF DEMs.

  1. New model. File → New. Save it once (File → Save As…) so the CRS has somewhere to live.
  2. Manholes. File → Import GIS Layer… → manholes.shp. The dialog picks Junction, names from NAME, maps INVERT to JUNCTIONS Elevation, and — because the layer has a .prj and the model has no CRS — ticks Use the layer's CRS as the model CRS. Click Preview ("3 junction(s)"), then Import. The model CRS is now EPSG:2264 and a .crs file sits beside your .inp.
  3. Pipes. File → Import GIS Layer… → pipes.shp. Conduit is picked; map DIAM to XSECTIONS Geom1 and N to CONDUITS Roughness. Both ends of each pipe land on a manhole, so no junctions are created; pipe P2 is multipart and its longer part becomes the route. Tick Keep as a reference layer and Import. Lengths are the drawn lengths in feet.
  4. Catchments. File → Import GIS Layer… → basins.geojson. Subcatchment is picked; map imperv to PctImperv. Import: B1's area is 660 × 660 ft less its 100 × 100 ft hole = 9.77 acres, and its outlet is the manhole nearest its centroid. One Edit → Undo removes the whole import.
  5. Check the position. In the Layers tab's GIS section, turn on Cursor lat/lon and hover over MH-1: about 35.2271° N, 80.8431° W (Charlotte).
  6. DEM. File → Import DEM… → dem_deflate_pred3_f32.tif (a Deflate-compressed float grid in the same CRS). Switch between Hillshade and Elevation tint in the Layers tab. The fixture DEM is tiny and lies south of the manholes; with your own DEM covering the network, File → Set Ground From DEM…, check the table, and Write MaxDepth.
  7. Export. File → Export GIS Layers… → GeoJSON, Reproject to WGS 84 (RFC 7946), Export…. Open the file in any web map: it lands on Charlotte.

19.10 Known limits