Đo khoảng cách giữa hai tọa độ trong PHP


145

Xin chào Tôi có nhu cầu tính khoảng cách giữa hai điểm có độ trễ và độ dài.

Tôi muốn tránh mọi cuộc gọi đến API bên ngoài.

Tôi đã cố gắng thực hiện Công thức Haversine trong PHP:

Đây là mã:

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;

      }
    }

Thử nghiệm nó với một số điểm nhất định có khoảng cách công khai tôi không nhận được kết quả đáng tin cậy.

Tôi không hiểu nếu có lỗi trong công thức ban đầu hoặc trong quá trình thực hiện của tôi


4
Tôi tìm thấy mã làm việc ở đây bằng nhiều ngôn ngữ, bao gồm php geodatasource.com/developers/php
krishna

Câu trả lời:


273

Cách đây không lâu, tôi đã viết một ví dụ về công thức haversine và xuất bản nó trên trang web của mình:

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

Lưu ý rằng bạn lấy lại khoảng cách trong cùng một đơn vị khi bạn chuyển qua với tham số $earthRadius. Giá trị mặc định là 6371000 mét, do đó, kết quả cũng sẽ nằm trong [m]. Để có được kết quả trong dặm, bạn có thể ví dụ như vượt qua 3959 dặm như $earthRadiusvà kết quả sẽ được [mi]. Theo tôi đó là một thói quen tốt để gắn bó với các đơn vị SI, nếu không có lý do cụ thể để làm khác.

Biên tập:

Như TreyA một cách chính xác chỉ ra, công thức Haversine có điểm yếu với điểm đối cực do làm tròn số lỗi (mặc dù nó ổn định cho khoảng cách nhỏ). Để đi xung quanh họ, bạn có thể sử dụng công thức Vincenty thay thế.

/**
 * 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 - Có nhiều phiên bản khác nhau, phiên bản này thực hiện công thức trong Wikipedia và được thử nghiệm tốt. Góc $ có nghĩa là góc ở giữa thế giới tính bằng radian, vì vậy người ta có thể nhân nó với bán kính trái đất. Tôi cũng có thể cung cấp một ví dụ về công thức Vincenty phức tạp hơn nếu ai đó quan tâm.
martinstoeckli

@TreyA - Có tôi biết, tôi không chắc bạn muốn nói gì với điều đó. Bạn đã kiểm tra chức năng và nó đã tính toán một kết quả sai? Và bạn đã xem công thức trong Wikipedia chưa? Bạn thực sự nên làm một bài kiểm tra của riêng bạn và cho tôi một ví dụ về những gì bạn nghĩ là tính toán sai.
martinstoeckli

Xin lỗi, nhưng tôi phải giải thích một số điều bây giờ. 1) Câu hỏi là về công thức Haversine, bạn nên cho chúng tôi biết nếu bạn đề nghị sử dụng một công thức khác. 2) Công thức Haversine có điểm yếu xung quanh các cực, nhưng chính xác cho khoảng cách nhỏ (đó là một vấn đề của công thức arccosine). 3) Bạn đã nói rằng thiếu một bước với góc $ được tính toán, điều đó đơn giản là sai, nó không thể cải thiện kết quả, vui lòng kiểm tra thử! 4) Tôi đồng ý rằng sẽ tốt hơn nếu sử dụng công thức ổn định của Vincenty, tôi đã đề nghị đưa ra một ví dụ. Có lẽ bạn có thể viết một câu trả lời là tốt?
martinstoeckli

@martinstoekli - bạn đã đúng, bạn không thiếu một bước nào trong công thức Haversine của mình. Tôi loại bỏ ý kiến ​​của tôi để không gây nhầm lẫn cho độc giả trong tương lai.
TreyA

1
@PratikCJoshi - Cuối cùng cũng tìm thấy thời gian để thêm ghi chú về việc sử dụng các đơn vị khác nhau.
martinstoeckli

62

tôi đã tìm thấy này cho tôi kết quả đáng tin cậy.

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

các kết quả :

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
công cụ tuyệt vời, tôi đã thử điều này và google maps cũng cho thấy khoảng cách tương tự chỉ thay đổi thập phân ở đây và đó ..
Zohair

Nếu bạn muốn tính khoảng cách giữa ba điểm thì sao?
kexxcream

3
gọi chức năng này hai lần và thêm chúng lên, thay phiên bạn thay đổi chức năng cho phù hợp
Janith Chinthana

trả về NaN trong một số điều kiện stackoverflow.com/questions/37184259/
Zahur Sh

23

Nó chỉ là bổ sung cho câu trả lời @martinstoeckli@Janith Chinthana . Đối với những người tò mò về thuật toán nào là nhanh nhất tôi đã viết bài kiểm tra hiệu suất . Kết quả hiệu suất tốt nhất cho thấy chức năng được tối ưu hóa từ 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;
}

Đây là kết quả kiểm tra:

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%

Trong mã của bạn, hệ số nhân cho thuật toán codexworlds là 1.852, trong khi bản gốc thực tế là 1.1515. Tại sao lại thế này? Tại sao lại có sự khác biệt?
GotB Pin

@GotBatteries mulitplier gốc là hàng dặm. Tối ưu hóa chức năng trả về kết quả tính bằng km. 1.1515 * 1.609344 = 1.853. Cảm ơn, đã sửa thành 1.853.
Alexander Yancharuk

Tại sao bạn không sử dụng M_PI / 180 và $ rad * 60 * 1.853 làm hằng số để có hiệu suất tốt hơn?
Evren Yurtesen

@EvrenYurtesen Ý tưởng hay nếu ưu tiên của bạn là hiệu suất. Nhưng khả năng duy trì và dễ đọc sẽ trở nên phức tạp hơn tôi nghĩ.
Alexander Yancharuk

Chỉ cần đặt một nhận xét trên dòng trước đó và nói // M_PI / 180 ... vv Tôi không biết tại sao nó sẽ gây khó khăn cho việc duy trì. Nó không phải là thứ bạn sẽ thay đổi.
Evren Yurtesen

10

Ở đây mã đơn giản và hoàn hảo để tính khoảng cách giữa hai vĩ độ và kinh độ. Mã sau đây đã được tìm thấy từ đây - http://www.codexworld.com/distance-b 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

Dành cho những người thích ngắn hơn và nhanh hơn (không gọi 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

Hãy thử điều này cho kết quả tuyệt vời

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

Câu hỏi khá cũ, nhưng đối với những người quan tâm đến mã PHP trả về kết quả giống như Google Maps, thì công việc sau đây:

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

Tôi đã thử nghiệm với nhiều tọa độ khác nhau và nó hoạt động hoàn hảo.

Tôi nghĩ rằng nó nên được nhanh hơn sau đó một số lựa chọn thay thế quá. Nhưng đã không kiểm tra điều đó.

Gợi ý: Google Maps sử dụng 6378137 làm bán kính Trái đất. Vì vậy, sử dụng nó với các thuật toán khác cũng có thể hoạt động.


1

Đối với các giá trị chính xác làm như vậy:

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

Hmm tôi nghĩ rằng nên làm điều đó ...

Biên tập:

Để biết các công thức và ít nhất là triển khai JS, hãy thử: http://www.movable-type.co.uk/scripts/latlong.html

Dám tôi ... Tôi đã quên khử tất cả các giá trị trong các hàm vòng tròn ...


Cảm ơn câu trả lời của bạn. Tôi đã kiểm tra việc thực hiện này bằng một phép tính đơn giản giữa điểmA (42,12) và điểmB (43,12) bằng cách sử dụng $ earth_radius = 6372.795477598 Tôi nhận được kết quả 12745.591 khi nó phải là khoảng 110,94
maxdangelo

1

Xin chào ở đây Mã để có được khoảng cách và thời gian sử dụng hai độ trễ và dài khác nhau

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

Bạn có thể kiểm tra Ví dụ bên dưới Liên kết nhận thời gian giữa hai vị trí khác nhau bằng cách sử dụng vĩ độ và kinh độ trong php


6
Có thể không cần thiết phải gọi một api cho một cái gì đó khá đơn giản có thể được tìm thấy bằng cách sử dụng toán học.
Ivotje50

1

Hãy thử chức năng này để tính khoảng cách giữa các điểm vĩ độ và kinh độ

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


    }

Sau đó sử dụng fucntion như

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

Hy vọng nó giúp


xác minh với kịch bản thực sự hoàn hảo công việc trong trường hợp của tôi.
Daxesh Vekariya

1
Mất gần 5 giờ để viết nó và xác minh nó trong kịch bản thực tế
Manojkiran.A

0

Hệ số nhân được thay đổi ở mọi tọa độ vì lý thuyết khoảng cách vòng tròn lớn như được viết ở đây:

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

và bạn có thể tính giá trị gần nhất bằng công thức này được mô tả ở đây:

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

khóa đang chuyển đổi từng độ - phút - giá trị thứ hai thành tất cả giá trị độ:

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

Một trong những cách dễ nhất là:

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

Nó sẽ làm tròn đến 2 điểm thập phân.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.