Initial commit - SaveTheMoment Atlas Basis-Setup
This commit is contained in:
233
lib/google-maps.ts
Normal file
233
lib/google-maps.ts
Normal file
@@ -0,0 +1,233 @@
|
||||
const GOOGLE_MAPS_API_KEY = process.env.GOOGLE_MAPS_API_KEY || '';
|
||||
|
||||
interface Stop {
|
||||
eventAddress: string;
|
||||
eventCity: string;
|
||||
eventZip: string;
|
||||
setupTimeStart: Date;
|
||||
setupTimeLatest?: Date;
|
||||
}
|
||||
|
||||
interface RouteResult {
|
||||
optimizedOrder: number[];
|
||||
totalDistance: number;
|
||||
totalDuration: number;
|
||||
waypoints: Array<{
|
||||
address: string;
|
||||
lat: number;
|
||||
lng: number;
|
||||
arrivalTime?: string;
|
||||
setupWindow?: string;
|
||||
}>;
|
||||
}
|
||||
|
||||
export async function optimizeRouteBySchedule(stops: Stop[]): Promise<RouteResult> {
|
||||
if (!GOOGLE_MAPS_API_KEY) {
|
||||
throw new Error('Google Maps API key not configured');
|
||||
}
|
||||
|
||||
if (stops.length === 0) {
|
||||
return {
|
||||
optimizedOrder: [],
|
||||
totalDistance: 0,
|
||||
totalDuration: 0,
|
||||
waypoints: [],
|
||||
};
|
||||
}
|
||||
|
||||
const stopsWithTimes = stops.map((stop, index) => ({
|
||||
...stop,
|
||||
index,
|
||||
setupStart: new Date(stop.setupTimeStart),
|
||||
setupLatest: stop.setupTimeLatest ? new Date(stop.setupTimeLatest) : null,
|
||||
}));
|
||||
|
||||
stopsWithTimes.sort((a, b) => a.setupStart.getTime() - b.setupStart.getTime());
|
||||
|
||||
const sortedIndices = stopsWithTimes.map(s => s.index);
|
||||
|
||||
let totalDistance = 0;
|
||||
let totalDuration = 0;
|
||||
const waypoints: RouteResult['waypoints'] = [];
|
||||
|
||||
for (let i = 0; i < stopsWithTimes.length; i++) {
|
||||
const stop = stopsWithTimes[i];
|
||||
const address = `${stop.eventAddress}, ${stop.eventZip} ${stop.eventCity}`;
|
||||
const geocoded = await geocodeAddress(address);
|
||||
|
||||
const setupWindow = stop.setupLatest
|
||||
? `${stop.setupStart.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })} - ${stop.setupLatest.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`
|
||||
: `ab ${stop.setupStart.toLocaleTimeString('de-DE', { hour: '2-digit', minute: '2-digit' })}`;
|
||||
|
||||
waypoints.push({
|
||||
address: `${stop.eventAddress}, ${stop.eventCity}`,
|
||||
lat: geocoded.lat,
|
||||
lng: geocoded.lng,
|
||||
setupWindow,
|
||||
});
|
||||
|
||||
if (i > 0) {
|
||||
const prevStop = stopsWithTimes[i - 1];
|
||||
const origin = `${prevStop.eventAddress}, ${prevStop.eventZip} ${prevStop.eventCity}`;
|
||||
const destination = address;
|
||||
|
||||
const { distance, duration } = await calculateDistance(origin, destination);
|
||||
totalDistance += distance;
|
||||
totalDuration += duration;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
optimizedOrder: sortedIndices,
|
||||
totalDistance,
|
||||
totalDuration,
|
||||
waypoints,
|
||||
};
|
||||
}
|
||||
|
||||
export async function optimizeRoute(stops: Stop[]): Promise<RouteResult> {
|
||||
if (!GOOGLE_MAPS_API_KEY) {
|
||||
throw new Error('Google Maps API key not configured');
|
||||
}
|
||||
|
||||
if (stops.length === 0) {
|
||||
return {
|
||||
optimizedOrder: [],
|
||||
totalDistance: 0,
|
||||
totalDuration: 0,
|
||||
waypoints: [],
|
||||
};
|
||||
}
|
||||
|
||||
if (stops.length === 1) {
|
||||
const geocoded = await geocodeAddress(
|
||||
`${stops[0].eventAddress}, ${stops[0].eventZip} ${stops[0].eventCity}`
|
||||
);
|
||||
return {
|
||||
optimizedOrder: [0],
|
||||
totalDistance: 0,
|
||||
totalDuration: 0,
|
||||
waypoints: [
|
||||
{
|
||||
address: `${stops[0].eventAddress}, ${stops[0].eventCity}`,
|
||||
lat: geocoded.lat,
|
||||
lng: geocoded.lng,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
const origin = `${stops[0].eventAddress}, ${stops[0].eventZip} ${stops[0].eventCity}`;
|
||||
const destination = `${stops[stops.length - 1].eventAddress}, ${stops[stops.length - 1].eventZip} ${stops[stops.length - 1].eventCity}`;
|
||||
|
||||
const waypoints = stops.slice(1, -1).map(stop =>
|
||||
`${stop.eventAddress}, ${stop.eventZip} ${stop.eventCity}`
|
||||
);
|
||||
|
||||
const url = new URL('https://maps.googleapis.com/maps/api/directions/json');
|
||||
url.searchParams.append('origin', origin);
|
||||
url.searchParams.append('destination', destination);
|
||||
if (waypoints.length > 0) {
|
||||
url.searchParams.append('waypoints', `optimize:true|${waypoints.join('|')}`);
|
||||
}
|
||||
url.searchParams.append('key', GOOGLE_MAPS_API_KEY);
|
||||
url.searchParams.append('mode', 'driving');
|
||||
url.searchParams.append('language', 'de');
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Google Maps API error: ${response.statusText}`);
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status !== 'OK') {
|
||||
throw new Error(`Google Maps API returned status: ${data.status}`);
|
||||
}
|
||||
|
||||
const route = data.routes[0];
|
||||
const waypointOrder = route.waypoint_order || [];
|
||||
|
||||
const optimizedOrder = [
|
||||
0,
|
||||
...waypointOrder.map((i: number) => i + 1),
|
||||
stops.length - 1,
|
||||
];
|
||||
|
||||
let totalDistance = 0;
|
||||
let totalDuration = 0;
|
||||
|
||||
route.legs.forEach((leg: any) => {
|
||||
totalDistance += leg.distance.value;
|
||||
totalDuration += leg.duration.value;
|
||||
});
|
||||
|
||||
const waypointsData = await Promise.all(
|
||||
stops.map(async (stop, index) => {
|
||||
const address = `${stop.eventAddress}, ${stop.eventZip} ${stop.eventCity}`;
|
||||
const geocoded = await geocodeAddress(address);
|
||||
return {
|
||||
address: `${stop.eventAddress}, ${stop.eventCity}`,
|
||||
lat: geocoded.lat,
|
||||
lng: geocoded.lng,
|
||||
};
|
||||
})
|
||||
);
|
||||
|
||||
return {
|
||||
optimizedOrder,
|
||||
totalDistance: Math.round(totalDistance / 1000),
|
||||
totalDuration: Math.round(totalDuration / 60),
|
||||
waypoints: waypointsData,
|
||||
};
|
||||
}
|
||||
|
||||
async function geocodeAddress(address: string): Promise<{ lat: number; lng: number }> {
|
||||
const url = new URL('https://maps.googleapis.com/maps/api/geocode/json');
|
||||
url.searchParams.append('address', address);
|
||||
url.searchParams.append('key', GOOGLE_MAPS_API_KEY);
|
||||
url.searchParams.append('language', 'de');
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
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 { lat: 0, lng: 0 };
|
||||
}
|
||||
|
||||
export async function calculateDistance(
|
||||
origin: string,
|
||||
destination: string
|
||||
): Promise<{ distance: number; duration: number }> {
|
||||
if (!GOOGLE_MAPS_API_KEY) {
|
||||
throw new Error('Google Maps API key not configured');
|
||||
}
|
||||
|
||||
const url = new URL('https://maps.googleapis.com/maps/api/distancematrix/json');
|
||||
url.searchParams.append('origins', origin);
|
||||
url.searchParams.append('destinations', destination);
|
||||
url.searchParams.append('key', GOOGLE_MAPS_API_KEY);
|
||||
url.searchParams.append('mode', 'driving');
|
||||
url.searchParams.append('language', 'de');
|
||||
|
||||
const response = await fetch(url.toString());
|
||||
const data = await response.json();
|
||||
|
||||
if (data.status === 'OK' && data.rows[0].elements[0].status === 'OK') {
|
||||
const element = data.rows[0].elements[0];
|
||||
return {
|
||||
distance: Math.round(element.distance.value / 1000),
|
||||
duration: Math.round(element.duration.value / 60),
|
||||
};
|
||||
}
|
||||
|
||||
return { distance: 0, duration: 0 };
|
||||
}
|
||||
Reference in New Issue
Block a user