Рассчитать расстояние между двумя точками широты и долготы? (Формула Haversine) - PullRequest
807 голосов
/ 26 августа 2008

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

Для пояснения, я бы хотел расстояние в километрах; точки используют систему WGS84, и я хотел бы понять относительную точность доступных подходов.

Ответы [ 37 ]

1008 голосов
/ 26 августа 2008

Эта ссылка может оказаться полезной для вас, поскольку в ней подробно описывается использование формулы Хаверсин для расчета расстояния.

Выдержка:

Этот скрипт [в Javascript] вычисляет расстояния по большому кругу между двумя точками - то есть самое короткое расстояние над поверхностью земли - используя Формула "Haversine".

function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}
305 голосов
/ 07 февраля 2014

Мне нужно было рассчитать много расстояний между точками для моего проекта, поэтому я пошел дальше и попытался оптимизировать код, который я нашел здесь. В среднем в разных браузерах моя новая реализация работает в 2 раза быстрее , чем ответ с наибольшим количеством голосов.

function distance(lat1, lon1, lat2, lon2) {
  var p = 0.017453292519943295;    // Math.PI / 180
  var c = Math.cos;
  var a = 0.5 - c((lat2 - lat1) * p)/2 + 
          c(lat1 * p) * c(lat2 * p) * 
          (1 - c((lon2 - lon1) * p))/2;

  return 12742 * Math.asin(Math.sqrt(a)); // 2 * R; R = 6371 km
}

Вы можете поиграть с моим jsPerf и посмотреть результаты здесь .

Недавно мне нужно было сделать то же самое в python, так что вот реализация python :

from math import cos, asin, sqrt
def distance(lat1, lon1, lat2, lon2):
    p = 0.017453292519943295     #Pi/180
    a = 0.5 - cos((lat2 - lat1) * p)/2 + cos(lat1 * p) * cos(lat2 * p) * (1 - cos((lon2 - lon1) * p)) / 2
    return 12742 * asin(sqrt(a)) #2*R*asin...

И ради полноты: Haversine на вики.

62 голосов
/ 19 октября 2008

Вот реализация C #:

static class DistanceAlgorithm
{
    const double PIx = 3.141592653589793;
    const double RADIUS = 6378.16;

    /// <summary>
    /// Convert degrees to Radians
    /// </summary>
    /// <param name="x">Degrees</param>
    /// <returns>The equivalent in radians</returns>
    public static double Radians(double x)
    {
        return x * PIx / 180;
    }

    /// <summary>
    /// Calculate the distance between two places.
    /// </summary>
    /// <param name="lon1"></param>
    /// <param name="lat1"></param>
    /// <param name="lon2"></param>
    /// <param name="lat2"></param>
    /// <returns></returns>
    public static double DistanceBetweenPlaces(
        double lon1,
        double lat1,
        double lon2,
        double lat2)
    {
        double dlon = Radians(lon2 - lon1);
        double dlat = Radians(lat2 - lat1);

        double a = (Math.Sin(dlat / 2) * Math.Sin(dlat / 2)) + Math.Cos(Radians(lat1)) * Math.Cos(Radians(lat2)) * (Math.Sin(dlon / 2) * Math.Sin(dlon / 2));
        double angle = 2 * Math.Atan2(Math.Sqrt(a), Math.Sqrt(1 - a));
        return angle * RADIUS;
    }

}
54 голосов
/ 26 сентября 2012

Вот Java-реализация формулы Haversine.

public final static double AVERAGE_RADIUS_OF_EARTH_KM = 6371;
public int calculateDistanceInKilometer(double userLat, double userLng,
  double venueLat, double venueLng) {

    double latDistance = Math.toRadians(userLat - venueLat);
    double lngDistance = Math.toRadians(userLng - venueLng);

    double a = Math.sin(latDistance / 2) * Math.sin(latDistance / 2)
      + Math.cos(Math.toRadians(userLat)) * Math.cos(Math.toRadians(venueLat))
      * Math.sin(lngDistance / 2) * Math.sin(lngDistance / 2);

    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

    return (int) (Math.round(AVERAGE_RADIUS_OF_EARTH_KM * c));
}

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

42 голосов
/ 07 декабря 2010

Большое спасибо за все это. В своем приложении Objective-C для iPhone я использовал следующий код:

const double PIx = 3.141592653589793;
const double RADIO = 6371; // Mean radius of Earth in Km

double convertToRadians(double val) {

   return val * PIx / 180;
}

-(double)kilometresBetweenPlace1:(CLLocationCoordinate2D) place1 andPlace2:(CLLocationCoordinate2D) place2 {

        double dlon = convertToRadians(place2.longitude - place1.longitude);
        double dlat = convertToRadians(place2.latitude - place1.latitude);

        double a = ( pow(sin(dlat / 2), 2) + cos(convertToRadians(place1.latitude))) * cos(convertToRadians(place2.latitude)) * pow(sin(dlon / 2), 2);
        double angle = 2 * asin(sqrt(a));

        return angle * RADIO;
}

Широта и долгота в десятичном формате. Я не использовал min () для вызова asin (), поскольку расстояния, которые я использую, настолько малы, что им это не требуется.

Он давал неправильные ответы, пока я не передал значения в радианах - теперь они в значительной степени совпадают со значениями, полученными из приложения Apple Map:

Дополнительное обновление:

Если вы используете iOS4 или более позднюю версию, Apple предоставит несколько способов сделать это, чтобы такая же функциональность была достигнута с помощью:

-(double)kilometresBetweenPlace1:(CLLocationCoordinate2D) place1 andPlace2:(CLLocationCoordinate2D) place2 {

    MKMapPoint  start, finish;


    start = MKMapPointForCoordinate(place1);
    finish = MKMapPointForCoordinate(place2);

    return MKMetersBetweenMapPoints(start, finish) / 1000;
}
38 голосов
/ 24 июня 2012

Это простая функция PHP, которая дает очень разумное приближение (с погрешностью +/- 1%).

<?php
function distance($lat1, $lon1, $lat2, $lon2) {

    $pi80 = M_PI / 180;
    $lat1 *= $pi80;
    $lon1 *= $pi80;
    $lat2 *= $pi80;
    $lon2 *= $pi80;

    $r = 6372.797; // mean radius of Earth in km
    $dlat = $lat2 - $lat1;
    $dlon = $lon2 - $lon1;
    $a = sin($dlat / 2) * sin($dlat / 2) + cos($lat1) * cos($lat2) * sin($dlon / 2) * sin($dlon / 2);
    $c = 2 * atan2(sqrt($a), sqrt(1 - $a));
    $km = $r * $c;

    //echo '<br/>'.$km;
    return $km;
}
?>

Как сказано ранее; Земля НЕ является сферой. Это похоже на старый, старый бейсбол, с которым Марк МакГвайр решил потренироваться - он полон вмятин и неровностей. Более простые вычисления (как это) рассматривают это как сферу.

Различные методы могут быть более или менее точными в зависимости от того, где вы находитесь на этом нерегулярном овоиде И как далеко друг от друга находятся ваши точки (чем ближе они, тем меньше абсолютная погрешность). Чем точнее ваши ожидания, тем сложнее математика.

Для получения дополнительной информации: Географическое расстояние Википедии

28 голосов
/ 13 марта 2012

Я публикую здесь мой рабочий пример.

Перечислите все точки в таблице, имеющие расстояние между обозначенной точкой (мы используем случайную точку - широта: 45,20327, длина: 23,7806), менее 50 км, с широтой и долготой, в MySQL (поля таблицы :ordin_lat иordin_long) :

Перечислите все ДИСТАНЦИЯ <50, в Километрах (с учетом радиуса Земли 6371 КМ): </strong>

SELECT denumire, (6371 * acos( cos( radians(45.20327) ) * cos( radians( coord_lat ) ) * cos( radians( 23.7806 ) - radians(coord_long) ) + sin( radians(45.20327) ) * sin( radians(coord_lat) ) )) AS distanta 
FROM obiective 
WHERE coord_lat<>'' 
    AND coord_long<>'' 
HAVING distanta<50 
ORDER BY distanta desc

Приведенный выше пример был протестирован в MySQL 5.0.95 и 5.5.16 (Linux).

26 голосов
/ 16 апреля 2014

В других ответах реализация в отсутствует.

Вычислить расстояние между двумя точками довольно просто с помощью функции distm из пакета geosphere:

distm(p1, p2, fun = distHaversine)

где:

p1 = longitude/latitude for point(s)
p2 = longitude/latitude for point(s)
# type of distance calculation
fun = distCosine / distHaversine / distVincentySphere / distVincentyEllipsoid 

Поскольку Земля не идеально сферическая, формула Винсенти для эллипсоидов , вероятно, является наилучшим способом вычисления расстояний. Таким образом, в пакете geosphere вы используете:

distm(p1, p2, fun = distVincentyEllipsoid)

Конечно, вам не обязательно использовать пакет geosphere, вы также можете рассчитать расстояние в базе R с помощью функции:

hav.dist <- function(long1, lat1, long2, lat2) {
  R <- 6371
  diff.long <- (long2 - long1)
  diff.lat <- (lat2 - lat1)
  a <- sin(diff.lat/2)^2 + cos(lat1) * cos(lat2) * sin(diff.long/2)^2
  b <- 2 * asin(pmin(1, sqrt(a))) 
  d = R * b
  return(d)
}
9 голосов
/ 04 ноября 2013

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

Эта публикация от человека из НАСА - лучшая, которую я нашел при обсуждении вариантов

http://www.cs.nyu.edu/visual/home/proj/tiger/gisfaq.html

Например, если вы просто сортируете строки по расстоянию в радиусе 100 миль. Формула плоской земли будет намного быстрее, чем haversine.

HalfPi = 1.5707963;
R = 3956; /* the radius gives you the measurement unit*/

a = HalfPi - latoriginrad;
b = HalfPi - latdestrad;
u = a * a + b * b;
v = - 2 * a * b * cos(longdestrad - longoriginrad);
c = sqrt(abs(u + v));
return R * c;

Обратите внимание, что есть только один косинус и один квадратный корень. Против 9 из них по формуле Haversine.

7 голосов
/ 26 июня 2013

Вы можете использовать сборку в CLLocationDistance для вычисления этого:

CLLocation *location1 = [[CLLocation alloc] initWithLatitude:latitude1 longitude:longitude1];
CLLocation *location2 = [[CLLocation alloc] initWithLatitude:latitude2 longitude:longitude2];
[self distanceInMetersFromLocation:location1 toLocation:location2]

- (int)distanceInMetersFromLocation:(CLLocation*)location1 toLocation:(CLLocation*)location2 {
    CLLocationDistance distanceInMeters = [location1 distanceFromLocation:location2];
    return distanceInMeters;
}

В вашем случае, если вы хотите получить километры, просто разделите их на 1000.

...