Измерение расстояния между двумя координатами в PHP


145

Привет, у меня есть необходимость рассчитать расстояние между двумя точками, имеющими широту и длину.

Я хотел бы избежать любого вызова внешнего API.

Я пытался реализовать формулу Haversine в PHP:

Вот код:

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;

      }
    }

Тестируя это с некоторыми заданными точками, которые имеют публичные расстояния, я не получаю надежного результата.

Я не понимаю, есть ли ошибка в исходной формуле или в моей реализации


4
Я нашел рабочий код здесь на многих языках, включая php geodatasource.com/developers/php
krishna

Ответы:


273

Недавно я написал пример формулы haversine и опубликовал ее на своем сайте:

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

➽ Обратите внимание, что вы получаете расстояние обратно в той же единице, что и при передаче с параметром $earthRadius. Значение по умолчанию - 6371000 метров, поэтому результат будет также в [м]. Чтобы получить результат в милях, вы можете, например, пройти 3959 миль, $earthRadiusи результат будет в [ми]. На мой взгляд, это хорошая привычка придерживаться единиц СИ, если нет особой причины поступать иначе.

Редактировать:

Как Treya правильно указали, Haversine формула имеет недостатки с диаметрально противоположными точками из - за ошибки округления (хотя это является стабильным для малых расстояний). Чтобы обойти их, вы могли бы вместо этого использовать формулу Винсенти.

/**
 * 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 - возможны разные версии, эта версия реализует формулу в Википедии и хорошо протестирована. Угол $ означает угол в середине мира в радианах, поэтому его можно умножить на радиус Земли. Я также могу привести пример более сложной формулы Винсенти, если кому-то это интересно.
martinstoeckli

@TreyA - Да, я знаю, я не уверен, что ты хочешь сказать с этим. Вы проверили функцию и она вычислила неправильный результат? А вы смотрели формулу в Википедии? Вы должны действительно сделать свой собственный тест и дать мне пример того, что вы считаете неправильным.
martinstoeckli

Извините, но сейчас я должен кое-что объяснить. 1) Вопрос был о формуле Haversine, вы должны сказать нам, если вы предлагаете использовать другую формулу. 2) Формула Haversine имеет слабые вокруг полюсов, но является точным для небольших расстояний (это проблема формулы Арккосинуса). 3) Вы заявили, что с вычисленным углом $ отсутствует шаг, это просто неверно, он не может улучшить результат, пожалуйста, проверьте его! 4) Я согласен, что было бы лучше использовать стабильную формулу Винсенти, я уже предлагал привести пример. Может быть, вы могли бы написать ответ?
martinstoeckli

@martinstoekli - вы правы, у вас нет пропущенного шага в вашей формуле Haversine. Я удалил свои комментарии, чтобы не запутать будущих читателей.
TreyA

1
@PratikCJoshi - Наконец-то нашлось время добавить заметку об использовании разных юнитов.
martinstoeckli

63

Я нашел этот код, который дает мне надежные результаты.

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

полученные результаты :

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
отличный материал, я попробовал это, а также Google Maps показывает одинаковое расстояние только десятичные изменения здесь и там ..
Zohair

Что если вы хотите рассчитать расстояние между тремя точками?
kexxcream

3
Вызовите эту функцию два раза и сложите их, поочередно вы соответственно измените функцию
Джанит Чинтана

в некоторых условиях возвращает NaN stackoverflow.com/questions/37184259/…
Захур Ш

23

Это просто дополнение к @martinstoeckli и @Janith Chinthana . Для тех, кому интересно, какой алгоритм самый быстрый, я написал тест производительности . Лучший результат показывает оптимизированная функция от 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;
}

Вот результаты теста:

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%

В вашем коде множитель для алгоритмов codexworlds равен 1,852, тогда как фактический оригинал равен 1,1515. Почему это? Почему разница?
GotBatteries

@GotBatteries Оригинальный множитель для миль. Оптимизированная функция возвращает результат в км. 1.1515 * 1.609344 = 1.853, Спасибо, исправлено до 1.853.
Александр Янчарук

Почему бы вам не использовать M_PI / 180 и $ rad * 60 * 1.853 в качестве констант для повышения производительности?
Эврен Юртесен

@EvrenYurtesen Хорошая идея, если ваш приоритет - производительность. Но удобство и удобство чтения станут более сложными, я думаю.
Александр Янчарук

Просто оставьте комментарий к предыдущей строке и скажите // M_PI / 180 ... и т. Д. Я не знаю, почему это будет трудно поддерживать. Это не то, что ты когда-либо изменишь.
Эврен Юртесен

10

Здесь простой и совершенный код для расчета расстояния между двумя широтой и долготой. Следующий код был найден здесь - 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

Для тех, кто любит короче и быстрее (не вызывая 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

Попробуйте это дает потрясающие результаты

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

Довольно старый вопрос, но для тех, кто интересуется PHP-кодом, который возвращает те же результаты, что и Google Maps, следующее делает работу:

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

Я проверил с различными координатами, и это работает отлично.

Я думаю, что это должно быть быстрее, чем некоторые альтернативы тоже. Но не проверял это.

Подсказка: Google Maps использует 6378137 в качестве радиуса Земли. Так что использование его с другими алгоритмами также может работать.


1

Для точных значений сделайте это так:

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

Хм, я думаю, что должен сделать это ...

Редактировать:

Для формул и, по крайней мере, JS-реализаций попробуйте: http://www.movable-type.co.uk/scripts/latlong.html

Смею меня ... я забыл deg2rad все значения в функции круга ...


Спасибо за Ваш ответ. Я проверил эту реализацию с помощью простого вычисления между точкой A (42,12) и точкой B (43,12), используя $ earth_radius = 6372.795477598. В результате я получаю 12745.591, когда это должно быть что-то около 110,94
maxdangelo

1

Здравствуйте, код для получения расстояния и времени, используя два разных широты и долготы

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

Вы можете проверить пример ниже ссылки получить время между двумя различными местоположениями, используя широту и долготу в php


6
Возможно, нет необходимости вызывать API для чего-то, что может быть просто найдено с помощью математики.
Ivotje50

1

Попробуйте эту функцию, чтобы рассчитать расстояние между точками широты и долготы

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


    }

Тогда используйте функцию как

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

Надеюсь, поможет


проверено реальным сценарием идеальной работы в моем случае
Дакеш Векария

1
Потребовалось почти 5 часов, чтобы написать это и проверить это в реальном сценарии
Manojkiran.A

0

Множитель изменяется в каждой координате из-за теории расстояния большого круга, как здесь написано:

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

и вы можете рассчитать ближайшее значение, используя эту формулу, описанную здесь:

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

ключ преобразует значение каждого градуса - минуты - секунды во все значения градуса:

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

Один из самых простых способов:

$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 десятичных знаков.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.