Fórmula de Haversine en Python (rumbo y distancia entre dos puntos GPS)

119

Problema

Me gustaría saber cómo obtener la distancia y el rumbo entre 2 puntos GPS . He investigado sobre la fórmula de Haversine. Alguien me dijo que también podía encontrar el rumbo usando los mismos datos.

Editar

Todo funciona bien, pero el rodamiento todavía no funciona bien. La salida del rodamiento es negativa, pero debe estar entre 0 y 360 grados. Los datos establecidos deben formar el rumbo horizontal 96.02166666666666 y son:

Start point: 53.32055555555556 , -1.7297222222222221   
Bearing:  96.02166666666666  
Distance: 2 km  
Destination point: 53.31861111111111, -1.6997222222222223  
Final bearing: 96.04555555555555

Aquí está mi nuevo código:

from math import *

Aaltitude = 2000
Oppsite  = 20000

lat1 = 53.32055555555556
lat2 = 53.31861111111111
lon1 = -1.7297222222222221
lon2 = -1.6997222222222223

lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

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))
Base = 6371 * c


Bearing =atan2(cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(lon2-lon1), sin(lon2-lon1)*cos(lat2)) 

Bearing = degrees(Bearing)
print ""
print ""
print "--------------------"
print "Horizontal Distance:"
print Base
print "--------------------"
print "Bearing:"
print Bearing
print "--------------------"


Base2 = Base * 1000
distance = Base * 2 + Oppsite * 2 / 2
Caltitude = Oppsite - Aaltitude

a = Oppsite/Base
b = atan(a)
c = degrees(b)

distance = distance / 1000

print "The degree of vertical angle is:"
print c
print "--------------------"
print "The distance between the Balloon GPS and the Antenna GPS is:"
print distance
print "--------------------"
avitex
fuente
La implementación de Python haversine se puede encontrar en codecodex.com/wiki/… . Sin embargo, para cálculos de distancias cortas existen formas muy simples. Ahora, ¿cuál es su distancia máxima esperada? ¿Puede obtener sus coordenadas en algún sistema de coordenadas cartesiano local?
comer
Algunas implementaciones en python: - code.activestate.com/recipes/… - platoscave.net/blog/2009/oct/5/…
Fábio Diniz
1
@James Dyson: con distancias como 15 km, el círculo creativo no cuenta nada. Mi sugerencia: averigua primero la solución con distancias euclidianas. Eso le dará una solución de trabajo y luego, más adelante, si sus distancias serán mucho más largas, ajuste su aplicación. Gracias
comer
1
@James Dyson: Si su comentario anterior estaba dirigido a mí (y a mi sugerencia anterior), la respuesta es seguramente (y bastante 'trivialmente' también). Es posible que pueda dar algún código de ejemplo, pero no utilizará trigonometría, sino geometría (así que no estoy seguro de si le ayudará en absoluto. ¿Está familiarizado con el concepto de vector? En su caso, las posiciones y direcciones podrían manejarse de la manera más sencilla con vectores).
comer
1
atan2(sqrt(a), sqrt(1-a))es lo mismo queasin(sqrt(a))
user102008

Respuestas:

241

Aquí hay una versión de Python:

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

def haversine(lon1, lat1, lon2, lat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    """
    # convert decimal degrees to radians 
    lon1, lat1, lon2, lat2 = map(radians, [lon1, lat1, lon2, lat2])

    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6371 # Radius of earth in kilometers. Use 3956 for miles
    return c * r
Michael Dunn
fuente
10
Podría usar la función math.radians () en lugar de multiplicar por pi / 180 - mismo efecto, pero un poco más autodocumentado.
Hugh Bothwell
4
Puedes, pero si lo dices import math, tienes que especificar math.pi, math.sinetc. Con from math import *obtienes acceso directo a todos los contenidos del módulo. Consulte los "espacios de nombres" en un tutorial de Python (como docs.python.org/tutorial/modules.html )
Michael Dunn
2
¿Cómo es que usas atan2 (sqrt (a), sqrt (1-a)) en lugar de solo asin (sqrt (a))? ¿Es atan2 más preciso en este caso?
Eyal
4
Si el radio medio de la Tierra se define como 6371 km, entonces eso equivale a 3959 millas, no a 3956 millas. Consulte Radios promedio globales para conocer varias formas de calcular estos valores.
ekhumoro
3
¿Qué es esto regresando? ¿El rumbo o la distancia?
AesculusMaximus
11

La mayoría de estas respuestas "redondean" el radio de la Tierra. Si los compara con otras calculadoras de distancia (como geopy), estas funciones estarán desactivadas.

Esto funciona bien:

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

def haversine(lat1, lon1, lat2, lon2):

      R = 3959.87433 # this is in miles.  For Earth radius in kilometers use 6372.8 km

      dLat = radians(lat2 - lat1)
      dLon = radians(lon2 - lon1)
      lat1 = radians(lat1)
      lat2 = radians(lat2)

      a = sin(dLat/2)**2 + cos(lat1)*cos(lat2)*sin(dLon/2)**2
      c = 2*asin(sqrt(a))

      return R * c

# Usage
lon1 = -103.548851
lat1 = 32.0004311
lon2 = -103.6041946
lat2 = 33.374939

print(haversine(lat1, lon1, lat2, lon2))
Arcilla
fuente
2
¡Este es mucho más preciso que los ejemplos anteriores!
Alex van Es
Esto no aborda la variación de R. 6356,752 km en los polos a 6378,137 km en el ecuador
ldmtwo
3
¿Ese error realmente es importante para su aplicación? cs.nyu.edu/visual/home/proj/tiger/gisfaq.html
Tejas Kale
8

También hay una implementación vectorizada , que permite usar 4 matrices numpy en lugar de valores escalares para coordenadas:

def distance(s_lat, s_lng, e_lat, e_lng):

   # approximate radius of earth in km
   R = 6373.0

   s_lat = s_lat*np.pi/180.0                      
   s_lng = np.deg2rad(s_lng)     
   e_lat = np.deg2rad(e_lat)                       
   e_lng = np.deg2rad(e_lng)  

   d = np.sin((e_lat - s_lat)/2)**2 + np.cos(s_lat)*np.cos(e_lat) * np.sin((e_lng - s_lng)/2)**2

   return 2 * R * np.arcsin(np.sqrt(d))
Sergey Malyutin
fuente
4

El cálculo del rumbo es incorrecto, debe cambiar las entradas a atan2.

    bearing = atan2(sin(long2-long1)*cos(lat2), cos(lat1)*sin(lat2)-sin(lat1)*cos(lat2)*cos(long2-long1))
    bearing = degrees(bearing)
    bearing = (bearing + 360) % 360

Esto le dará el rumbo correcto.

Jon Anderson
fuente
De hecho, estoy luchando por comprender cómo se derivaron estas ecuaciones mientras leo un artículo. Me ha dado un consejo: haversine formulala primera vez que escucho esto, gracias.
arilwan
4

Puedes probar lo siguiente:

from haversine import haversine
haversine((45.7597, 4.8422),(48.8567, 2.3508), unit='mi')
243.71209416020253
Vamshi G
fuente
¿Cómo se puede usar esto en una consulta ORM de Django?
Gocht
3

Aquí hay una implementación vectorizada numerosa de la Fórmula Haversine dada por @Michael Dunn, brinda una mejora de 10 a 50 veces sobre los vectores grandes.

from numpy import radians, cos, sin, arcsin, sqrt

def haversine(lon1, lat1, lon2, lat2):
    """
    Calculate the great circle distance between two points 
    on the earth (specified in decimal degrees)
    """

    #Convert decimal degrees to Radians:
    lon1 = np.radians(lon1.values)
    lat1 = np.radians(lat1.values)
    lon2 = np.radians(lon2.values)
    lat2 = np.radians(lat2.values)

    #Implementing Haversine Formula: 
    dlon = np.subtract(lon2, lon1)
    dlat = np.subtract(lat2, lat1)

    a = np.add(np.power(np.sin(np.divide(dlat, 2)), 2),  
                          np.multiply(np.cos(lat1), 
                                      np.multiply(np.cos(lat2), 
                                                  np.power(np.sin(np.divide(dlon, 2)), 2))))
    c = np.multiply(2, np.arcsin(np.sqrt(a)))
    r = 6371

    return c*r
Shubham Singh Yadav
fuente
2

Puede resolver el problema del rodamiento negativo agregando 360 °. Desafortunadamente, esto puede resultar en rodamientos mayores de 360 ​​° para rodamientos positivos. Este es un buen candidato para el operador de módulo, por lo que, en general, debe agregar la línea

Bearing = (Bearing + 360) % 360

al final de su método.

OBu
fuente
1
Creo que es solo: Bearing = Bearing% 360
Holger Bille
1

La Y en atan2 es, por defecto, el primer parámetro. Aquí está la documentación . Deberá cambiar sus entradas para obtener el ángulo de orientación correcto.

bearing = atan2(sin(lon2-lon1)*cos(lat2), cos(lat1)*sin(lat2)in(lat1)*cos(lat2)*cos(lon2-lon1))
bearing = degrees(bearing)
bearing = (bearing + 360) % 360
gisdude
fuente
0

Aquí hay dos funciones para calcular la distancia y el rumbo, que se basan en el código de los mensajes anteriores y https://gist.github.com/jeromer/2005586 (se agregó el tipo de tupla para puntos geográficos en formato lat, lon para ambas funciones para mayor claridad ). Probé ambas funciones y parecen funcionar bien.

#coding:UTF-8
from math import radians, cos, sin, asin, sqrt, atan2, degrees

def haversine(pointA, pointB):

    if (type(pointA) != tuple) or (type(pointB) != tuple):
        raise TypeError("Only tuples are supported as arguments")

    lat1 = pointA[0]
    lon1 = pointA[1]

    lat2 = pointB[0]
    lon2 = pointB[1]

    # convert decimal degrees to radians 
    lat1, lon1, lat2, lon2 = map(radians, [lat1, lon1, lat2, lon2]) 

    # haversine formula 
    dlon = lon2 - lon1 
    dlat = lat2 - lat1 
    a = sin(dlat/2)**2 + cos(lat1) * cos(lat2) * sin(dlon/2)**2
    c = 2 * asin(sqrt(a)) 
    r = 6371 # Radius of earth in kilometers. Use 3956 for miles
    return c * r


def initial_bearing(pointA, pointB):

    if (type(pointA) != tuple) or (type(pointB) != tuple):
        raise TypeError("Only tuples are supported as arguments")

    lat1 = radians(pointA[0])
    lat2 = radians(pointB[0])

    diffLong = radians(pointB[1] - pointA[1])

    x = sin(diffLong) * cos(lat2)
    y = cos(lat1) * sin(lat2) - (sin(lat1)
            * cos(lat2) * cos(diffLong))

    initial_bearing = atan2(x, y)

    # Now we have the initial bearing but math.atan2 return values
    # from -180° to + 180° which is not what we want for a compass bearing
    # The solution is to normalize the initial bearing as shown below
    initial_bearing = degrees(initial_bearing)
    compass_bearing = (initial_bearing + 360) % 360

    return compass_bearing

pA = (46.2038,6.1530)
pB = (46.449, 30.690)

print haversine(pA, pB)

print initial_bearing(pA, pB)
Oleksiy Muzalyev
fuente
¡Este método da otros resultados que todos los demás métodos anteriores!
basilisco