Obter distância entre dois pontos com base na latitude / longitude

156

Eu tentei implementar esta fórmula: http://andrew.hedges.name/experiments/haversine/ O aplet faz bem para os dois pontos que estou testando:

insira a descrição da imagem aqui

No entanto, meu código não está funcionando.

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

A distância que ele retorna é 5447.05546147 . Por quê?

gwaramadze
fonte

Respostas:

206

Edit: Apenas como uma observação, se você precisa de uma maneira rápida e fácil de encontrar a distância entre dois pontos, eu recomendo usar a abordagem descrita na resposta de Kurt abaixo, em vez de reimplementar o Haversine - veja seu post para justificar.

Esta resposta se concentra apenas em responder ao bug específico que o OP encontrou.


É porque no Python, todas as funções trigonométricas usam radianos , não graus.

Você pode converter os números manualmente em radianos ou usar a radiansfunção do módulo de matemática:

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

A distância agora está retornando o valor correto de 278.545589351km.

Michael0x2a
fonte
13
isso é verdade em qualquer linguagem de programação e também no cálculo diferencial. o uso de graus é a exceção, e usado apenas na fala humana.
Bluesmoon 17/10/2013
11
Palavra ao sábio, esta fórmula exige que todos os graus sejam positivos. radians(abs(52.123))deve fazer o truque ...
Richard Dunn
1
Você tem certeza de que todos os graus (ângulos?) São positivos? Acho que isso está errado. Considere se lat1, lon1 = 10, 10 (graus) e lat2, lon2 = -10, -10 (graus). Ao adicionar um abs () em torno dos graus, a distância seria zero, o que está incorreto. Talvez você pretenda pegar o valor absoluto de dlon e / ou dlat, mas se você olhar para os valores de dlon, dlat no cálculo de a, seno é uma função par e cosseno ao quadrado é uma função par, então não veja qualquer benefício em obter um valor absoluto de dlat ou dlon.
Dave LeCompte
238

Atualização: 04/2018: Observe que a distância Vincenty está obsoleta desde a versão 1.13 do GeoPy - você deve usar geopy.distance.distance ()!


As respostas acima são baseadas na fórmula de Haversine , que assume que a Terra é uma esfera, que resulta em erros de até 0,5% (de acordo com help(geopy.distance)). A distância Vincenty usa modelos elipsoidais mais precisos, como o WGS-84 , e é implementada em geopia . Por exemplo,

import geopy.distance

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

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

imprimirá a distância de 279.352901604quilômetros usando o elipsóide padrão WGS-84. (Você também pode escolher .milesou uma das várias outras unidades de distância).

Kurt Peek
fonte
1
Obrigado. Você pode atualizar sua resposta com as coordenadas fornecidas em questão, em vez de Newport e Cleveland. Isso dará uma melhor compreensão aos futuros leitores.
precisa saber é o seguinte
1
Os locais arbitrários de Newport e Cleveland vêm da documentação exemplo geopy na PyPI lista: pypi.python.org/pypi/geopy
Jason Parham
Eu tive que modificar a resposta de Kurt Peek a isto: Capitalização exigido:print geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604
Jim
4
Você provavelmente deve usar um geopy.distance.distance(…)código que seja um alias da melhor fórmula de distância atualmente (= mais precisa). (Vincenty no momento.)
mbirth
10
Usando geopy.distance.vincenty nas saídas geopy-1.18.1: O Vincenty foi descontinuado e será removido no geopy 2.0. Use geopy.distance.geodesic(ou o padrão geopy.distance.distance), o que é mais preciso e sempre converge.
juanmah
88

Para pessoas (como eu) vindo aqui pelo mecanismo de busca e procurando apenas uma solução que funcione imediatamente, recomendo a instalação mpu. Instale-o via pip install mpu --usere use-o desta maneira para obter a distância do Haversine :

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.

Um pacote alternativo é gpxpy.

Se você não deseja dependências, pode usar:

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

O outro pacote alternativo é [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

Eles afirmam ter otimização de desempenho para distâncias entre todos os pontos em dois vetores

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])
Martin Thoma
fonte
Existe uma maneira de mudar o Highet dado de um dos pontos?
yovel Cohen
Você pode simplesmente adicionar a diferença de altura à distância. Eu não faria isso, no entanto.
Martin Thoma
16

Cheguei a uma solução muito mais simples e robusta que está usando geodesicdo geopypacote, pois você provavelmente o utilizará em seu projeto de qualquer maneira, portanto, não é necessária nenhuma instalação extra de pacote.

Aqui está a minha solução:

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

geopy

Ramy M. Mousa
fonte
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)
Sudhirln92
fonte