Files
intaleq_v2/app/Http/Controllers/TrackingController.php
2026-04-24 15:12:12 +03:00

234 lines
8.1 KiB
PHP

<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Http\JsonResponse;
use Illuminate\Support\Facades\DB;
/**
* Tracking Controller
* Replaces: ride/rides/get_driver_location.php, public_track_location.php, getRealTimeHeatmap.php
*/
/**
* متحكم التتبع (Tracking Controller)
*
* الغرض من الملف:
* توفير بيانات المواقع الجغرافية الحية للسائقين والركاب.
*
* كيفية العمل:
* 1. يجلب إحداثيات السائق من قاعدة بيانات التتبع (tracking) لعرضها للراكب.
* 2. يوفر بيانات "خريطة الحرارة" (Heatmap) لمعرفة أماكن تركز الطلبات.
* 3. يتيح تتبع الرحلة بشكل علني (Public Tracking) عبر رابط خارجي.
*/
class TrackingController extends Controller
{
/** GET /v2/tracking/driver/{rideId} */
public function driverLocation(Request $request, int $rideId): JsonResponse
{
$ride = DB::connection('ride')->table('ride')
->where('id', $rideId)
->whereIn('status', ['Applied', 'Arrived', 'Begin'])
->first();
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not active'], 404);
}
$location = DB::connection('tracking')->table('car_locations')
->where('driver_id', $ride->driver_id)
->first();
if (!$location) {
return response()->json(['status' => 'failure', 'message' => 'Driver location not available'], 404);
}
return response()->json([
'status' => 'success',
'data' => [
'latitude' => $location->latitude,
'longitude' => $location->longitude,
'heading' => $location->heading,
'speed' => $location->speed,
'updated_at' => $location->updated_at,
],
]);
}
/**
* GET /v2/tracking/public/{rideId}?hash=XXX
* Public tracking link for parents/friends — uses HMAC hash instead of auth
*/
public function publicTrack(Request $request, int $rideId): JsonResponse
{
$hash = $request->input('hash');
if (!$hash) {
return response()->json(['status' => 'failure', 'message' => 'Missing hash'], 400);
}
// Verify hash: HMAC-SHA256(ride_id, secret_salt)
$expectedHash = hash_hmac('sha256', (string) $rideId, config('intaleq.secret_salt_parent'));
if (!hash_equals($expectedHash, $hash)) {
return response()->json(['status' => 'failure', 'message' => 'Invalid hash'], 403);
}
$ride = DB::connection('ride')->table('ride')
->where('id', $rideId)
->whereIn('status', ['Applied', 'Arrived', 'Begin'])
->first();
if (!$ride) {
return response()->json(['status' => 'failure', 'message' => 'Ride not active'], 404);
}
$location = DB::connection('tracking')->table('car_locations')
->where('driver_id', $ride->driver_id)
->first();
return response()->json([
'status' => 'success',
'data' => [
'latitude' => $location->latitude ?? null,
'longitude' => $location->longitude ?? null,
'heading' => $location->heading ?? null,
'ride_status' => $ride->status,
],
]);
}
/**
* GET /v2/tracking/heatmap
* المطورة لتطابق V1 مع تحسين الأداء
*/
public function heatmap(Request $request): JsonResponse
{
$precision = 2; // دقة الشبكة (~1 كم)
$grid = [];
// 1. جلب طلبات الانتظار (Waiting) من قاعدة البيانات الأساسية
$waitingRides = DB::connection('primary')->table('waitingRides')
->select('start_lat', 'start_lng')
->whereIn('status', ['wait', 'waiting'])
->get();
foreach ($waitingRides as $ride) {
$this->addToGrid($grid, $ride->start_lat, $ride->start_lng, $precision, 5);
}
// 2. جلب الطلبات الضائعة (Timeout/Cancelled) من خادم الرحلات - آخر 20 دقيقة
$missedRides = DB::connection('ride')->table('ride')
->select('start_location')
->whereIn('status', ['timeout', 'cancelled_no_driver_found'])
->where('created_at', '>=', now()->subMinutes(20))
->get();
foreach ($missedRides as $ride) {
$parts = explode(',', $ride->start_location);
if (count($parts) == 2) {
$this->addToGrid($grid, $parts[0], $parts[1], $precision, 8);
}
}
// 3. جلب الطلبات النشطة (Active) - آخر 15 دقيقة
$activeRides = DB::connection('ride')->table('ride')
->select('start_location')
->where('created_at', '>=', now()->subMinutes(15))
->whereNotIn('status', ['timeout', 'cancelled_no_driver_found'])
->get();
foreach ($activeRides as $ride) {
$parts = explode(',', $ride->start_location);
if (count($parts) == 2) {
$this->addToGrid($grid, $parts[0], $parts[1], $precision, 1);
}
}
// 4. معالجة البيانات النهائية (التصنيف والـ Surge)
$finalData = [];
foreach ($grid as $cell) {
$score = $cell['score'];
$count = $cell['count'];
$intensity = 'normal';
$surge = 1.0;
if ($score >= 15 || $count >= 5) {
$intensity = 'high';
$surge = 1.5;
} elseif ($score >= 8 || $count >= 3) {
$intensity = 'medium';
$surge = 1.2;
}
$finalData[] = [
'lat' => $cell['lat'],
'lng' => $cell['lng'],
'count' => $count,
'intensity' => $intensity,
'surge' => $surge
];
}
return response()->json([
'status' => 'success',
'data' => $finalData
]);
}
/** دالة مساعدة لتجميع النقاط في الشبكة */
private function addToGrid(&$grid, $lat, $lng, $precision, $weight): void
{
if (empty($lat) || empty($lng)) return;
$rLat = round((float)$lat, $precision);
$rLng = round((float)$lng, $precision);
$key = "{$rLat},{$rLng}";
if (!isset($grid[$key])) {
$grid[$key] = ['lat' => $rLat, 'lng' => $rLng, 'count' => 0, 'score' => 0];
}
$grid[$key]['count']++;
$grid[$key]['score'] += $weight;
}
/** GET /v2/tracking/captain-stats */
public function captainStats(Request $request): JsonResponse
{
$driverId = $request->attributes->get('_jwt_user_id');
$totalRides = DB::connection('ride')->table('ride')
->where('driver_id', $driverId)
->where('status', 'finish')
->count();
$todayRides = DB::connection('ride')->table('ride')
->where('driver_id', $driverId)
->where('status', 'finish')
->whereDate('rideTimeFinish', today())
->count();
$todayEarnings = DB::connection('ride')->table('ride')
->where('driver_id', $driverId)
->where('status', 'finish')
->whereDate('rideTimeFinish', today())
->sum('price_for_driver');
$workHours = DB::connection('tracking')->table('driver_daily_summary')
->where('driver_id', $driverId)
->where('date', today()->toDateString())
->value('total_seconds') ?? 0;
return response()->json([
'status' => 'success',
'data' => [
'total_rides' => $totalRides,
'today_rides' => $todayRides,
'today_earnings' => round($todayEarnings, 2),
'today_work_seconds' => $workHours,
],
]);
}
}