DistanceService.php 1.9 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Contracts\Database\Query\Expression;
  4. use Illuminate\Support\Facades\DB;
  5. class DistanceService
  6. {
  7. private const EARTH_RADIUS_KM = 6371;
  8. public static function calculate(?float $lat1, ?float $lng1, ?float $lat2, ?float $lng2): ?float
  9. {
  10. if ($lat1 === null || $lng1 === null || $lat2 === null || $lng2 === null) {
  11. return null;
  12. }
  13. $lat1Rad = deg2rad($lat1);
  14. $lat2Rad = deg2rad($lat2);
  15. $lng1Rad = deg2rad($lng1);
  16. $lng2Rad = deg2rad($lng2);
  17. $cosValue = cos($lat1Rad) * cos($lat2Rad) * cos($lng2Rad - $lng1Rad)
  18. + sin($lat1Rad) * sin($lat2Rad);
  19. $cosValue = min(1.0, max(-1.0, $cosValue));
  20. return round(self::EARTH_RADIUS_KM * acos($cosValue), 1);
  21. }
  22. public static function sqlExpression(
  23. ?float $clientLatitude,
  24. ?float $clientLongitude,
  25. string $targetLatCol = 'provider_address.latitude',
  26. string $targetLngCol = 'provider_address.longitude',
  27. string $alias = 'distance_km'
  28. ): Expression {
  29. if ($clientLatitude === null || $clientLongitude === null) {
  30. return DB::raw("NULL as {$alias}");
  31. }
  32. return DB::raw("
  33. CASE
  34. WHEN {$targetLatCol} IS NOT NULL
  35. AND {$targetLngCol} IS NOT NULL
  36. THEN ROUND((
  37. ".self::EARTH_RADIUS_KM." * acos(
  38. least(1, greatest(-1,
  39. cos(radians({$clientLatitude}))
  40. * cos(radians({$targetLatCol}))
  41. * cos(radians({$targetLngCol}) - radians({$clientLongitude}))
  42. + sin(radians({$clientLatitude}))
  43. * sin(radians({$targetLatCol}))
  44. ))
  45. )
  46. )::numeric, 1)
  47. ELSE NULL
  48. END as {$alias}
  49. ");
  50. }
  51. }