Formula Haversine in Python (rilevamento e distanza tra due punti GPS)


119

Problema

Vorrei sapere come ottenere la distanza e il rilevamento tra 2 punti GPS . Ho studiato la formula haversine. Qualcuno mi ha detto che avrei potuto trovare anche il rilevamento utilizzando gli stessi dati.

modificare

Funziona tutto bene ma il cuscinetto non funziona ancora bene. Il rilevamento è negativo ma dovrebbe essere compreso tra 0 e 360 ​​gradi. I dati impostati dovrebbero rendere il rilevamento orizzontale 96.02166666666666 ed è:

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

Ecco il mio nuovo codice:

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

L'implementazione di Python haversine può essere trovata codecodex.com/wiki/… . Tuttavia per i calcoli a breve distanza esistono modi molto semplici. Qual è la tua distanza massima prevista? Sei in grado di ottenere le tue coordinate in qualche sistema di coordinate cartesiane locale?
mangiare il


1
@James Dyson: con distanze come 15 km, il cerchio di creazione non conta nulla. Il mio consiglio: scopri prima la soluzione con le distanze euclidee! Questo ti darà una soluzione funzionante e poi se le tue distanze saranno molto più lunghe, allora aggiusta la tua applicazione. Grazie
mangia il

1
@James Dyson: Se il tuo commento sopra era rivolto a me (e al mio suggerimento precedente), la risposta è sicuramente (e anche abbastanza "banalmente"). Potrei essere in grado di fornire un codice di esempio, ma non utilizzerà la trigonometria, piuttosto la geometria (quindi non sono sicuro che ti aiuterà affatto. Hai familiarità con il concetto di vettore? Nel tuo caso posizioni e le indicazioni stradali potrebbero essere gestite nel modo più semplice con i vettori).
mangiare il

1
atan2(sqrt(a), sqrt(1-a))è uguale aasin(sqrt(a))
user102008

Risposte:


241

Ecco una versione di 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

10
Potrebbe usare la funzione math.radians () invece di moltiplicare per pi / 180 - stesso effetto, ma un po 'più auto-documentato.
Hugh Bothwell

4
Puoi, ma se lo dici import mathdevi specificare math.pi, math.sinecc. Con from math import *hai accesso diretto a tutti i contenuti del modulo. Dai un'occhiata agli "spazi dei nomi" in un tutorial su Python (come docs.python.org/tutorial/modules.html )
Michael Dunn

2
Come mai usi atan2 (sqrt (a), sqrt (1-a)) invece di asin (sqrt (a))? Atan2 è più preciso in questo caso?
Eyal

4
Se il raggio terrestre medio è definito come 6371 km, allora è equivalente a 3959 miglia, non 3956 miglia. Vedere Raggi medi globali per i vari modi di calcolare questi valori.
ekhumoro

3
che cosa è questo ritorno? Il rilevamento o la distanza?
AesculusMaximus

11

La maggior parte di queste risposte "arrotondano" il raggio della terra. Se li confronti con altri calcolatori di distanza (come geopy), queste funzioni saranno disattivate.

Funziona bene:

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

2
Questo è molto più accurato degli esempi sopra!
Alex van Es

Questo non affronta la variazione di R. 6356.752 km ai poli a 6378.137 km all'equatore
ldmtwo

3
Questo errore è davvero importante per la tua applicazione? cs.nyu.edu/visual/home/proj/tiger/gisfaq.html
Tejas Kale

8

Esiste anche un'implementazione vettorializzata , che consente di utilizzare 4 array numpy invece di valori scalari per le coordinate:

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

4

Il calcolo del cuscinetto non è corretto, è necessario scambiare gli input con 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

Questo ti darà il rilevamento corretto.


In realtà sto lottando per capire come sono state derivate queste equazioni mentre leggo un articolo. Mi hai dato un suggerimento: è haversine formulala prima volta che lo sento, grazie.
Arilwan

4

Puoi provare quanto segue:

from haversine import haversine
haversine((45.7597, 4.8422),(48.8567, 2.3508), unit='mi')
243.71209416020253

Come può essere utilizzato nella query ORM di Django?
Gocht

3

Ecco un'implementazione vettorializzata numpy della formula Haversine fornita da @Michael Dunn, che offre un miglioramento di 10-50 volte rispetto a vettori di grandi dimensioni.

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

2

È possibile risolvere il problema del cuscinetto negativo aggiungendo 360 °. Sfortunatamente, ciò potrebbe portare a cuscinetti più grandi di 360 ° per cuscinetti positivi. Questo è un buon candidato per l'operatore modulo, quindi tutto sommato dovresti aggiungere la riga

Bearing = (Bearing + 360) % 360

alla fine del tuo metodo.


1
Penso che sia solo: Bearing = Bearing% 360
Holger Bille

1

La Y in atan2 è, per impostazione predefinita, il primo parametro. Ecco la documentazione . Dovrai cambiare i tuoi input per ottenere l'angolo di rilevamento corretto.

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


0

Ecco due funzioni per calcolare la distanza e il rilevamento, che si basano sul codice nei messaggi precedenti e su https://gist.github.com/jeromer/2005586 (aggiunto il tipo di tupla per i punti geografici in formato lat, lon per entrambe le funzioni per maggiore chiarezza ). Ho testato entrambe le funzioni e sembrano funzionare bene.

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

questo metodo fornisce altri risultati rispetto a tutti gli altri metodi sopra!
basilisco
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.