Custom names and IDs for links, nodes, subareas¶
This is a sample notebook demonstrating how to use the ShapefileToSwiftConverter class from the geosdhydro package to convert a shapefile with custom names and custom IDs for links, nodes, and subareas into a JSON format compatible with the swift2 hydrological modeling framework.
We demonstrate custom IDs for subareas only for the sake of conciseness, but custom IDs for links is similarly possible.
from pathlib import Path
import geopandas as gpd
from shapely.geometry import LineString
from geosdhydro import ShapefileToSwiftConverter
Create synthetic test data¶
Side note that even if string columns for IDS are preferable, the converter will convert them to string if they are not, as will be the case for ToNodeID below.
We use the default column names for the required fields:
data = {
"LinkID": ["1", "2", "3", "4", "5"], # As strings
"FromNodeID": ["2", "3", "4", "5", "6"], # As strings
"ToNodeID": [1, 2, 2, 2, 5], # we tolerate ints, they are stringified by the converter
"SPathLen": [1000.0, 1500.0, 2000.0, 800.0, 1200.0],
"DArea2": [3000000.0, 4000000.0, 2500000.0, -1.0, 3500000.0], # Link 4, or rather subarea sa4, has negative area
"geometry": [
LineString([(2.1, 2.2), (1.1, 1.2)]), # Link 1: node 2 -> node 1
LineString([(3.1, 3.2), (2.1, 2.2)]), # Link 2: node 3 -> node 2
LineString([(4.1, 4.2), (2.1, 2.2)]), # Link 3: node 4 -> node 2
LineString([(5.1, 5.2), (2.1, 2.2)]), # Link 4: node 5 -> node 2
LineString([(6.1, 6.2), (5.1, 5.2)]), # Link 5: node 6 -> node 5
],
}
That was the minimally required columns. We can add a few more to illustrate custom names:
custom_linkname_fieldname = "LinkName"
data[custom_linkname_fieldname] = [f"CustomLinkName_{i}" for i in range(5)]
# Custom names for subareas
custom_subarea_name_fieldname = "SubAreaName"
data[custom_subarea_name_fieldname] = [f"CustomSubAreaName_{i}" for i in range(5)]
custom_subarea_id_fieldname = "CustomSubareaID"
data[custom_subarea_id_fieldname] = ["sa1", "sa2", "sa3", "sa4", "sa5"] # As strings
gdf = gpd.GeoDataFrame(data)
gdf
| LinkID | FromNodeID | ToNodeID | SPathLen | DArea2 | geometry | LinkName | SubAreaName | CustomSubareaID | |
|---|---|---|---|---|---|---|---|---|---|
| 0 | 1 | 2 | 1 | 1000.0 | 3000000.0 | LINESTRING (2.1 2.2, 1.1 1.2) | CustomLinkName_0 | CustomSubAreaName_0 | sa1 |
| 1 | 2 | 3 | 2 | 1500.0 | 4000000.0 | LINESTRING (3.1 3.2, 2.1 2.2) | CustomLinkName_1 | CustomSubAreaName_1 | sa2 |
| 2 | 3 | 4 | 2 | 2000.0 | 2500000.0 | LINESTRING (4.1 4.2, 2.1 2.2) | CustomLinkName_2 | CustomSubAreaName_2 | sa3 |
| 3 | 4 | 5 | 2 | 800.0 | -1.0 | LINESTRING (5.1 5.2, 2.1 2.2) | CustomLinkName_3 | CustomSubAreaName_3 | sa4 |
| 4 | 5 | 6 | 5 | 1200.0 | 3500000.0 | LINESTRING (6.1 6.2, 5.1 5.2) | CustomLinkName_4 | CustomSubAreaName_4 | sa5 |
# Custom node names: we map from the node IDs to custom names
custom_nodenames = {str(i): f"CustomNodeName_{i}" for i in range(1, 7)}
custom_nodenames
{'1': 'CustomNodeName_1',
'2': 'CustomNodeName_2',
'3': 'CustomNodeName_3',
'4': 'CustomNodeName_4',
'5': 'CustomNodeName_5',
'6': 'CustomNodeName_6'}
Creation of the swift JSON structure¶
converter = ShapefileToSwiftConverter(
gdf,
linkname_field=custom_linkname_fieldname,
subarea_name_field=custom_subarea_name_fieldname,
subareaid_field=custom_subarea_id_fieldname,
node_names=custom_nodenames,
darea_field="DArea2",
)
result = converter.convert()
result.keys()
dict_keys(['Links', 'Nodes', 'SubAreas'])
result["Nodes"][0]
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '1',
'Name': 'CustomNodeName_1',
'Reservoir': {'ReservoirType': 'NoReservoir'}}
result["Links"][0]
{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '1',
'ID': '1',
'Length': 1000.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_0',
'Slope': 1.0,
'UpstreamNodeID': '2',
'f': 1.0}
We check that one of the subarea is not present (area negative), even if a mapped name was specified:
[s['Name'] for s in result["SubAreas"]]
['CustomSubAreaName_0', 'CustomSubAreaName_1', 'CustomSubAreaName_2', 'CustomSubAreaName_4']
And, since we specified a custom field for subarea IDs, let us check this is distinct from the link IDs (default)
[s['ID'] for s in result["SubAreas"]]
['sa1', 'sa2', 'sa3', 'sa5']
distinct from:
[s['ID'] for s in result["Links"]]
['1', '2', '3', '4', '5']
Full JSON file¶
The serialised file would look like:
result
{'Links': [{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '1',
'ID': '1',
'Length': 1000.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_0',
'Slope': 1.0,
'UpstreamNodeID': '2',
'f': 1.0},
{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '2',
'ID': '2',
'Length': 1500.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_1',
'Slope': 1.0,
'UpstreamNodeID': '3',
'f': 1.0},
{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '2',
'ID': '3',
'Length': 2000.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_2',
'Slope': 1.0,
'UpstreamNodeID': '4',
'f': 1.0},
{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '2',
'ID': '4',
'Length': 800.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_3',
'Slope': 1.0,
'UpstreamNodeID': '5',
'f': 1.0},
{'ChannelRouting': {'ChannelRoutingType': 'NoRouting'},
'DownstreamNodeID': '5',
'ID': '5',
'Length': 1200.0,
'ManningsN': 1.0,
'Name': 'CustomLinkName_4',
'Slope': 1.0,
'UpstreamNodeID': '6',
'f': 1.0}],
'Nodes': [{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '1',
'Name': 'CustomNodeName_1',
'Reservoir': {'ReservoirType': 'NoReservoir'}},
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '2',
'Name': 'CustomNodeName_2',
'Reservoir': {'ReservoirType': 'NoReservoir'}},
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '3',
'Name': 'CustomNodeName_3',
'Reservoir': {'ReservoirType': 'NoReservoir'}},
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '4',
'Name': 'CustomNodeName_4',
'Reservoir': {'ReservoirType': 'NoReservoir'}},
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '5',
'Name': 'CustomNodeName_5',
'Reservoir': {'ReservoirType': 'NoReservoir'}},
{'ErrorCorrection': {'ErrorCorrectionType': 'NoErrorCorrection'},
'ID': '6',
'Name': 'CustomNodeName_6',
'Reservoir': {'ReservoirType': 'NoReservoir'}}],
'SubAreas': [{'AreaKm2': 3.0,
'ID': 'sa1',
'LinkID': '1',
'Name': 'CustomSubAreaName_0',
'RunoffModel': {'PercFactor': 2.25,
'R': 0.0,
'RunoffModelType': 'GR4J',
'S': 0.0,
'SurfaceRunoffRouting': {'SurfaceRunoffRoutingType': 'NoRouting'},
'UHExponent': 2.5,
'x1': 350.0,
'x2': 0.0,
'x3': 40.0,
'x4': 0.5}},
{'AreaKm2': 4.0,
'ID': 'sa2',
'LinkID': '2',
'Name': 'CustomSubAreaName_1',
'RunoffModel': {'PercFactor': 2.25,
'R': 0.0,
'RunoffModelType': 'GR4J',
'S': 0.0,
'SurfaceRunoffRouting': {'SurfaceRunoffRoutingType': 'NoRouting'},
'UHExponent': 2.5,
'x1': 350.0,
'x2': 0.0,
'x3': 40.0,
'x4': 0.5}},
{'AreaKm2': 2.5,
'ID': 'sa3',
'LinkID': '3',
'Name': 'CustomSubAreaName_2',
'RunoffModel': {'PercFactor': 2.25,
'R': 0.0,
'RunoffModelType': 'GR4J',
'S': 0.0,
'SurfaceRunoffRouting': {'SurfaceRunoffRoutingType': 'NoRouting'},
'UHExponent': 2.5,
'x1': 350.0,
'x2': 0.0,
'x3': 40.0,
'x4': 0.5}},
{'AreaKm2': 3.5,
'ID': 'sa5',
'LinkID': '5',
'Name': 'CustomSubAreaName_4',
'RunoffModel': {'PercFactor': 2.25,
'R': 0.0,
'RunoffModelType': 'GR4J',
'S': 0.0,
'SurfaceRunoffRouting': {'SurfaceRunoffRoutingType': 'NoRouting'},
'UHExponent': 2.5,
'x1': 350.0,
'x2': 0.0,
'x3': 40.0,
'x4': 0.5}}]}