Messen des Abstands zwischen zwei Koordinaten in PHP


145

Hallo, ich muss den Abstand zwischen zwei Punkten mit Lat und Long berechnen.

Ich möchte jeden Aufruf einer externen API vermeiden.

Ich habe versucht, die Haversine-Formel in PHP zu implementieren:

Hier ist der 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;

      }
    }

Wenn ich es mit bestimmten Punkten teste, die öffentliche Entfernungen haben, erhalte ich kein verlässliches Ergebnis.

Ich verstehe nicht, ob es einen Fehler in der Originalformel oder in meiner Implementierung gibt


4
Ich fand funktionierenden Code hier in vielen Sprachen wie PHP geodatasource.com/developers/php
krishna

Antworten:


273

Vor kurzem habe ich ein Beispiel für die Haversine-Formel geschrieben und auf meiner Website veröffentlicht:

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

➽ Beachten Sie, dass Sie den Abstand in derselben Einheit zurückerhalten, in der Sie den Parameter eingegeben haben $earthRadius. Der Standardwert ist 6371000 Meter, daher wird das Ergebnis auch in [m] angezeigt. Um das Ergebnis in Meilen zu erhalten, könnten Sie z. B. 3959 Meilen überschreiten, $earthRadiusund das Ergebnis wäre in [mi]. Meiner Meinung nach ist es eine gute Angewohnheit, bei den SI-Einheiten zu bleiben, wenn es keinen besonderen Grund gibt, etwas anderes zu tun.

Bearbeiten:

Wie TreyA richtig hervorhob, weist die Haversine-Formel aufgrund von Rundungsfehlern Schwächen mit antipodalen Punkten auf (obwohl sie für kleine Entfernungen stabil ist ). Um sie zu umgehen , können Sie stattdessen die Vincenty-Formel verwenden.

/**
 * 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 - Es sind verschiedene Versionen möglich, diese Version implementiert die Formel in Wikipedia und ist gut getestet. Der Winkel $ bedeutet den Winkel in der Mitte der Welt im Bogenmaß, sodass man ihn mit dem Erdradius multiplizieren kann. Ich kann auch ein Beispiel für die komplexere Vincenty-Formel geben, wenn jemand interessiert ist.
Martinstoeckli

@TreyA - Ja, ich weiß, ich bin mir nicht sicher, was du damit sagen willst. Haben Sie die Funktion getestet und ein falsches Ergebnis berechnet? Und haben Sie sich die Formel in Wikipedia angesehen? Sie sollten wirklich einen eigenen Test machen und mir ein Beispiel geben, was Ihrer Meinung nach falsch berechnet wird.
Martinstoeckli

Entschuldigung, aber ich muss jetzt einige Dinge erklären. 1) Die Frage betraf die Haversine-Formel. Sie sollten uns mitteilen, ob Sie die Verwendung einer anderen Formel vorschlagen. 2) Die Haversine Formel hat Schwächen um die Pole, aber ist genau für kleine Entfernungen (es ist ein Problem der Arkuskosinus Formel). 3) Sie haben angegeben, dass ein Schritt mit dem berechneten $ -Winkel fehlt. Das ist einfach falsch. Es kann das Ergebnis nicht verbessern. Bitte testen Sie es! 4) Ich stimme zu, dass es besser wäre, die stabile Vincenty-Formel zu verwenden. Ich habe bereits angeboten, ein Beispiel zu nennen. Vielleicht könnten Sie auch eine Antwort schreiben?
Martinstoeckli

@martinstoekli - Sie haben Recht, in Ihrer Haversine-Formel fehlt kein Schritt. Ich habe meine Kommentare entfernt, um zukünftige Leser nicht zu verwirren.
TreyA

1
@PratikCJoshi - Endlich die Zeit gefunden, einen Hinweis zur Verwendung verschiedener Einheiten hinzuzufügen.
Martinstoeckli

62

Ich habe diesen Code gefunden, der mir zuverlässige Ergebnisse liefert.

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

Ergebnisse :

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
tolles Zeug, ich habe es versucht und auch Google Maps zeigt hier und da nur dezimale Änderungen der gleichen Entfernung.
Zohair

Was ist, wenn Sie den Abstand zwischen drei Punkten berechnen möchten?
Kexxcream

3
Rufen Sie diese Funktion zweimal auf und addieren Sie sie. Alternativ ändern Sie die Funktion entsprechend
Janith Chinthana

gibt NaN unter bestimmten Bedingungen zurück stackoverflow.com/questions/37184259/…
Zahur Sh

23

Es ist nur eine Ergänzung zu den Antworten von @martinstoeckli und @Janith Chinthana . Für diejenigen, die neugierig sind, welcher Algorithmus am schnellsten ist, habe ich den Leistungstest geschrieben . Das beste Leistungsergebnis zeigt die optimierte Funktion von 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;
}

Hier sind die Testergebnisse:

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%

In Ihrem Code beträgt der Multiplikator für die Algorithmen von codexworlds 1,852, während das tatsächliche Original 1,1515 beträgt. Warum ist das? Warum der Unterschied?
GotBatteries

@ GotBatteries Original Mulitplier ist für Meilen. Optimierte Funktionsrückgabe ergibt km. 1.1515 * 1.609344 = 1.853. Danke, behoben auf 1.853.
Alexander Yancharuk

Warum verwenden Sie nicht M_PI / 180 und $ rad * 60 * 1.853 als Konstanten für eine bessere Leistung?
Evren Yurtesen

@EvrenYurtesen Gute Idee, wenn Ihre Priorität die Leistung ist. Aber Wartbarkeit und Lesbarkeit werden meiner Meinung nach komplizierter.
Alexander Yancharuk

Setzen Sie einfach einen Kommentar in die vorherige Zeile und sagen Sie // M_PI / 180 ... usw. Ich weiß nicht, warum es schwierig sein würde, sie zu pflegen. Es ist nichts, was Sie jemals ändern werden.
Evren Yurtesen

10

Hier der einfache und perfekte Code zur Berechnung der Entfernung zwischen zwei Breiten- und Längengraden. Der folgende Code wurde von hier gefunden - 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

Für diejenigen, die kürzer und schneller mögen (ohne 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

Versuchen Sie dies ergibt fantastische Ergebnisse

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

Eine ziemlich alte Frage, aber für diejenigen, die an einem PHP-Code interessiert sind, der die gleichen Ergebnisse wie Google Maps liefert, ist Folgendes der Fall:

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

Ich habe mit verschiedenen Koordinaten getestet und es funktioniert perfekt.

Ich denke, es sollte schneller sein als einige Alternativen. Aber das habe ich nicht getestet.

Hinweis: Google Maps verwendet 6378137 als Erdradius. Die Verwendung mit anderen Algorithmen könnte also auch funktionieren.


1

Für genaue Werte machen Sie es so:

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 ich denke das sollte es tun ...

Bearbeiten:

Für Formulare und mindestens JS-Implementierungen versuchen Sie: http://www.movable-type.co.uk/scripts/latlong.html

Wagen Sie es ... Ich habe vergessen, alle Werte in den Kreisfunktionen zu deg2rad ...


Danke für deine Antwort. Ich habe diese Implementierung mit einer einfachen Berechnung zwischen Punkt A (42,12) und Punkt B (43,12) unter Verwendung von $ earth_radius = 6372.795477598 überprüft. Ich erhalte als Ergebnis 12745.591, wenn es etwas um 110,94 sein sollte
maxdangelo

1

Hallo hier Code zum Abrufen von Entfernung und Zeit mit zwei verschiedenen Lat und 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);

Sie können das Beispiel unter Link überprüfen, um die Zeit zwischen zwei verschiedenen Orten mithilfe von Längen- und Breitengrad in PHP abzurufen


6
Es kann unnötig sein, eine API für etwas aufzurufen, das ganz einfach mithilfe von Mathematik gefunden werden kann.
Ivotje50

1

Probieren Sie diese Funktion aus, um die Entfernung zwischen den Längen- und Breitengraden zu berechnen

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


    }

Verwenden Sie dann die Funktion als

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

Ich hoffe es hilft


verifiziert mit realem Szenario perfekte Arbeit in meinem Fall.
Daxesh Vekariya

1
Es dauerte fast 5 Stunden, um es zu schreiben und im realen Szenario zu überprüfen
Manojkiran.A

0

Der Multiplikator wird an jeder Koordinate aufgrund der hier beschriebenen Großkreisentfernungstheorie geändert:

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

und Sie können den nächsten Wert mit dieser hier beschriebenen Formel berechnen:

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

Der Schlüssel konvertiert jeden Grad - Minute - Sekunde - Wert in alle Gradwerte:

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

Eine der einfachsten Möglichkeiten ist:

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

Es werden bis zu 2 Dezimalstellen gerundet.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.