interface RouteStop { address: string; lat?: number; lng?: number; bookingId?: string; setupTime?: string; } interface OptimizedRoute { stops: RouteStop[]; totalDistance: number; totalDuration: number; legs: any[]; } export class RouteOptimizationService { private apiKey: string; constructor() { this.apiKey = process.env.GOOGLE_MAPS_API_KEY || ''; if (!this.apiKey) { console.warn('Google Maps API Key nicht konfiguriert'); } } async geocodeAddress(address: string): Promise<{ lat: number; lng: number } | null> { try { const response = await fetch( `https://maps.googleapis.com/maps/api/geocode/json?address=${encodeURIComponent(address)}&key=${this.apiKey}` ); const data = await response.json(); if (data.status === 'OK' && data.results.length > 0) { const location = data.results[0].geometry.location; return { lat: location.lat, lng: location.lng }; } return null; } catch (error) { console.error('Geocoding error:', error); return null; } } async calculateDistanceMatrix( origins: string[], destinations: string[] ): Promise { try { const originsParam = origins.map(o => encodeURIComponent(o)).join('|'); const destinationsParam = destinations.map(d => encodeURIComponent(d)).join('|'); const response = await fetch( `https://maps.googleapis.com/maps/api/distancematrix/json?origins=${originsParam}&destinations=${destinationsParam}&key=${this.apiKey}` ); const data = await response.json(); if (data.status === 'OK') { return data; } return null; } catch (error) { console.error('Distance Matrix error:', error); return null; } } async optimizeRoute(stops: RouteStop[], startLocation?: string): Promise { try { if (stops.length === 0) { return null; } for (const stop of stops) { if (!stop.lat || !stop.lng) { const coords = await this.geocodeAddress(stop.address); if (coords) { stop.lat = coords.lat; stop.lng = coords.lng; } } } if (stops.length === 1) { return { stops: stops, totalDistance: 0, totalDuration: 0, legs: [], }; } const origin = startLocation || stops[0].address; const destination = stops[stops.length - 1].address; const waypoints = stops.slice(1, -1).map(s => s.address); const waypointsParam = waypoints.length > 0 ? `&waypoints=optimize:true|${waypoints.map(w => encodeURIComponent(w)).join('|')}` : ''; const response = await fetch( `https://maps.googleapis.com/maps/api/directions/json?origin=${encodeURIComponent(origin)}&destination=${encodeURIComponent(destination)}${waypointsParam}&key=${this.apiKey}` ); const data = await response.json(); if (data.status === 'OK' && data.routes.length > 0) { const route = data.routes[0]; const legs = route.legs; let totalDistance = 0; let totalDuration = 0; legs.forEach((leg: any) => { totalDistance += leg.distance.value; totalDuration += leg.duration.value; }); const waypointOrder = route.waypoint_order || []; const optimizedStops = [stops[0]]; waypointOrder.forEach((index: number) => { optimizedStops.push(stops[index + 1]); }); if (stops.length > 1) { optimizedStops.push(stops[stops.length - 1]); } return { stops: optimizedStops, totalDistance: totalDistance / 1000, totalDuration: Math.round(totalDuration / 60), legs: legs, }; } return null; } catch (error) { console.error('Route optimization error:', error); return null; } } async calculateSimpleRoute( startAddress: string, endAddress: string ): Promise<{ distance: number; duration: number } | null> { try { const response = await fetch( `https://maps.googleapis.com/maps/api/directions/json?origin=${encodeURIComponent(startAddress)}&destination=${encodeURIComponent(endAddress)}&key=${this.apiKey}` ); const data = await response.json(); if (data.status === 'OK' && data.routes.length > 0) { const route = data.routes[0]; const leg = route.legs[0]; return { distance: leg.distance.value / 1000, duration: Math.round(leg.duration.value / 60), }; } return null; } catch (error) { console.error('Simple route calculation error:', error); return null; } } sortStopsByTime(stops: RouteStop[]): RouteStop[] { return stops.sort((a, b) => { if (!a.setupTime || !b.setupTime) return 0; return new Date(a.setupTime).getTime() - new Date(b.setupTime).getTime(); }); } async optimizeRouteWithTimeWindows( stops: RouteStop[], startLocation?: string ): Promise { const sortedStops = this.sortStopsByTime(stops); return this.optimizeRoute(sortedStops, startLocation); } } export const routeService = new RouteOptimizationService();