174 lines
4.4 KiB
PHP
174 lines
4.4 KiB
PHP
|
|
<?php
|
||
|
|
|
||
|
|
declare(strict_types=1);
|
||
|
|
|
||
|
|
namespace Modules\RestaurantDelivery\Models;
|
||
|
|
|
||
|
|
use Illuminate\Database\Eloquent\Factories\HasFactory;
|
||
|
|
use Illuminate\Database\Eloquent\Model;
|
||
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
||
|
|
|
||
|
|
class LocationLog extends Model
|
||
|
|
{
|
||
|
|
use HasFactory;
|
||
|
|
|
||
|
|
public const TABLE_NAME = 'restaurant_rider_location_logs';
|
||
|
|
|
||
|
|
protected $table = self::TABLE_NAME;
|
||
|
|
|
||
|
|
protected $fillable = [
|
||
|
|
'rider_id',
|
||
|
|
'delivery_id',
|
||
|
|
'latitude',
|
||
|
|
'longitude',
|
||
|
|
'speed',
|
||
|
|
'bearing',
|
||
|
|
'accuracy',
|
||
|
|
'altitude',
|
||
|
|
'battery_level',
|
||
|
|
'is_charging',
|
||
|
|
'network_type',
|
||
|
|
'source',
|
||
|
|
'recorded_at',
|
||
|
|
];
|
||
|
|
|
||
|
|
protected $casts = [
|
||
|
|
'latitude' => 'decimal:7',
|
||
|
|
'longitude' => 'decimal:7',
|
||
|
|
'speed' => 'float',
|
||
|
|
'bearing' => 'float',
|
||
|
|
'accuracy' => 'float',
|
||
|
|
'altitude' => 'float',
|
||
|
|
'battery_level' => 'integer',
|
||
|
|
'is_charging' => 'boolean',
|
||
|
|
'recorded_at' => 'datetime',
|
||
|
|
];
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Relationships
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function rider(): BelongsTo
|
||
|
|
{
|
||
|
|
return $this->belongsTo(Rider::class, 'rider_id');
|
||
|
|
}
|
||
|
|
|
||
|
|
public function delivery(): BelongsTo
|
||
|
|
{
|
||
|
|
return $this->belongsTo(Delivery::class, 'delivery_id');
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Scopes
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function scopeForRider($query, int $riderId)
|
||
|
|
{
|
||
|
|
return $query->where('rider_id', $riderId);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeForDelivery($query, int $deliveryId)
|
||
|
|
{
|
||
|
|
return $query->where('delivery_id', $deliveryId);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeRecent($query, int $minutes = 60)
|
||
|
|
{
|
||
|
|
return $query->where('recorded_at', '>=', now()->subMinutes($minutes));
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeInDateRange($query, $startDate, $endDate)
|
||
|
|
{
|
||
|
|
return $query->whereBetween('recorded_at', [$startDate, $endDate]);
|
||
|
|
}
|
||
|
|
|
||
|
|
public function scopeHighAccuracy($query, float $maxAccuracy = 50)
|
||
|
|
{
|
||
|
|
return $query->where('accuracy', '<=', $maxAccuracy);
|
||
|
|
}
|
||
|
|
|
||
|
|
/*
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
| Methods
|
||
|
|
|--------------------------------------------------------------------------
|
||
|
|
*/
|
||
|
|
|
||
|
|
public function getCoordinates(): array
|
||
|
|
{
|
||
|
|
return [
|
||
|
|
'lat' => $this->latitude,
|
||
|
|
'lng' => $this->longitude,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
|
||
|
|
public function hasLowBattery(): bool
|
||
|
|
{
|
||
|
|
return $this->battery_level !== null && $this->battery_level < 20;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function hasPoorAccuracy(): bool
|
||
|
|
{
|
||
|
|
return $this->accuracy !== null && $this->accuracy > 50;
|
||
|
|
}
|
||
|
|
|
||
|
|
public function distanceTo(float $latitude, float $longitude): float
|
||
|
|
{
|
||
|
|
$earthRadius = 6371; // km
|
||
|
|
|
||
|
|
$dLat = deg2rad($latitude - $this->latitude);
|
||
|
|
$dLng = deg2rad($longitude - $this->longitude);
|
||
|
|
|
||
|
|
$a = sin($dLat / 2) * sin($dLat / 2) +
|
||
|
|
cos(deg2rad($this->latitude)) * cos(deg2rad($latitude)) *
|
||
|
|
sin($dLng / 2) * sin($dLng / 2);
|
||
|
|
|
||
|
|
$c = 2 * atan2(sqrt($a), sqrt(1 - $a));
|
||
|
|
|
||
|
|
return round($earthRadius * $c, 3);
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function getTrackingPath(int $deliveryId): array
|
||
|
|
{
|
||
|
|
return static::forDelivery($deliveryId)
|
||
|
|
->orderBy('recorded_at')
|
||
|
|
->get()
|
||
|
|
->map(fn ($log) => [
|
||
|
|
'lat' => $log->latitude,
|
||
|
|
'lng' => $log->longitude,
|
||
|
|
'timestamp' => $log->recorded_at->toIso8601String(),
|
||
|
|
'speed' => $log->speed,
|
||
|
|
])
|
||
|
|
->toArray();
|
||
|
|
}
|
||
|
|
|
||
|
|
public static function calculateTotalDistance(int $deliveryId): float
|
||
|
|
{
|
||
|
|
$logs = static::forDelivery($deliveryId)
|
||
|
|
->orderBy('recorded_at')
|
||
|
|
->get();
|
||
|
|
|
||
|
|
if ($logs->count() < 2) {
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
$totalDistance = 0;
|
||
|
|
$previousLog = null;
|
||
|
|
|
||
|
|
foreach ($logs as $log) {
|
||
|
|
if ($previousLog) {
|
||
|
|
$totalDistance += $log->distanceTo(
|
||
|
|
$previousLog->latitude,
|
||
|
|
$previousLog->longitude
|
||
|
|
);
|
||
|
|
}
|
||
|
|
$previousLog = $log;
|
||
|
|
}
|
||
|
|
|
||
|
|
return round($totalDistance, 2);
|
||
|
|
}
|
||
|
|
}
|