Building Bridges using Python

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

  1. Make the graph network on neighbouring shapefiles
  2. Create a dataframe of potential connections (roads, ferries…etc.)
  3. Find the connections which have elements outside the shapefile boundaries
  4. 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.

NetworkX graph network of neighbouring LSOAs
Not a single bridge. I guess I’ll have to swim

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

Going from a gdf of roads to just the bridges across the Thames
Progression from the roads shapefile to just the bridges

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])
Updated network with the bridges included

Our network can now cross the river using the bridges.

Leave a Comment

Your email address will not be published. Required fields are marked *