PHP'de iki koordinat arasındaki mesafeyi ölçme


146

Merhaba lat ve uzun olan iki nokta arasındaki mesafeyi hesaplamak için ihtiyacım var.

Harici API'ya yapılan çağrılardan kaçınmak istiyorum.

PHP Haversine Formula uygulamak çalıştı:

İşte kod:

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;

      }
    }

Kamusal mesafeleri olan bazı noktalarla test ederken güvenilir bir sonuç alamıyorum.

Orijinal formülde veya uygulamamda bir hata olup olmadığını anlamıyorum


4
Burada php dahil olmak üzere birçok dilde çalışma kodu buldum geodatasource.com/developers/php
krishna

Yanıtlar:


273

Çok geçmeden haversin formülünün bir örneğini yazdım ve web sitemde yayınladım:

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

The Mesafeyi, parametreyle geçirdiğinizle aynı birimde geri aldığınızı unutmayın $earthRadius. Varsayılan değer 6371000 metredir, bu nedenle sonuç [m] cinsinden olur. Mil cinsinden sonuç almak için, örneğin 3959 mil$earthRadiusMil ve sonuç [mi] olur. Benim düşünceme göre, SI birimlerine bağlı kalmak iyi bir alışkanlıktır, aksi takdirde bunu yapmak için özel bir neden yoksa.

Düzenle:

TreyA doğru işaret edildiği gibi, Haversine formülü ile zayıf yönleri vardır antipot noktaları için (o yuvarlatma hataları arasında olan küçük mesafeler için kararlı). Onları aşmak için bunun yerine Vincenty formülünü kullanabilirsiniz .

/**
 * 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 - Farklı sürümler olabilir, bu sürüm Wikipedia'da formülü uygular ve iyi test edilmiştir. $ Açısı, dünyanın ortasındaki radyan cinsinden açı anlamına gelir, böylece kişi onu dünya yarıçapı ile çarpabilir. Birisi ilgiliyse daha karmaşık Vincenty formülüne de bir örnek verebilirim.
martinstoeckli

@TreyA - Evet biliyorum, bununla ne söylemek istediğinden emin değilim. İşlevi test ettiniz ve yanlış bir sonuç hesapladınız mı? Ve Wikipedia'daki formüle baktınız mı? Gerçekten kendi testinizi yapmalı ve bana yanlış hesaplandığını düşündüğünüz bir örnek vermelisiniz.
martinstoeckli

Üzgünüm, ama şimdi bazı şeyleri açıklamak zorundayım. 1) Soru Haversine formülü ile ilgiliydi, başka bir formül kullanmanızı önerirseniz bize söylemelisiniz. 2) Haversine formülü direkleri etrafında zayıf yönleri vardır, ama olan küçük mesafelerde (o arkkosinüs formülünün bir sorun) için doğru. 3) Hesaplanan $ açısı ile eksik bir adım olduğunu belirttiniz, bu sadece yanlış, sonucu iyileştiremez, lütfen test edin! 4) Kararlı Vincenty formülünü kullanmanın daha iyi olacağını kabul ediyorum, zaten bir örnek vermeyi teklif ettim. Belki de bir cevap yazabilirsin?
martinstoeckli

@martinstoekli - haklısın, Haversine formülünde bir adım eksik. Gelecekteki okuyucuları karıştırmamak için yorumlarımı kaldırdım.
TreyA

1
@PratikCJoshi - Sonunda farklı birimlerin kullanımı hakkında bir not ekleme zamanını buldu.
martinstoeckli

64

Bana güvenilir sonuçlar veren bu kodu buldum .

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

Sonuçlar :

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
harika şeyler, bunu denedim ve aynı zamanda google haritalar burada ve orada sadece ondalık değişiklikleri aynı mesafeyi gösterir ..
Zohair

Üç nokta arasındaki mesafeyi hesaplamak isterseniz ne olur?
kexxcream

3
Bu işlevi iki kez çağırın ve ekleyin, alternatif olarak işlevi buna göre değiştirin
Janith Chinthana

bazı koşullarda NaN döndürür stackoverflow.com/questions/37184259/…
Zahur Sh

23

Sadece @martinstoeckli ve @Janith Chinthana cevaplarına ek . Hangi algoritmanın en hızlı olduğunu merak edenler için performans testi yazdım . En iyi performans sonucu codexworld.com'dan optimize edilmiş işlevi gösterir :

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

İşte test sonuçları:

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%

Kodunuzda, codexworlds algoritmalarının çarpanı 1.852, asıl orijinal ise 1.1515'tir. Bu neden? Neden fark var?
GotBatteries

@GotBatteries Orijinal mulitplier mil içindir. Optimize edilmiş fonksiyon km cinsinden sonuç döndürür. 1.1515 * 1.609344 = 1.853. Teşekkürler, 1.853 olarak düzeltildi.
Alexander Yancharuk

Daha iyi performans için neden M_PI / 180 ve $ rad * 60 * 1.853 sabitleri olarak kullanmıyorsunuz?
Evren Yurtesen

@EvrenYurtesen Önceliğiniz performans ise güzel bir fikir. Ancak bakım ve okunabilirlik daha karmaşık hale gelecek.
Alexander Yancharuk

Sadece önceki satıra yorum yapın ve // ​​M_PI / 180 ... vb. Deyin. Bunun neden bakımını zorlaştıracağını bilmiyorum. Hiç değiştirmeyeceğiniz bir şey değil.
Evren Yurtesen

10

Burada iki enlem ve boylam arasındaki mesafeyi hesaplamak için basit ve mükemmel kod. Aşağıdaki kodu buradan bulabilirsiniz - 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

Daha kısa ve daha hızlı sevenler için (deg2rad () çağırmamak).

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

Bunu deneyin harika sonuçlar verir

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

Oldukça eski bir soru, ancak Google Haritalar ile aynı sonuçları döndüren bir PHP koduyla ilgilenenler için aşağıdakiler iş yapar:

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

Çeşitli koordinatlarla test ettim ve mükemmel çalışıyor.

Bazı alternatiflerden daha hızlı olması gerektiğini düşünüyorum. Ama bunu test etmedi.

İpucu: Google Haritalar, dünya yarıçapı olarak 6378137 kullanır. Yani diğer algoritmalarla kullanmak da işe yarayabilir.


1

Kesin değerler için şu şekilde yapın:

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 bence bunu yapmalı ...

Düzenle:

Formüler ve en azından JS uygulamaları için şunu deneyin: http://www.movable-type.co.uk/scripts/latlong.html

Cesaret edersem ... Çember fonksiyonlarındaki tüm değerleri deg2rad yapmayı unuttum ...


Cevabınız için teşekkürler. Ben arasına basit bir hesaplama ile bu uygulamayı kontrol ettim pointA (42.12) ve pointB (43,12) $ earth_radius = 6372,795477598 o 110,94 etrafında şey olması gerekirken ben sonucu 12745,591 olarak almak kullanarak
maxdangelo

1

Merhaba, İki Farklı Lat ve Uzun Kullanarak Mesafe ve Zaman Alınması İçin Kod

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

Aşağıdaki Örnek php enlem ve boylam kullanarak iki farklı konumlar arasındaki bağlantı olsun zaman kontrol edebilirsiniz


6
Matematiği kullanarak kolayca bulunabilecek bir şey için bir api çağırmak gereksiz olabilir.
Ivotje50

1

Enlem ve boylam noktaları arasındaki mesafeyi hesaplamak için bu işlevi deneyin

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


    }

Sonra fucntion kullanın

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

Umarım yardımcı olur


benim durumumda gerçek senaryo ile mükemmel çalışma doğruladı.
Daxesh Vekariya

1
Yazmak ve gerçek senaryoda doğrulamak için yaklaşık 5 saat
sürdü

0

Burada yazılan büyük daire mesafesi teorisi nedeniyle çarpan her koordinatta değiştirilir:

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

ve burada açıklanan bu formülü kullanarak en yakın değeri hesaplayabilirsiniz:

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

anahtar, her derece - dakika - saniye değerini tüm derece değerine dönüştürmektir:

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

En kolay yollardan biri:

$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 ondalık basamağa kadar yuvarlanır.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.