'Missing required parameters: awal, akhir, tid', 'required_format' => [ 'awal' => 'YYYY-MM-DD HH:MM:SS', 'akhir' => 'YYYY-MM-DD HH:MM:SS', 'tid' => 'tracking_id' ] ]); exit; } // Sanitize input parameters $awal = trim($_GET['awal']); $akhir = trim($_GET['akhir']); $tid = trim($_GET['tid']); $chunkSize = isset($_GET['chunk_size']) ? (int)$_GET['chunk_size'] : 1000; // Debug: Log sanitized parameters // error_log("History Total KM API - Params: awal={$awal}, akhir={$akhir}, tid={$tid}, chunk_size={$chunkSize}"); // Validasi format datetime $awalTime = DateTime::createFromFormat('Y-m-d H:i:s', $awal); $akhirTime = DateTime::createFromFormat('Y-m-d H:i:s', $akhir); if (!$awalTime || !$akhirTime) { http_response_code(400); echo json_encode([ 'error' => 'Invalid datetime format. Use: YYYY-MM-DD HH:MM:SS', 'received' => ['awal' => $awal, 'akhir' => $akhir] ]); exit; } // Validasi range waktu (maksimal 30 hari untuk big data protection) $diffDays = $akhirTime->diff($awalTime)->days; if ($diffDays > 30) { http_response_code(400); echo json_encode([ 'error' => 'Date range too large for big data processing. Maximum 30 days allowed.', 'requested_days' => $diffDays, 'suggestion' => 'Use smaller date ranges for better performance' ]); exit; } // Get GMT setting $gmt = isset($_SESSION['GMT']) ? $_SESSION['GMT'] : 7; // Debug: Log GMT and validation // error_log("History Total KM API - GMT: {$gmt}, Date range: {$diffDays} days"); // OPTIMIZED SQL QUERY untuk Big Data dengan SQL Server compatibility // Menggunakan GROUP BY untuk mengurangi duplicate coordinates // Tanpa index hints yang mungkin tidak ada $sql = "SELECT lat, lon, speed, status, MIN(time) as first_time FROM tblocations WHERE DATEADD(HOUR, {$gmt}, time) BETWEEN ? AND ? AND tid = ? AND fixGPS = '1' AND oilLevel = 0 GROUP BY lat, lon, speed, status ORDER BY MIN(time) ASC"; // Debug: Log optimized SQL // error_log("History Total KM API - Optimized SQL: " . $sql); // Execute query dengan error handling yang lebih baik $pdo = Database::getConnection(); // Jangan set timeout attribute untuk SQL Server compatibility // $pdo->setAttribute(PDO::ATTR_TIMEOUT, 300); // Tidak support di SQL Server $stmt = $pdo->prepare($sql); $stmt->execute([$awal, $akhir, $tid]); // Execute query dan process semua data sekaligus (lebih kompatibel dengan SQL Server) $pdo = Database::getConnection(); $stmt = $pdo->prepare($sql); $stmt->execute([$awal, $akhir, $tid]); // Fetch semua data sekaligus untuk SQL Server compatibility $allData = $stmt->fetchAll(PDO::FETCH_ASSOC); $totalRecords = count($allData); $uniqueCoordinates = $totalRecords; // Debug: Log query result // error_log("History Total KM API - Query result count: " . $totalRecords); // Process data untuk menghitung jarak $totalDistance = 0.0; $previousLat = null; $previousLon = null; $validDistanceCount = 0; // Debug: Log processing start // error_log("History Total KM API - Starting distance calculation"); foreach ($allData as $index => $record) { // Skip jika koordinat tidak valid if ($record['lat'] == 0 || $record['lon'] == 0) { continue; } // Hitung jarak dari koordinat sebelumnya if ($previousLat !== null && $previousLon !== null) { // Hitung distance menggunakan Haversine formula $distance = SessionHelper::calculateDistance( $previousLat, $previousLon, $record['lat'], $record['lon'] ); // Filter noise: hanya tambahkan jika jarak > 0.01 km (10 meter) if ($distance > 0.01) { $totalDistance += $distance; $validDistanceCount++; } } $previousLat = $record['lat']; $previousLon = $record['lon']; // Debug: Log sample processed record // if ($index < 3) { // error_log("History Total KM API - Sample record {$index}: lat={$record['lat']}, lon={$record['lon']}"); // } } // Memory cleanup unset($allData); // Performance monitoring $endTime = microtime(true); $endMemory = memory_get_usage(true); $processingTime = round($endTime - $startTime, 2); $memoryUsed = round(($endMemory - $startMemory) / 1024 / 1024, 2); // Debug: Log final statistics // error_log("History Total KM API - Processing completed:"); // error_log(" - Total distance: " . number_format($totalDistance, 2) . " km"); // error_log(" - Total records: " . $totalRecords); // error_log(" - Unique coordinates: " . $uniqueCoordinates); // error_log(" - Valid distance calculations: " . $validDistanceCount); // error_log(" - Processing time: " . $processingTime . " seconds"); // error_log(" - Memory used: " . $memoryUsed . " MB"); // Format response $response = [ 'total_km' => number_format($totalDistance, 2, '.', ',') . " km", 'total_km_numeric' => round($totalDistance, 2), 'total_records' => $totalRecords, 'unique_coordinates' => $uniqueCoordinates, 'valid_distance_calculations' => $validDistanceCount, 'processing_time' => $processingTime . " seconds", 'memory_usage' => $memoryUsed . " MB", 'date_range_days' => $diffDays // Debug info (uncomment jika perlu): // 'debug_info' => [ // 'start_memory' => number_format($startMemory / 1024 / 1024, 2) . " MB", // 'end_memory' => number_format($endMemory / 1024 / 1024, 2) . " MB", // 'peak_memory' => number_format(memory_get_peak_usage(true) / 1024 / 1024, 2) . " MB", // 'chunk_size' => $chunkSize, // 'gmt_offset' => $gmt // ] ]; // Set response headers header('Content-Type: application/json'); // Return optimized response echo json_encode($response); } catch (Exception $e) { // Log error dengan performance info $errorTime = microtime(true); $errorMemory = memory_get_usage(true); error_log("History Total KM API Error: " . $e->getMessage()); error_log("Error occurred at: " . round($errorTime - $startTime, 2) . " seconds"); error_log("Memory at error: " . number_format($errorMemory / 1024 / 1024, 2) . " MB"); error_log("Error trace: " . $e->getTraceAsString()); http_response_code(500); echo json_encode([ 'error' => 'Failed to calculate total distance: ' . $e->getMessage(), 'processing_time' => round($errorTime - $startTime, 2) . " seconds", 'memory_usage' => number_format(($errorMemory - $startMemory) / 1024 / 1024, 2) . " MB" // Debug version dengan detail error: // 'error_details' => [ // 'message' => $e->getMessage(), // 'file' => $e->getFile(), // 'line' => $e->getLine(), // 'trace' => $e->getTraceAsString() // ], // 'performance_info' => [ // 'processing_time' => round($errorTime - $startTime, 2) . " seconds", // 'memory_used' => number_format(($errorMemory - $startMemory) / 1024 / 1024, 2) . " MB", // 'peak_memory' => number_format(memory_get_peak_usage(true) / 1024 / 1024, 2) . " MB" // ] ]); } finally { // Cleanup memory gc_collect_cycles(); }