234 lines
8.1 KiB
PHP
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->input('_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,
|
|
],
|
|
]);
|
|
}
|
|
}
|