avatarRenee LIN
# Summary

The web content details a process for adjusting and analyzing an OpenStreetMap street network graph using the NetworkX library in Python, with a focus on renaming nodes and edges, accessing neighbors, and validating the number of connections.

# Abstract

The article describes how to manipulate a street network graph derived from OpenStreetMap data using NetworkX, GeoPandas, and OSMnx in Python. It guides the reader through visualizing the network, renaming nodes and edges for simplified lookup, accessing neighboring roads, sorting neighbors in a specific order, and validating the number of connections at each node. The tutorial emphasizes practical steps, such as transforming the network into a GeoDataFrame, performing spatial analysis, and plotting the graph to highlight specific nodes or edges. The author also discusses the inherent directionality of the graph and the need to sort neighbors based on compass direction to standardize the graph representation.

# Opinions

- The author suggests that OSM IDs are too long and recommends re-indexing for better usability.
- There is an acknowledgment that the OSMnx-generated graph's directionality affects neighbor lookup, which only lists the forward direction of travel.
- The article posits that the order of neighbors in the graph may not follow a specific sequence, prompting the need for custom sorting methods.
- The author's approach to sorting neighbors in an anticlockwise order is presented as a solution to the lack of a defined order in the graph's edges.
- The author observes that nodes typically connect to a maximum of four neighbors, based on the data from the OpenStreetMap network.

Adjust the OpenStreetMap Street Network Graph with NetworkX

https://bigdataschool.ru/blog/connected-components-in-directed-graph-with-networkx-in-colab.html

I have obtained a street network using a polygon, if you are interested in how to obtain the road network you want, you can check this post. I will check

0. Visualize the network and a bit of GeoDataframe

  1. Renaming the Nodes and Edges
  2. Accessing the neighbors/downstream roads, updating the neighbor info
  3. Validate the neighbor quantity

0. Visualize the network and a bit of GeoDataframe

import osmnx as ox
from shapely.geometry import Polygon

coords = [...see the complete coords at the end of this post]
poly = Polygon(coords)
graph = ox.graph_from_polygon(poly, network_type='drive',simplify=True)
ox.plot_graph(graph)

We can transform the network in the GeoDataframe using GeoPandas.

nodes, edges = ox.utils_graph.graph_to_gdfs(graph)

Then we could conduct spatial analysis, for example, find out how many nodes are within a certain distance of a point:

from shapely.geometry import Point
import geopandas as gpd

# Creating a point geometry
point = Point(23.7265, 37.9838) 
buffer_distance = 0.01 

# Creating a buffer around the point
buffer = point.buffer(buffer_distance)

# Performing a spatial join with nodes within the buffer
nodes_within_buffer = gpd.sjoin(nodes[nodes.within(buffer)], nodes, how="inner")

print(f"Number of nodes within the buffer: {len(nodes_within_buffer)}")

And then visualize it:

import matplotlib.pyplot as plt

fig, ax = plt.subplots()
edges.plot(ax=ax, linewidth=1, edgecolor='#BC8F8F')
nodes.plot(ax=ax, marker='o', color='red', markersize=5)

# Highlighting nodes within the buffer
nodes_within_buffer.plot(ax=ax, marker='o', color='blue', markersize=10)

plt.show()

The graph is created based on Networkx package.

As the graph has been created for me using osmnx, I will focus on how to use this tool to access the edges and neighbors, and on adjusting the attributes to graphs, nodes, and edges. For a complete tutorial, which includes how to create a graph from scratch, add nodes and edges, perform graph operations, etc., you can refer to the official documentation here.

1. Renaming the Nodes and Edges

For my first step, I want to rename the nodes and edges for easier lookup, as the osmid for nodes and edges, such as ‘31179466’, is too long.

import osmnx as ox
import networkx as nx

# mapping from the original OSM node IDs to indices
node_mapping = {node: idx for idx, node in enumerate(graph.nodes())}
graph_rename = nx.relabel_nodes(graph, node_mapping)
nodes_reindexed, edges_reindexed = ox.graph_to_gdfs(graph_rename)

import matplotlib.pyplot as plt
fig, ax = ox.plot_graph(graph_rename, show=False, close=False, node_color='r', node_size=30)
for node, data in graph_rename.nodes(data=True):
    ax.annotate(text=node, xy=(data['x'], data['y']), xytext=(5, 0), textcoords='offset points', horizontalalignment='center', verticalalignment='center',
                size=10,color='white')

plt.show()

Edges can be renamed too, but there isn’t a relabel function for the edge in Networkx:

fig, ax = ox.plot_graph(graph_rename, show=False, close=False, node_color='r', node_size=30)

# Annotate every n-th edge to reduce clutter
n = 3  # Adjust as needed
for idx, (u, v, data) in enumerate(graph_rename.edges(data=True)):
    if idx % n == 0: 
        start_node = graph_rename.nodes[u]
        end_node = graph_rename.nodes[v]

        # Calculate the mid-point
        mid_x = (start_node['x'] + end_node['x']) / 2
        mid_y = (start_node['y'] + end_node['y']) / 2

        ax.annotate(text=str(idx), xy=(mid_x, mid_y), xytext=(0, 3), textcoords='offset points', horizontalalignment='center', verticalalignment='center',
                    size=8, color='white')  # Adjust text size as needed

plt.show()

2. Accessing the neighbors/downstream roads

Then, I need to access the neighbours or downstream link/road using the node name, for example node #85

list(graph_rename.neighbors(85))

However, you can see there are two nodes connecting #85, #84, and #134; why does it only output #134? This occurs due to the direction; hence, the vehicle can only move from #85 to #134. (The graph created by osmnx from OpenStreetMap data is typically a directed graph, meaning that edges have a direction, going from one node to another.)

Another question is how the neighbors are arranged. I’ve checked several points and can’t find any particular order (might be clockwise). Thus, it seems the nodes added to the graph may not be strictly defined. Consequently, when you use graph.neighbors(node), the returned orders also do not appear to follow a specific sequence.

I want to rearrange the neighbors of a node in anti-clockwise order (east, north, west, and south). We can calculate the bearing of the line connecting the node to each of its neighbors and then sort the neighbors based on this bearing. The bearing is the compass direction to travel from one point to another.

import math

def sort_neighbors_clockwise(graph, node):
    node_data = graph.nodes[node]
    neighbors = list(graph.neighbors(node))

    def calculate_bearing(n):
        neighbor_data = graph.nodes[n]
        delta_x = neighbor_data['x'] - node_data['x']
        delta_y = neighbor_data['y'] - node_data['y']
        
        # Calculate the angle in radians between the line connecting the node and the neighbor 
        # and the line parallel to the x-axis (East direction)
        angle = math.atan2(delta_y, delta_x)
        
        # Convert the angle to degrees and adjust it to represent bearing
        bearing = math.degrees(angle)
        bearing = (bearing + 360) % 360  # Ensure the bearing is positive
        return bearing
    
    # Sort neighbors based on bearing
    sorted_neighbors = sorted(neighbors, key=calculate_bearing)
    return sorted_neighbors


node = 0
sorted_neighbors = sort_neighbors_clockwise(graph_rename, node)
print('neighbors',list(graph_rename.neighbors(node)))
print(sorted_neighbors)

Adding sorted neighbors to the nodes.

# Update each node in the graph with the sorted neighbors
for node in graph_rename.nodes():
    graph_rename.nodes[node]['sorted_neighbors'] = sort_neighbors_clockwise(graph_rename, node)

3. Validate the neighbor quantity

One more question for now: How many neighbors can one node have? Typically, at an intersection, one can go left, right, or move forward. Is there another option, meaning might one node connect to more than four nodes? The results show no nodes connecting to more than four nodes.

nodes_with_many_neighbors = {}

for node in graph_rename.nodes():
    neighbors = list(graph_rename.neighbors(node))
    # print('neighbors',neighbors)
    if len(neighbors) > 3:
        nodes_with_many_neighbors[node] = len(neighbors)

print(nodes_with_many_neighbors)
# the polygon coords
[[ 23.724023443717012, 37.989214749626427 ], 
[ 23.727989595441151, 37.988666794454019 ],
[ 23.728250526475634, 37.989945356522981 ], 
[ 23.728981133372187, 37.993024342729882 ], 
[ 23.736887343717015, 37.992345922040222 ], 
[ 23.736078457510118, 37.989032097902289 ], 
[ 23.732112305785982, 37.989658332385048 ], 
[ 23.731355605785982, 37.98694464962643 ], 
[ 23.732294957510117, 37.985483435833324 ],
 [ 23.733547426475635, 37.986083577212639 ], 
[ 23.74001851613081, 37.977629411695396 ], 
[ 23.736522040268738, 37.97507228755746 ], 
[ 23.734408498889429, 37.975046194454016 ], 
[ 23.728120060958393, 37.9828741254885 ],
 [ 23.727258988544602, 37.982665380660912 ], 
[ 23.725797774751495, 37.982482728936773 ],
 [ 23.722797067854945, 37.986683718591948 ], 
[ 23.724023443717012, 37.989214749626427 ] ]
Networkx
Spatial Data Analysis
Data Analysis
Data Visualization
Openstreetmap
Recommended from ReadMedium