PHP에서 두 좌표 사이의 거리 측정


145

안녕 나는 위도와 경도를 가진 두 점 사이의 거리를 계산할 필요가있다.

외부 API에 대한 호출을 피하고 싶습니다.

PHP에서 Haversine Formula를 구현하려고했습니다.

코드는 다음과 같습니다.

class CoordDistance
 {
    public $lat_a = 0;
    public $lon_a = 0;
    public $lat_b = 0;
    public $lon_b = 0;

    public $measure_unit = 'kilometers';

    public $measure_state = false;

    public $measure = 0;

    public $error = '';



    public function DistAB()

      {
          $delta_lat = $this->lat_b - $this->lat_a ;
          $delta_lon = $this->lon_b - $this->lon_a ;

          $earth_radius = 6372.795477598;

          $alpha    = $delta_lat/2;
          $beta     = $delta_lon/2;
          $a        = sin(deg2rad($alpha)) * sin(deg2rad($alpha)) + cos(deg2rad($this->lat_a)) * cos(deg2rad($this->lat_b)) * sin(deg2rad($beta)) * sin(deg2rad($beta)) ;
          $c        = asin(min(1, sqrt($a)));
          $distance = 2*$earth_radius * $c;
          $distance = round($distance, 4);

          $this->measure = $distance;

      }
    }

공공 거리가있는 특정 지점으로 테스트하면 신뢰할 수있는 결과를 얻지 못합니다.

원래 수식이나 구현에 오류가 있는지 이해할 수 없습니다.


답변:


273

얼마 전 나는 헤르 세인 공식의 예를 작성하여 내 웹 사이트에 게시했습니다.

/**
 * Calculates the great-circle distance between two points, with
 * the Haversine formula.
 * @param float $latitudeFrom Latitude of start point in [deg decimal]
 * @param float $longitudeFrom Longitude of start point in [deg decimal]
 * @param float $latitudeTo Latitude of target point in [deg decimal]
 * @param float $longitudeTo Longitude of target point in [deg decimal]
 * @param float $earthRadius Mean earth radius in [m]
 * @return float Distance between points in [m] (same as earthRadius)
 */
function haversineGreatCircleDistance(
  $latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
{
  // convert from degrees to radians
  $latFrom = deg2rad($latitudeFrom);
  $lonFrom = deg2rad($longitudeFrom);
  $latTo = deg2rad($latitudeTo);
  $lonTo = deg2rad($longitudeTo);

  $latDelta = $latTo - $latFrom;
  $lonDelta = $lonTo - $lonFrom;

  $angle = 2 * asin(sqrt(pow(sin($latDelta / 2), 2) +
    cos($latFrom) * cos($latTo) * pow(sin($lonDelta / 2), 2)));
  return $angle * $earthRadius;
}

parameter 파라메타로 전달한 것과 동일한 단위로 거리를 다시 얻습니다 $earthRadius. 기본값은 6371000 미터이므로 결과도 [m]이됩니다. 마일로 결과를 얻으려면 예를 들어 3959 마일을 통과 $earthRadius하면 결과는 [mi]가됩니다. 제 생각에는 달리 할 특별한 이유가 없다면 SI 단위를 고수하는 것이 좋습니다.

편집하다:

TreyA가 올바르게 지적한 바와 같이, Haversine 공식은 반올림 오차로 인해 대 지점에 약점이 있습니다 ( 소 거리 에서는 안정적 임). 그것들을 해결하기 위해 대신 Vincenty 공식을 사용할 수 있습니다 .

/**
 * Calculates the great-circle distance between two points, with
 * the Vincenty formula.
 * @param float $latitudeFrom Latitude of start point in [deg decimal]
 * @param float $longitudeFrom Longitude of start point in [deg decimal]
 * @param float $latitudeTo Latitude of target point in [deg decimal]
 * @param float $longitudeTo Longitude of target point in [deg decimal]
 * @param float $earthRadius Mean earth radius in [m]
 * @return float Distance between points in [m] (same as earthRadius)
 */
public static function vincentyGreatCircleDistance(
  $latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo, $earthRadius = 6371000)
{
  // convert from degrees to radians
  $latFrom = deg2rad($latitudeFrom);
  $lonFrom = deg2rad($longitudeFrom);
  $latTo = deg2rad($latitudeTo);
  $lonTo = deg2rad($longitudeTo);

  $lonDelta = $lonTo - $lonFrom;
  $a = pow(cos($latTo) * sin($lonDelta), 2) +
    pow(cos($latFrom) * sin($latTo) - sin($latFrom) * cos($latTo) * cos($lonDelta), 2);
  $b = sin($latFrom) * sin($latTo) + cos($latFrom) * cos($latTo) * cos($lonDelta);

  $angle = atan2(sqrt($a), $b);
  return $angle * $earthRadius;
}

1
@TreyA-가능한 다른 버전이 있습니다.이 버전은 Wikipedia 의 공식을 구현하며 테스트를 거쳤습니다. $ angle은 세계 중앙의 각도를 라디안 단위로 나타내므로 지구 반지름과 곱할 수 있습니다. 누군가 관심이 있다면 더 복잡한 Vincenty 공식의 예를 제공 할 수도 있습니다.
martinstoeckli

@TreyA-예, 알고 있습니다. 당신이 그 말을하고 싶은 것이 확실하지 않습니다. 함수를 테스트했으며 잘못된 결과를 계산 했습니까? 그리고 Wikipedia의 공식을 보셨습니까? 당신은 정말로 당신 자신의 시험을하고 당신이 생각하는 것이 잘못 계산되었다는 것을 보여 주어야합니다.
martinstoeckli

죄송하지만 지금 몇 가지를 설명해야합니다. 1) 질문은 Haversine 공식에 관한 것이기 때문에 다른 공식을 사용하도록 제안하면 알려주십시오. 2) Haversine 공식은 극 주위에 약점이 있지만 작은 거리 에서는 정확합니다 (arccosine 공식의 문제입니다). 3) 계산 된 $ angle에 누락 된 단계가 있다고 말했는데, 이는 단순히 잘못되었습니다. 결과를 개선 할 수 없습니다. 테스트 해보십시오! 4) 나는 안정적인 Vincenty 공식을 사용하는 것이 낫다는 데 동의합니다. 나는 이미 예를 제시했습니다. 아마 당신도 대답을 쓸 수 있습니까?
martinstoeckli

@martinstoekli-맞습니다. Haversine 수식에 단계가 없습니다. 나는 미래의 독자들을 혼동하지 않기 위해 내 의견을 삭제했습니다.
TreyA

1
@PratikCJoshi-마지막으로 다른 단위 사용에 대한 메모를 추가 할 시간을 찾았습니다.
martinstoeckli

63

신뢰할 수있는 결과를 제공하는 이 코드 를 찾았습니다 .

function distance($lat1, $lon1, $lat2, $lon2, $unit) {

  $theta = $lon1 - $lon2;
  $dist = sin(deg2rad($lat1)) * sin(deg2rad($lat2)) +  cos(deg2rad($lat1)) * cos(deg2rad($lat2)) * cos(deg2rad($theta));
  $dist = acos($dist);
  $dist = rad2deg($dist);
  $miles = $dist * 60 * 1.1515;
  $unit = strtoupper($unit);

  if ($unit == "K") {
      return ($miles * 1.609344);
  } else if ($unit == "N") {
      return ($miles * 0.8684);
  } else {
      return $miles;
  }
}

결과 :

echo distance(32.9697, -96.80322, 29.46786, -98.53506, "M") . " Miles<br>";
echo distance(32.9697, -96.80322, 29.46786, -98.53506, "K") . " Kilometers<br>";
echo distance(32.9697, -96.80322, 29.46786, -98.53506, "N") . " Nautical Miles<br>";

2
좋은 물건, 나는 이것을 시도하고 또한 구글지도는 여기 저기 동일한 거리 만 소수점 변화를 보여줍니다 ..
Zohair

세 점 사이의 거리를 계산하려면 어떻게해야합니까?
kexxcream

3
이 함수를 두 번 호출하고 합산하십시오. 교대로 함수를 변경하십시오
Janith Chinthana

일부 조건에서 NaN을 반환 stackoverflow.com/questions/37184259/…
Zahur Sh

23

@martinstoeckli@Janith Chinthana 답변에 추가되었습니다 . 어떤 알고리즘이 가장 빠른지 궁금한 사람들을 위해 성능 테스트를 작성했습니다 . 최고의 성능 결과는 codexworld.com의 최적화 된 기능을 보여줍니다 .

/**
 * Optimized algorithm from http://www.codexworld.com
 *
 * @param float $latitudeFrom
 * @param float $longitudeFrom
 * @param float $latitudeTo
 * @param float $longitudeTo
 *
 * @return float [km]
 */
function codexworldGetDistanceOpt($latitudeFrom, $longitudeFrom, $latitudeTo, $longitudeTo)
{
    $rad = M_PI / 180;
    //Calculate distance from latitude and longitude
    $theta = $longitudeFrom - $longitudeTo;
    $dist = sin($latitudeFrom * $rad) 
        * sin($latitudeTo * $rad) +  cos($latitudeFrom * $rad)
        * cos($latitudeTo * $rad) * cos($theta * $rad);

    return acos($dist) / $rad * 60 *  1.853;
}

테스트 결과는 다음과 같습니다.

Test name       Repeats         Result          Performance     
codexworld-opt  10000           0.084952 sec    +0.00%
codexworld      10000           0.104127 sec    -22.57%
custom          10000           0.107419 sec    -26.45%
custom2         10000           0.111576 sec    -31.34%
custom1         10000           0.136691 sec    -60.90%
vincenty        10000           0.165881 sec    -95.26%

코드에서 codexworlds 알고리즘의 승수는 1.852 인 반면 실제 원본은 1.1515입니다. 왜 이런거야? 왜 차이점이 있습니까?
GotBatteries

@GotBatteries 원래 mulitplier는 마일입니다. 최적화 된 함수는 km 단위의 결과를 반환합니다. 1.1515 * 1.609344 = 1.853. 1.853으로 수정되었습니다.
Alexander Yancharuk

더 나은 성능을 위해 M_PI / 180 및 $ rad * 60 * 1.853을 상수로 사용하지 않는 이유는 무엇입니까?
Evren Yurtesen

@EvrenYurtesen 우선 순위가 성능이라면 좋은 생각입니다. 그러나 유지 보수와 가독성은 더 복잡해질 것이라고 생각합니다.
Alexander Yancharuk

이전 줄에 주석을 달고 // M_PI / 180 ... 등을 말하십시오. 왜 유지 관리가 어려운지 모르겠습니다. 그것은 당신이 변화시킬 것이 아닙니다.
Evren Yurtesen

10

다음은 위도와 경도 사이의 거리를 계산하기위한 간단하고 완벽한 코드입니다. 다음 코드는 여기에서 발견되었습니다-http: //www.codexworld.com/distance-between-two-addresses-google-maps-api-php/

$latitudeFrom = '22.574864';
$longitudeFrom = '88.437915';

$latitudeTo = '22.568662';
$longitudeTo = '88.431918';

//Calculate distance from latitude and longitude
$theta = $longitudeFrom - $longitudeTo;
$dist = sin(deg2rad($latitudeFrom)) * sin(deg2rad($latitudeTo)) +  cos(deg2rad($latitudeFrom)) * cos(deg2rad($latitudeTo)) * cos(deg2rad($theta));
$dist = acos($dist);
$dist = rad2deg($dist);
$miles = $dist * 60 * 1.1515;

$distance = ($miles * 1.609344).' km';

5

짧고 빠른 것을 좋아하는 사람들을 위해 (deg2rad ()를 호출하지 않음).

function circle_distance($lat1, $lon1, $lat2, $lon2) {
  $rad = M_PI / 180;
  return acos(sin($lat2*$rad) * sin($lat1*$rad) + cos($lat2*$rad) * cos($lat1*$rad) * cos($lon2*$rad - $lon1*$rad)) * 6371;// Kilometers
}

2

이 멋진 결과를 제공하십시오

function getDistance($point1_lat, $point1_long, $point2_lat, $point2_long, $unit = 'km', $decimals = 2) {
        // Calculate the distance in degrees
        $degrees = rad2deg(acos((sin(deg2rad($point1_lat))*sin(deg2rad($point2_lat))) + (cos(deg2rad($point1_lat))*cos(deg2rad($point2_lat))*cos(deg2rad($point1_long-$point2_long)))));

        // Convert the distance in degrees to the chosen unit (kilometres, miles or nautical miles)
        switch($unit) {
            case 'km':
                $distance = $degrees * 111.13384; // 1 degree = 111.13384 km, based on the average diameter of the Earth (12,735 km)
                break;
            case 'mi':
                $distance = $degrees * 69.05482; // 1 degree = 69.05482 miles, based on the average diameter of the Earth (7,913.1 miles)
                break;
            case 'nmi':
                $distance =  $degrees * 59.97662; // 1 degree = 59.97662 nautic miles, based on the average diameter of the Earth (6,876.3 nautical miles)
        }
        return round($distance, $decimals);
    }

2

아주 오래된 질문이지만 Google지도와 동일한 결과를 반환하는 PHP 코드에 관심이있는 사람들은 다음과 같은 일을합니다.

/**
 * Computes the distance between two coordinates.
 *
 * Implementation based on reverse engineering of
 * <code>google.maps.geometry.spherical.computeDistanceBetween()</code>.
 *
 * @param float $lat1 Latitude from the first point.
 * @param float $lng1 Longitude from the first point.
 * @param float $lat2 Latitude from the second point.
 * @param float $lng2 Longitude from the second point.
 * @param float $radius (optional) Radius in meters.
 *
 * @return float Distance in meters.
 */
function computeDistance($lat1, $lng1, $lat2, $lng2, $radius = 6378137)
{
    static $x = M_PI / 180;
    $lat1 *= $x; $lng1 *= $x;
    $lat2 *= $x; $lng2 *= $x;
    $distance = 2 * asin(sqrt(pow(sin(($lat1 - $lat2) / 2), 2) + cos($lat1) * cos($lat2) * pow(sin(($lng1 - $lng2) / 2), 2)));

    return $distance * $radius;
}

다양한 좌표로 테스트했으며 완벽하게 작동합니다.

나는 그것이 다른 대안보다 빠를 것이라고 생각합니다. 그러나 그것을 테스트하지 않았습니다.

힌트 : Google지도는 지구 반지름으로 6378137을 사용합니다. 따라서 다른 알고리즘과 함께 사용하면 효과가 있습니다.


1

정확한 값을 얻으려면 다음과 같이하십시오.

public function DistAB()
{
      $delta_lat = $this->lat_b - $this->lat_a ;
      $delta_lon = $this->lon_b - $this->lon_a ;

      $a = pow(sin($delta_lat/2), 2);
      $a += cos(deg2rad($this->lat_a9)) * cos(deg2rad($this->lat_b9)) * pow(sin(deg2rad($delta_lon/29)), 2);
      $c = 2 * atan2(sqrt($a), sqrt(1-$a));

      $distance = 2 * $earth_radius * $c;
      $distance = round($distance, 4);

      $this->measure = $distance;
}

흠 ...

편집하다:

공식 화자 및 적어도 JS 구현의 경우 다음을 시도하십시오. http://www.movable-type.co.uk/scripts/latlong.html

감히 ... 원 함수의 모든 값을 deg2rad하는 것을 잊었습니다 ...


답변 주셔서 감사합니다. 나는 사이의 간단한 계산으로이 구현을 확인했습니다 pointA (42,12)와 pointB (43,12) $ EARTH_RADIUS = 6372.795477598은 110,94 주위에 뭔가를해야 할 때 나는 결과 12745.591로 얻을 사용
maxdangelo

1

두 가지 다른 위도 및 경도를 사용하여 거리 및 시간 가져 오기 코드

$url ="https://maps.googleapis.com/maps/api/distancematrix/json?units=imperial&origins=16.538048,80.613266&destinations=23.0225,72.5714";



    $ch = curl_init();
    // Disable SSL verification

    curl_setopt($ch, CURLOPT_SSL_VERIFYPEER, false);
    // Will return the response, if false it print the response
    curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
    // Set the url
    curl_setopt($ch, CURLOPT_URL,$url);
    // Execute
    $result=curl_exec($ch);
    // Closing
    curl_close($ch);

    $result_array=json_decode($result);
print_r($result_array);

당신은 링크 아래의 예를 확인할 수 PHP의 위도와 경도를 사용하여 서로 다른 두 위치 사이의 GET 시간


6
수학을 사용하여 쉽게 찾을 수있는 것에 대해 API를 호출 할 필요는 없습니다.
Ivotje50

1

위도와 경도 지점 사이의 거리를 계산하려면이 기능을 사용해보십시오.

function calculateDistanceBetweenTwoPoints($latitudeOne='', $longitudeOne='', $latitudeTwo='', $longitudeTwo='',$distanceUnit ='',$round=false,$decimalPoints='')
    {
        if (empty($decimalPoints)) 
        {
            $decimalPoints = '3';
        }
        if (empty($distanceUnit)) {
            $distanceUnit = 'KM';
        }
        $distanceUnit = strtolower($distanceUnit);
        $pointDifference = $longitudeOne - $longitudeTwo;
        $toSin = (sin(deg2rad($latitudeOne)) * sin(deg2rad($latitudeTwo))) + (cos(deg2rad($latitudeOne)) * cos(deg2rad($latitudeTwo)) * cos(deg2rad($pointDifference)));
        $toAcos = acos($toSin);
        $toRad2Deg = rad2deg($toAcos);

        $toMiles  =  $toRad2Deg * 60 * 1.1515;
        $toKilometers = $toMiles * 1.609344;
        $toNauticalMiles = $toMiles * 0.8684;
        $toMeters = $toKilometers * 1000;
        $toFeets = $toMiles * 5280;
        $toYards = $toFeets / 3;


              switch (strtoupper($distanceUnit)) 
              {
                  case 'ML'://miles
                         $toMiles  = ($round == true ? round($toMiles) : round($toMiles, $decimalPoints));
                         return $toMiles;
                      break;
                  case 'KM'://Kilometers
                        $toKilometers  = ($round == true ? round($toKilometers) : round($toKilometers, $decimalPoints));
                        return $toKilometers;
                      break;
                  case 'MT'://Meters
                        $toMeters  = ($round == true ? round($toMeters) : round($toMeters, $decimalPoints));
                        return $toMeters;
                      break;
                  case 'FT'://feets
                        $toFeets  = ($round == true ? round($toFeets) : round($toFeets, $decimalPoints));
                        return $toFeets;
                      break;
                  case 'YD'://yards
                        $toYards  = ($round == true ? round($toYards) : round($toYards, $decimalPoints));
                        return $toYards;
                      break;
                  case 'NM'://Nautical miles
                        $toNauticalMiles  = ($round == true ? round($toNauticalMiles) : round($toNauticalMiles, $decimalPoints));
                        return $toNauticalMiles;
                      break;
              }


    }

그런 다음 기능을 다음과 같이 사용하십시오.

echo calculateDistanceBetweenTwoPoints('11.657740','77.766270','11.074820','77.002160','ML',true,5);

그것이 도움이되기를 바랍니다.


내 경우에는 실제 시나리오 완벽한 작업으로 검증되었습니다.
Daxesh Vekariya 2016 년

1
를 작성하고 실제 시나리오에서 그것을 확인하기 위해 거의 5 시간 툭
Manojkiran.A

0

승수는 다음과 같이 큰 원거리 이론으로 인해 모든 좌표에서 변경됩니다.

http://en.wikipedia.org/wiki/Great-circle_distance

여기에 설명 된이 공식을 사용하여 가장 가까운 값을 계산할 수 있습니다.

http://en.wikipedia.org/wiki/Great-circle_distance#Worked_example

키는 각도-분-초 값을 모든도 값으로 변환합니다.

N 36°7.2', W 86°40.2'  N = (+) , W = (-), S = (-), E = (+) 
referencing the Greenwich meridian and Equator parallel

(phi)     36.12° = 36° + 7.2'/60' 

(lambda)  -86.67° = 86° + 40.2'/60'

0

가장 쉬운 방법 중 하나는 다음과 같습니다.

$my_latitude = "";
$my_longitude = "";
$her_latitude = "";
$her_longitude = "";

$distance = round((((acos(sin(($my_latitude*pi()/180)) * sin(($her_latitude*pi()/180))+cos(($my_latitude*pi()/180)) * cos(($her_latitude*pi()/180)) * cos((($my_longitude- $her_longitude)*pi()/180))))*180/pi())*60*1.1515*1.609344), 2);
echo $distance;

소수점 2 자리까지 반올림합니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.