275 lines
7.8 KiB
TypeScript
275 lines
7.8 KiB
TypeScript
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' }
|
||
}
|
||
);
|
||
}
|
||
}
|