Heterogeneous Urban Graphs¶
This tutorial walks through constructing and visualising an UrbanGraph: a rich heterogeneous graph encoding four spatial node types and their inter-layer topology. Access shapefile here Shapefile
| Node type | Geometry | Typical count |
|---|---|---|
| Urban plots | Polygon | City blocks / land parcels |
| Buildings | Polygon | Individual footprints |
| Streets | Point (midpoint) | Road segments |
| Intersections | Point | Street junctions |
Setup¶
from urbanity import Map
from urbanity.visualisation import (
plot_graph,
plot_urban_graph_overview,
plot_urban_graph_edges,
plot_buildings,
plot_node_attribute,
plot_street_network,
)
import geopandas as gpd
import matplotlib.pyplot as plt
m = Map(country='Singapore')
m.add_polygon_boundary('path_to_shapefile',
layer_name='Downtown Core')
print("Area (km²):", round(
m.polygon_bounds.to_crs(m.polygon_bounds.estimate_utm_crs()).area.values[0] / 1e6, 3
))
Constructing the Urban Graph¶
urban_graph = m.get_urban_graph(
bandwidth=100, # buffer distance (m) beyond the boundary
minimum_area=30, # filter plots smaller than 30 m²
network_type='driving',
population_layer='meta', # use Meta 30-m resolution population data
)
What get_urban_graph() returns:
An UrbanGraph object with:
- urban_graph.geo_store — dict of node GeoDataFrames keyed by type
- urban_graph.edge_store — dict of edge arrays (populated after initialize_edges())
# Node counts by type
for layer, gdf in urban_graph.geo_store.items():
if hasattr(gdf, '__len__'):
print(f"{layer:>14s}: {len(gdf):>6,} features")
Initialising Edges¶
Before visualising edges or exporting to graph-ML, call initialize_edges():
# 3 nearest-neighbour building edges, max 100 m distance
urban_graph.initialize_edges(building_neighbours='knn', knn=3, distance=100)
# Inspect created edge types
for edge_type, arr in urban_graph.edge_store.items():
print(f"{edge_type}: {arr.shape}")
Four-Panel Overview¶
fig = plot_urban_graph_overview(
urban_graph.geo_store,
title='Tanjong Pagar — UrbanGraph Layers',
dark_mode=True,
)
plt.show()
Edge Visualisation¶
fig = plot_urban_graph_edges(
urban_graph.geo_store,
urban_graph.edge_store,
edge_types=["plot_to_plot"], # choose which edge type to show
)
plt.show()
Available edge types after initialize_edges():
plot_to_plot— plots sharing a street boundarybuilding_to_building— KNN building neighboursbuilding_to_street— nearest street to each buildingintersection_to_street— streets at each intersectionbuilding_in_plot— buildings contained within plots
Building Footprint Visualisation¶
fig = plot_buildings(
urban_graph.geo_store['building'],
boundary=urban_graph.geo_store['boundary'],
title='Building Footprints — Tanjong Pagar',
dark_mode=True,
)
plt.show()
# Colour by footprint area
fig = plot_buildings(
urban_graph.geo_store['building'],
colname='bid_area',
cmap='viridis',
boundary=urban_graph.geo_store['boundary'],
title='Building Footprint Area (m²)',
dark_mode=True,
)
plt.show()
Urban Plot Attribute Map¶
numeric_cols = (urban_graph.geo_store['plot']
.select_dtypes(include='number').columns.tolist())
print("Numeric plot columns:", numeric_cols[:10])
fig = plot_node_attribute(
urban_graph.geo_store['plot'],
colname=numeric_cols[0],
geometry_type='polygon',
cmap='RdYlGn',
boundary=urban_graph.geo_store['boundary'],
title=f'{numeric_cols[0]}',
dark_mode=True,
)
plt.show()
Interactive 3-D View with PyDeck¶
plot_graph() renders an interactive Deck.gl 3-D visualisation inside Jupyter:
# Building-centric view with height extrusion
plot_graph(
urban_graph.geo_store,
urban_graph.edge_store,
node_type='building',
colname='bid_height',
)
Highlight a single node and all its cross-layer neighbours:
plot_graph(
urban_graph.geo_store,
urban_graph.edge_store,
node_type='plot',
node_id=0, # highlights plot #0 in red
)
Export to PyTorch Geometric¶
See the full Graph ML tutorial for a complete training example.
Save and Load¶
# Save all node GeoDataFrames and edge arrays to a ZIP
urban_graph.save_graph('./data/graph.zip')
# Load in a future session
from urbanity.data_class import UrbanGraph
new_graph = UrbanGraph()
new_graph.load_graph('./data/graph.zip')
Save before initialising edges
Edge connectivity is cheap to recompute but the attribute-rich node GeoDataFrames are not. Save before calling initialize_edges().
Summary¶
| Task | Function |
|---|---|
| Build heterogeneous graph | m.get_urban_graph() |
| Initialise edges | urban_graph.initialize_edges(knn=3) |
| Four-panel overview | plot_urban_graph_overview(objects) |
| Edge visualisation | plot_urban_graph_edges(objects, edges) |
| Building footprint map | plot_buildings(objects['building']) |
| Any node × attribute map | plot_node_attribute(gdf, colname=...) |
| Interactive 3-D view | plot_graph(objects, edges, node_type=...) |
| Export to PyG | urban_graph.to_pyg_graph() |
| Save / load | save_graph(path) / load_graph(path) |