Initial commit - SaveTheMoment Atlas Basis-Setup
This commit is contained in:
192
lib/route-optimization.ts
Normal file
192
lib/route-optimization.ts
Normal file
@@ -0,0 +1,192 @@
|
||||
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();
|
||||
Reference in New Issue
Block a user