In a previous post I used NetworkX functions to convert a shapefile into a graph network for use in spatial smoothing. But there is a problem! Rivers and seas were un-crossable voids. I don’t really want to swim across the rivers, particularly the Thames, but luckily for us there are bridges across rivers and ferries that cross the sea. Lets see how we can add these to our networks. Code for this on my GitHub page.
Algorithm for crossing a river
- Make the graph network on neighbouring shapefiles
- Create a dataframe of potential connections (roads, ferries…etc.)
- Find the connections which have elements outside the shapefile boundaries
- Remove the lines which start and end outside of the polygon boundaries and the lines which start and end in the same polygon and add to the network
Example: Crossing the Thames
The first two steps are covered in the previous post and can be done with a single function, defined in said post.
G, positions = kf.create_network_from_shapes(LSOA)
Now we have the LSOA polygons and a network of their boundaries. Our problem is clear… there isn’t a way to cross the Thames.
Build the bridges
Use the unary_union function on the LSOA polygon geometries to find their combined outer edge. In our example this makes two shapes, North and South London.
Then use the within function to find the roads with elements outside these two polygons.
In this there are some geometries that are just points. We don’t want these, so filter to just keep the LineStrings.
bridges = roads[~roads.geometry.within(LSOA.geometry.unary_union)]
bridges = bridges[bridges.geometry.type == 'LineString'].reset_index(drop=True)
These two lines of code find all of the (road) bridges in London
Bridges should cross the river, not stay on the same side
This is really more than one step, but as it’s all done in 6 lines of code I’ll cover in one go.
A bridge shouldn’t take you back to where you started. With that in mind we need to first find the names of polygons the bridges start and end in.
for b in bridges.geometry.boundary:
start, end = b.geoms
st_nm = LSOA.loc[LSOA.geometry.contains(start), "Name"].to_list()
ed_nm = LSOA.loc[LSOA.geometry.contains(end), "Name"].to_list()
Loop over the end points of the bridges dataframe we made above using the boundary function, this returns two Points, the start and end of the bridge Linestring.
Use the Shapely contains function to find the LSOA it’s in and then filter to just get the name. If the line ends outside of a polygon this will return an empty list [].
Filter out the lines which start and end outside of the polygon boundaries and filter out the loops. Then use add_edge with the node name calculated above to add bridges into our network.
NetworkX won’t add edges where an edge already exists so we don’t need to cater for this.
if st_nm != [] and ed_nm != [] and st_nm != ed_nm:
G.add_edge(st_nm[0], ed_nm[0])
Our network can now cross the river using the bridges.