Generating swift catchment structure¶
This is a sample notebook demonstrating how to use the geosdhydro
package to convert a geopandas dataframe to a swift json file. A browsable form of this notebook should be at the package documentation.
import geopandas as gpd
from pathlib import Path
fpath = Path.home() / "data/wnsw/Abercrombie/Abercrombie_links4swift.shp"
fpath.exists()
True
link_specs = gpd.read_file(fpath)
print(link_specs.head())
OBJECTID Shape_Leng LinkID FromNodeID ToNodeID HeadLink SPathCnt \ 0 14 0.063634 14 43408481 43346081 0 11 1 15 0.139247 15 43352571 43346081 0 11 2 16 0.133341 16 43349393 43346081 0 18 3 17 0.135098 17 43636314 43346081 0 14 4 18 0.323005 18 43408465 43346081 0 40 LPathCnt SPathLen LPathLen DArea Prim_Link SWIFT_ID \ 0 11 12423.789416 12423.789416 1.0 0 1801 1 11 24712.339973 24712.339973 1.0 0 1804 2 18 26499.836521 26499.836521 1.0 0 1805 3 14 34835.592042 34835.592042 1.0 0 1802 4 40 57889.009690 57889.009690 510940000.0 1 18 ToNodeID2 DArea2 geometry 0 0 -1.0 LINESTRING (149.33174 -34.01786, 149.31528 -33... 1 0 -1.0 LINESTRING (149.45444 -33.95167, 149.31528 -33... 2 0 -1.0 LINESTRING (149.38417 -33.84222, 149.31528 -33... 3 0 -1.0 LINESTRING (149.41821 -34.04389, 149.31528 -33... 4 43346081 510940000.0 LINESTRING (149.59776 -34.11303, 149.31528 -33...
{x: link_specs[x].dtype for x in link_specs.columns}
{'OBJECTID': dtype('int64'), 'Shape_Leng': dtype('float64'), 'LinkID': dtype('int64'), 'FromNodeID': dtype('int64'), 'ToNodeID': dtype('int64'), 'HeadLink': dtype('int32'), 'SPathCnt': dtype('int64'), 'LPathCnt': dtype('int64'), 'SPathLen': dtype('float64'), 'LPathLen': dtype('float64'), 'DArea': dtype('float64'), 'Prim_Link': dtype('int32'), 'SWIFT_ID': dtype('int32'), 'ToNodeID2': dtype('int64'), 'DArea2': dtype('float64'), 'geometry': <geopandas.array.GeometryDtype at 0x7fc22562dfd0>}
Note that some of the input columns, LinkID, ToNodeID, FromNodeID, are integers, because of habits. It is preferable to have them as strings, but the converter will transparently convert them to string. Another thing is that there is a duplicated ID in the links:
gdf = link_specs
# Check for duplicates in the 'LinkID' column
duplicates = gdf['LinkID'][gdf['LinkID'].duplicated(keep=False)]
# Display the duplicated IDs
print(duplicates)
3 17 17 17 Name: LinkID, dtype: int64
Let's see what happens:
from geosdhydro import ShapefileToSwiftConverter
converter = ShapefileToSwiftConverter(link_specs)
--------------------------------------------------------------------------- ValueError Traceback (most recent call last) Cell In[9], line 1 ----> 1 converter = ShapefileToSwiftConverter(link_specs) File ~/src/geosdhydro/src/geosdhydro/_internal/swift.py:21, in ShapefileToSwiftConverter.__init__(self, gdf, include_coordinates) 19 self.gdf = gdf 20 self.include_coordinates = include_coordinates ---> 21 self._check_geodf() File ~/src/geosdhydro/src/geosdhydro/_internal/swift.py:76, in ShapefileToSwiftConverter._check_geodf(self) 74 if not duplicates.empty: 75 duplicate_indices = self.gdf[self.gdf["LinkID"].isin(duplicates.index)].index.tolist() ---> 76 raise ValueError(f"Column 'LinkID' contains duplicate values: {duplicates.index.tolist()} at indices {duplicate_indices}.") ValueError: Column 'LinkID' contains duplicate values: ['17'] at indices [3, 17].
link_specs.iloc[[3, 17]]
OBJECTID | Shape_Leng | LinkID | FromNodeID | ToNodeID | HeadLink | SPathCnt | LPathCnt | SPathLen | LPathLen | DArea | Prim_Link | SWIFT_ID | ToNodeID2 | DArea2 | geometry | |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
3 | 17 | 0.135098 | 17 | 43636314 | 43346081 | 0 | 14 | 14 | 34835.592042 | 34835.592042 | 1.0 | 0 | 1802 | 0 | -1.0 | LINESTRING (149.41821 -34.04389, 149.31528 -33... |
17 | 17 | 0.128853 | 17 | 43351601 | 43349393 | 1 | 0 | 0 | 1.000000 | 0.000000 | 145644000.0 | 1 | 55 | 43349393 | 145644000.0 | LINESTRING (149.42191 -33.79131, 149.38417 -33... |
Not quite sure what was intended with the above. One of the entry has 1 meter-length links SPathLen
, but a catchment area, the other is the other way around. I may have been a legacy workaround, or a data bug. Be it as it may, this is a useful way to illustrate the need to look at data, and the build-in checks in the package/features.
For the sake of the example, let us just drop these.
link_specs = link_specs.drop(index=[3,17])
converter = ShapefileToSwiftConverter(link_specs)
result = converter.convert()
f"there are {len(result["Links"])} links, {len(result["Nodes"])} nodes, {len(result["SubAreas"])} subareas"
'there are 27 links, 29 nodes, 16 subareas'
The object converter
has a save_to_file
method, or you can use:
import json
fp = Path.home() / "tmp" / "abercrombie_swift.json"
# with open(fp, "w") as f:
# json.dump(result, f, indent=2)
Checking the json output loads as a catchment structure¶
# This is be done if you have `swift2` in your python env using:
# from swift2.model_definitions import model_from_json_file
# sim = model_from_json_file(fp)