DistanceService.php 3.0 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697
  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. $formula = self::distanceFormula($clientLatitude, $clientLongitude, $targetLatCol, $targetLngCol);
  33. return DB::raw("
  34. CASE
  35. WHEN {$targetLatCol} IS NOT NULL
  36. AND {$targetLngCol} IS NOT NULL
  37. THEN ROUND(({$formula})::numeric, 1)
  38. ELSE NULL
  39. END AS {$alias}
  40. ");
  41. }
  42. public static function withinRadiusSqlCondition(
  43. float $clientLatitude,
  44. float $clientLongitude,
  45. float $radiusKm,
  46. string $targetLatCol = 'provider_address.latitude',
  47. string $targetLngCol = 'provider_address.longitude',
  48. ): string {
  49. $formula = self::distanceFormula($clientLatitude, $clientLongitude, $targetLatCol, $targetLngCol);
  50. return "
  51. (
  52. {$targetLatCol} IS NOT NULL
  53. AND {$targetLngCol} IS NOT NULL
  54. AND ROUND(({$formula})::numeric, 1) <= {$radiusKm}
  55. )
  56. ";
  57. }
  58. private static function distanceFormula(
  59. float $clientLatitude,
  60. float $clientLongitude,
  61. string $targetLatCol,
  62. string $targetLngCol,
  63. ): string {
  64. return "
  65. " . self::EARTH_RADIUS_KM . " * ACOS(
  66. LEAST(
  67. 1,
  68. GREATEST(
  69. -1,
  70. COS(RADIANS({$clientLatitude}))
  71. * COS(RADIANS({$targetLatCol}))
  72. * COS(
  73. RADIANS({$targetLngCol})
  74. - RADIANS({$clientLongitude})
  75. )
  76. + SIN(RADIANS({$clientLatitude}))
  77. * SIN(RADIANS({$targetLatCol}))
  78. )
  79. )
  80. )
  81. ";
  82. }
  83. }