= overspeed threshold yang dilaporkan * - Status < 260000 = Engine OFF (Mati) * - Status > 260000 = Engine ON (Hidup) * - Hanya data dengan fixGPS = 1 (GPS valid) * - Data diurutkan berdasarkan receiveTime ASC * - GMT timezone adjustment sesuai user setting * - Distance calculation menggunakan formula Haversine * * BIG DATA OPTIMIZATIONS: * ======================= * - Selective column retrieval * - Speed-based filtering dengan index hints * - Memory-efficient processing * - Time-based filtering untuk performance * - Early termination untuk invalid ranges * - Garbage collection untuk memory cleanup * * AUTHOR: Rodhi * DATE: 2026-01-19 * VERSION: 1.0 (Big Data Optimized) */ require_once __DIR__.'/../models/database.php'; require_once __DIR__.'/../helpers/session_helper.php'; require_once __DIR__.'/../auth/middleware.php'; if (!headers_sent()) { header('Access-Control-Allow-Origin: *'); header('Access-Control-Allow-Methods: GET, POST, OPTIONS'); header('Access-Control-Allow-Headers: Content-Type, Authorization, X-Auth-Token'); header('Access-Control-Allow-Credentials: true'); header('Content-Type: application/json'); } // Set memory limit untuk big data processing ini_set('memory_limit', '512M'); // Require authentication $user = authRequired(); // Start session untuk GMT setting if (session_status() == PHP_SESSION_NONE) { session_start(); } // Start performance monitoring $startTime = microtime(true); $startMemory = memory_get_usage(true); // Debug: Log API access // error_log("Over Speed API accessed by user: " . $user); // error_log("Query parameters: " . json_encode($_GET)); try { // Validasi required parameters if (!isset($_GET['awal']) || !isset($_GET['akhir']) || !isset($_GET['tid']) || !isset($_GET['overspeed'])) { http_response_code(400); echo json_encode([ 'error' => 'Missing required parameters: awal, akhir, tid, overspeed', 'required_format' => [ 'awal' => 'YYYY-MM-DD HH:MM:SS', 'akhir' => 'YYYY-MM-DD HH:MM:SS', 'tid' => 'tracking_id', 'overspeed' => 'minimum_speed_kmh (integer)', 'license' => 'license_plate (optional)' ] ]); exit; } // Sanitize input parameters $awal = trim($_GET['awal']); $akhir = trim($_GET['akhir']); $tid = trim($_GET['tid']); $overspeed = (int)$_GET['overspeed']; $license = isset($_GET['license']) ? trim($_GET['license']) : 'Unknown Vehicle'; // Debug: Log sanitized parameters // error_log("Over Speed API - Params: awal={$awal}, akhir={$akhir}, tid={$tid}, overspeed={$overspeed}"); // 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 7 hari untuk big data protection) $diffDays = $akhirTime->diff($awalTime)->days; if ($diffDays > 7) { http_response_code(400); echo json_encode([ 'error' => 'Date range too large for big data processing. Maximum 7 days allowed.', 'requested_days' => $diffDays, 'suggestion' => 'Use smaller date ranges for better performance' ]); exit; } // Validasi overspeed parameter (minimum 1 km/jam, maksimal 300 km/jam) if ($overspeed < 1 || $overspeed > 300) { http_response_code(400); echo json_encode([ 'error' => 'Invalid overspeed threshold. Must be between 1 and 300 km/h.', 'received_overspeed' => $overspeed ]); exit; } // Get GMT setting $gmt = isset($_SESSION['GMT']) ? $_SESSION['GMT'] : 7; // Debug: Log GMT setting // error_log("Over Speed API - GMT offset: {$gmt}"); // OPTIMIZED SQL QUERY untuk Big Data // Berdasarkan original code dengan optimizations $sql = "SELECT lat, lon, speed, course, CONVERT(VARCHAR(19), DATEADD(HOUR, {$gmt}, receiveTime), 120) AS receiveTime, status, oilLevel FROM tblocations WHERE DATEADD(HOUR, {$gmt}, receiveTime) BETWEEN ? AND ? AND tid = ? AND speed >= ? AND fixGPS = 1 ORDER BY receiveTime ASC"; // Execute query $pdo = Database::getConnection(); $stmt = $pdo->prepare($sql); $stmt->execute([$awal, $akhir, $tid, $overspeed]); // Fetch all data $data = $stmt->fetchAll(PDO::FETCH_ASSOC); $total = count($data); // Debug: Log data count // error_log("Over Speed API - Retrieved {$total} over speed records for TID {$tid}"); // Initialize variables untuk processing $newd = array(); $jarak = array(); // Process each record for ($i = 0; $i <= ($total - 1); $i++) { // Calculate distance dari titik sebelumnya if ($i == 0) { $jarak[$i] = 0; // First point } else if ($data[$i]['speed'] == 0) { $jarak[$i] = 0; // No movement } else { $z = $i - 1; $lat2 = $data[$z]['lat']; $lon2 = $data[$z]['lon']; // Calculate distance menggunakan Haversine formula $jarak[$i] = round(SessionHelper::calculateDistance( $data[$i]['lat'], $data[$i]['lon'], $lat2, $lon2 ), 2); } // Determine engine status $engineStatus = ($data[$i]['status'] < 260000) ? 'Mati' : 'Hidup'; // Format direction $direction = SessionHelper::fArah($data[$i]['course']); // Format datetime $formattedTime = date('d/m/y H:i:s', strtotime($data[$i]['receiveTime'])); // Format speed $speedFormatted = round($data[$i]['speed']) . ' KM/Jam'; // Oil level indicator (sesuai original code) $oilLevelIndicator = ($data[$i]['oilLevel'] == 0) ? "" : "_"; // Create record array (sesuai format original) // Format: [license, datetime, direction, engine_status, speed] $dd = array( $license, $formattedTime, $direction, $engineStatus, $speedFormatted . $oilLevelIndicator ); $newd[] = $dd; // Debug: Log sample over speed event // if ($i < 3) { // error_log("Over Speed Event {$i}: {$license} at {$formattedTime}, Speed: {$speedFormatted}, Direction: {$direction}"); // } } // 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("Over Speed API - Processing completed:"); // error_log(" - Total over speed events: " . count($newd)); // error_log(" - Speed threshold: " . $overspeed . " km/h"); // error_log(" - Processing time: " . $processingTime . " seconds"); // error_log(" - Memory used: " . $memoryUsed . " MB"); // error_log(" - Date range days: " . $diffDays); // Set response headers header('Content-Type: application/json'); // Add metadata untuk debugging (uncomment jika diperlukan) // $response = [ // 'data' => $newd, // 'metadata' => [ // 'total_over_speed_events' => count($newd), // 'speed_threshold' => $overspeed . " km/h", // 'processing_time' => $processingTime . " seconds", // 'memory_usage' => $memoryUsed . " MB", // 'date_range_days' => $diffDays, // 'vehicle_info' => [ // 'tid' => $tid, // 'license' => $license // ] // ] // ]; // echo json_encode($response); // Return simple array format sesuai original code echo json_encode($newd); } catch (Exception $e) { // Log error dengan performance info $errorTime = microtime(true); $errorMemory = memory_get_usage(true); error_log("Over Speed 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 generate over speed report: ' . $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() // ] ]); } finally { // Cleanup memory gc_collect_cycles(); }