diff --git a/server/routes.ts b/server/routes.ts index 2140b7d..df0e994 100644 --- a/server/routes.ts +++ b/server/routes.ts @@ -4412,6 +4412,115 @@ export async function registerRoutes(app: Express): Promise { } }); + // ============= ROUTE OPTIMIZATION API (OSRM + TSP) ============= + + app.post("/api/optimize-route", isAuthenticated, async (req: any, res) => { + try { + const { coordinates } = req.body; + + // Validazione: array di coordinate [{lat, lon, id}] + if (!Array.isArray(coordinates) || coordinates.length < 2) { + return res.status(400).json({ + message: "Almeno 2 coordinate richieste per l'ottimizzazione" + }); + } + + // Verifica formato coordinate + for (const coord of coordinates) { + if (!coord.lat || !coord.lon || !coord.id) { + return res.status(400).json({ + message: "Ogni coordinata deve avere lat, lon e id" + }); + } + } + + // STEP 1: Calcola matrice distanze usando OSRM Table API + const coordsString = coordinates.map(c => `${c.lon},${c.lat}`).join(';'); + const osrmTableUrl = `https://router.project-osrm.org/table/v1/driving/${coordsString}?annotations=distance,duration`; + + const osrmResponse = await fetch(osrmTableUrl); + if (!osrmResponse.ok) { + throw new Error(`OSRM API error: ${osrmResponse.status}`); + } + + const osrmData = await osrmResponse.json(); + + if (osrmData.code !== 'Ok' || !osrmData.distances || !osrmData.durations) { + throw new Error("OSRM non ha restituito dati validi"); + } + + const distances = osrmData.distances; // Matrice NxN in metri + const durations = osrmData.durations; // Matrice NxN in secondi + + // STEP 2: Applica algoritmo TSP Nearest Neighbor + // Inizia dalla prima tappa (indice 0) + const n = coordinates.length; + const visited = new Set(); + const route: number[] = []; + let current = 0; + let totalDistance = 0; + let totalDuration = 0; + + visited.add(current); + route.push(current); + + // Trova sempre il vicino più vicino non visitato + for (let i = 1; i < n; i++) { + let nearest = -1; + let minDistance = Infinity; + + for (let j = 0; j < n; j++) { + if (!visited.has(j) && distances[current][j] < minDistance) { + minDistance = distances[current][j]; + nearest = j; + } + } + + if (nearest !== -1) { + totalDistance += distances[current][nearest]; + totalDuration += durations[current][nearest]; + visited.add(nearest); + route.push(nearest); + current = nearest; + } + } + + // Ritorna al punto di partenza (circuito chiuso) + totalDistance += distances[current][0]; + totalDuration += durations[current][0]; + + // STEP 3: Prepara risposta + const optimizedRoute = route.map(index => ({ + ...coordinates[index], + order: route.indexOf(index) + 1, + })); + + res.json({ + optimizedRoute, + totalDistanceMeters: Math.round(totalDistance), + totalDistanceKm: (totalDistance / 1000).toFixed(2), + totalDurationSeconds: Math.round(totalDuration), + totalDurationMinutes: Math.round(totalDuration / 60), + estimatedTimeFormatted: formatDuration(totalDuration), + }); + + } catch (error) { + console.error("Error optimizing route:", error); + res.status(500).json({ message: "Errore durante l'ottimizzazione del percorso" }); + } + }); + + // Helper per formattare durata in ore e minuti + function formatDuration(seconds: number): string { + const hours = Math.floor(seconds / 3600); + const minutes = Math.floor((seconds % 3600) / 60); + + if (hours > 0) { + return `${hours}h ${minutes}m`; + } + return `${minutes}m`; + } + const httpServer = createServer(app); return httpServer; }