Interactive directions map with OSM and networkx
By: Talles Felix
Tags: Openstreetmap, Router, Graph, Maps, Python

How it works?

We can think in a city region with your street and corners as a graph, with all your properties. For example the corners are the nodes and street segments are the vertices connecting all these nodes, weighted by the length of the segment. The advantage of this is that with a graph we are capable to use all graph algorithms to calculate weighted shortest paths.

In this tutorial we will get the shortest path between two nodes in a city region, using Dijkstra weighted shortest path algorithm, provided by Networkx library and we will use Ipyleaflet to get an interactive view of how these paths are generated.

Diving into the code

First we need to import the python libraries

import networkx as nx

import osmnx as ox

from shapely.geometry import LineString, mapping

import geopandas as gpd

from ipyleaflet import *

We are using Networkx to work with the graph itself, OSMNX to retrieve Open Street Map data, shapely to draw the routes on the map, GeoPandas to see the dataframes and Ipyleaflet to actually see what is going on.

Choose an area to work with

place_name = “Kamppi, Helsinki, Finland”

graph = ox.graph_from_place(place_name)

fig, ax = ox.plot_graph(graph)

Graph from Kammpi, Helsinki, Finland

OSMNX has a function called graph_from_place, so you can retrieve OSM data by choosing a neighborhood, city and country and all these data come in form of a netoworkx multi digraph.

nodes, edges = ox.graph_to_gdfs(graph)

nodes

Here is one of the forms to see and get an easy way to work with the graph data, with Geopandas GeoDataFrame.

Creating an interactive map

center = (60.16607, 24.93116) m = Map(center=center, basemap=basemaps.CartoDB.Positron, zoom=15)

m

With the map created, we can create the markers to interact with it and see the directions being drawn, the first will be our start point and the second our destination.

#style for the destination marker

to_marker_style = AwesomeIcon(

name='circle',

icon_color='white',

marker_color='red',

spin=False

) from_marker = Marker(location=center)

to_marker = Marker(location=center, icon=to_marker_style)

After creating the markers we must set two functions, one to get the nearest node based on the marker position and the other to handle with change of location of the marker, calculate the shortest path and add it to the map.

def set_nearest_node(marker):

marker.nearest_node = ox.get_nearest_node(graph, marker.location)

return

This function will inject the nearest_node attribute to the marker instance, so we can always search between the shortest path between the nearest nodes of these two markers.

Finally dealing with marker changing location

Ipyleaflet has an observe function, that is called when the state of the layer is changed and you can filter in what kind of change you want to call the callback function.

So we will create a handle_change_location function for dealing with the markers location and plot the route create by Dijkstra algorithm.

path_layer_list = [] def handle_change_location(event, marker):

event_owner = event['owner']

event_owner.nearest_node = ox.get_nearest_node(graph, event_owner.location)

marker.nearest_node = ox.get_nearest_node(graph, marker.location)



shortest_path = nx.dijkstra_path(graph, event_owner.nearest_node, marker.nearest_node,

weight='length')



if len(path_layer_list) == 1:

m.remove_layer(path_layer_list[0])

path_layer_list.pop()



shortest_path_points = nodes.loc[shortest_path]

path = gpd.GeoDataFrame([LineString(shortest_path_points.geometry.values)], columns=['geometry'])

path_layer = GeoData(geo_dataframe=path, style={'color':'black', 'weight':2})

m.add_layer(path_layer)

path_layer_list.append(path_layer)

path_layer_list is a list to store our path layer, with this function we get the marker instance that is throwing the callback and the other instance in which the shortest route need to be calculated.

The nearest node is calculated again, now using the new marker location. We use the networkx dijkstra_path algorithm to calculate the shortest path between these two nodes. This function returns a list of nodes representing the route itself and we can get the nodes geometries by filtering these nodes on the Geopandas Dataframe created earlier.

Now is just create the Line String to plot in our map, create an ipyleaflet layer of it and add the layer to the map. We must also remind to remove the old path layer if there is one and add the new layer to the list.

With our function created, now we just need to add it as a callback to the observe method of the marker.

from_marker.observe(lambda event: handle_change_location(event, to_marker), 'location')

to_marker.observe(lambda event: handle_change_location(event, from_marker), 'location')

And then add the layers to the map and call set_nearest_node.

m.add_layer(from_marker)

m.add_layer(to_marker)

set_nearest_node(from_marker)

set_nearest_node(to_marker)

m

Now you can play these two markers and see the interaction between, creating new shortests paths in street network.

Conclusions

There area a lot of cool stuff to work with graphs and maps, routing is just one of them!

The link of the repository with the code is bellow, feel free to copy and change it as you wish!