193 lines
5.2 KiB
TypeScript
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();
|