UNPKG

smarteta

Version:

Smart urban ETA estimator based on time, distance, vehicle type and real-world corrections

96 lines (80 loc) 3.48 kB
// smart_eta/src/index.ts export type VehicleType = 'car' | 'scooter'; export interface HistoricalEntry { fromLat: number; fromLng: number; toLat: number; toLng: number; actualMinutes: number; } export interface EstimateInput { startLat: number; startLng: number; endLat: number; endLng: number; vehicleType: VehicleType; now?: Date; historicalData?: HistoricalEntry[]; urbanOverride?: boolean; } export interface EstimateResult { min: number; avg: number; max: number; } // Helper function for Haversine distance calculation function haversineDistance(coords1: [number, number], coords2: [number, number]): number { const R = 6371; // Earth's radius in kilometers const dLat = (coords2[0] - coords1[0]) * Math.PI / 180; const dLon = (coords2[1] - coords1[1]) * Math.PI / 180; const a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(coords1[0] * Math.PI / 180) * Math.cos(coords2[0] * Math.PI / 180) * Math.sin(dLon / 2) * Math.sin(dLon / 2); const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); return R * c; // Distance in km } export function estimateSmartETA(input: EstimateInput): EstimateResult { // Helper function for additional delays function extraDelay(vehicleType: VehicleType, isUrban: boolean, distanceKm: number): number { if (isUrban && distanceKm < 6) return vehicleType === 'car' ? 4 : 2; // For short urban trips return vehicleType === 'car' ? 1 : 0.5; // Reduced for inter-city to avoid overestimation } // 1. Input reception const start: [number, number] = [input.startLat, input.startLng]; const end: [number, number] = [input.endLat, input.endLng]; const vehicleType = input.vehicleType; // 'car' or 'scooter' const now = input.now instanceof Date ? input.now : new Date(); const urbanOverride = input.urbanOverride; // 2. Calculate air distance in km const distanceKm = haversineDistance(start, end); // 3. Determine if urban trip const isUrban = typeof urbanOverride === 'boolean' ? urbanOverride : distanceKm < 15; // 4. Determine base speed based on distance and time of day const hour = now.getHours() + now.getMinutes() / 60; let baseSpeed: number; if (distanceKm > 40) { // Tuned for ~47 km (Be'er Sheva to Ashdod) to take ~60 minutes baseSpeed = vehicleType === 'car' ? 48 : 55; // Adjusted from 45 to 48 km/h } else if (distanceKm < 6) { // Tuned for ~5 km (within Be'er Sheva) to take ~15 minutes baseSpeed = vehicleType === 'car' ? 20 : 25; } else { // Fallback for other distances baseSpeed = vehicleType === 'car' ? 30 : 40; } // 5. Day-of-week correction (Wednesday/Thursday busier) const day = now.getDay(); // 0 = Sunday, ..., 6 = Saturday const speedCorrectionFactor = day === 3 || day === 4 ? 0.95 : 1.0; const correctedSpeed = baseSpeed * speedCorrectionFactor; // 6. Calculate base ETA let baseETA = (distanceKm / correctedSpeed) * 60 + extraDelay(vehicleType, isUrban, distanceKm); // 7. Ensure minimum time baseETA = Math.max(baseETA, 2); // 8. Add deviation range return { min: Math.round(Math.max(baseETA * 0.9, 2) * 10) / 10, avg: Math.round(baseETA * 10) / 10, max: Math.round(Math.max(baseETA * 1.1, 2.1) * 10) / 10 }; }