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

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

Я бы не хотел обращаться к внешнему API.

Я попытался реализовать формулу Хаверсина в 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;

      }
    }

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

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


person maxdangelo    schedule 07.04.2012    source источник
comment
Я нашел здесь рабочий код на многих языках, включая php geodatasource.com/developers/php   -  person krishna    schedule 09.09.2014


Ответы (12)


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

/**
 * 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, и результат будет в [mi]. На мой взгляд, это хорошая привычка придерживаться единиц СИ, если нет особой причины поступать иначе.

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

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

/**
 * 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;
}
person martinstoeckli    schedule 07.04.2012
comment
@TreyA - Возможны разные версии, эта версия реализует формулу в Википедии и хорошо протестирован. Угол $ означает угол в центре мира в радианах, поэтому его можно умножить на радиус Земли. Я также могу привести пример более сложной формулы Винсенти, если кому-то интересно. - person martinstoeckli; 08.04.2012
comment
@TreyA - Да, я знаю, я не уверен, что ты хочешь этим сказать. Вы проверили функцию и не получили ли она неверный результат? А вы смотрели формулу в Википедии? Вам действительно стоит провести собственное испытание и привести мне пример того, что, по вашему мнению, неправильно рассчитано. - person martinstoeckli; 08.04.2012
comment
Извините, но мне нужно кое-что объяснить. 1) Вопрос касался формулы хаверсина, сообщите нам, если вы предложите использовать другую формулу. 2) У формулы Хаверсина есть недостатки вокруг полюсов, но она точна для малых расстояний (это проблема формулы арккозинуса). 3) Вы заявили, что отсутствует шаг с рассчитанным углом $, это просто неверно, это не может улучшить результат, пожалуйста, протестируйте его! 4) Я согласен с тем, что было бы лучше использовать стабильную формулу Винсенти, я уже предлагал привести пример. Может, ты тоже мог бы написать ответ? - person martinstoeckli; 08.04.2012
comment
@martinstoekli - вы правы, в вашей формуле хаверсина не пропущен ни один шаг. Я удалил свои комментарии, чтобы не запутать будущих читателей. - person TreyA; 02.05.2012
comment
@capikaw - Как видно в комментариях, единицы измерения - десятичные градусы и метры. Фактически единица, которую вы передаете параметру $earthRadius, определяет единицу результата, это означает, что если вы передадите км как радиус Земли, вы получите расстояние в км. - person martinstoeckli; 23.05.2013
comment
насколько велико небольшое расстояние? пожалуйста - person Salketer; 30.10.2013
comment
@Salketer - На самом деле это не имеет значения, обе формулы, Haversine и Vincenty стабильны для малых расстояний. Это еще одна формула: арккозиновая формула имеет проблемы, поиск ошибок округления в связанная статья в Википедии. - person martinstoeckli; 30.10.2013
comment
@martinstoeckli, спасибо, но я пытаюсь понять, большие или маленькие расстояния, которые я планирую вычислить ... Это очень субъективно ... - person Salketer; 30.10.2013
comment
@Salketer - Да, это, конечно, относительное, один метр может быть как длинным, так и коротким, вам нужно будет больше сказать о своих намерениях. Если вас беспокоят ошибки округления, зачем использовать функцию arccosine? Вы можете легко вычислить самостоятельно, что ошибка округления $angle означает для расстояния, просто возьмите произведение ошибки на радиус Земли: $roundingError * $earthRadius. - person martinstoeckli; 30.10.2013
comment
А как преобразовать значение в Мили? В каких единицах возвращается возвращаемое значение ??? Просьба уточнить ! - person Pratik; 22.07.2015
comment
@Pratik - это та же единица, что и в земном радиусе. В примере это метр. См. Комментарий к capikaw выше. - person martinstoeckli; 24.07.2015
comment
@martinstoeckli, понимаете, люди не будут читать комментарии, а будут читать только ответы. Вам действительно нужно отредактировать ответ и указать, какой параметр передать, чтобы получить расстояние в милях. Многие пользователи, подобные мне, приходят сюда за ответом по поводу Distance in miles и разочаровываются. Поэтому, пожалуйста, подумайте о редактировании анклава. Не думаю, что на редактирование ответа уйдет больше 45 минут. Спасибо - person Pratik; 25.07.2015
comment
@PratikCJoshi - Наконец-то нашел время добавить примечание об использовании разных единиц измерения. - person martinstoeckli; 24.08.2015
comment
Рад, что у тебя было время :) Спасибо! - person Pratik; 24.08.2015
comment
Всем, кто ищет результат в милях. Умножьте результат в метрах на 0.000621371192, и вы получите свои мили. - person Adam Joseph Looze; 11.05.2016
comment
есть ли у кого-нибудь php-код для поиска координат после путешествия в заданном направлении на x миль, я нашел я там, но он находится в JS. movable-type.co.uk/scripts/latlong.html - person Orlando P.; 29.03.2018
comment
Я пробовал vincentyGreatCircleDistance и результат уже в км, а не в метрах - person Arief Wijaya; 05.02.2021

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

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>";
person Janith Chinthana    schedule 31.05.2015
comment
отличный материал, я пробовал это, а также карты Google показывают одинаковое расстояние, только десятичные изменения здесь и там .. - person Zohair; 30.10.2015
comment
Что, если вы хотите рассчитать расстояние между тремя точками? - person kexxcream; 19.05.2016
comment
вызовите эту функцию два раза и сложите их, поочередно вы измените функцию соответствующим образом - person Janith Chinthana; 19.05.2016
comment
возвращает NaN в некоторых условиях stackoverflow.com/questions / 37184259 / - person Zahur Sh; 01.04.2019

Это просто дополнение к @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%
person Alexander Yancharuk    schedule 02.12.2016
comment
В вашем коде множитель для алгоритмов codexworlds равен 1,852, тогда как фактический оригинал равен 1,1515. Почему это? В чем разница? - person GotBatteries; 04.12.2017
comment
@GotBatteries Оригинальный множитель для миль. Оптимизированная функция возвращает результат в км. 1.1515 * 1.609344 = 1.853. Спасибо, исправил на 1.853. - person Alexander Yancharuk; 04.12.2017
comment
Почему бы вам не использовать M_PI / 180 и $ rad * 60 * 1.853 в качестве констант для повышения производительности? - person Evren Yurtesen; 05.12.2017
comment
@EvrenYurtesen Хорошая идея, если ваш приоритет - производительность. Но я думаю, что удобство обслуживания и читаемость станут более сложными. - person Alexander Yancharuk; 05.12.2017
comment
Просто оставьте комментарий к предыдущей строке и скажите // M_PI / 180 ... и т. Д. Я не знаю, почему это затрудняет поддержку. Это не то, что вы когда-либо измените. - person Evren Yurtesen; 05.12.2017
comment
@Alexander Yancharuk, что ожидается при работе с числами с плавающей запятой. Это не ошибка. См .: php.net/manual/en/language.types.float.php Вам, наверное, и так не нужна такая высокая точность до последней цифры. Ваш код показывает только небольшую разницу в точности. Это не значит, какой результат точнее. Это зависит от ваших требований. - person Evren Yurtesen; 03.06.2018

Вот простой и идеальный код для расчета расстояния между двумя координатами широты и долготы. Отсюда был найден следующий код - 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';
person JoyGuru    schedule 19.04.2016

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

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

person Diego Andrade    schedule 30.10.2018

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

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);
    }
person Amit    schedule 23.11.2016

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

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

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

person Manojkiran.A    schedule 19.11.2018
comment
проверено реальным сценарием, идеальная работа в моем случае. - person Daxesh Vekariya; 07.06.2019
comment
Почти 5 часов ушло на то, чтобы написать и проверить в реальных условиях. - person Manojkiran.A; 09.06.2019

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

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.

Осмелитесь ... Я забыл выровнять все значения в круговых функциях ...

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

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

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'
person Taha Paksu    schedule 07.04.2012

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

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

person NAVNEET CHANDAN    schedule 14.03.2020

person    schedule
comment
Возможно, нет необходимости вызывать api для чего-то, что довольно просто найти с помощью математики. - person Ivotje50; 26.08.2016