DistanceService.php 2.3 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071
  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. (
  38. " . self::EARTH_RADIUS_KM . " * ACOS(
  39. LEAST(
  40. 1,
  41. GREATEST(
  42. -1,
  43. COS(RADIANS({$clientLatitude}))
  44. * COS(RADIANS({$targetLatCol}))
  45. * COS(
  46. RADIANS({$targetLngCol})
  47. - RADIANS({$clientLongitude})
  48. )
  49. + SIN(RADIANS({$clientLatitude}))
  50. * SIN(RADIANS({$targetLatCol}))
  51. )
  52. )
  53. )
  54. )::numeric,
  55. 1
  56. )
  57. ELSE NULL
  58. END AS {$alias}
  59. ");
  60. }
  61. }