Mengukur jarak antara dua koordinat dalam PHP


145

Hai Saya punya kebutuhan untuk menghitung jarak antara dua titik memiliki lat dan panjang.

Saya ingin menghindari panggilan ke API eksternal.

Saya mencoba menerapkan Formula Haversine di PHP:

Ini kodenya:

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;

      }
    }

Mengujinya dengan beberapa titik tertentu yang memiliki jarak publik saya tidak mendapatkan hasil yang dapat diandalkan.

Saya tidak mengerti apakah ada kesalahan dalam rumus asli atau dalam implementasi saya


4
Saya menemukan kode yang berfungsi di sini dalam banyak bahasa termasuk php geodatasource.com/developers/php
krishna

Jawaban:


273

Belum lama ini saya menulis contoh formula haversine, dan menerbitkannya di situs web saya:

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

➽ Perhatikan bahwa Anda mendapatkan jarak kembali di unit yang sama saat Anda melewati parameter $earthRadius. Nilai standarnya adalah 6371000 meter sehingga hasilnya akan berada di [m] juga. Untuk mendapatkan hasil dalam mil, misalnya Anda bisa melewati 3959 mil sebagai $earthRadiusdan hasilnya akan di [mi]. Menurut pendapat saya itu adalah kebiasaan yang baik untuk tetap dengan unit SI, jika tidak ada alasan khusus untuk melakukan sebaliknya.

Edit:

Sebagai TreyA benar menunjukkan, formula Haversine memiliki kelemahan dengan poin antipodal karena kesalahan pembulatan (meskipun adalah stabil untuk jarak kecil). Untuk menyiasatinya, Anda bisa menggunakan rumus Vincenty sebagai gantinya.

/**
 * 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 - Ada beberapa versi yang mungkin, versi ini mengimplementasikan formula di Wikipedia dan telah teruji dengan baik. $ Angle berarti sudut di tengah dunia dalam radian, sehingga orang dapat mengalikannya dengan jari-jari bumi. Saya juga dapat memberikan contoh rumus Vincenty yang lebih kompleks jika seseorang tertarik.
martinstoeckli

@TreyA - Ya saya tahu, saya tidak yakin apa yang ingin Anda katakan dengan itu. Sudahkah Anda menguji fungsi dan apakah itu menghitung hasil yang salah? Dan apakah Anda sudah melihat formula di Wikipedia? Anda harus benar-benar melakukan tes Anda sendiri dan memberi saya contoh apa yang menurut Anda salah dihitung.
martinstoeckli

Maaf, tetapi saya harus menjelaskan beberapa hal sekarang. 1) Pertanyaannya adalah tentang formula Haversine, Anda harus memberi tahu kami jika Anda menyarankan untuk menggunakan formula lain. 2) Haversine Rumus memiliki kelemahan di sekitar kutub, tetapi adalah akurat untuk jarak kecil (itu masalah dari rumus arckosinus). 3) Anda menyatakan bahwa ada langkah yang hilang dengan $ sudut yang dihitung, itu hanya salah, itu tidak dapat meningkatkan hasilnya, silakan uji! 4) Saya setuju bahwa akan lebih baik menggunakan rumus Vincenty yang stabil, saya sudah menawarkan untuk memberikan contoh. Mungkin Anda bisa menulis jawaban juga?
martinstoeckli

@martinstoekli - Anda benar, Anda tidak memiliki langkah yang hilang dalam rumus Haversine Anda. Saya menghapus komentar saya agar tidak membingungkan pembaca di masa depan.
TreyA

1
@PratikCJoshi - Akhirnya menemukan waktu untuk menambahkan catatan tentang menggunakan unit yang berbeda.
martinstoeckli

63

Saya menemukan kode ini yang memberi saya hasil yang dapat diandalkan.

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

hasil:

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
hebat, saya mencoba ini dan juga peta google menunjukkan jarak yang sama hanya perubahan desimal di sana-sini ..
Zohair

Bagaimana jika Anda ingin menghitung jarak antara tiga titik?
kexxcream

3
panggil fungsi ini dua kali dan tambahkan, secara bergantian Anda mengubah fungsinya
Janith Chinthana

mengembalikan NaN dalam beberapa kondisi stackoverflow.com/questions/37184259/…
Zahur Sh

23

Itu hanya tambahan jawaban @martinstoeckli dan @Janith Chinthana . Bagi mereka yang ingin tahu tentang algoritma mana yang tercepat saya menulis tes kinerja . Hasil kinerja terbaik menunjukkan fungsi yang dioptimalkan dari 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;
}

Ini adalah hasil tes:

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%

Dalam kode Anda, pengali untuk algoritme codexworlds adalah 1,852, sedangkan yang asli adalah 1,1515. Kenapa ini? Kenapa bedanya?
GotBatteries

@GotBatteries mulitplier asli adalah untuk mil. Fungsi yang dioptimalkan menghasilkan km. 1.1515 * 1.609344 = 1.853. Terima kasih, diperbaiki ke 1.853.
Alexander Yancharuk

Mengapa Anda tidak menggunakan M_PI / 180 dan $ rad * 60 * 1.853 sebagai konstanta untuk kinerja yang lebih baik?
Evren Yurtesen

@EvrenYurtesen Ide bagus jika prioritas Anda adalah kinerja. Tetapi pemeliharaan dan keterbacaan akan menjadi lebih rumit menurut saya.
Alexander Yancharuk

Cukup beri komentar pada baris sebelumnya dan katakan // M_PI / 180 ... dll. Saya tidak tahu mengapa itu akan membuatnya sulit untuk dipertahankan. Itu bukan sesuatu yang akan Anda ubah.
Evren Yurtesen

10

Di sini kode sederhana dan sempurna untuk menghitung jarak antara dua garis lintang dan garis bujur. Kode berikut telah ditemukan dari sini - 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

Untuk mereka yang suka lebih pendek dan lebih cepat (tidak memanggil 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

Coba ini memberikan hasil yang luar biasa

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

Pertanyaan yang cukup lama, tetapi bagi mereka yang tertarik dengan kode PHP yang mengembalikan hasil yang sama seperti Google Maps, berikut ini pekerjaannya:

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

Saya sudah menguji dengan berbagai koordinat dan berfungsi dengan baik.

Saya pikir itu harus lebih cepat daripada beberapa alternatif juga. Tetapi tidak diuji itu.

Petunjuk: Google Maps menggunakan 6378137 sebagai radius Bumi. Jadi menggunakannya dengan algoritma lain mungkin bekerja juga.


1

Untuk nilai yang tepat, lakukan seperti itu:

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 saya pikir itu harus dilakukan ...

Edit:

Untuk formulars dan setidaknya implementasi JS, cobalah: http://www.movable-type.co.uk/scripts/latlong.html

Berani saya ... Saya lupa deg2rad semua nilai dalam fungsi lingkaran ...


Terima kasih atas jawaban anda. Saya telah memeriksa implementasi ini dengan perhitungan sederhana antara pointA (42,12) dan pointB (43,12) menggunakan $ earth_radius = 6372,795477598 saya mendapatkan hasil 12745,591 ketika seharusnya sekitar 110,94
maxdangelo

1

Halo di sini Kode Untuk Dapatkan Jarak dan Waktu Menggunakan Dua Lat dan Panjang Yang Berbeda

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

Anda dapat memeriksa Contoh Di Bawah Tautan dapatkan waktu antara dua lokasi berbeda menggunakan lintang dan bujur di php


6
Mungkin tidak perlu memanggil api untuk sesuatu yang cukup mudah ditemukan dengan menggunakan matematika.
Ivotje50

1

Coba fungsi ini untuk menghitung jarak antara ke titik lintang dan bujur

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


    }

Kemudian gunakan fucntion sebagai

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

Semoga ini bisa membantu


diverifikasi dengan skenario nyata yang sempurna dalam kasus saya.
Daxesh Vekariya,

1
Butuh waktu hampir 5 jam untuk menulisnya dan memverifikasinya dalam skenario nyata
Manojkiran.A

0

Pengganda diubah di setiap koordinat karena teori jarak lingkaran besar seperti yang ditulis di sini:

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

dan Anda dapat menghitung nilai terdekat menggunakan rumus ini yang dijelaskan di sini:

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

kuncinya adalah mengubah setiap derajat - menit - nilai kedua ke semua nilai derajat:

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

Salah satu cara termudah adalah:

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

Ini akan membulatkan hingga 2 poin desimal.

Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.