275 lines
7.8 KiB
TypeScript
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import { NextResponse } from 'next/server';
import { getServerSession } from 'next-auth';
import { authOptions } from '../../auth/[...nextauth]/route';
import { prisma } from '@/lib/prisma';
interface Coordinate {
lat: number;
lon: number;
}
interface AddressPoint {
coordinate: Coordinate;
city: string;
street: string;
originalIndex: number;
}
// İki nokta arası mesafeyi hesapla (Haversine formülü)
function calculateDistance(coord1: Coordinate, coord2: Coordinate): number {
const R = 6371; // Dünya'nın yarıçapı (km)
const dLat = toRad(coord2.lat - coord1.lat);
const dLon = toRad(coord2.lon - coord1.lon);
const lat1 = toRad(coord1.lat);
const lat2 = toRad(coord2.lat);
const a = Math.sin(dLat/2) * Math.sin(dLat/2) +
Math.sin(dLon/2) * Math.sin(dLon/2) * Math.cos(lat1) * Math.cos(lat2);
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
return R * c;
}
function toRad(value: number): number {
return value * Math.PI / 180;
}
async function geocodeAddress(address: string): Promise<Coordinate | null> {
try {
const query = encodeURIComponent(address + ", Switzerland");
const response = await fetch(
`https://nominatim.openstreetmap.org/search?q=${query}&format=json&limit=1`,
{
headers: {
'User-Agent': 'PostaciApp/1.0'
}
}
);
if (!response.ok) {
throw new Error('Geocoding request failed');
}
const data = await response.json();
if (data && data.length > 0) {
return {
lat: parseFloat(data[0].lat),
lon: parseFloat(data[0].lon)
};
}
return null;
} catch (error) {
console.error('Geocoding error:', error);
return null;
}
}
function optimizeRoute(points: AddressPoint[]): number[] {
if (points.length < 2) return points.map((_, i) => i);
const optimizedRoute: number[] = [0]; // Başlangıç noktası
const unvisited = points.slice(1).map((_, i) => i + 1);
while (unvisited.length > 0) {
const currentPoint = points[optimizedRoute[optimizedRoute.length - 1]];
let nearestPoint = -1;
let nearestIndex = -1;
let minScore = Infinity;
// Tüm ziyaret edilmemiş noktaları değerlendir
for (let i = 0; i < unvisited.length; i++) {
const pointIndex = unvisited[i];
const candidatePoint = points[pointIndex];
// Mesafe ve şehir bazlı skor hesapla
const distance = calculateDistance(currentPoint.coordinate, candidatePoint.coordinate);
// Şehir aynıysa ekstra bonus ver (daha düşük skor daha iyi)
const cityScore = currentPoint.city === candidatePoint.city ? 0 : 2;
// Toplam skoru hesapla (mesafe + şehir skoru)
const totalScore = distance + cityScore;
// En düşük skorlu noktayı bul
if (totalScore < minScore) {
minScore = totalScore;
nearestPoint = pointIndex;
nearestIndex = i;
}
}
if (nearestPoint !== -1) {
optimizedRoute.push(nearestPoint);
unvisited.splice(nearestIndex, 1);
// Debug bilgisi
console.log(`Eklenen nokta: ${points[nearestPoint].street}, ${points[nearestPoint].city}`);
console.log(`Mesafe skoru: ${minScore}`);
}
}
return optimizedRoute;
}
export async function POST(request: Request) {
try {
// Oturum kontrolü
const session = await getServerSession(authOptions);
if (!session?.user?.email) {
return new Response(
JSON.stringify({
success: false,
error: 'Oturum gerekli'
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}
const { addresses, routeId } = await request.json();
console.log('Gelen adresler:', addresses);
if (!Array.isArray(addresses) || addresses.length < 2) {
return new Response(
JSON.stringify({
success: false,
error: 'En az 2 adres gerekli'
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Adresleri geocode et
const points: AddressPoint[] = [];
for (let i = 0; i < addresses.length; i++) {
const address = addresses[i];
const parts = address.split(',');
const cityMatch = parts[1]?.match(/\d{4}\s+([^\d]+)/);
const city = cityMatch ? cityMatch[1].trim() : '';
const coordinate = await geocodeAddress(address);
console.log(`Adres ${i + 1} koordinatları:`, { address, city, coordinate });
if (coordinate) {
points.push({
coordinate,
city,
street: parts[0].trim(),
originalIndex: i
});
}
// Rate limiting
if (i < addresses.length - 1) {
await new Promise(resolve => setTimeout(resolve, 1000));
}
}
console.log('Geocode edilmiş noktalar:', points);
if (points.length < 2) {
return new Response(
JSON.stringify({
success: false,
error: 'Yeterli sayıda geçerli adres bulunamadı'
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}
// Rotayı optimize et
const optimizedIndices = optimizeRoute(points);
console.log('Optimize edilmiş sıralama:', optimizedIndices);
// Doğrudan points dizisinden alarak optimizedRoute'u oluştur
const optimizedRoute = optimizedIndices.map(i => ({
index: i, // originalIndex yerine doğrudan sıra numarasını kullan
lat: points[i].coordinate.lat,
lon: points[i].coordinate.lon,
address: addresses[i] // points[i].originalIndex yerine doğrudan i kullan
}));
console.log('Optimize edilmiş rota:', optimizedRoute);
// Toplam mesafeyi hesapla
let totalDistance = 0;
for (let i = 0; i < optimizedRoute.length - 1; i++) {
totalDistance += calculateDistance(
{ lat: optimizedRoute[i].lat, lon: optimizedRoute[i].lon },
{ lat: optimizedRoute[i + 1].lat, lon: optimizedRoute[i + 1].lon }
);
}
// Tahmini süreyi hesapla (ortalama 30 km/saat hız varsayarak)
const estimatedDuration = (totalDistance / 30) * 60; // dakika cinsinden
// Rotanın durumunu güncelle
if (routeId) {
// Önce tüm adresleri getir
const existingAddresses = await prisma.route.findUnique({
where: { id: routeId },
include: { addresses: true }
});
if (existingAddresses) {
// Her optimize edilmiş adres için güncelleme yap
await Promise.all(optimizedRoute.map(async (route, newOrder) => {
const addressToUpdate = existingAddresses.addresses.find(
addr => addr.street === route.address.split(',')[0].trim()
);
if (addressToUpdate) {
await prisma.address.update({
where: { id: addressToUpdate.id },
data: {
order: newOrder,
latitude: route.lat,
longitude: route.lon
}
});
}
}));
// Route'un durumunu güncelle
await prisma.route.update({
where: { id: routeId },
data: { status: 'OPTIMIZED' }
});
}
}
return new Response(
JSON.stringify({
success: true,
data: {
route: optimizedRoute,
distance: Math.round(totalDistance * 10) / 10, // 1 ondalık basamak
duration: Math.round(estimatedDuration)
}
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
} catch (error) {
console.error('Route optimization error:', error);
return new Response(
JSON.stringify({
success: false,
error: 'Rota optimizasyonu sırasında bir hata oluştu'
}),
{
status: 200,
headers: { 'Content-Type': 'application/json' }
}
);
}
}