false, 'error' => 'Action inconnue']); exit; } // --- Read POST body --- $body = file_get_contents('php://input'); $params = json_decode($body, true); if (!$params || empty($params['stops']) || count($params['stops']) < 2) { echo json_encode(['ok' => false, 'error' => 'Paramètres invalides (stops requis, min 2)']); exit; } $stops = $params['stops']; $fuelL100 = (float)($params['fuel_l100'] ?? 7); $fuelPrice= (float)($params['fuel_price'] ?? 1.85); // --- Load pre-indexed PHP database --- $tollsDataPath = __DIR__ . '/data/tolls.data.php'; $gpsPath = __DIR__ . '/data/toll_gps.json'; if (!file_exists($tollsDataPath)) { echo json_encode(['ok' => false, 'error' => 'Base de péages introuvable']); exit; } // Grâce à OPcache, le fichier est exécuté et stocké en RAM partagée $tollIndex = require $tollsDataPath; $gpsData = []; if (file_exists($gpsPath)) { $gpsData = json_decode(file_get_contents($gpsPath), true) ?? []; } // --- Helper: great-circle distance (km) --- function haversine(float $lat1, float $lng1, float $lat2, float $lng2): float { $R = 6371.0; $dLat = deg2rad($lat2 - $lat1); $dLng = deg2rad($lng2 - $lng1); $a = sin($dLat/2)**2 + cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * sin($dLng/2)**2; return $R * 2 * atan2(sqrt($a), sqrt(1 - $a)); } // --- Find plazas near a coordinate (within $maxKm) --- // Returns array of [plaza_name => distance_km] function plazasNear(array $gpsData, float $lat, float $lng, float $maxKm): array { $result = []; foreach ($gpsData as $name => $info) { $d = haversine($lat, $lng, $info['lat'], $info['lng']); if ($d <= $maxKm) { $result[$name] = $d; } } asort($result); return $result; } // --- Look up toll price for (op, entry, exit) with fallback --- function lookupToll(array $tollIndex, string $op, string $entry, string $exit): ?float { // Direct lookup if (isset($tollIndex[$op][$entry][$exit])) { return $tollIndex[$op][$entry][$exit]; } // Reversed if (isset($tollIndex[$op][$exit][$entry])) { return $tollIndex[$op][$exit][$entry]; } return null; } // --- OSRM route distance --- function getRouteDistanceKm(float $lat1, float $lng1, float $lat2, float $lng2): ?float { $url = sprintf( 'https://router.project-osrm.org/route/v1/driving/%f,%f;%f,%f?overview=false', $lng1, $lat1, $lng2, $lat2 ); $ctx = stream_context_create([ 'http' => [ 'timeout' => 10, 'header' => "User-Agent: HouseHub/1.0\r\n" ] ]); $resp = @file_get_contents($url, false, $ctx); if ($resp === false) return null; $data = json_decode($resp, true); if (!empty($data['routes'][0]['distance'])) { return (float)$data['routes'][0]['distance'] / 1000.0; } return null; } // --- Main: process each consecutive stop pair --- $segments = []; $totalKm = 0.0; $totalToll = 0.0; for ($i = 0; $i < count($stops) - 1; $i++) { $orig = $stops[$i]; $dest = $stops[$i + 1]; $oLat = (float)$orig['lat']; $oLng = (float)$orig['lng']; $dLat = (float)$dest['lat']; $dLng = (float)$dest['lng']; $fromName = $orig['name'] ?? "Étape " . ($i + 1); $toName = $dest['name'] ?? "Étape " . ($i + 2); // Distance via OSRM $distKm = getRouteDistanceKm($oLat, $oLng, $dLat, $dLng); if ($distKm === null) { // Fallback: straight-line distance * 1.25 $distKm = haversine($oLat, $oLng, $dLat, $dLng) * 1.25; } $totalKm += $distKm; // --- Find toll --- $segToll = 0.0; $entryPlaza = null; $exitPlaza = null; $tollNote = null; if (!empty($gpsData)) { // Find plazas near origin (80km) and destination (80km) $nearOrig = plazasNear($gpsData, $oLat, $oLng, 80.0); $nearDest = plazasNear($gpsData, $dLat, $dLng, 80.0); $bestToll = null; $bestDistSum = PHP_FLOAT_MAX; $bestEntry = null; $bestExit = null; $bestOp = null; // Try each operator foreach ($tollIndex as $op => $opEntries) { // Find candidate entry plazas (near origin, present in this operator) $candidateEntries = []; foreach ($nearOrig as $pName => $dist) { if (isset($opEntries[$pName]) || isset(array_flip(array_keys($opEntries))[$pName])) { // Plaza exists as entry in this operator if (isset($opEntries[$pName])) { $candidateEntries[$pName] = $dist; } } // Also check if it's an exit plaza (reversed lookup later) // Include any plaza from this operator that is within range } // Build set of all plazas for this operator $opPlazas = []; foreach ($opEntries as $entry => $exits) { $opPlazas[$entry] = true; foreach ($exits as $exit => $price) { $opPlazas[$exit] = true; } } // Filter nearOrig and nearDest to only plazas in this operator $opNearOrig = array_intersect_key($nearOrig, $opPlazas); $opNearDest = array_intersect_key($nearDest, $opPlazas); if (empty($opNearOrig) || empty($opNearDest)) continue; // Try best 5 entry and best 5 exit candidates $topEntries = array_slice($opNearOrig, 0, 5, true); $topExits = array_slice($opNearDest, 0, 5, true); foreach ($topEntries as $ePlaza => $eDist) { foreach ($topExits as $xPlaza => $xDist) { if ($ePlaza === $xPlaza) continue; $price = lookupToll($tollIndex, $op, $ePlaza, $xPlaza); if ($price !== null) { $distSum = $eDist + $xDist; if ($distSum < $bestDistSum) { $bestDistSum = $distSum; $bestToll = $price; $bestEntry = $ePlaza; $bestExit = $xPlaza; $bestOp = $op; } } } } } if ($bestToll !== null) { $segToll = $bestToll; $entryPlaza = $bestEntry; $exitPlaza = $bestExit; } else { $tollNote = 'péages non estimés pour ce trajet'; } } else { $tollNote = 'base GPS des péages non disponible'; } $totalToll += $segToll; $seg = [ 'from' => $fromName, 'to' => $toName, 'distance_km' => round($distKm, 1), 'toll' => round($segToll, 2), ]; if ($entryPlaza) $seg['entry_plaza'] = $entryPlaza; if ($exitPlaza) $seg['exit_plaza'] = $exitPlaza; if ($bestOp) $seg['op'] = $bestOp; if ($tollNote) $seg['note'] = $tollNote; $segments[] = $seg; } $fuelCost = round(($totalKm / 100.0) * $fuelL100 * $fuelPrice, 2); $grandTotal = round($totalToll + $fuelCost, 2); echo json_encode([ 'ok' => true, 'segments' => $segments, 'total_km' => round($totalKm, 1), 'total_toll' => round($totalToll, 2), 'fuel_cost' => $fuelCost, 'grand_total' => $grandTotal, ], JSON_UNESCAPED_UNICODE);