Update: 2026-06-21 02:07:00

This commit is contained in:
Hamza-Ayed
2026-06-21 02:07:00 +03:00
parent af3dcae5b7
commit b2fae9ec66
23 changed files with 1412 additions and 210 deletions

View File

@@ -1,6 +1,7 @@
<?php
require_once __DIR__ . '/../../connect.php'; // يفترض أن هذا الملف ينشئ $con و $con_tracking
//getSpeed.php
require_once __DIR__ . '/../../connect.php'; // Provides $con, $redisLocation, $encryptionHelper, jsonSuccess/jsonError
// getSpeed.php (Redis-Optimized Version)
try {
// 1) قراءة والتحقق من الإحداثيات
$southwestLat = filterRequest("southwestLat");
@@ -13,89 +14,107 @@ try {
exit;
}
$freshSeconds = 180; // 3 دقائق
// =================================================================
// الخطوة 1: جلب المواقع والمعرفات من قاعدة بيانات التتبع
// الخطوة 1: البحث في Redis باستخدام تقنية GeoRadius (أسرع 100 مرة من MySQL)
// =================================================================
$boundingBoxWKT = sprintf(
'POLYGON((%f %f, %f %f, %f %f, %f %f, %f %f))',
$southwestLon, $southwestLat,
$northeastLon, $southwestLat,
$northeastLon, $northeastLat,
$southwestLon, $northeastLat,
$southwestLon, $southwestLat
);
$centerLat = ($southwestLat + $northeastLat) / 2.0;
$centerLon = ($southwestLon + $northeastLon) / 2.0;
// نجلب مجموعة من المرشحين المحتملين للفلترة والترتيب لاحقاً
$sql_locations = "
SELECT driver_id, latitude, longitude, heading, speed, status, updated_at
FROM car_locations
WHERE
ST_CONTAINS(ST_GeomFromText(:boundingBox, 4326), location_point)
AND status = 'off'
AND updated_at >= NOW() - INTERVAL :freshSeconds SECOND
ORDER BY updated_at DESC
LIMIT 100; -- نجلب 100 مرشح محتمل
";
// حساب تقريبي لنصف القطر بالكيلومترات بناءً على الصندوق (Bounding Box)
$earth_radius = 6371;
$dLat = deg2rad($southwestLat - $centerLat);
$dLon = deg2rad($southwestLon - $centerLon);
$a = sin($dLat/2) * sin($dLat/2) + cos(deg2rad($centerLat)) * cos(deg2rad($southwestLat)) * sin($dLon/2) * sin($dLon/2);
$c = 2 * asin(sqrt($a));
$radiusKm = max(1, ($earth_radius * $c) + 1);
$stmt_locations = $con_tracking->prepare($sql_locations);
$stmt_locations->bindValue(':boundingBox', $boundingBoxWKT);
$stmt_locations->bindValue(':freshSeconds', $freshSeconds, PDO::PARAM_INT);
$stmt_locations->execute();
$locations = $stmt_locations->fetchAll(PDO::FETCH_ASSOC);
// سحب معرفات السائقين المتاحين حول الراكب من سيرفر المواقع (Dual Redis)
$driver_ids = [];
if (isset($redisLocation)) {
$redisResults = $redisLocation->geoRadius('geo:drivers:available', $centerLon, $centerLat, $radiusKm, 'km');
if ($redisResults) {
foreach ($redisResults as $res) {
// قد يرجع Redis مصفوفة داخلية إذا تم تمرير خيارات، ولكن بالوضع الافتراضي يرجع سلاسل نصية
$driver_ids[] = is_array($res) ? $res[0] : $res;
}
}
}
if (!$locations) {
if (empty($driver_ids)) {
jsonError("No car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 2: تجميع معرفات السائقين (driver_id)
// الخطوة 2: جلب تفاصيل الموقع الدقيقة والسرعة لكل سائق من Redis Pipeline
// =================================================================
$driver_ids = array_column($locations, 'driver_id');
$pipe = $redisLocation->pipeline();
foreach ($driver_ids as $id) {
$pipe->hGetAll("driver:profile:$id");
}
$profiles = $pipe->exec();
// =================================================================
// الخطوة 3: جلب البيانات الثابتة من القاعدة الأساسية وتطبيق الفلاتر الإضافية
// =================================================================
$drivers_info = [];
if (!empty($driver_ids)) {
$placeholders = implode(',', array_fill(0, count($driver_ids), '?'));
$locations = [];
foreach ($driver_ids as $index => $id) {
$profile = $profiles[$index];
if (!$profile || empty($profile['lat'])) continue;
// هنا نطبق الشروط الخاصة بهذا السكريبت (موديل السيارة > 2000)
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
-- AND COALESCE(cr.year, 0) > 2000 -- ⭐ الشرط الخاص بهذا السكريبت
-- AND (cr.make NOT LIKE '%دراج%' AND cr.model NOT LIKE '%دراج%')
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
// تجاهل المواقع القديمة (أكثر من 3 دقائق)
$updatedAt = $profile['updated_at'] ?? 0;
if (time() - $updatedAt > 180) continue;
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
$locations[] = [
'driver_id' => $id,
'latitude' => $profile['lat'],
'longitude' => $profile['lng'],
'heading' => $profile['heading'] ?? 0,
'speed' => $profile['speed'] ?? 0,
'status' => 'off', // متواجدون في geo:drivers:available
'updated_at' => date('Y-m-d H:i:s', $updatedAt)
];
}
// تحويل المصفوفة لتسهيل عملية الدمج لاحقاً
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
if (empty($locations)) {
jsonError("No fresh car locations found in the specified area.");
exit;
}
// =================================================================
// الخطوة 4: دمج النتائج في PHP
// الخطوة 3: جلب البيانات الثابتة (السيارة، الموديل، التقييم) من MySQL
// =================================================================
$drivers_info = [];
$valid_driver_ids = array_column($locations, 'driver_id');
$placeholders = implode(',', array_fill(0, count($valid_driver_ids), '?'));
$sql_drivers_info = "
SELECT
d.id AS driver_id, d.phone, d.email, d.birthdate, d.first_name, d.last_name, d.gender, d.maritalStatus,
cr.make, cr.model, cr.color, cr.color_hex, cr.year,
dt.token,
COALESCE(rdAvg.ratingDriver, 0) AS ratingDriver,
COALESCE(rdAvg.ratingCount, 0) AS ratingCount
FROM driver d
LEFT JOIN CarRegistration cr ON cr.driverID = d.id
LEFT JOIN driverToken dt ON dt.captain_id = d.id
LEFT JOIN (
SELECT driver_id, AVG(rating) AS ratingDriver, COUNT(id) AS ratingCount
FROM ratingDriver
GROUP BY driver_id
) rdAvg ON rdAvg.driver_id = d.id
WHERE d.id IN ($placeholders)
AND (cr.model NOT LIKE '%Van%' AND cr.make NOT LIKE '%Van%')
";
$stmt_drivers_info = $con->prepare($sql_drivers_info);
$stmt_drivers_info->execute($valid_driver_ids);
$drivers_info_raw = $stmt_drivers_info->fetchAll(PDO::FETCH_ASSOC);
foreach ($drivers_info_raw as $driver) {
$drivers_info[$driver['driver_id']] = $driver;
}
// =================================================================
// الخطوة 4: دمج النتائج والترتيب
// =================================================================
$final_results = [];
foreach ($locations as $location) {
@@ -105,9 +124,6 @@ try {
}
}
// =================================================================
// الخطوة 5: تطبيق الترتيب والحد النهائي في PHP
// =================================================================
usort($final_results, function ($a, $b) {
if ($a['ratingDriver'] != $b['ratingDriver']) {
return $b['ratingDriver'] <=> $a['ratingDriver'];
@@ -121,14 +137,14 @@ try {
$limited_results = array_slice($final_results, 0, 10);
if (empty($limited_results)) {
jsonError("No cars matching the specific criteria (year > 2000) found.");
jsonError("No cars matching the specific criteria found.");
exit;
}
// =================================================================
// الخطوة 6: فك التشفير وحساب العمر (بدون تغيير)
// الخطوة 5: فك التشفير وحساب العمر
// =================================================================
$fieldsToDecrypt = [ 'phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin' ];
$fieldsToDecrypt = ['phone','email','gender','birthdate', 'first_name','last_name', 'token','car_plate','vin'];
foreach ($limited_results as &$row) {
foreach ($fieldsToDecrypt as $field) {
if (isset($row[$field]) && !empty($row[$field])) {
@@ -151,7 +167,7 @@ try {
jsonSuccess($limited_results);
} catch (PDOException $e) {
error_log("[getSpeed.php] " . $e->getMessage());
error_log("[getSpeed.php PDO] " . $e->getMessage());
jsonError("An internal error occurred. Please try again later.");
} catch (Throwable $e) {
error_log("[getSpeed.php] " . $e->getMessage());