Add route optimization to improve patrol efficiency

Implement a new API endpoint that uses OSRM and the Nearest Neighbor algorithm to optimize delivery routes, calculating distances, durations, and providing an ordered list of stops.

Replit-Commit-Author: Agent
Replit-Commit-Session-Id: e0b5b11c-5b75-4389-8ea9-5f3cd9332f88
Replit-Commit-Checkpoint-Type: intermediate_checkpoint
Replit-Commit-Screenshot-Url: https://storage.googleapis.com/screenshot-production-us-central1/6d543d2c-20b9-4ea6-93fe-70fe9b1d9f80/e0b5b11c-5b75-4389-8ea9-5f3cd9332f88/N0pfy8w
This commit is contained in:
marco370 2025-10-25 08:53:50 +00:00
parent efa056dd98
commit 5c22ec14f1

View File

@ -4412,6 +4412,115 @@ export async function registerRoutes(app: Express): Promise<Server> {
}
});
// ============= 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<number>();
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;
}