【问题标题】:Measuring the distance between two coordinates in PHP在PHP中测量两个坐标之间的距离
【发布时间】:2012-04-20 15:42:14
【问题描述】:

您好,我需要计算经纬度两点之间的距离。

我想避免对外部 API 的任何调用。

我尝试在 PHP 中实现 Haversine 公式:

代码如下:

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;

      }
    }

用一些具有公共距离的给定点测试它我没有得到可靠的结果。

不明白原来的公式或者我的实现是否有错误

【问题讨论】:

标签: php coordinates geocoding haversine


【解决方案1】:

不久前我写了一个半正弦公式的例子,并在我的网站上发布了它:

/**
 * 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;
}

➽ 请注意,您使用参数$earthRadius 传入的单位是相同的单位。默认值为 6371000 米,因此结果也将以 [m] 为单位。要以英里为单位获得结果,您可以例如以$earthRadius 传递 3959 英里,结果将以 [mi] 为单位。在我看来,如果没有特别的理由,坚持使用 SI 单位是一个好习惯。

编辑:

正如 TreyA 正确指出的那样,Haversine 公式在 antipodal points 方面存在弱点,因为舍入误差(尽管它对于小距离是稳定的)。要绕过它们,您可以改用Vincenty formula

/**
 * 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;
}

【讨论】:

  • @TreyA - 可能有不同的版本,这个版本实现了Wikipedia 中的公式,并且经过了很好的测试。 $angle 表示以弧度表示的世界中间的角度,因此可以将其与地球半径相乘。如果有人感兴趣,我还可以提供更复杂的 Vincenty 公式的示例。
  • @TreyA - 是的,我知道,我不确定你想说什么。您是否测试过该函数并计算出错误的结果?你看过维基百科中的公式吗?你真的应该自己做一个测试,给我一个你认为计算错误的例子。
  • 对不起,我现在必须解释一些事情。 1) 问题是关于 Haversine 公式,如果您建议使用其他公式,您应该告诉我们。 2) Haversine 公式在极点附近有弱点,但 对于小距离是准确的(这是反余弦公式的问题)。 3) 你说计算出的$angle 少了一个步骤,这是完全错误的,它不能改善结果,请测试一下! 4) 我同意它会更好用稳定的文森蒂公式,我已经提供了一个例子。也许你也可以写一个答案?
  • @capikaw - 正如您在 cmets 中看到的,单位是十进制度和米。实际上,您传递给参数$earthRadius 的单位决定了结果的单位,也就是说,如果您将km 作为地球半径,您将获得以km 为单位的距离.
  • @PratikCJoshi - 终于有时间添加关于使用不同单位的说明。
【解决方案2】:

我找到了this code,这给了我可靠的结果。

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>";

【讨论】:

  • 好东西,我试过了,谷歌地图也显示相同的距离,只有十进制的变化在这里和那里..
  • 如果要计算三点之间的距离怎么办?
  • 调用此函数两次并将它们相加,或者您相应地更改函数
  • 在某些情况下返回 NaN stackoverflow.com/questions/37184259/…
【解决方案3】:

这只是对@martinstoeckli@Janith Chinthana 答案的补充。对于那些好奇哪种算法最快的人,我写了performance test。最佳性能结果显示来自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 原始乘数为英里。优化后的函数以公里为单位返回结果。 1.1515 * 1.609344 = 1.853。谢谢,固定为 1.853。
  • 为什么不使用 M_PI / 180 和 $rad * 60 * 1.853 作为常数以获得更好的性能?
  • @EvrenYurtesen 如果您的首要任务是性能,那真是个好主意。但我认为可维护性和可读性会变得更加复杂。
  • 只需在上一行添加评论并说 // M_PI / 180 ... 等等。我不知道为什么它会使其难以维护。这不是你会改变的。
【解决方案4】:

这里是计算两个纬度和经度之间距离的简单而完美的代码。以下代码已从此处找到 - 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】:

    很老的问题,但是对于那些对返回与 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 作为地球半径。因此,将其与其他算法一起使用也可能会奏效。

    【讨论】:

      【解决方案6】:

      对于那些喜欢更短更快(不调用 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
      }
      

      【讨论】:

        【解决方案7】:

        试试这个效果很好

        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);
            }
        

        【讨论】:

          【解决方案8】:

          试试这个函数来计算经纬度点之间的距离

          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);
          

          希望对你有帮助

          【讨论】:

          • 在我的案例中通过真实场景完美工作验证。
          • 花了近5个小时写出来并在真实场景中验证
          【解决方案9】:

          对于精确值,这样做:

          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...

          【讨论】:

          • 感谢您的回答。我已经使用 $earth_radius = 6372.795477598 在 pointA(42,12) 和 pointB(43,12) 之间通过简单的计算检查了这个实现,我得到的结果是 12745.591,它应该是 110,94 左右
          【解决方案10】:

          由于大圆距离理论,乘数在每个坐标处都会发生变化:

          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'
          

          【讨论】:

            【解决方案11】:

            你好这里使用两个不同的纬度和经度获取距离和时间的代码

            $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);
            

            您可以查看以下链接get time between two different locations using latitude and longitude in php 的示例

            【讨论】:

            • 对于可以通过数学非常简单地找到的东西,可能没有必要调用 api。
            【解决方案12】:

            最简单的方法之一是:

            $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;
            

            它会四舍五入到小数点后两位。

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              • 2010-11-23
              • 1970-01-01
              • 2012-08-06
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多