Misurare la distanza tra due coordinate in PHP


145

Ciao ho la necessità di calcolare la distanza tra due punti con lat e long.

Vorrei evitare qualsiasi chiamata all'API esterna.

Ho provato a implementare la formula Haversine in PHP:

Ecco il codice:

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;

      }
    }

Provandolo con alcuni punti che hanno distanze pubbliche non ottengo un risultato affidabile.

Non capisco se c'è un errore nella formula originale o nella mia implementazione


4
Ho trovato il codice funzionante qui in molte lingue tra cui php geodatasource.com/developers/php
krishna,

Risposte:


273

Non molto tempo fa ho scritto un esempio della formula haversine e l'ho pubblicato sul mio sito 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;
}

➽ Notare che si ottiene la distanza indietro nella stessa unità in cui si passa con il parametro $earthRadius. Il valore predefinito è 6371000 metri, quindi il risultato sarà anche in [m]. Per ottenere il risultato in miglia, potresti ad esempio passare 3959 miglia come $earthRadiuse il risultato sarebbe in [mi]. Secondo me è una buona abitudine attenersi alle unità SI, se non vi è alcun motivo particolare per fare diversamente.

Modificare:

Come correttamente sottolineato da TreyA, la formula di Haversine presenta punti deboli con punti antipodali a causa di errori di arrotondamento (sebbene sia stabile per le piccole distanze). Per aggirarli , puoi invece usare la formula Vincenty .

/**
 * 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 - Esistono diverse versioni possibili, questa versione implementa la formula in Wikipedia ed è ben testata. L'angolo $ indica l'angolo in mezzo al mondo in radianti, quindi si può moltiplicarlo per il raggio terrestre. Posso anche fornire un esempio della formula Vincenty più complessa se qualcuno è interessato.
martinstoeckli

@TreyA - Sì, lo so, non sono sicuro di cosa tu voglia dire. Hai testato la funzione e ha calcolato un risultato sbagliato? E hai guardato la formula in Wikipedia? Dovresti davvero fare un test tutto tuo e darmi un esempio di ciò che pensi sia calcolato in modo errato.
martinstoeckli

Scusa, ma devo spiegare alcune cose ora. 1) La domanda era sulla formula di Haversine, dovresti dirci se suggerisci di usare un'altra formula. 2) La formula Haversine presenta punti deboli attorno ai poli, ma è precisa per le piccole distanze (è un problema della formula arccosina). 3) Hai dichiarato che manca un passaggio con l'angolo $ calcolato, è semplicemente sbagliato, non può migliorare il risultato, per favore , provalo! 4) Concordo sul fatto che sarebbe meglio usare la formula vincenty stabile, ho già offerto di fare un esempio. Forse potresti anche scrivere una risposta?
martinstoeckli

@martinstoekli: hai ragione, non hai un passaggio mancante nella tua formula Haversine. Ho rimosso i miei commenti per non confondere i futuri lettori.
TreyA,

1
@PratikCJoshi - Finalmente ho trovato il tempo di aggiungere una nota sull'uso di unità diverse.
martinstoeckli,

63

Ho trovato questo codice che mi sta dando risultati affidabili.

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

risultati:

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
grandi cose, ho provato questo e anche google maps mostra la stessa distanza solo cambiamenti decimali qua e là ..
Zohair

Cosa succede se si desidera calcolare la distanza tra tre punti?
kexxcream,

3
chiama questa funzione due volte e aggiungile, in alternativa cambi la funzione di conseguenza
Janith Chinthana,

restituisce NaN in alcune condizioni stackoverflow.com/questions/37184259/...
Zahur Sh

23

È solo un'aggiunta alle risposte di @martinstoeckli e @Janith Chinthana . Per coloro che sono curiosi di sapere quale algoritmo è il più veloce ho scritto il test delle prestazioni . Il miglior risultato prestazionale mostra una funzione ottimizzata da 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;
}

Ecco i risultati dei test:

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%

Nel tuo codice il moltiplicatore per gli algoritmi di codexworlds è 1.852, mentre l'originale effettivo è 1.1515. Perchè è questo? Perché la differenza
GotBatteries il

@GotBatteries Il mulitplier originale è per miglia. La funzione ottimizzata restituisce il risultato in km. 1.1515 * 1.609344 = 1.853. Grazie, fissato a 1.853.
Alexander Yancharuk,

Perché non usi M_PI / 180 e $ rad * 60 * 1.853 come costanti per prestazioni migliori?
Evren Yurtesen,

@EvrenYurtesen Bella idea se la tua priorità è la prestazione. Ma la manutenibilità e la leggibilità diventeranno più complicate credo.
Alexander Yancharuk,

Basta inserire un commento sulla riga precedente e dire // M_PI / 180 ... ecc. Non so perché renderebbe difficile mantenerlo. Non è qualcosa che cambierai mai.
Evren Yurtesen il

10

Ecco il codice semplice e perfetto per calcolare la distanza tra due latitudini e longitudini. Il seguente codice è stato trovato da qui: 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

Per quelli a cui piacciono i più brevi e più veloci (senza chiamare 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

Prova questo dà risultati fantastici

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

Una domanda piuttosto vecchia, ma per coloro che sono interessati a un codice PHP che restituisce gli stessi risultati di Google Maps, il seguente fa il lavoro:

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

Ho provato con varie coordinate e funziona perfettamente.

Penso che dovrebbe essere più veloce di alcune alternative. Ma non l'ho provato.

Suggerimento: Google Maps utilizza 6378137 come raggio terrestre. Quindi usarlo con altri algoritmi potrebbe funzionare anche.


1

Per i valori esatti fallo in questo modo:

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 penso che dovrebbe farlo ...

Modificare:

Per i formule e almeno le implementazioni JS, prova: http://www.movable-type.co.uk/scripts/latlong.html

Abbiate il coraggio ... Ho dimenticato di degradare tutti i valori nelle funzioni del cerchio ...


Grazie per la tua risposta. Ho verificato questa implementazione con un semplice calcolo tra il punto A (42,12) e il punto B (43,12) usando $ earth_radius = 6372.795477598 ottengo il risultato 12745.591 quando dovrebbe essere qualcosa attorno a 110,94
maxdangelo

1

Ciao qui Codice per ottenere distanza e tempo utilizzando due diversi Lat e Long

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

Puoi controllare Esempio sotto Link per ottenere il tempo tra due diverse posizioni usando latitudine e longitudine in php


6
Potrebbe non essere necessario chiamare un api per qualcosa che può essere semplicemente trovato usando la matematica.
Ivotje50,

1

Prova questa funzione per calcolare la distanza tra i punti di latitudine e longitudine

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


    }

Quindi utilizzare la fucntion come

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

Spero che sia d'aiuto


verificato con scenario reale lavoro perfetto nel mio caso.
Daxesh Vekariya,


0

Il moltiplicatore viene modificato ad ogni coordinata a causa della grande teoria della distanza del cerchio come scritta qui:

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

e puoi calcolare il valore più vicino usando questa formula qui descritta:

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

la chiave sta convertendo ciascun valore in gradi - minuti - secondi in tutti i valori in gradi:

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

Uno dei modi più semplici è:

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

Arrotonderà fino a 2 punti decimali.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.