Get Directions
Get driving directions between two locations using OSRM (Open Source Routing Machine)
Source Code
const [origin, destination, format = "summary"] = process.argv.slice(2);
if (!origin || !destination) {
console.error("Error: both origin and destination are required");
process.exit(1);
}
const USER_AGENT = "SaunaGeoOperations/1.0";
// Check if string looks like coordinates
function isCoordinates(str) {
const coordPattern = /^-?\d+\.?\d*,\s*-?\d+\.?\d*$/;
return coordPattern.test(str.trim());
}
// Parse coordinate string
function parseCoordinates(str) {
const [lat, lon] = str.split(",").map((s) => parseFloat(s.trim()));
return { lat, lon };
}
// Geocode address to coordinates
async function geocode(address) {
const params = new URLSearchParams({
q: address,
format: "json",
limit: "1",
});
const response = await fetch(
`https://nominatim.openstreetmap.org/search?${params}`,
{ headers: { "User-Agent": USER_AGENT } }
);
if (!response.ok) {
throw new Error(`Geocoding failed: ${response.status}`);
}
const results = await response.json();
if (!results.length) {
throw new Error(`Location not found: "${address}"`);
}
return {
lat: parseFloat(results[0].lat),
lon: parseFloat(results[0].lon),
displayName: results[0].display_name,
};
}
// Get location coordinates (geocode if needed)
async function resolveLocation(location) {
if (isCoordinates(location)) {
const coords = parseCoordinates(location);
return { ...coords, displayName: `${coords.lat}, ${coords.lon}` };
}
return await geocode(location);
}
// Format duration in human-readable form
function formatDuration(seconds) {
const hours = Math.floor(seconds / 3600);
const minutes = Math.floor((seconds % 3600) / 60);
if (hours > 0) {
return `${hours}h ${minutes}m`;
}
return `${minutes} min`;
}
// Format distance
function formatDistance(meters) {
if (meters >= 1000) {
return `${(meters / 1000).toFixed(1)} km`;
}
return `${Math.round(meters)} m`;
}
// Get OSRM route
async function getRoute(originCoords, destCoords) {
// OSRM expects lon,lat order (not lat,lon)
const coords = `${originCoords.lon},${originCoords.lat};${destCoords.lon},${destCoords.lat}`;
const url = `https://router.project-osrm.org/route/v1/driving/${coords}?overview=full&steps=true&geometries=geojson`;
const response = await fetch(url, {
headers: { "User-Agent": USER_AGENT },
});
if (!response.ok) {
throw new Error(`Routing failed: ${response.status}`);
}
const data = await response.json();
if (data.code !== "Ok") {
throw new Error(`Routing failed: ${data.code} - ${data.message || ""}`);
}
return data.routes[0];
}
// Parse OSRM step into readable instruction
function parseStep(step) {
const maneuver = step.maneuver;
let instruction = "";
switch (maneuver.type) {
case "depart":
instruction = `Start on ${step.name || "the road"}`;
break;
case "arrive":
instruction = "Arrive at destination";
break;
case "turn":
instruction = `Turn ${maneuver.modifier || ""} onto ${step.name || "the road"}`;
break;
case "continue":
instruction = `Continue on ${step.name || "the road"}`;
break;
case "merge":
instruction = `Merge onto ${step.name || "the road"}`;
break;
case "fork":
instruction = `Take the ${maneuver.modifier || ""} fork onto ${step.name || "the road"}`;
break;
case "roundabout":
instruction = `Take exit ${maneuver.exit || ""} from roundabout onto ${step.name || "the road"}`;
break;
default:
instruction = `${maneuver.type} ${maneuver.modifier || ""} - ${step.name || "continue"}`;
}
return {
instruction: instruction.trim(),
distance: formatDistance(step.distance),
duration: formatDuration(step.duration),
};
}
async function main() {
console.log(`Getting directions from "${origin}" to "${destination}"`);
// Resolve both locations
console.log("Resolving origin...");
const originCoords = await resolveLocation(origin);
console.log(`Origin: ${originCoords.displayName}`);
// Add small delay to respect Nominatim rate limit
if (!isCoordinates(origin)) {
await new Promise((r) => setTimeout(r, 1100));
}
console.log("Resolving destination...");
const destCoords = await resolveLocation(destination);
console.log(`Destination: ${destCoords.displayName}`);
// Get route from OSRM
console.log("Calculating route...");
const route = await getRoute(originCoords, destCoords);
const result = {
success: true,
origin: {
query: origin,
displayName: originCoords.displayName,
coordinates: { lat: originCoords.lat, lon: originCoords.lon },
},
destination: {
query: destination,
displayName: destCoords.displayName,
coordinates: { lat: destCoords.lat, lon: destCoords.lon },
},
route: {
distance: formatDistance(route.distance),
distanceMeters: route.distance,
duration: formatDuration(route.duration),
durationSeconds: route.duration,
},
};
console.log(`\nRoute: ${result.route.distance}, ${result.route.duration}`);
if (format === "detailed") {
// Extract steps from all legs
const steps = [];
for (const leg of route.legs) {
for (const step of leg.steps) {
steps.push(parseStep(step));
}
}
result.steps = steps;
console.log("\nDirections:");
steps.forEach((step, i) => {
console.log(`${i + 1}. ${step.instruction} (${step.distance})`);
});
}
console.log(JSON.stringify(result));
}
main().catch((err) => {
console.error("Routing failed:", err.message);
process.exit(1);
});