Mesurer la distance entre deux coordonnées en PHP

145

Salut, j'ai le besoin de calculer la distance entre deux points ayant le lat et le long.

Je souhaite éviter tout appel à une API externe.

J'ai essayé d'implémenter la formule Haversine en PHP:

Voici le code:

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;

      }
    }

En le testant avec des points donnés qui ont des distances publiques, je n'obtiens pas de résultat fiable.

Je ne comprends pas s'il y a une erreur dans la formule d'origine ou dans mon implémentation

maxdangelo
la source
4
J'ai trouvé du code de travail ici dans de nombreuses langues, y compris php geodatasource.com/developers/php
krishna

Réponses:

273

Il n'y a pas longtemps, j'ai écrit un exemple de la formule haversine et l'ai publié sur mon site Web:

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

➽ Notez que vous récupérez la distance dans la même unité que vous passez avec le paramètre $earthRadius. La valeur par défaut est 6371000 mètres donc le résultat sera également en [m]. Pour obtenir le résultat en miles, vous pourriez par exemple passer 3959 miles $earthRadiuset le résultat serait en [mi]. À mon avis, c'est une bonne habitude de s'en tenir aux unités SI, s'il n'y a aucune raison particulière de faire autrement.

Éditer:

Comme TreyA l'a correctement souligné, la formule Haversine présente des faiblesses avec les points antipodaux en raison d'erreurs d'arrondi (bien qu'elle soit stable sur de petites distances). Pour les contourner, vous pouvez utiliser la formule Vincenty à la place.

/**
 * 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;
}
martinstoeckli
la source
1
@TreyA - Il existe différentes versions possibles, cette version implémente la formule de Wikipédia et est bien testée. L'angle $ signifie l'angle au milieu du monde en radians, donc on peut le multiplier par le rayon terrestre. Je peux également donner un exemple de la formule Vincenty plus complexe si quelqu'un est intéressé.
martinstoeckli
@TreyA - Oui je sais, je ne suis pas sûr de ce que tu veux dire avec ça. Avez-vous testé la fonction et avez-vous calculé un mauvais résultat? Et avez-vous regardé la formule sur Wikipedia? Vous devriez vraiment faire votre propre test et me donner un exemple de ce que vous pensez être mal calculé.
martinstoeckli
Désolé, mais je dois expliquer certaines choses maintenant. 1) La question portait sur la formule Haversine, vous devriez nous dire si vous suggérez d'utiliser une autre formule. 2) La formule Haversine a des faiblesses autour des pôles, mais est précise pour les petites distances (c'est un problème de la formule arccosine). 3) Vous avez déclaré qu'il manque une étape avec l'angle $ calculé, c'est tout simplement faux, cela ne peut pas améliorer le résultat, veuillez le tester! 4) Je suis d'accord qu'il serait préférable d'utiliser la formule Vincenty stable, j'ai déjà proposé de donner un exemple. Peut-être pourriez-vous aussi écrire une réponse?
martinstoeckli
@martinstoekli - vous avez raison, il ne vous manque aucune étape dans votre formule Haversine. J'ai supprimé mes commentaires pour ne pas dérouter les futurs lecteurs.
TreyA
1
@PratikCJoshi - Enfin trouvé le temps d'ajouter une note sur l'utilisation de différentes unités.
martinstoeckli
63

J'ai trouvé ce code qui me donne des résultats fiables.

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

résultats :

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>";
Janith Chinthana
la source
2
super truc, j'ai essayé ceci et aussi google maps montre la même distance que des changements décimaux ici et là ..
Zohair
Et si vous souhaitez calculer la distance entre trois points?
kexxcream
3
appelez cette fonction deux fois et additionnez-les,
sinon
renvoie NaN dans certaines conditions stackoverflow.com/questions/37184259/…
Zahur Sh
23

C'est juste un ajout aux réponses @martinstoeckli et @Janith Chinthana . Pour ceux qui souhaitent savoir quel algorithme est le plus rapide, j'ai écrit le test de performance . Le meilleur résultat de performance montre une fonction optimisée de 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;
}

Voici les résultats des tests:

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%
Alexander Yancharuk
la source
Dans votre code, le multiplicateur des algorithmes codexworlds est 1,852, alors que l'original réel est 1,1515. Pourquoi est-ce? Pourquoi la différence?
GotBatteries
@GotBatteries Le multiplicateur d'origine est pour des miles. La fonction optimisée renvoie le résultat en km. 1.1515 * 1.609344 = 1.853. Merci, fixé à 1.853.
Alexander Yancharuk
Pourquoi n'utilisez-vous pas M_PI / 180 et $ rad * 60 * 1.853 comme constantes pour de meilleures performances?
Evren Yurtesen
@EvrenYurtesen Bonne idée si votre priorité est la performance. Mais la maintenabilité et la lisibilité deviendront plus compliquées je pense.
Alexander Yancharuk
Mettez simplement un commentaire sur la ligne précédente et dites // M_PI / 180 ... etc. Je ne sais pas pourquoi cela rendrait la maintenance difficile. Ce n'est pas quelque chose que vous changerez jamais.
Evren Yurtesen
10

Voici le code simple et parfait pour calculer la distance entre deux latitude et longitude. Le code suivant a été trouvé à partir d'ici - 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';
JoyGuru
la source
5

Pour ceux qui aiment le plus court et le plus rapide (ne pas appeler 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
}
Semra
la source
2

Essayez ceci donne des résultats impressionnants

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);
    }
Amit
la source
2

Question assez ancienne, mais pour ceux qui s'intéressent à un code PHP qui renvoie les mêmes résultats que Google Maps, ce qui suit fait le travail:

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

J'ai testé avec différentes coordonnées et cela fonctionne parfaitement.

Je pense que cela devrait être plus rapide que certaines alternatives. Mais je n'ai pas testé cela.

Astuce: Google Maps utilise 6378137 comme rayon de la Terre. Donc, l'utiliser avec d'autres algorithmes pourrait également fonctionner.

Diego Andrade
la source
1

Pour les valeurs exactes, faites-le comme ça:

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 je pense que ça devrait le faire ...

Éditer:

Pour les formulaires et au moins les implémentations JS, essayez: http://www.movable-type.co.uk/scripts/latlong.html

Osez-moi ... j'ai oublié de deg2rad toutes les valeurs dans les fonctions de cercle ...

Legy
la source
Merci pour votre réponse. J'ai vérifié cette implémentation avec un calcul simple entre pointA (42,12) et pointB (43,12) en utilisant $ earth_radius = 6372.795477598 J'obtiens comme résultat 12745.591 alors que cela devrait être quelque chose autour de 110,94
maxdangelo
1

Bonjour ici Code pour obtenir la distance et le temps en utilisant deux lat et long différents

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

Vous pouvez vérifier l'exemple ci-dessous Lien obtenir le temps entre deux endroits différents en utilisant la latitude et la longitude en php

Jydipsinh Parmar
la source
6
Il peut être inutile d'appeler une API pour quelque chose qui peut tout simplement être trouvé en utilisant les mathématiques.
Ivotje50 du
1

Essayez cette fonction pour calculer la distance entre des points de latitude et de longitude

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


    }

Ensuite, utilisez la fonction comme

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

J'espère que ça aide

Manojkiran.A
la source
vérifié avec un vrai scénario de travail parfait dans mon cas.
Daxesh Vekariya
1
Il a fallu près de 5 heures pour l'écrire et le vérifier dans un scénario réel
Manojkiran.A
0

Le multiplicateur est changé à chaque coordonnée en raison de la théorie de la distance des grands cercles comme écrit ici:

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

et vous pouvez calculer la valeur la plus proche en utilisant cette formule décrite ici:

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

la clé convertit chaque degré - minute - seconde valeur en toute valeur en degré:

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'
Taha Paksu
la source
0

L'un des moyens les plus simples est:

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

Il arrondira jusqu'à 2 décimales.

NAVNEET CHANDAN
la source