Route Optimization using OpenRouteService¶
This was a personal project to explore the OpenRouteService Optimization API to find the optimal route between a set of locations.
In [ ]:
Copied!
# Address of BangaloreOne Centers list for Aadhaar Enrollment
# Data from https://www.karnatakaone.gov.in/Info/Public/UIDAI
locations = [
('AECS Layout (Kundalahalli)', 'AECS Layout, Kundalahalli, Bangalore 560037'),
('Agrahara Dasarahalli', '7th Main, Agrahara Dasarahalli, Bangalore 560079'),
('Airport Road', 'Bescom, Airport Road, Bangalore 560017'),
('Arkere', 'Arekere Mico Layout, Bangalore 560076'),
('Azad nagar', 'Mysore road, Azad Nagar, Bangalore 560026')
]
# Address of BangaloreOne Centers list for Aadhaar Enrollment
# Data from https://www.karnatakaone.gov.in/Info/Public/UIDAI
locations = [
('AECS Layout (Kundalahalli)', 'AECS Layout, Kundalahalli, Bangalore 560037'),
('Agrahara Dasarahalli', '7th Main, Agrahara Dasarahalli, Bangalore 560079'),
('Airport Road', 'Bescom, Airport Road, Bangalore 560017'),
('Arkere', 'Arekere Mico Layout, Bangalore 560076'),
('Azad nagar', 'Mysore road, Azad Nagar, Bangalore 560026')
]
In [2]:
Copied!
import time
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent='spatialthoughts', timeout=10)
results = []
for name, address in locations:
print('Geocoding: ', address)
location = geolocator.geocode(address)
if location:
results.append((name, location.latitude, location.longitude))
else:
print(f'Error: geocode failed on address {address}')
time.sleep(1)
print('Results', results)
import time
from geopy.geocoders import Nominatim
geolocator = Nominatim(user_agent='spatialthoughts', timeout=10)
results = []
for name, address in locations:
print('Geocoding: ', address)
location = geolocator.geocode(address)
if location:
results.append((name, location.latitude, location.longitude))
else:
print(f'Error: geocode failed on address {address}')
time.sleep(1)
print('Results', results)
Geocoding: AECS Layout, Kundalahalli, Bangalore 560037
Geocoding: 7th Main, Agrahara Dasarahalli, Bangalore 560079
Geocoding: Bescom, Airport Road, Bangalore 560017
Geocoding: Arekere Mico Layout, Bangalore 560076
Geocoding: Mysore road, Azad Nagar, Bangalore 560026
Results [('AECS Layout (Kundalahalli)', 12.9647797, 77.7183067), ('Agrahara Dasarahalli', 12.9855754, 77.5412584), ('Airport Road', 13.0029149, 77.6195854), ('Arkere', 12.8894455, 77.5938362), ('Azad nagar', 12.9598693, 77.556317)]
Visualize addresses on a map
In [ ]:
Copied!
import folium
# Centre the map on the mean coordinates of all points
mean_lat = sum(r[1] for r in results) / len(results)
mean_lon = sum(r[2] for r in results) / len(results)
m = folium.Map(
location=[mean_lat, mean_lon],
zoom_start=12,
tiles='CartoDB positron',
)
for name, lat, lon in results:
folium.Marker(
location=[lat, lon],
popup=folium.Popup(
f'<b>{name}</b><br>{address}',
max_width=220,
),
tooltip=name,
icon=folium.Icon(color='red', icon='home', prefix='fa'),
).add_to(m)
m
import folium
# Centre the map on the mean coordinates of all points
mean_lat = sum(r[1] for r in results) / len(results)
mean_lon = sum(r[2] for r in results) / len(results)
m = folium.Map(
location=[mean_lat, mean_lon],
zoom_start=12,
tiles='CartoDB positron',
)
for name, lat, lon in results:
folium.Marker(
location=[lat, lon],
popup=folium.Popup(
f'<b>{name}</b><br>{address}',
max_width=220,
),
tooltip=name,
icon=folium.Icon(color='red', icon='home', prefix='fa'),
).add_to(m)
m
Out[ ]:
Make this Notebook Trusted to load map: File -> Trust Notebook
Route optimization¶
In [ ]:
Copied!
ORS_API_KEY = '<YOUR_ORS_API_KEY>'
ORS_API_KEY = '<YOUR_ORS_API_KEY>'
In [11]:
Copied!
import requests
# ── 1. Build the ORS Optimization payload ────────────────────────────────────
# 'results' is your list of (name, lat, lon) tuples
start_name, start_lat, start_lon = results[0]
jobs = [
{
"id": idx,
"description": name,
"location": [lon, lat], # ORS expects [lon, lat]
}
for idx, (name, lat, lon) in enumerate(results[1:], start=1)
]
payload = {
"jobs": jobs,
"shipments": [],
"vehicles": [
{
"id": 1,
"profile": "driving-car",
"start": [start_lon, start_lat],
}
],
}
headers = {
"Authorization": ORS_API_KEY,
"Content-Type": "application/json",
}
# ── 2. Call the Optimization API ─────────────────────────────────────────────
ORS_OPT_URL = "https://api.openrouteservice.org/optimization"
opt_resp = requests.post(ORS_OPT_URL, json=payload, headers=headers)
opt_resp.raise_for_status()
opt_data = opt_resp.json()
# ── 3. Extract ordered waypoints from the optimised route ────────────────────
route = opt_data["routes"][0]
ordered_coords = [] # (lat, lon) for folium
ordered_names = []
for step in route["steps"]:
lon, lat = step["location"]
ordered_coords.append((lat, lon))
if step["type"] == "job":
job_id = step["id"]
ordered_names.append(results[job_id][0])
elif step["type"] == "start":
ordered_names.append(start_name)
else:
ordered_names.append(start_name)
total_duration_min = route["duration"] / 60
print("Optimised stop order:")
for i, name in enumerate(ordered_names):
prefix = "▶ START" if i == 0 else ("■ END" if i == len(ordered_names) - 1 else f" {i}.")
print(f" {prefix} {name}")
print(f"Total duration : {total_duration_min:.0f} min")
import requests
# ── 1. Build the ORS Optimization payload ────────────────────────────────────
# 'results' is your list of (name, lat, lon) tuples
start_name, start_lat, start_lon = results[0]
jobs = [
{
"id": idx,
"description": name,
"location": [lon, lat], # ORS expects [lon, lat]
}
for idx, (name, lat, lon) in enumerate(results[1:], start=1)
]
payload = {
"jobs": jobs,
"shipments": [],
"vehicles": [
{
"id": 1,
"profile": "driving-car",
"start": [start_lon, start_lat],
}
],
}
headers = {
"Authorization": ORS_API_KEY,
"Content-Type": "application/json",
}
# ── 2. Call the Optimization API ─────────────────────────────────────────────
ORS_OPT_URL = "https://api.openrouteservice.org/optimization"
opt_resp = requests.post(ORS_OPT_URL, json=payload, headers=headers)
opt_resp.raise_for_status()
opt_data = opt_resp.json()
# ── 3. Extract ordered waypoints from the optimised route ────────────────────
route = opt_data["routes"][0]
ordered_coords = [] # (lat, lon) for folium
ordered_names = []
for step in route["steps"]:
lon, lat = step["location"]
ordered_coords.append((lat, lon))
if step["type"] == "job":
job_id = step["id"]
ordered_names.append(results[job_id][0])
elif step["type"] == "start":
ordered_names.append(start_name)
else:
ordered_names.append(start_name)
total_duration_min = route["duration"] / 60
print("Optimised stop order:")
for i, name in enumerate(ordered_names):
prefix = "▶ START" if i == 0 else ("■ END" if i == len(ordered_names) - 1 else f" {i}.")
print(f" {prefix} {name}")
print(f"Total duration : {total_duration_min:.0f} min")
Optimised stop order:
▶ START AECS Layout (Kundalahalli)
1. Airport Road
2. Agrahara Dasarahalli
3. Azad nagar
4. Arkere
■ END AECS Layout (Kundalahalli)
Total duration : 62 min
In [15]:
Copied!
import folium
from IPython.display import display
# ── 1. Fetch road geometry via the Directions API (waypoints from Cell 1) ────
# Build semicolon-separated coordinate string in lon,lat order (ORS v2 format)
coord_str = "|".join(f"{lon},{lat}" for lat, lon in ordered_coords)
ORS_DIR_URL = "https://api.openrouteservice.org/v2/directions/driving-car/geojson"
dir_resp = requests.post(
ORS_DIR_URL,
json={"coordinates": [[lon, lat] for lat, lon in ordered_coords]},
headers={
"Authorization": ORS_API_KEY,
"Content-Type": "application/json",
},
)
# Fall back to straight-line segments if directions call fails
if dir_resp.ok:
geometry = dir_resp.json()["features"][0]["geometry"]["coordinates"]
polyline_coords = [(lat, lon) for lon, lat in geometry]
else:
print(f"Directions API failed ({dir_resp.status_code}), using straight-line fallback.")
polyline_coords = ordered_coords
# ── 2. Draw the map ───────────────────────────────────────────────────────────
center_lat = sum(c[0] for c in ordered_coords) / len(ordered_coords)
center_lon = sum(c[1] for c in ordered_coords) / len(ordered_coords)
m = folium.Map(location=[center_lat, center_lon], zoom_start=12, tiles="CartoDB positron")
# Route polyline
folium.PolyLine(polyline_coords, color="#E74C3C", weight=4, opacity=0.8).add_to(m)
# Markers in optimised order
for visit_order, ((lat, lon), name) in enumerate(zip(ordered_coords, ordered_names)):
is_start = visit_order == 0
is_end = visit_order == len(ordered_coords) - 1
if is_start or is_end:
color, icon = ("green", "play") if is_start else ("red", "stop")
folium.Marker(
location=[lat, lon],
tooltip=f"{'▶ START' if is_start else '■ END'}: {name}",
icon=folium.Icon(color=color, icon=icon, prefix="fa"),
).add_to(m)
else:
folium.CircleMarker(
location=[lat, lon],
radius=8,
color="white",
fill=True,
fill_color="#2980B9",
fill_opacity=0.9,
tooltip=f"Stop {visit_order}: {name}",
).add_to(m)
# Numbered label
folium.Marker(
location=[lat, lon],
icon=folium.DivIcon(
html=f'<div style="font-size:10px;font-weight:bold;color:white;'
f'background:#2C3E50;border-radius:50%;width:18px;height:18px;'
f'text-align:center;line-height:18px;margin-top:-9px;margin-left:-9px">'
f'{visit_order + 1}</div>',
icon_size=(18, 18),
),
).add_to(m)
display(m)
import folium
from IPython.display import display
# ── 1. Fetch road geometry via the Directions API (waypoints from Cell 1) ────
# Build semicolon-separated coordinate string in lon,lat order (ORS v2 format)
coord_str = "|".join(f"{lon},{lat}" for lat, lon in ordered_coords)
ORS_DIR_URL = "https://api.openrouteservice.org/v2/directions/driving-car/geojson"
dir_resp = requests.post(
ORS_DIR_URL,
json={"coordinates": [[lon, lat] for lat, lon in ordered_coords]},
headers={
"Authorization": ORS_API_KEY,
"Content-Type": "application/json",
},
)
# Fall back to straight-line segments if directions call fails
if dir_resp.ok:
geometry = dir_resp.json()["features"][0]["geometry"]["coordinates"]
polyline_coords = [(lat, lon) for lon, lat in geometry]
else:
print(f"Directions API failed ({dir_resp.status_code}), using straight-line fallback.")
polyline_coords = ordered_coords
# ── 2. Draw the map ───────────────────────────────────────────────────────────
center_lat = sum(c[0] for c in ordered_coords) / len(ordered_coords)
center_lon = sum(c[1] for c in ordered_coords) / len(ordered_coords)
m = folium.Map(location=[center_lat, center_lon], zoom_start=12, tiles="CartoDB positron")
# Route polyline
folium.PolyLine(polyline_coords, color="#E74C3C", weight=4, opacity=0.8).add_to(m)
# Markers in optimised order
for visit_order, ((lat, lon), name) in enumerate(zip(ordered_coords, ordered_names)):
is_start = visit_order == 0
is_end = visit_order == len(ordered_coords) - 1
if is_start or is_end:
color, icon = ("green", "play") if is_start else ("red", "stop")
folium.Marker(
location=[lat, lon],
tooltip=f"{'▶ START' if is_start else '■ END'}: {name}",
icon=folium.Icon(color=color, icon=icon, prefix="fa"),
).add_to(m)
else:
folium.CircleMarker(
location=[lat, lon],
radius=8,
color="white",
fill=True,
fill_color="#2980B9",
fill_opacity=0.9,
tooltip=f"Stop {visit_order}: {name}",
).add_to(m)
# Numbered label
folium.Marker(
location=[lat, lon],
icon=folium.DivIcon(
html=f'<div style="font-size:10px;font-weight:bold;color:white;'
f'background:#2C3E50;border-radius:50%;width:18px;height:18px;'
f'text-align:center;line-height:18px;margin-top:-9px;margin-left:-9px">'
f'{visit_order + 1}</div>',
icon_size=(18, 18),
),
).add_to(m)
display(m)
Make this Notebook Trusted to load map: File -> Trust Notebook