112 lines
4.1 KiB
JavaScript
112 lines
4.1 KiB
JavaScript
/**
|
|
* Driving Distance, Duration & Route Polyline Calculator for DoorMile
|
|
* Uses OSRM as primary routing engine with GeoJSON polyline extraction,
|
|
* and falls back seamlessly to Haversine with a 1.3 road winding multiplier.
|
|
*/
|
|
|
|
import { coordsOf } from '@/lib/coords';
|
|
|
|
let lastRouteDurationMin = null;
|
|
|
|
export const getLastRouteDurationMin = () => lastRouteDurationMin;
|
|
|
|
/**
|
|
* Calculates complete driving route with distance, duration, and road polyline
|
|
* @param {Object} origin - { latitude, longitude }
|
|
* @param {Object} destination - { latitude, longitude }
|
|
* @returns {Promise<{ distance: number, minutes: number, polyline: Array<[number, number]>, resolved: boolean }>}
|
|
*/
|
|
export const calculateDrivingRoute = async (origin, destination) => {
|
|
// Validated through lib/coords rather than with Number.isFinite.
|
|
//
|
|
// The previous guard was `Number.isFinite(Number(value))`, which accepts
|
|
// null, '' and false — all three coerce to 0, and 0 is finite. A missing pin
|
|
// therefore reached OSRM as (0, 0). OSRM answers HTTP 200 with no `routes`
|
|
// for a point in open ocean, so control fell through to the Haversine
|
|
// fallback below and a Coimbatore pickup measured against 0,0 returned
|
|
// roughly 11,000 km. That number becomes the row's charge and the backend
|
|
// honours it verbatim, so the guard being wrong here was a billing defect,
|
|
// not a display one.
|
|
const from = coordsOf(origin);
|
|
const to = coordsOf(destination);
|
|
if (!from || !to) {
|
|
throw new Error("Invalid coordinates");
|
|
}
|
|
|
|
const lat1 = from.latitude;
|
|
const lon1 = from.longitude;
|
|
const lat2 = to.latitude;
|
|
const lon2 = to.longitude;
|
|
|
|
// 1. Try OSRM API with full GeoJSON geometry for live polyline map preview
|
|
const osrmBaseUrl =
|
|
(typeof import.meta !== 'undefined' && import.meta?.env?.VITE_OSRM_URL) ||
|
|
'https://router.project-osrm.org';
|
|
try {
|
|
const url = `${osrmBaseUrl}/route/v1/driving/${lon1},${lat1};${lon2},${lat2}?overview=full&geometries=geojson`;
|
|
const response = await fetch(url);
|
|
if (response.ok) {
|
|
const data = await response.json();
|
|
if (data.routes && data.routes.length > 0) {
|
|
const route = data.routes[0];
|
|
const distanceKm = Math.round(route.distance / 1000);
|
|
const durationMin = Number.isFinite(route.duration) ? Math.round(route.duration / 60) : null;
|
|
// OSRM geojson coordinates are [lng, lat] -> convert to Leaflet [lat, lng]
|
|
const polyline = (route.geometry?.coordinates || []).map(([lng, lat]) => [lat, lng]);
|
|
lastRouteDurationMin = durationMin;
|
|
return {
|
|
distance: Math.max(0, distanceKm),
|
|
minutes: durationMin,
|
|
polyline: polyline.length > 0 ? polyline : [[lat1, lon1], [lat2, lon2]],
|
|
resolved: true
|
|
};
|
|
}
|
|
}
|
|
} catch (error) {
|
|
console.warn("OSRM API failed, falling back to Haversine math:", error);
|
|
}
|
|
|
|
// 2. Fallback: Haversine Formula with 1.3x road factor
|
|
const toRad = (val) => (val * Math.PI) / 180;
|
|
const R = 6371; // Earth radius in km
|
|
const dLat = toRad(lat2 - lat1);
|
|
const dLon = toRad(lon2 - lon1);
|
|
const a =
|
|
Math.sin(dLat / 2) * Math.sin(dLat / 2) +
|
|
Math.cos(toRad(lat1)) * Math.cos(toRad(lat2)) *
|
|
Math.sin(dLon / 2) * Math.sin(dLon / 2);
|
|
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
|
|
const aerialDistance = Math.round(R * c * 1.3);
|
|
|
|
lastRouteDurationMin = null;
|
|
return {
|
|
distance: aerialDistance,
|
|
minutes: null,
|
|
polyline: [[lat1, lon1], [lat2, lon2]],
|
|
resolved: true
|
|
};
|
|
};
|
|
|
|
/**
|
|
* Calculates distance (in km) between origin and destination
|
|
*/
|
|
export const calculateDrivingDistance = async (origin, destination) => {
|
|
const res = await calculateDrivingRoute(origin, destination);
|
|
return res.distance;
|
|
};
|
|
|
|
/**
|
|
* Calculates total charge based on distance and pricing tier
|
|
*/
|
|
export const calculateTotalCharge = (distanceKm, basePrice, pricePerKm, minKm) => {
|
|
const dist = Number(distanceKm) || 0;
|
|
const base = Number(basePrice) || 40;
|
|
const perKm = Number(pricePerKm) || 10;
|
|
const min = Number(minKm) || 2;
|
|
|
|
if (dist <= min) {
|
|
return base;
|
|
}
|
|
return (dist - min) * perKm + base;
|
|
};
|