Вопрос по coordinates, geocoding, haversine, php – Измерение расстояния между двумя координатами в PHP

115

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

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

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

Вот код:

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

      }
    }
</code>

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

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

насколько точно это нужно? Legy
Я нашел рабочий код здесь на многих языках, включая PHPgeodatasource.com/developers/php krishna

Ваш Ответ

11   ответов
225

Недавно я написал пример формулы 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;
}

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

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

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

/**
 * 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;
}
@TreyA - Да, я знаю, я не уверен, что ты хочешь сказать с этим. Вы проверили функцию, и она рассчитала неправильный результат? А вы смотрели на формулу в Википедии? Вы должны действительно сделать свой собственный тест и дать мне пример того, что вы считаете неправильным.
@martinstoekli - вы правы, у вас нет пропущенного шага в вашей формуле Haversine. Я удалил свои комментарии, чтобы не запутать будущих читателей.
@PratikCJoshi - Наконец-то нашлось время добавить заметку об использовании разных юнитов.
Извините, но сейчас я должен кое-что объяснить. 1) Вопрос был о формуле Haversine, вы должны сказать нам, если вы предлагаете использовать другую формулу. 2) Формула Хаверсайна имеет слабости вокруг полюсов, ноis точный для небольших расстояний (это проблема формулы арккозина). 3) Вы указали, что с вычисленным углом $ отсутствует шаг, который просто неверен, он не может улучшить результат, пожалуйста,test it out! 4) Я согласен, что было бы лучше использовать стабильную формулу Винсенти, я уже предлагал привести пример. Может быть, вы могли бы написать ответ?
@TreyA - возможны разные версии, эта версия реализует формулу вWikipedia и хорошо проверен. Угол $ означает угол в середине мира в радианах, поэтому его можно умножить на радиус Земли. Я также могу привести пример более сложной формулы Винсенти, если кому-то это интересно.
1

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

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

я нашелэтот код что дает мне надежные результаты.

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>";
Что если вы хотите рассчитать расстояние между тремя точками?
это на самом деле дает почти правильные результаты.
отличный материал, я пробовал это, а также Google Maps показывает одинаковое расстояние только десятичные изменения здесь и там ..
вызовите эту функцию два раза и сложите их, поочередно вы меняете функцию соответственно
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);
    }
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';
18

Это просто дополнение к@martinstoeckli а также@ Джанит Чинтана ответы. Для тех, кому интересно, какой алгоритм самый быстрый, я написалтест производительности, Лучший результат показывает оптимизированную функцию от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%
Почему вы не используете M_PI / 180 и $ rad * 60 * 1.853 в качестве констант для повышения производительности?
@GotBatteries Оригинальный множитель для миль. Оптимизированная функция возвращает результат в км.1.1515 * 1.609344 = 1.853, Спасибо, исправлено до 1.853.
В вашем коде множитель для алгоритмов codexworlds равен 1,852, тогда как фактический оригинал равен 1,1515. Почему это? Почему разница?
Просто добавьте комментарий к предыдущей строке и скажите // M_PI / 180 ... и т. Д. Я не знаю, почему это затруднит обслуживание. Это не то, что ты когда-либо изменишь.
@EvrenYurtesen Хорошая идея, если ваш приоритет - производительность. Но удобство и удобство чтения станут более сложными, я думаю.
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

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

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

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

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

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

Похожие вопросы