spa-l3-paris-emergency-routing
Map (reference ← swipe → agent)
0:00
Need the dispatch coverage model rebuilt before Thursday's briefing. The eight historical emergency-call locations are in `incidents.csv` (columns: incident_id, latitude, longitude, label). Pull the driveable road network and all hospitals from OpenStreetMap via Overpass for the area bbox 48.83,2.30 to 48.88,2.38. I need three deliverables: the shortest driving route from each incident to its closest hospital, a distance matrix of each incident against its three nearest hospitals, and a 15-minute drive-time isochrone around every hospital to visualise coverage gaps. Assume 30 km/h for edges without a maxspeed tag; use posted maxspeed otherwise.
Everything goes into a single GPKG called `emergency_routing.gpkg` with all layers in the official metric coordinate system for Paris and exactly these four layer names. The `incidents` layer has Point (or MultiPoint) geometry and an `incident_id` column. The `closest_hospital` layer has LineString (or MultiLineString) geometry representing the route, with columns `incident_id`, `hospital_name`, and `network_distance_m` (numeric, metres). One row per incident. The `distance_matrix` layer is tabular (geometry may be empty or null) with columns `incident_id`, `hospital_name`, `rank` (integer 1 through 3, ascending by `network_distance_m` within each incident), and `network_distance_m`. The `isochrones_15min` layer has MultiPolygon geometry (Polygon also accepted) with columns `hospital_name` and `travel_time_min`. One row per hospital.
0:00
System info
{
"adapter": "openrouter",
"model": "google/gemma-4-26b-a4b-it",
"base_url": "https://openrouter.ai/api/v1"
}
0:02
Bash
ls -F
incidents.csv
0:04
Read
incidents.csv
1 incident_id,latitude,longitude,label
2 INC_001,48.8566,2.3522,near Notre-Dame
3 INC_002,48.8620,2.3360,near Louvre
4 INC_003,48.8462,2.3464,Quartier Latin
5 INC_004,48.8700,2.3431,near Gare du Nord area
6 INC_005,48.8530,2.3325,Luxembourg Garden area
7 INC_006,48.8600,2.3700,Bastille area
8 INC_007,48.8450,2.3100,near Montparnasse
9 INC_008,48.8750,2.3600,near Republique
1:48
Write
solve.py
content · 132 lines · py
import osmnx as ox
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon
import networkx as nx
import numpy as np
# 1. Load Incidents
incidents_df = pd.read_csv('incidents.csv')
incidents_gdf = gpd.GeoDataFrame(
incidents_df,
geometry=gpd.points_from_xy(incidents_df.longitude, incidents_df.latitude),
crs="EPSG:4326"
)
# Target CRS: Paris metric (EPSG:2154 - Lambert-93)
target_crs = "EPSG:2154"
incidents_gdf = incidents_gdf.to_crs(target_crs)
# Bounding box for OSM (from prompt)
bbox = (48.83, 2.30, 48.88, 2.38) # minlat, minlon, maxlat, maxlon
# 2. Fetch OSM Data
print("Fetching road network and hospitals...")
# Fetch graph
G = ox.graph_from_bbox(bbox[0], bbox[2], bbox[1], bbox[3], network_type='drive')
# Fetch hospitals
# Overpass query for hospitals (amenity=hospital)
# Note: ox.geometries (deprecated) or ox.features_from_bbox (new)
hospitals = ox.features_from_bbox(bbox[0], bbox[2], bbox[1], bbox[3], tags={'amenity': 'hospital'})
# Pre-process graph for speed and speed assumptions
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
# Ensure all edges have a travel time based on 30km/h if missing
# (ox.add_edge_speeds handles maxspeed, but if still nan, we set to 30km/h)
for u, v, k, data in G.edges(data=True, keys=True):
if 'travel_time' not in data or np.isnan(data['travel_time']):
# 30 km/h = 30000 m / 3600 s = 8.333 m/s
# length in meters
length = data.get('length', 1.0)
data['travel_time'] = length / 8.333
# Prepare hospital list
# We need the nearest node for each hospital to use in network calculations
# and for distances.
hospitals = hospitals.to_crs(target_crs)
# We'll use the centroid of the hospital if it's a polygon, or the point itself
hospitals['geometry'] = hospitals.geometry.centroid
# 3. Analysis Functions
def get_nearest_node(G, point):
return ox.nearest_nodes(G, point.x, point.y)
# For distances, we will find the closest hospital to each incident and the top 3.
# First, let's find the nearest nodes for each hospital
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node(G, p))
# Distance Matrix & Closest Hospital
results_closest = []
results_matrix = []
for _, incident in incidents_gdf.iterrows():
inc_id = incident['incident_id']
inc_node = get_nearest_node(G, incident.geometry)
# Calculate distances to all hospitals via network
# To be efficient, we find shortest path distances from inc_node to all hospital nodes
# However, we only have a few hospitals, so it's fine to loop.
h_distances = []
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp.get('name', 'Unnamed Hospital')
try:
# Shortest path distance (length)
dist = nx.shortest_path_length(G, inc_node, h_node, weight='length')
# Shortest path time
time = nx.shortest_path_length(G, inc_node, h_node, weight='travel_time')
# Path geometry for the closest one
path_nodes = nx.shortest_path(G, inc_node, h_node, weight='length')
path_geom = ox.utils_graph.get_route_edge_attributes(G, path_nodes, 'geometry')
# This is complex, simpler way to get geometry:
# nx.shortest_path returns nodes, we need to collect edges.
h_distances.append({
'hospital_name': h_name,
'network_distance_m': dist,
'travel_time_s': time,
'path_nodes': path_nodes
})
except nx.NetworkXNoPath:
continue
# Sort distances for this incident
h_distances.sort(key=lambda x: x['network_distance_m'])
# Closest hospital (rank 1)
if h_distances:
best = h_distances[0]
# Create LineString for the route
# We'll use ox.routing.route_to_gdf or manual reconstruction
# Manually:
edges = []
for i in range(len(best['path_nodes']) - 1):
u, v = best['path_nodes'][i], best['path_nodes'][i+1]
# Handle multi-edges (simplification: take first)
edge_data = list(G.get_edge_data(u, v).values())[0]
if 'geometry' in edge_data:
edges.append(edge_data['geometry'])
else:
# If no geometry (straight line)
edges.append(LineString([G.nodes[u]['x'], G.nodes[u]['y'], G.nodes[v]['x'], G.nodes[v]['y']])) # This is wrong, LineString needs 2 points
# Correct fallback:
p1 = Point(G.nodes[u]['x'], G.nodes[u]['y'])
p2 = Point(G.nodes[v]['x'], G.nodes[v]['y'])
edges.append(LineString([p1, p2]))
# Combine edges into a single LineString or MultiLineString
# Since we are in target_crs, the x,y from G.nodes are in target_crs?
# No, ox.graph_from_bbox uses lat/lon. We need to ensure graph is in target_crs.
# Wait, I should project the graph first.
pass
print("Wait, I need to re-structure the script to project the graph properly.")
Wrote solve.py (5231 chars).
2:22
Write
solve.py
content · 248 lines · py
import osmnx as ox
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon
import networkx as nx
import numpy as np
# 1. Load Incidents
incidents_df = pd.read_csv('incidents.csv')
incidents_gdf = gpd.GeoDataFrame(
incidents_df,
geometry=gpd.points_from_xy(incidents_df.longitude, incidents_df.latitude),
crs="EPSG:4326"
)
# Target CRS: Paris metric (EPSG:2154 - Lambert-93)
target_crs = "EPSG:2154"
incidents_gdf = incidents_gdf.to_crs(target_crs)
# Bounding box for OSM (from prompt) - note: lat/lon
bbox = (48.83, 2.30, 48.88, 2.38) # minlat, minlon, maxlat, maxlon
# 2. Fetch OSM Data
print("Fetching road network and hospitals...")
G = ox.graph_from_bbox(bbox[0], bbox[2], bbox[1], bbox[3], network_type='drive')
# Fetch hospitals
hospitals = ox.features_from_bbox(bbox[0], bbox[2], bbox[1], bbox[3], tags={'amenity': 'hospital'})
# Pre-process graph
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
# Apply default 30 km/h if speed/travel_time is missing
# 30 km/h = 8.333 m/s
for u, v, k, data in G.edges(data=True, keys=True):
if 'travel_time' not in data or np.isnan(data['travel_time']):
length = data.get('length', 1.0)
data['travel_time'] = length / 8.333333333333333
# Project the graph to target CRS
# This makes all node geometries and edge geometries metric
G_proj = ox.project_graph(G, to_crs=target_crs)
# Prepare hospital list
hospitals = hospitals.to_crs(target_crs)
# Use centroid for hospitals
hospitals['geometry'] = hospitals.geometry.centroid
# Ensure hospital names are unique and clean
hospitals['hospital_name'] = hospitals['name'].fillna('Unnamed Hospital')
# If duplicates exist in name, append index
hospitals['hospital_name'] = hospitals['hospital_name'].astype(str) + "_" + hospitals.index.astype(str)
# For distance, we need to find nearest nodes in projected graph
# Get node coords in target CRS
nodes_proj = ox.graph_to_gdfs(G_proj, nodes=True, edges=False)
def get_nearest_node_proj(graph_nodes, point):
# Using the nodes dataframe to find nearest node
# A simple approach with scipy or ox.nearest_nodes
# Since graph_nodes is a GeoDataFrame, we can use it
return ox.nearest_nodes(G_proj, point.x, point.y)
# 3. Analysis
print("Calculating routes and distances...")
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
closest_hospital_rows = []
distance_matrix_rows = []
for _, incident in incidents_gdf.iterrows():
inc_id = incident['incident_id']
inc_node = get_nearest_node_proj(nodes_proj, incident.geometry)
h_stats = []
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
try:
# Network distance (length)
dist = nx.shortest_path_length(G_proj, inc_node, h_node, weight='length')
# Network time (seconds)
time = nx.shortest_path_length(G_proj, inc_node, h_node, weight='travel_time')
# Path for geometry
path = nx.shortest_path(G_proj, inc_node, h_node, weight='length')
h_stats.append({
'hospital_name': h_name,
'network_distance_m': dist,
'travel_time_s': time,
'path': path
})
except nx.NetworkXNoPath:
continue
# Sort by distance
h_stats.sort(key=lambda x: x['network_distance_m'])
# 1. Distance Matrix (up to 3)
for rank, stat in enumerate(h_stats[:3], 1):
distance_matrix_rows.append({
'incident_id': inc_id,
'hospital_name': stat['hospital_name'],
'rank': rank,
'network_distance_m': stat['network_distance_m']
})
# 2. Closest Hospital
if h_stats:
best = h_stats[0]
# Create route geometry
path_nodes = best['path']
# Build route from edges
route_edges = []
for i in range(len(path_nodes) - 1):
u, v = path_nodes[i], path_nodes[i+1]
# Get edge geometry
edge_data = G_proj.get_edge_data(u, v)
if edge_data:
# Pick the first edge if there are multiple (usually 1)
# edge_data is a dict of dicts (k)
k = list(edge_data.keys())[0]
if 'geometry' in edge_data[k]:
route_edges.append(edge_data[k]['geometry'])
else:
# Fallback to straight line
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
route_edges.append(LineString([p1, p2]))
if route_edges:
from shapely.ops import linemerge
merged_route = linemerge(route_edges)
closest_hospital_rows.append({
'incident_id': inc_id,
'hospital_name': best['hospital_name'],
'network_distance_m': best['network_distance_m'],
'geometry': merged_route
})
# 4. Isochrones (15 min)
print("Calculating 15-min isochrones...")
isochrones_rows = []
# 15 minutes = 900 seconds
limit_s = 15 * 60
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
# Get all nodes reachable within limit_s
subgraph = nx.ego_graph(G_proj, h_node, radius=limit_s, distance='travel_time')
if len(subgraph.nodes) > 0:
# Get all edges in the subgraph
sub_edges = subgraph.edges(data=True)
edge_geoms = []
for u, v, data in sub_edges:
if 'geometry' in data:
edge_geoms.append(data['geometry'])
else:
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
edge_geoms.append(LineString([p1, p2]))
if edge_geoms:
from shapely.ops import linemerge, unary_union
merged_lines = linemerge(edge_geoms)
# To create a polygon, we need to buffer the lines.
# A standard way to do isochrones is to buffer the edges,
# but that's not quite a "coverage area".
# However, for a "travel-time isochrone" in a network context,
# a common approach is the union of buffers of the reachable edges.
# Let's buffer edges by something reasonable or just find the boundary.
# Wait, a more accurate way is the concave hull or alpha shape of the nodes.
# But buffering edges is a good approximation for "area reached".
# Let's buffer edges by a small amount to ensure they connect, then unary_union.
# Actually, let's buffer edges by say 20m.
buffered_edges = unary_union([geom.buffer(20) for geom in edge_geoms])
# For a cleaner isochrone, we can take the convex hull or a concave hull.
# But the prompt asks for a "15-minute drive-time isochrone".
# Let's use the union of buffered edges.
isochrones_rows.append({
'hospital_name': h_name,
'travel_time_min': 15.0,
'geometry': buffered_edges
})
else:
# No edges? Just buffer the node
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({
'hospital_name': h_name,
'travel_time_min': 15.0,
'geometry': p.buffer(50) # Small default
})
else:
# No nodes reachable?
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({
'hospital_name': h_name,
'travel_time_min': 15.0,
'geometry': p.buffer(50)
})
# 5. Prepare Outputs
print("Saving outputs to GPKG...")
# incidents layer
closest_hospital_gdf = gpd.GeoDataFrame(closest_hospital_rows, crs=target_crs)
# Note: closest_hospital_gdf might have MultiLineString or LineString
# If merged_route is a LineString, it's fine.
# distance_matrix layer
dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, crs=target_crs)
# The prompt says: distance_matrix layer is tabular (geometry may be empty or null)
# So we set geometry to None.
dist_matrix_gdf.set_geometry(None, inplace=True)
# isochrones layer
isochrones_gdf = gpd.GeoDataFrame(isochrones_rows, crs=target_crs)
# Ensure MultiPolygon/Polygon
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].apply(lambda x: x if x.geom_type in ['Polygon', 'MultiPolygon'] else x.convex_hull)
# Final save
with pd.io.formats.format.DataFrameFormatter.precision_context(6):
with gpd.GeoDataFrame(incidents_gdf, crs=target_crs).to_crs(target_crs).explode(index_parts=False) as inc_exploded:
# Actually the prompt says incidents layer has Point (or MultiPoint)
# We'll just use incidents_gdf directly.
pass
# Let's do a more robust way to ensure geometry type for isochrones
# Some union results might be weird.
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].buffer(0) # Clean up
# Create the GPKG
# Layers: incidents, closest_hospital, distance_matrix, isochrones_15min
incidents_gdf.to_file('emergency_routing.gpkg', layer='incidents', driver='GPKG')
closest_hospital_gdf.to_file('emergency_routing.gpkg', layer='closest_hospital', driver='GPKG')
dist_matrix_gdf.to_file('emergency_routing.gpkg', layer='distance_matrix', driver='GPKG')
isochrones_gdf.to_file('emergency_routing.gpkg', layer='isochrones_15min', driver='GPKG')
print("Done!")
Wrote solve.py (9826 chars).
2:24
Bash
python solve.py
Fetching road network and hospitals...
Traceback (most recent call last):
File "/work/solve.py", line 25, in <module>
G = ox.graph_from_bbox(bbox[0], bbox[2], bbox[1], bbox[3], network_type='drive')
TypeError: graph_from_bbox() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given
2:33
Edit
solve.py
Edited solve.py.
2:41
Edit
solve.py
Edited solve.py.
2:42
Bash
python solve.py
Fetching road network and hospitals...
Traceback (most recent call last):
File "/work/solve.py", line 25, in <module>
G = ox.graph_from_bbox(north=bbox[0], south=bbox[2], east=bbox[3], west=bbox[1], network_type='drive')
TypeError: graph_from_bbox() got an unexpected keyword argument 'north'
2:46
Read
solve.py
1 import osmnx as ox
2 import pandas as pd
3 import geopandas as gpd
4 from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon
5 import networkx as nx
6 import numpy as np
7
8 # 1. Load Incidents
9 incidents_df = pd.read_csv('incidents.csv')
10 incidents_gdf = gpd.GeoDataFrame(
11 incidents_df,
12 geometry=gpd.points_from_xy(incidents_df.longitude, incidents_df.latitude),
13 crs="EPSG:4326"
14 )
15
16 # Target CRS: Paris metric (EPSG:2154 - Lambert-93)
17 target_crs = "EPSG:2154"
18 incidents_gdf = incidents_gdf.to_crs(target_crs)
19
20 # Bounding box for OSM (from prompt) - note: lat/lon
21 bbox = (48.83, 2.30, 48.88, 2.38) # minlat, minlon, maxlat, maxlon
22
23 # 2. Fetch OSM Data
24 print("Fetching road network and hospitals...")
25 G = ox.graph_from_bbox(north=bbox[0], south=bbox[2], east=bbox[3], west=bbox[1], network_type='drive')
26
27 # Fetch hospitals
28 hospitals = ox.features_from_bbox(north=bbox[0], south=bbox[2], east=bbox[3], west=bbox[1], tags={'amenity': 'hospital'})
29
30 # Pre-process graph
31 G = ox.add_edge_speeds(G)
32 G = ox.add_edge_travel_times(G)
33
34 # Apply default 30 km/h if speed/travel_time is missing
35 # 30 km/h = 8.333 m/s
36 for u, v, k, data in G.edges(data=True, keys=True):
37 if 'travel_time' not in data or np.isnan(data['travel_time']):
38 length = data.get('length', 1.0)
39 data['travel_time'] = length / 8.333333333333333
40
41 # Project the graph to target CRS
42 # This makes all node geometries and edge geometries metric
43 G_proj = ox.project_graph(G, to_crs=target_crs)
44
45 # Prepare hospital list
46 hospitals = hospitals.to_crs(target_crs)
47 # Use centroid for hospitals
48 hospitals['geometry'] = hospitals.geometry.centroid
49 # Ensure hospital names are unique and clean
50 hospitals['hospital_name'] = hospitals['name'].fillna('Unnamed Hospital')
51 # If duplicates exist in name, append index
52 hospitals['hospital_name'] = hospitals['hospital_name'].astype(str) + "_" + hospitals.index.astype(str)
53
54 # For distance, we need to find nearest nodes in projected graph
55 # Get node coords in target CRS
56 nodes_proj = ox.graph_to_gdfs(G_proj, nodes=True, edges=False)
57
58 def get_nearest_node_proj(graph_nodes, point):
59 # Using the nodes dataframe to find nearest node
60 # A simple approach with scipy or ox.nearest_nodes
61 # Since graph_nodes is a GeoDataFrame, we can use it
62 return ox.nearest_nodes(G_proj, point.x, point.y)
63
64 # 3. Analysis
65 print("Calculating routes and distances...")
66
67 hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
68
69 closest_hospital_rows = []
70 distance_matrix_rows = []
71
72 for _, incident in incidents_gdf.iterrows():
73 inc_id = incident['incident_id']
74 inc_node = get_nearest_node_proj(nodes_proj, incident.geometry)
75
76 h_stats = []
77
78 for _, hosp in hospitals.iterrows():
79 h_node = hosp['nearest_node']
80 h_name = hosp['hospital_name']
81
82 try:
83 # Network distance (length)
84 dist = nx.shortest_path_length(G_proj, inc_node, h_node, weight='length')
85 # Network time (seconds)
86 time = nx.shortest_path_length(G_proj, inc_node, h_node, weight='travel_time')
87 # Path for geometry
88 path = nx.shortest_path(G_proj, inc_node, h_node, weight='length')
89
90 h_stats.append({
91 'hospital_name': h_name,
92 'network_distance_m': dist,
93 'travel_time_s': time,
94 'path': path
95 })
96 except nx.NetworkXNoPath:
97 continue
98
99 # Sort by distance
100 h_stats.sort(key=lambda x: x['network_distance_m'])
101
102 # 1. Distance Matrix (up to 3)
103 for rank, stat in enumerate(h_stats[:3], 1):
104 distance_matrix_rows.append({
105 'incident_id': inc_id,
106 'hospital_name': stat['hospital_name'],
107 'rank': rank,
108 'network_distance_m': stat['network_distance_m']
109 })
110
111 # 2. Closest Hospital
112 if h_stats:
113 best = h_stats[0]
114 # Create route geometry
115 path_nodes = best['path']
116
117 # Build route from edges
118 route_edges = []
119 for i in range(len(path_nodes) - 1):
120 u, v = path_nodes[i], path_nodes[i+1]
121 # Get edge geometry
122 edge_data = G_proj.get_edge_data(u, v)
123 if edge_data:
124 # Pick the first edge if there are multiple (usually 1)
125 # edge_data is a dict of dicts (k)
126 k = list(edge_data.keys())[0]
127 if 'geometry' in edge_data[k]:
128 route_edges.append(edge_data[k]['geometry'])
129 else:
130 # Fallback to straight line
131 p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
132 p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
133 route_edges.append(LineString([p1, p2]))
134
135 if route_edges:
136 from shapely.ops import linemerge
137 merged_route = linemerge(route_edges)
138 closest_hospital_rows.append({
139 'incident_id': inc_id,
140 'hospital_name': best['hospital_name'],
141 'network_distance_m': best['network_distance_m'],
142 'geometry': merged_route
143 })
144
145 # 4. Isochrones (15 min)
146 print("Calculating 15-min isochrones...")
147 isochrones_rows = []
148 # 15 minutes = 900 seconds
149 limit_s = 15 * 60
150
151 for _, hosp in hospitals.iterrows():
152 h_node = hosp['nearest_node']
153 h_name = hosp['hospital_name']
154
155 # Get all nodes reachable within limit_s
156 subgraph = nx.ego_graph(G_proj, h_node, radius=limit_s, distance='travel_time')
157
158 if len(subgraph.nodes) > 0:
159 # Get all edges in the subgraph
160 sub_edges = subgraph.edges(data=True)
161 edge_geoms = []
162 for u, v, data in sub_edges:
163 if 'geometry' in data:
164 edge_geoms.append(data['geometry'])
165 else:
166 p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
167 p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
168 edge_geoms.append(LineString([p1, p2]))
169
170 if edge_geoms:
171 from shapely.ops import linemerge, unary_union
172 merged_lines = linemerge(edge_geoms)
173 # To create a polygon, we need to buffer the lines.
174 # A standard way to do isochrones is to buffer the edges,
175 # but that's not quite a "coverage area".
176 # However, for a "travel-time isochrone" in a network context,
177 # a common approach is the union of buffers of the reachable edges.
178 # Let's buffer edges by something reasonable or just find the boundary.
179 # Wait, a more accurate way is the concave hull or alpha shape of the nodes.
180 # But buffering edges is a good approximation for "area reached".
181 # Let's buffer edges by a small amount to ensure they connect, then unary_union.
182 # Actually, let's buffer edges by say 20m.
183 buffered_edges = unary_union([geom.buffer(20) for geom in edge_geoms])
184
185 # For a cleaner isochrone, we can take the convex hull or a concave hull.
186 # But the prompt asks for a "15-minute drive-time isochrone".
187 # Let's use the union of buffered edges.
188 isochrones_rows.append({
189 'hospital_name': h_name,
190 'travel_time_min': 15.0,
191 'geometry': buffered_edges
192 })
193 else:
194 # No edges? Just buffer the node
195 p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
196 isochrones_rows.append({
197 'hospital_name': h_name,
198 'travel_time_min': 15.0,
199 'geometry': p.buffer(50) # Small default
200 })
201 else:
202 # No nodes reachable?
203 p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
204 isochrones_rows.append({
205 'hospital_name': h_name,
206 'travel_time_min': 15.0,
207 'geometry': p.buffer(50)
208 })
209
210 # 5. Prepare Outputs
211 print("Saving outputs to GPKG...")
212
213 # incidents layer
214 closest_hospital_gdf = gpd.GeoDataFrame(closest_hospital_rows, crs=target_crs)
215 # Note: closest_hospital_gdf might have MultiLineString or LineString
216 # If merged_route is a LineString, it's fine.
217
218 # distance_matrix layer
219 dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, crs=target_crs)
220 # The prompt says: distance_matrix layer is tabular (geometry may be empty or null)
221 # So we set geometry to None.
222 dist_matrix_gdf.set_geometry(None, inplace=True)
223
224 # isochrones layer
225 isochrones_gdf = gpd.GeoDataFrame(isochrones_rows, crs=target_crs)
226 # Ensure MultiPolygon/Polygon
227 isochrones_gdf['geometry'] = isochrones_gdf['geometry'].apply(lambda x: x if x.geom_type in ['Polygon', 'MultiPolygon'] else x.convex_hull)
228
229 # Final save
230 with pd.io.formats.format.DataFrameFormatter.precision_context(6):
231 with gpd.GeoDataFrame(incidents_gdf, crs=target_crs).to_crs(target_crs).explode(index_parts=False) as inc_exploded:
232 # Actually the prompt says incidents layer has Point (or MultiPoint)
233 # We'll just use incidents_gdf directly.
234 pass
235
236 # Let's do a more robust way to ensure geometry type for isochrones
237 # Some union results might be weird.
238 isochrones_gdf['geometry'] = isochrones_gdf['geometry'].buffer(0) # Clean up
239
240 # Create the GPKG
241 # Layers: incidents, closest_hospital, distance_matrix, isochrones_15min
242 incidents_gdf.to_file('emergency_routing.gpkg', layer='incidents', driver='GPKG')
243 closest_hospital_gdf.to_file('emergency_routing.gpkg', layer='closest_hospital', driver='GPKG')
244 dist_matrix_gdf.to_file('emergency_routing.gpkg', layer='distance_matrix', driver='GPKG')
245 isochrones_gdf.to_file('emergency_routing.gpkg', layer='isochrones_15min', driver='GPKG')
246
247 print("Done!")
2:48
Bash
python solve.py
Fetching road network and hospitals...
Traceback (most recent call last):
File "/work/solve.py", line 25, in <module>
G = ox.graph_from_bbox(north=bbox[0], south=bbox[2], east=bbox[3], west=bbox[1], network_type='drive')
TypeError: graph_from_bbox() got an unexpected keyword argument 'north'
2:53
Bash
python -c "import osmnx; print(help(osmnx.graph_from_bbox))"
Help on function graph_from_bbox in module osmnx.graph:
graph_from_bbox(
bbox: tuple[float, float, float, float],
*,
network_type: str = 'all',
simplify: bool = True,
retain_all: bool = False,
truncate_by_edge: bool = False,
custom_filter: str | list[str] | None = None
) -> nx.MultiDiGraph
Download and create a graph within a lat-lon bounding box.
This function uses filters to query the Overpass API: you can either
specify a pre-defined `network_type` or provide your own `custom_filter`
with Overpass QL.
Use the `settings` module's `useful_tags_node` and `useful_tags_way`
settings to configure which OSM node/way tags are added as graph node/edge
attributes. If you want a fully bidirectional network, ensure your
`network_type` is in `settings.bidirectional_network_types` before
creating your graph. You can also use the `settings` module to retrieve a
snapshot of historical OSM data as of a certain date, or to configure the
Overpass server timeout, memory allocation, and other customizations.
Parameters
----------
bbox
Bounding box as `(left, bottom, right, top)`. Coordinates should be in
unprojected latitude-longitude degrees (EPSG:4326).
network_type
{"all", "all_public", "bike", "drive", "drive_service", "walk"}
What type of street network to retrieve if `custom_filter` is None.
simplify
If True, simplify graph topology via the `simplify_graph` function.
retain_all
If True, return the entire graph even if it is not connected. If
False, retain only the largest weakly connected component.
truncate_by_edge
If True, retain nodes the outside bounding box if at least one of
the node's neighbors lies within the bounding box.
custom_filter
A custom ways filter to be used instead of the `network_type` presets,
e.g. `'["power"~"line"]' or '["highway"~"motorway|trunk"]'`. If `str`,
the intersection of keys/values will be used, e.g., `'[maxspeed=50][lanes=2]'`
will return all ways having both maxspeed of 50 and two lanes. If
`list`, the union of the `list` items will be used, e.g.,
`['[maxspeed=50]', '[lanes=2]']` will return all ways having either
maximum speed of 50 or two lanes. Also pass in a `network_type` that
is in `settings.bidirectional_network_types` if you want the graph to
be fully bidirectional.
Returns
-------
G
The resulting MultiDiGraph.
Notes
-----
Very large query areas use the `utils_geo._consolidate_subdivide_geometry`
function to automatically make multiple requests: see that function's
documentation for caveats.
None
2:59
Edit
solve.py
Edited solve.py.
3:02
Edit
solve.py
Edited solve.py.
3:03
Bash
python -c "import osmnx; print(help(osmnx.features_from_bbox))"
Help on function features_from_bbox in module osmnx.features:
features_from_bbox(
bbox: tuple[float, float, float, float],
tags: dict[str, bool | str | list[str]]
) -> gpd.GeoDataFrame
Download OSM features within a lat-lon bounding box.
You can use the `settings` module to retrieve a snapshot of historical OSM
data as of a certain date, or to configure the Overpass server timeout,
memory allocation, and other custom settings. This function searches for
features using tags. For more details, see:
https://wiki.openstreetmap.org/wiki/Map_features
Parameters
----------
bbox
Bounding box as `(left, bottom, right, top)`. Coordinates should be in
unprojected latitude-longitude degrees (EPSG:4326).
tags
Tags for finding elements in the selected area. Results are the union,
not intersection of the tags and each result matches at least one tag.
The keys are OSM tags (e.g. `building`, `landuse`, `highway`, etc) and
the values can be either `True` to retrieve all elements matching the
tag, or a string to retrieve a single `tag:value` combination, or a
list of strings to retrieve multiple values for the tag. For example,
`tags = {'building': True}` would return all buildings in the area.
Or, `tags = {'amenity':True, 'landuse':['retail','commercial'],
'highway':'bus_stop'}` would return all amenities, any landuse=retail,
any landuse=commercial, and any highway=bus_stop.
Returns
-------
gdf
The features, multi-indexed by element type and OSM ID.
None
3:07
Edit
solve.py
Edited solve.py.
3:09
Bash
python solve.py
<exit_code>-1</exit_code> <error>Command timed out after 120000ms</error>
5:33
Write
solve.py
content · 157 lines · py
import osmnx as ox
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon
import networkx as nx
import numpy as np
from shapely.ops import linemerge, unary_union
# 1. Load Incidents
incidents_df = pd.read_csv('incidents.csv')
incidents_gdf = gpd.GeoDataFrame(
incidents_df,
geometry=gpd.points_from_xy(incidents_df.longitude, incidents_df.latitude),
crs="EPSG:4326"
)
# Target CRS: Paris metric (EPSG:2154 - Lambert-93)
target_crs = "EPSG:2154"
incidents_gdf = incidents_gdf.to_crs(target_crs)
# Bounding box for OSM (left, bottom, right, top)
bbox = (2.30, 48.83, 2.38, 48.88)
# 2. Fetch OSM Data
print("Fetching road network and hospitals...")
G = ox.graph_from_bbox(bbox, network_type='drive')
hospitals = ox.features_from_bbox(bbox, tags={'amenity': 'hospital'})
# Pre-process graph
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
# Apply default 30 km/h if speed/travel_time is missing
for u, v, k, data in G.edges(data=True, keys=True):
if 'travel_time' not in data or np.isnan(data['travel_time']):
length = data.get('length', 1.0)
data['travel_time'] = length / 8.333333333333333
# Project the graph to target CRS
G_proj = ox.project_graph(G, to_crs=target_crs)
# Prepare hospital list
hospitals = hospitals.to_crs(target_crs)
hospitals['geometry'] = hospitals.geometry.centroid
hospitals['hospital_name'] = hospitals['name'].fillna('Unnamed Hospital').astype(str) + "_" + hospitals.index.astype(str)
# Get node coords in target CRS
nodes_proj = ox.graph_to_gdfs(G_proj, nodes=True, edges=False)
def get_nearest_node_proj(graph_nodes, point):
return ox.nearest_nodes(G_proj, point.x, point.y)
# 3. Analysis
print("Calculating routes and distances...")
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
closest_hospital_rows = []
distance_matrix_rows = []
for _, incident in incidents_gdf.iterrows():
inc_id = incident['incident_id']
inc_node = get_nearest_node_proj(nodes_proj, incident.geometry)
h_stats = []
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
try:
dist = nx.shortest_path_length(G_proj, inc_node, h_node, weight='length')
time = nx.shortest_path_length(G_proj, inc_node, h_node, weight='travel_time')
path = nx.shortest_path(G_proj, inc_node, h_node, weight='length')
h_stats.append({'hospital_name': h_name, 'network_distance_m': dist, 'travel_time_s': time, 'path': path})
except nx.NetworkXNoPath:
continue
h_stats.sort(key=lambda x: x['network_distance_m'])
# 1. Distance Matrix (up to 3)
for rank, stat in enumerate(h_stats[:3], 1):
distance_matrix_rows.append({
'incident_id': inc_id,
'hospital_name': stat['hospital_name'],
'rank': rank,
'network_distance_m': stat['network_distance_m']
})
# 2. Closest Hospital
if h_stats:
best = h_stats[0]
path_nodes = best['path']
route_edges = []
for i in range(len(path_nodes) - 1):
u, v = path_nodes[i], path_nodes[i+1]
edge_data = G_proj.get_edge_data(u, v)
if edge_data:
k = list(edge_data.keys())[0]
if 'geometry' in edge_data[k]:
route_edges.append(edge_data[k]['geometry'])
else:
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
route_edges.append(LineString([p1, p2]))
if route_edges:
merged_route = linemerge(route_edges)
closest_hospital_rows.append({
'incident_id': inc_id,
'hospital_name': best['hospital_name'],
'network_distance_m': best['network_distance_m'],
'geometry': merged_route
})
# 4. Isochrones (15 min)
print("Calculating 15-min isochrones...")
isochrones_rows = []
limit_s = 15 * 60
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
subgraph = nx.ego_graph(G_proj, h_node, radius=limit_s, distance='travel_time')
if len(subgraph.nodes) > 0:
sub_edges = subgraph.edges(data=True)
edge_geoms = []
for u, v, data in sub_edges:
if 'geometry' in data:
edge_geoms.append(data['geometry'])
else:
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
edge_geoms.append(LineString([p1, p2]))
if edge_geoms:
buffered_edges = unary_union([geom.buffer(20) for geom in edge_geoms])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': buffered_edges})
else:
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': p.buffer(50)})
else:
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': p.buffer(50)})
# 5. Prepare Outputs
print("Saving outputs to GPKG...")
closest_hospital_gdf = gpd.GeoDataFrame(closest_hospital_rows, crs=target_crs)
dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, crs=target_crs)
dist_matrix_gdf.set_geometry(None, inplace=True)
isochrones_gdf = gpd.GeoDataFrame(isochrones_rows, crs=target_crs)
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].apply(lambda x: x if x.geom_type in ['Polygon', 'MultiPolygon'] else x.convex_hull)
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].buffer(0)
incidents_gdf.to_file('emergency_routing.gpkg', layer='incidents', driver='GPKG')
closest_hospital_gdf.to_file('emergency_routing.gpkg', layer='closest_hospital', driver='GPKG')
dist_matrix_gdf.to_file('emergency_routing.gpkg', layer='distance_matrix', driver='GPKG')
isochrones_gdf.to_file('emergency_routing.gpkg', layer='isochrones_15min', driver='GPKG')
print("Done!")
Wrote solve.py (6364 chars).
5:35
Bash
python solve.py
<exit_code>-1</exit_code> <error>Command timed out after 120000ms</error>
7:42
Bash
timeout 10s python -c "import osmnx as ox; G = ox.graph_from_bbox(2.30, 48.83, 2...
Traceback (most recent call last):
File "<string>", line 1, in <module>
import osmnx as ox; G = ox.graph_from_bbox(2.30, 48.83, 2.38, 48.88, network_type='drive'); print(len(G))
~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: graph_from_bbox() takes 1 positional argument but 4 positional arguments (and 1 keyword-only argument) were given
7:48
Bash
timeout 10s python -c "import osmnx as ox; G = ox.graph_from_bbox(bbox=(2.30, 48...
3459
7:57
Bash
timeout 10s python -c "import osmnx as ox; G = ox.graph_from_bbox(bbox=(2.30, 48...
Success
14:07
Write
solve.py
content · 158 lines · py
import osmnx as ox
import pandas as pd
import geopandas as gpd
from shapely.geometry import Point, LineString, MultiLineString, Polygon, MultiPolygon
import networkx as nx
import numpy as np
from shapely.ops import linemerge, unary_union
# 1. Load Incidents
incidents_df = pd.read_csv('incidents.csv')
incidents_gdf = gpd.GeoDataFrame(
incidents_df,
geometry=gpd.points_from_xy(incidents_df.longitude, incidents_df.latitude),
crs="EPSG:4326"
)
# Target CRS: Paris metric (EPSG:2154 - Lambert-93)
target_crs = "EPSG:2154"
incidents_gdf = incidents_gdf.to_crs(target_crs)
# Bounding box for OSM (left, bottom, right, top)
bbox = (2.30, 48.83, 2.38, 48.88)
# 2. Fetch OSM Data
print("Fetching road network and hospitals...")
G = ox.graph_from_bbox(bbox=bbox, network_type='drive')
hospitals = ox.features_from_bbox(bbox=bbox, tags={'amenity': 'hospital'})
# Pre-process graph
G = ox.add_edge_speeds(G)
G = ox.add_edge_travel_times(G)
# Apply default 30 km/h if speed/travel_time is missing
for u, v, k, data in G.edges(data=True, keys=True):
if 'travel_time' not in data or np.isnan(data['travel_time']):
length = data.get('length', 1.0)
data['travel_time'] = length / 8.333333333333333
# Project the graph to target CRS
G_proj = ox.project_graph(G, to_crs=target_crs)
# Prepare hospital list
hospitals = hospitals.to_crs(target_crs)
hospitals['geometry'] = hospitals.geometry.centroid
# Ensure hospital names are unique and clean
hospitals['hospital_name'] = hospitals['name'].fillna('Unnamed Hospital').astype(str) + "_" + hospitals.index.astype(str)
# Get node coords in target CRS
nodes_proj = ox.graph_to_gdfs(G_proj, nodes=True, edges=False)
def get_nearest_node_proj(graph_nodes, point):
return ox.nearest_nodes(G_proj, point.x, point.y)
# 3. Analysis
print("Calculating routes and distances...")
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
closest_hospital_rows = []
distance_matrix_rows = []
for _, incident in incidents_gdf.iterrows():
inc_id = incident['incident_id']
inc_node = get_nearest_node_proj(nodes_proj, incident.geometry)
h_stats = []
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
try:
dist = nx.shortest_path_length(G_proj, inc_node, h_node, weight='length')
time = nx.shortest_path_length(G_proj, inc_node, h_node, weight='travel_time')
path = nx.shortest_path(G_proj, inc_node, h_node, weight='length')
h_stats.append({'hospital_name': h_name, 'network_distance_m': dist, 'travel_time_s': time, 'path': path})
except nx.NetworkXNoPath:
continue
h_stats.sort(key=lambda x: x['network_distance_m'])
# 1. Distance Matrix (up to 3)
for rank, stat in enumerate(h_stats[:3], 1):
distance_matrix_rows.append({
'incident_id': inc_id,
'hospital_name': stat['hospital_name'],
'rank': rank,
'network_distance_m': stat['network_distance_m']
})
# 2. Closest Hospital
if h_stats:
best = h_stats[0]
path_nodes = best['path']
route_edges = []
for i in range(len(path_nodes) - 1):
u, v = path_nodes[i], path_nodes[i+1]
edge_data = G_proj.get_edge_data(u, v)
if edge_data:
k = list(edge_data.keys())[0]
if 'geometry' in edge_data[k]:
route_edges.append(edge_data[k]['geometry'])
else:
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
route_edges.append(LineString([p1, p2]))
if route_edges:
merged_route = linemerge(route_edges)
closest_hospital_rows.append({
'incident_id': inc_id,
'hospital_name': best['hospital_name'],
'network_distance_m': best['network_distance_m'],
'geometry': merged_route
})
# 4. Isochrones (15 min)
print("Calculating 15-min isochrones...")
isochrones_rows = []
limit_s = 15 * 60
for _, hosp in hospitals.iterrows():
h_node = hosp['nearest_node']
h_name = hosp['hospital_name']
subgraph = nx.ego_graph(G_proj, h_node, radius=limit_s, distance='travel_time')
if len(subgraph.nodes) > 0:
sub_edges = subgraph.edges(data=True)
edge_geoms = []
for u, v, data in sub_edges:
if 'geometry' in data:
edge_geoms.append(data['geometry'])
else:
p1 = Point(G_proj.nodes[u]['x'], G_proj.nodes[u]['y'])
p2 = Point(G_proj.nodes[v]['x'], G_proj.nodes[v]['y'])
edge_geoms.append(LineString([p1, p2]))
if edge_geoms:
buffered_edges = unary_union([geom.buffer(20) for geom in edge_geoms])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': buffered_edges})
else:
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': p.buffer(50)})
else:
p = Point(G_proj.nodes[h_node]['x'], G_proj.nodes[h_node]['y'])
isochrones_rows.append({'hospital_name': h_name, 'travel_time_min': 15.0, 'geometry': p.buffer(50)})
# 5. Prepare Outputs
print("Saving outputs to GPKG...")
closest_hospital_gdf = gpd.GeoDataFrame(closest_hospital_rows, crs=target_crs)
dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, crs=target_crs)
dist_matrix_gdf.set_geometry(None, inplace=True)
isochrones_gdf = gpd.GeoDataFrame(isochrones_rows, crs=target_crs)
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].apply(lambda x: x if x.geom_type in ['Polygon', 'MultiPolygon'] else x.convex_hull)
isochrones_gdf['geometry'] = isochrones_gdf['geometry'].buffer(0)
incidents_gdf.to_file('emergency_routing.gpkg', layer='incidents', driver='GPKG')
closest_hospital_gdf.to_file('emergency_routing.gpkg', layer='closest_hospital', driver='GPKG')
dist_matrix_gdf.to_file('emergency_routing.gpkg', layer='distance_matrix', driver='GPKG')
isochrones_gdf.to_file('emergency_routing.gpkg', layer='isochrones_15min', driver='GPKG')
print("Done!")
Wrote solve.py (6419 chars).
14:09
Bash
python solve.py
Fetching road network and hospitals...
Traceback (most recent call last):
File "/work/solve.py", line 46, in <module>
hospitals['hospital_name'] = hospitals['name'].fillna('Unnamed Hospital').astype(str) + "_" + hospitals.index.astype(str)
~~~~~~~~~~~~~~~~~~~~~~^^^^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/multi.py", line 4401, in astype
raise TypeError(
...<2 lines>...
)
TypeError: Setting a MultiIndex dtype to anything other than object is not supported
14:23
Edit
solve.py
Edited solve.py.
14:26
Bash
python solve.py
Fetching road network and hospitals... Calculating routes and distances...
Traceback (most recent call last):
File "/work/solve.py", line 56, in <module>
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/geoseries.py", line 800, in apply
result = super().apply(func, args=args, **kwargs)
File "/usr/local/lib/python3.14/site-packages/pandas/core/series.py", line 5084, in apply
).apply()
~~~~~^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/apply.py", line 1520, in apply
return self.apply_standard()
~~~~~~~~~~~~~~~~~~~^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/apply.py", line 1578, in apply_standard
mapped = obj._map_values(mapper=curried)
File "/usr/local/lib/python3.14/site-packages/pandas/core/base.py", line 1020, in _map_values
return arr.map(mapper, na_action=na_action)
~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/pandas/core/arrays/base.py", line 2745, in map
return map_array(self, mapper, na_action=na_action)
File "/usr/local/lib/python3.14/site-packages/pandas/core/algorithms.py", line 1715, in map_array
return lib.map_infer(values, mapper)
~~~~~~~~~~~~~^^^^^^^^^^^^^^^^
File "pandas/_libs/lib.pyx", line 3071, in pandas._libs.lib.map_infer
File "/work/solve.py", line 56, in <lambda>
hospitals['nearest_node'] = hospitals.geometry.apply(lambda p: get_nearest_node_proj(nodes_proj, p))
~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^
File "/work/solve.py", line 52, in get_nearest_node_proj
return ox.nearest_nodes(G_proj, point.x, point.y)
~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/osmnx/distance.py", line 368, in nearest_nodes
raise ImportError(msg)
ImportError: scipy must be installed as an optional dependency to search a projected graph.
14:33
Bash
pip install --user scipy
Collecting scipy Downloading scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl.metadata (62 kB) Requirement already satisfied: numpy<2.7,>=1.26.4 in /usr/local/lib/python3.14/site-packages (from scipy) (2.4.6) Downloading scipy-1.17.1-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl (35.2 MB) ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 35.2/35.2 MB 67.6 MB/s 0:00:00 Installing collected packages: scipy Successfully installed scipy-1.17.1
14:42
Bash
python solve.py
Fetching road network and hospitals... Calculating routes and distances... Calculating 15-min isochrones... Saving outputs to GPKG...
Traceback (most recent call last):
File "/work/solve.py", line 146, in <module>
dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, crs=target_crs)
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 246, in __init__
raise ValueError(
...<3 lines>...
)
ValueError: Assigning CRS to a GeoDataFrame without a geometry column is not supported. Supply geometry using the 'geometry=' keyword argument, or by providing a DataFrame with column name 'geometry'
15:30
Edit
solve.py
Edited solve.py.
15:33
Bash
python solve.py
Fetching road network and hospitals... Calculating routes and distances... Calculating 15-min isochrones... Saving outputs to GPKG...
Traceback (most recent call last):
File "/work/solve.py", line 146, in <module>
dist_matrix_gdf = gpd.GeoDataFrame(distance_matrix_rows, geometry=None, crs=target_crs)
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 246, in __init__
raise ValueError(
...<3 lines>...
)
ValueError: Assigning CRS to a GeoDataFrame without a geometry column is not supported. Supply geometry using the 'geometry=' keyword argument, or by providing a DataFrame with column name 'geometry'
16:19
Edit
solve.py
Edited solve.py.
16:22
Bash
python solve.py
Fetching road network and hospitals... Calculating routes and distances... Calculating 15-min isochrones... Saving outputs to GPKG...
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 3641, in get_loc
return self._engine.get_loc(casted_key)
~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^
File "pandas/_libs/index.pyx", line 168, in pandas._libs.index.IndexEngine.get_loc
File "pandas/_libs/index.pyx", line 197, in pandas._libs.index.IndexEngine.get_loc
File "pandas/_libs/hashtable_class_helper.pxi", line 7668, in pandas._libs.hashtable.PyObjectHashTable.get_item
File "pandas/_libs/hashtable_class_helper.pxi", line 7676, in pandas._libs.hashtable.PyObjectHashTable.get_item
KeyError: nan
The above exception was the direct cause of the following exception:
Traceback (most recent call last):
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 420, in set_geometry
level = frame[col]
~~~~~^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 1891, in __getitem__
result = super().__getitem__(key)
File "/usr/local/lib/python3.14/site-packages/pandas/core/frame.py", line 4378, in __getitem__
indexer = self.columns.get_loc(key)
File "/usr/local/lib/python3.14/site-packages/pandas/core/indexes/base.py", line 3648, in get_loc
raise KeyError(key) from err
KeyError: None
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/work/solve.py", line 150, in <module>
dist_matrix_gdf.set_geometry(None, inplace=True)
~~~~~~~~~~~~~~~~~~~~~~~~~~~~^^^^^^^^^^^^^^^^^^^^
File "/usr/local/lib/python3.14/site-packages/geopandas/geodataframe.py", line 422, in set_geometry
raise ValueError(f"Unknown column {col}")
ValueError: Unknown column None
17:27
Edit
Invalid JSON arguments: Expecting ',' delimiter: line 1 column 2395 (char 2394)