Python API - SpatialGraph¶
The SpatialGraph object wraps a loaded road-network graph and its spatial index.
Construct one with gw.SpatialGraph.from_place(...), from_pbf(...), or from_osm(...).
Reusing a SpatialGraph across multiple queries avoids rebuilding the network
and lets you run isochrones from many different origin points over the same
loaded graph.
graph = gw.SpatialGraph.from_place("Marienplatz, Munich, Germany", network="drive", max_dist=10_000)
print(graph) # SpatialGraph(nodes=6251, edges=15356, network_type=Drive)
Inspection¶
node_count¶
Number of nodes in the graph after simplification (unless retain_all=True was passed to the constructor).
edge_count¶
Number of directed edges in the graph.
nearest_node¶
Return (osm_id, lat, lon) for the graph node nearest to (lat, lon).
Uses the internal R-tree spatial index -- O(log n) lookup regardless of graph size.
Returns None if the graph is empty.
Example
osm_id, node_lat, node_lon = graph.nearest_node(48.137144, 11.575399)
print(f"Snapped to OSM node {osm_id} at ({node_lat:.6f}, {node_lon:.6f})")
Isochrones¶
isochrone¶
graph.isochrone(
origin: tuple[float, float],
minutes: list[float],
max_snap_m: float | None = 100.0,
) -> list[IsochroneResult]
Compute isochrones from an origin using the travel times of this graph's network type.
One Dijkstra pass is run from the nearest graph node; one triangulated contour polygon is computed per time limit.
Parameters
| Parameter | Type | Default | Description |
|---|---|---|---|
origin |
tuple[float, float] |
- | (lat, lon) origin |
minutes |
list[float] |
- | Travel-time thresholds in minutes |
max_snap_m |
float or None |
100.0 |
Reject the query if the origin snaps farther than this many meters from the graph; pass None to allow unlimited snapping |
Returns list[IsochroneResult] - one structured polygon result per time
limit, in the same order as minutes. Use .to_geojson() for mapping tools.
Example
isos = graph.isochrone((48.137144, 11.575399), minutes=[5, 10, 15, 20])
first_geojson = isos[0].to_geojson()
Routing¶
route¶
graph.route(
origin: tuple[float, float],
destination: tuple[float, float],
max_snap_m: float | None = 100.0,
) -> RouteResult
Find the fastest route between two coordinates using A*.
The network type (drive/walk/bike) is inherited from the SpatialGraph.
Coordinates snap to the nearest graph node. Pass max_snap_m to reject routes
whose origin or destination is too far from the road network.
Parameters
| Parameter | Type | Description |
|---|---|---|
origin |
tuple[float, float] |
(lat, lon) origin |
destination |
tuple[float, float] |
(lat, lon) destination |
max_snap_m |
float or None |
Maximum snap distance for both endpoints; defaults to 100.0, pass None to allow unlimited snapping |
Returns RouteResult with properties:
| Property | Type | Description |
|---|---|---|
distance_m |
float |
Total route distance in meters |
duration_s |
float |
Total travel time in seconds |
cumulative_times_s |
list[float] |
Elapsed travel time at each waypoint |
origin_snap |
SnapResult |
Snap diagnostics for the origin |
destination_snap |
SnapResult |
Snap diagnostics for the destination |
Example
route = graph.route((48.137144, 11.575399), (48.154560, 11.530840))
print(f"Distance: {route.distance_m:.0f} m")
print(f"Duration: {route.duration_s / 60:.1f} min")
print(f"Waypoints: {len(route.coordinates)}")
route_geojson = route.to_geojson()
Reachability¶
reachable¶
graph.reachable(
origin: tuple[float, float],
minutes: float,
max_snap_m: float | None = 100.0,
) -> ReachableGraph
Return a travel-time-labeled view of the graph reachable from an origin.
Inspection and GeoJSON export use the parent graph plus reachable-node labels,
so they do not copy the road network. Constrained routing and isochrones
materialize a bounded subgraph internally only when those methods are called.
Pass max_snap_m to reject origins that are too far from the graph.
Example
import json
reachable = graph.reachable((48.137144, 11.575399), minutes=15)
nodes = reachable.nodes()
node_layer = json.loads(reachable.nodes_geojson())
edge_layer = json.loads(reachable.edges_geojson())
network_layer = json.loads(reachable.to_geojson())
route = reachable.route((48.137144, 11.575399), (48.142, 11.58))
isos = reachable.isochrone((48.137144, 11.575399), minutes=[5, 10, 15])
ReachableGraph methods:
| Method | Returns | Description |
|---|---|---|
node_count() |
int |
Number of reachable nodes |
edge_count() |
int |
Number of reachable directed edges |
nearest_node(lat, lon) |
tuple or None |
Nearest node inside the reachable subgraph |
contains_node(node_id) |
bool |
Whether an OSM node id is reachable |
travel_time_to_node_id(node_id) |
float or None |
Travel time to an OSM node id |
nodes() |
list[dict] |
Reachable nodes with node_id, lat, lon, travel_time_s |
nodes_geojson() |
str |
Reachable nodes as GeoJSON points |
edges_geojson() |
str |
Edges whose source and target are both reachable |
to_geojson() |
str |
Reachable nodes and edges in one FeatureCollection |
route(origin, destination, max_snap_m=100.0) |
RouteResult |
Route constrained to the reachable subgraph |
isochrone(origin, minutes, max_snap_m=100.0) |
list[IsochroneResult] |
Isochrones constrained to the reachable subgraph |
Network-Time Prisms¶
prism¶
graph.prism(
origin: tuple[float, float],
destination: tuple[float, float],
max_minutes: float,
stop_minutes: float = 0.0,
buffer_minutes: float = 0.0,
max_snap_m: float | None = 100.0,
) -> PrismGraph
Return a graph view of possible stops between an origin and a destination within a fixed time window. A node is inside the prism when:
This is useful for "what can I do on the way?" analysis without turning the
library into a trip-planning or stop-order optimizer.
Pass max_snap_m to reject origins or destinations that are too far from the
graph.
Like ReachableGraph, PrismGraph is a lightweight view. It stores the parent
graph plus inbound, outbound, and slack labels; constrained routes or isochrones
materialize a bounded subgraph only when needed.
Example
prism = graph.prism(
origin=(48.137144, 11.575399),
destination=(48.154560, 11.530840),
max_minutes=45,
stop_minutes=10,
buffer_minutes=5,
)
nodes = prism.nodes()
network_layer = prism.to_geojson()
slack = prism.slack_polygon(min_slack_s=5 * 60)
route = prism.route((48.137144, 11.575399), (48.142, 11.56))
PrismGraph methods:
| Method | Returns | Description |
|---|---|---|
node_count() |
int |
Number of nodes inside the prism |
edge_count() |
int |
Number of directed edges inside the prism |
nearest_node(lat, lon) |
tuple or None |
Nearest node inside the prism graph |
contains_node(node_id) |
bool |
Whether an OSM node id is inside the prism |
slack_at_node_id(node_id) |
float or None |
Remaining slack for an OSM node id |
nodes() |
list[dict] |
Nodes with node_id, lat, lon, inbound_time_s, outbound_time_s, slack_s |
nodes_geojson() |
str |
Prism nodes as GeoJSON points |
edges_geojson() |
str |
Edges whose source and target are inside the prism |
to_geojson() |
str |
Prism nodes and edges in one FeatureCollection |
slack_polygon(min_slack_s) |
str or None |
Polygon enclosing nodes with at least the requested slack |
route(origin, destination, max_snap_m=100.0) |
RouteResult |
Route constrained to the prism graph |
isochrone(origin, minutes, max_snap_m=100.0) |
list[IsochroneResult] |
Isochrones constrained to the prism graph |
Points of interest¶
fetch_pois¶
Fetch OSM points of interest that fall within a given isochrone polygon.
Queries Overpass for the polygon's bounding box, using the XML cache when available, then filters returned nodes to those geometrically inside the polygon.
Parameters
| Parameter | Type | Description |
|---|---|---|
isochrone |
IsochroneResult or str |
An isochrone result or a GeoJSON geometry string |
Returns PoiCollection with structured Poi objects. Call .to_geojson()
for a GeoJSON FeatureCollection.
Example
isos = graph.isochrone((48.137144, 11.575399), minutes=[10])
pois = graph.fetch_pois(isos[0])
restaurants = [
poi.tags.get("name", "?")
for poi in pois.pois
if poi.tags.get("amenity") == "restaurant"
]
print(f"Found {len(restaurants)} restaurants within 10 minutes")
Visualisation¶
nodes_geojson¶
Return all graph nodes as a GeoJSON FeatureCollection of Point features.
Each feature has properties: id (OSM node id), lat, lon.
Useful for visualising the network in mapping tools.
Example
import folium, json
nodes = json.loads(graph.nodes_geojson())
m = folium.Map(location=[48.137, 11.575], zoom_start=13)
for feature in nodes["features"]:
lat = feature["properties"]["lat"]
lon = feature["properties"]["lon"]
folium.CircleMarker([lat, lon], radius=2).add_to(m)
edges_geojson¶
Return all graph edges as a GeoJSON FeatureCollection of LineString features.
Each feature has properties:
| Property | Type | Description |
|---|---|---|
highway |
str |
OSM highway tag value (e.g. "residential") |
length_m |
float |
Edge length in meters |
speed_kph |
float |
Assigned speed in km/h |
drive_time_s |
float |
Drive travel time in seconds |
walk_time_s |
float |
Walk travel time in seconds |
bike_time_s |
float |
Bike travel time in seconds |
Example