Files
Atlas/lib/route-optimization.ts
2025-11-12 20:21:32 +01:00

193 lines
5.2 KiB
TypeScript

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<any> {
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<OptimizedRoute | null> {
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<OptimizedRoute | null> {
const sortedStops = this.sortStopsByTime(stops);
return this.optimizeRoute(sortedStops, startLocation);
}
}
export const routeService = new RouteOptimizationService();