Отримання відстані між двома точками на основі широти / довготи


157

Я спробував реалізувати цю формулу: http://andrew.hedges.name/experiment/haversine/ Аплет робить добре для двох тестів, які я тестую:

введіть тут опис зображення

Але мій код не працює.

from math import sin, cos, sqrt, atan2

R = 6373.0

lat1 = 52.2296756
lon1 = 21.0122287
lat2 = 52.406374
lon2 = 16.9251681

dlon = lon2 - lon1
dlat = lat2 - lat1
a = (sin(dlat/2))**2 + cos(lat1) * cos(lat2) * (sin(dlon/2))**2
c = 2 * atan2(sqrt(a), sqrt(1-a))
distance = R * c

print "Result", distance
print "Should be", 278.546

Відстань, яку він повертає, становить 5447.05546147 . Чому?

Відповіді:


206

Редагувати: Як зауваження, якщо вам просто потрібен швидкий і простий спосіб пошуку відстані між двома точками, я настійно рекомендую використовувати підхід, описаний у відповіді Курта нижче, замість того, щоб повторно реалізовувати Гаверсін - див. Його допис для обґрунтування.

Ця відповідь фокусується саме на тому, щоб відповісти на конкретну помилку, на яку потрапила ОП.


Це тому, що в Python усі тригельні функції використовують радіани , а не градуси.

Ви можете або перетворити числа вручну в радіани, або скористатися radiansфункцією з математичного модуля:

from math import sin, cos, sqrt, atan2, radians

# approximate radius of earth in km
R = 6373.0

lat1 = radians(52.2296756)
lon1 = radians(21.0122287)
lat2 = radians(52.406374)
lon2 = radians(16.9251681)

dlon = lon2 - lon1
dlat = lat2 - lat1

a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))

distance = R * c

print("Result:", distance)
print("Should be:", 278.546, "km")

Відстань тепер повертає правильне значення 278.545589351км.


13
це справедливо для будь-якої мови програмування, а також для диференціального числення. використання ступенів є винятком і використовується лише в мовленні людини.
блюзмоун

11
Слово мудрому, ця формула вимагає, щоб усі ступені були позитивними. radians(abs(52.123))слід зробити трюк ...
Річард Данн

1
Ви впевнені, що всі ступені (кути?) Є позитивними? Я думаю, що це неправильно. Поміркуйте, якщо lat1, lon1 = 10, 10 (градуси) та lat2, lon2 = -10, -10 (градуси). Додавши абс () навколо градусів, відстань дорівнювала б нулю, що невірно. Можливо, ви мали намір взяти абсолютне значення dlon та / або dlat, але якщо ви подивитеся на dlon, значення dlat в обчисленні a, sine - це рівномірна функція, а косинус у квадраті - це рівномірна функція, тому я не побачити будь-яку користь від прийому абсолютної величини dlat або dlon, будь-якого.
Дейв Лекомпте

238

Оновлення: 04/2018: Зауважте, що відстань Вінсенті застаріла з моменту версії 1.13 GeoPy - замість цього слід використовувати geopy.distance.distance ()!


Наведені вище відповіді ґрунтуються на формулі Гаверсіна , яка передбачає, що земля - ​​це сфера, що призводить до помилок приблизно до 0,5% (згідно help(geopy.distance)). Відстань Вінсенті використовує більш точні еліпсоїдальні моделі, такі як WGS-84 , і реалізована в геопії . Наприклад,

import geopy.distance

coords_1 = (52.2296756, 21.0122287)
coords_2 = (52.406374, 16.9251681)

print geopy.distance.vincenty(coords_1, coords_2).km

буде надрукувати відстань 279.352901604кілометрів за допомогою еліпсоїда WGS-84 за замовчуванням. (Ви також можете вибрати .milesодну або декілька інших одиниць відстані).


1
Дякую. Чи можете ви, будь ласка, оновити свою відповідь координатами, які я вказав замість Ньюпорта та Клівленда. Це дасть краще розуміння майбутнім читачам.
gwaramadze

1
Довільні місця Ньюпорта та Клівленда походять з прикладної документації з геопії в списку PyPI
Джейсон Пархам

Мені довелося змінити відповідь Курта Піка на це: Необхідна капіталізація:print geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604
Джим,

4
Ймовірно, ви повинні використовувати geopy.distance.distance(…)в коді, який є псевдонімом найкращої (= найбільш точної) формули відстані на даний момент. (На даний момент
Вінсент

10
Використання geopy.distance.vincenty у висновках geopy-1.18.1: Вінсенті застарілий і його буде видалено в geopy 2.0. Натомість скористайтеся geopy.distance.geodesic(або за замовчуванням geopy.distance.distance), що є більш точним і завжди збігається.
juanmah

88

Людям (як я), які приходять сюди через пошукову систему і просто шукають рішення, яке працює поза коробкою, рекомендую встановити mpu. Встановіть його за допомогою pip install mpu --userта використовуйте так, щоб отримати відстань від хаверсину :

import mpu

# Point one
lat1 = 52.2296756
lon1 = 21.0122287

# Point two
lat2 = 52.406374
lon2 = 16.9251681

# What you were looking for
dist = mpu.haversine_distance((lat1, lon1), (lat2, lon2))
print(dist)  # gives 278.45817507541943.

Альтернативний пакет є gpxpy.

Якщо ви не хочете залежностей, ви можете використовувати:

import math


def distance(origin, destination):
    """
    Calculate the Haversine distance.

    Parameters
    ----------
    origin : tuple of float
        (lat, long)
    destination : tuple of float
        (lat, long)

    Returns
    -------
    distance_in_km : float

    Examples
    --------
    >>> origin = (48.1372, 11.5756)  # Munich
    >>> destination = (52.5186, 13.4083)  # Berlin
    >>> round(distance(origin, destination), 1)
    504.2
    """
    lat1, lon1 = origin
    lat2, lon2 = destination
    radius = 6371  # km

    dlat = math.radians(lat2 - lat1)
    dlon = math.radians(lon2 - lon1)
    a = (math.sin(dlat / 2) * math.sin(dlat / 2) +
         math.cos(math.radians(lat1)) * math.cos(math.radians(lat2)) *
         math.sin(dlon / 2) * math.sin(dlon / 2))
    c = 2 * math.atan2(math.sqrt(a), math.sqrt(1 - a))
    d = radius * c

    return d


if __name__ == '__main__':
    import doctest
    doctest.testmod()

Інший альтернативний пакет - [haversine][1]

from haversine import haversine, Unit

lyon = (45.7597, 4.8422) # (lat, lon)
paris = (48.8567, 2.3508)

haversine(lyon, paris)
>> 392.2172595594006  # in kilometers

haversine(lyon, paris, unit=Unit.MILES)
>> 243.71201856934454  # in miles

# you can also use the string abbreviation for units:
haversine(lyon, paris, unit='mi')
>> 243.71201856934454  # in miles

haversine(lyon, paris, unit=Unit.NAUTICAL_MILES)
>> 211.78037755311516  # in nautical miles

Вони стверджують, що мають оптимізацію продуктивності для відстаней між усіма точками у двох векторах

from haversine import haversine_vector, Unit

lyon = (45.7597, 4.8422) # (lat, lon)
paris = (48.8567, 2.3508)
new_york = (40.7033962, -74.2351462)

haversine_vector([lyon, lyon], [paris, new_york], Unit.KILOMETERS)

>> array([ 392.21725956, 6163.43638211])

Чи є спосіб змінити дану Хігет одну з точок?
yovel cohen

Ви можете просто додати різницю висоти до відстані. Я б цього не робив.
Мартін Тома

16

Я прийшов до набагато простішого і надійного рішення, яке використовується geodesic з geopyпакету, оскільки ви, швидше за все, будете використовувати його у своєму проекті, так що не потрібна додаткова установка пакету.

Ось моє рішення:

from geopy.distance import geodesic


origin = (30.172705, 31.526725)  # (latitude, longitude) don't confuse
dist = (30.288281, 31.732326)

print(geodesic(origin, dist).meters)  # 23576.805481751613
print(geodesic(origin, dist).kilometers)  # 23.576805481751613
print(geodesic(origin, dist).miles)  # 14.64994773134371

геопія


5
import numpy as np


def Haversine(lat1,lon1,lat2,lon2, **kwarg):
    """
    This uses the ‘haversine’ formula to calculate the great-circle distance between two points – that is, 
    the shortest distance over the earth’s surface – giving an ‘as-the-crow-flies’ distance between the points 
    (ignoring any hills they fly over, of course!).
    Haversine
    formula:    a = sin²(Δφ/2) + cos φ1 ⋅ cos φ2 ⋅ sin²(Δλ/2)
    c = 2 ⋅ atan2( √a, √(1−a) )
    d = R ⋅ c
    where   φ is latitude, λ is longitude, R is earth’s radius (mean radius = 6,371km);
    note that angles need to be in radians to pass to trig functions!
    """
    R = 6371.0088
    lat1,lon1,lat2,lon2 = map(np.radians, [lat1,lon1,lat2,lon2])

    dlat = lat2 - lat1
    dlon = lon2 - lon1
    a = np.sin(dlat/2)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2) **2
    c = 2 * np.arctan2(a**0.5, (1-a)**0.5)
    d = R * c
    return round(d,4)

0

Існує кілька способів обчислення відстані на основі координат, тобто широти і довготи

Встановити та імпортувати

from geopy import distance
from math import sin, cos, sqrt, atan2, radians
from sklearn.neighbors import DistanceMetric
import osrm
import numpy as np

Визначте координати

lat1, lon1, lat2, lon2, R = 20.9467,72.9520, 21.1702, 72.8311, 6373.0
coordinates_from = [lat1, lon1]
coordinates_to = [lat2, lon2]

Використання гаверину

dlon = radians(lon2) - radians(lon1)
dlat = radians(lat2) - radians(lat1)
    
a = sin(dlat / 2)**2 + cos(lat1) * cos(lat2) * sin(dlon / 2)**2
c = 2 * atan2(sqrt(a), sqrt(1 - a))
    
distance_haversine_formula = R * c
print('distance using haversine formula: ', distance_haversine_formula)

Використання гаверсину зі склеарном

dist = DistanceMetric.get_metric('haversine')
    
X = [[radians(lat1), radians(lon1)], [radians(lat2), radians(lon2)]]
distance_sklearn = R * dist.pairwise(X)
print('distance using sklearn: ', np.array(distance_sklearn).item(1))

Використання OSRM

osrm_client = osrm.Client(host='http://router.project-osrm.org')
coordinates_osrm = [[lon1, lat1], [lon2, lat2]] # note that order is lon, lat
    
osrm_response = osrm_client.route(coordinates=coordinates_osrm, overview=osrm.overview.full)
dist_osrm = osrm_response.get('routes')[0].get('distance')/1000 # in km
print('distance using OSRM: ', dist_osrm)

Використання геопії

distance_geopy = distance.distance(coordinates_from, coordinates_to).km
print('distance using geopy: ', distance_geopy)
    
distance_geopy_great_circle = distance.great_circle(coordinates_from, coordinates_to).km 
print('distance using geopy great circle: ', distance_geopy_great_circle)

Вихідні дані

distance using haversine formula:  26.07547017310917
distance using sklearn:  27.847882224769783
distance using OSRM:  33.091699999999996
distance using geopy:  27.7528030550408
distance using geopy great circle:  27.839182219511834
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.