A Coding Implementation on Spatial Graph Neural Networks for Urban Function Inference Using city2graph, OSMnx, and PyTorch Geometric
Back to Tutorials
techTutorialbeginner

A Coding Implementation on Spatial Graph Neural Networks for Urban Function Inference Using city2graph, OSMnx, and PyTorch Geometric

June 12, 202636 views5 min read

Learn to build a spatial graph neural network for urban function inference using city2graph, OSMnx, and PyTorch Geometric. This hands-on tutorial walks you through fetching city data, creating graphs, and training a model to predict urban functions.

Introduction

In this tutorial, you'll learn how to build a spatial graph neural network for urban function inference using Python. We'll use city2graph, OSMnx, and PyTorch Geometric to create a pipeline that analyzes city data and predicts the function of different urban areas. This is a foundational step toward understanding how AI can be used to analyze and understand city structures.

Prerequisites

Before starting this tutorial, you should have:

  • Basic Python knowledge
  • Python installed on your computer (preferably Python 3.8 or higher)
  • Installed packages: osmnx, city2graph, torch, torch-geometric, networkx, numpy, pandas

Step-by-Step Instructions

1. Setting Up Your Environment

1.1 Install Required Packages

First, we need to install the necessary libraries. Open your terminal or command prompt and run:

pip install osmnx city2graph torch torch-geometric networkx numpy pandas

Why: These packages provide the tools to fetch city data, create graphs, and build neural networks for spatial data analysis.

1.2 Import Libraries

Now, let's start by importing the necessary libraries in Python:

import osmnx as ox
import city2graph
import torch
import torch.nn.functional as F
from torch_geometric.data import Data
import networkx as nx
import numpy as np
import pandas as pd

Why: These imports will give us access to the tools we need to work with city data, graphs, and neural networks.

2. Fetching Urban Data from OpenStreetMap

2.1 Define the City Area

We'll start by defining the area we want to analyze. For this tutorial, we'll use a small city or district:

place_name = "Downtown, San Francisco, CA, USA"
graph = ox.graph_from_place(place_name, network_type='all')
print(f"Graph nodes: {len(graph.nodes())}, edges: {len(graph.edges())}")

Why: This command fetches the street network data for the specified area. OSMnx uses OpenStreetMap data to create a graph representation of the city.

2.2 Extract Points of Interest (POIs)

Next, we'll extract POIs like restaurants, parks, and shops from the area:

poi_tags = ['amenity', 'shop', 'tourism']
pois = ox.geometries_from_place(place_name, tags={'amenity': True})
print(f"POIs found: {len(pois)}")

Why: POIs are important features in urban analysis. They help us understand what functions different parts of the city serve.

3. Creating Spatial Graphs

3.1 Convert to City2Graph Format

We'll now convert our graph data into a format that city2graph can work with:

city_graph = city2graph.CityGraph.from_networkx(graph)
print(f"City graph created with {len(city_graph.nodes)} nodes and {len(city_graph.edges)} edges")

Why: city2graph helps us create more complex graph representations that can include spatial relationships and features.

3.2 Build Proximity Graphs

Now, we'll create different types of proximity graphs to represent the spatial relationships in the city:

# Create a k-nearest neighbors graph
knn_graph = city_graph.knn_graph(k=5)
print(f"KNN Graph nodes: {len(knn_graph.nodes)}, edges: {len(knn_graph.edges)}")

# Create a radius-based graph
radius_graph = city_graph.radius_graph(radius=500)
print(f"Radius Graph nodes: {len(radius_graph.nodes)}, edges: {len(radius_graph.edges)}")

Why: Different graph types help us understand spatial relationships in different ways. KNN looks at the nearest neighbors, while radius-based graphs consider all nodes within a certain distance.

4. Feature Engineering

4.1 Add Spatial Features to Nodes

We'll now add spatial features to each node in the graph:

for node_id in city_graph.nodes:
    node = city_graph.nodes[node_id]
    node['x'] = node['x']  # longitude
    node['y'] = node['y']  # latitude
    node['degree'] = city_graph.degree(node_id)

print("Features added to nodes")

Why: Adding features like coordinates and degree helps the neural network understand the spatial structure of the city.

4.2 Prepare POI Categories

We'll now map POI categories to numerical labels:

# Create a mapping of POI categories to integers
poi_categories = pois['amenity'].dropna().unique()
category_to_id = {cat: i for i, cat in enumerate(poi_categories)}

# Assign category labels to POIs
pois['category_id'] = pois['amenity'].map(category_to_id)
print(f"Categories: {list(category_to_id.keys())}")

Why: Neural networks work with numbers, so we need to convert text categories into numerical labels.

5. Converting to PyTorch Geometric Format

5.1 Prepare Data for PyTorch

We'll now convert our graph into a format that PyTorch Geometric can use:

# Get node features and edge indices
node_features = []
edge_indices = []

for node_id in city_graph.nodes:
    node = city_graph.nodes[node_id]
    node_features.append([node['x'], node['y'], node['degree']])

# Convert to tensors
x = torch.tensor(node_features, dtype=torch.float)

# Convert edges to PyTorch format
edges = list(city_graph.edges)
edge_index = torch.tensor(edges, dtype=torch.long).t().contiguous()

# Create PyTorch Geometric data object
data = Data(x=x, edge_index=edge_index)
print(f"Data object created with {data.num_nodes} nodes and {data.num_edges} edges")

Why: PyTorch Geometric requires data in a specific format. This step prepares our graph for neural network training.

6. Building a Simple Graph Neural Network

6.1 Define a GraphSAGE Model

Now we'll create a simple GraphSAGE model:

from torch_geometric.nn import SAGEConv

class GraphSAGE(torch.nn.Module):
    def __init__(self, input_dim, hidden_dim, output_dim):
        super(GraphSAGE, self).__init__()
        self.conv1 = SAGEConv(input_dim, hidden_dim)
        self.conv2 = SAGEConv(hidden_dim, output_dim)
        self.dropout = torch.nn.Dropout(0.5)

    def forward(self, x, edge_index):
        x = self.conv1(x, edge_index)
        x = F.relu(x)
        x = self.dropout(x)
        x = self.conv2(x, edge_index)
        return F.log_softmax(x, dim=1)

model = GraphSAGE(input_dim=3, hidden_dim=16, output_dim=len(category_to_id))
print("GraphSAGE model created")

Why: This model will learn to predict POI categories based on the spatial structure of the city.

6.2 Train the Model

Let's train our model with some sample data:

# Dummy training (in practice, you'd use real labels)
optimizer = torch.optim.Adam(model.parameters(), lr=0.01)

for epoch in range(100):
    model.train()
    optimizer.zero_grad()
    out = model(data.x, data.edge_index)
    loss = F.nll_loss(out, torch.tensor([0]*len(data.x)))  # Dummy labels
    loss.backward()
    optimizer.step()

    if epoch % 20 == 0:
        print(f'Epoch {epoch}, Loss: {loss.item():.4f}')

print("Model training complete")

Why: Training the model allows it to learn patterns in the data that help predict urban functions.

Summary

In this tutorial, you've learned how to create a spatial graph neural network for urban function inference. You've:

  • Fetched city data using OSMnx
  • Created different types of proximity graphs
  • Engineered spatial features for nodes
  • Converted data to PyTorch Geometric format
  • Built and trained a simple GraphSAGE model

This is just the beginning of spatial graph neural network applications. You can extend this pipeline by using real POI labels, adding more complex features, or experimenting with different graph types and neural network architectures.

Source: MarkTechPost

Related Articles