Formule Haversine en Python (relèvement et distance entre deux points GPS)


120

Problème

Je voudrais savoir comment obtenir la distance et le relèvement entre 2 points GPS . J'ai fait des recherches sur la formule haversine. Quelqu'un m'a dit que je pouvais aussi trouver le roulement en utilisant les mêmes données.

Éditer

Tout fonctionne bien mais le roulement ne fonctionne pas encore tout à fait correctement. Le roulement est négatif mais doit être compris entre 0 et 360 degrés. Les données définies doivent faire le relèvement horizontal 96.02166666666666 et sont:

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

Voici mon nouveau code:

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'implémentation de Python haversine peut être trouvée codecodex.com/wiki/… . Cependant, pour les calculs à courte distance, il existe des moyens très simples. Maintenant, quelle est votre distance maximale attendue? Pouvez-vous obtenir vos coordonnées dans un système local de coordonnées cartésiennes?
manger le


1
@James Dyson: avec des distances comme 15 km, le cercle de création ne compte rien. Ma suggestion: trouvez d'abord la solution avec les distances euclidiennes! Cela vous donnera une solution de travail et plus tard si vos distances seront beaucoup plus longues, ajustez votre application. Merci
mangez le

1
@James Dyson: Si votre commentaire ci-dessus s'adressait à moi (et à ma suggestion précédente), la réponse est sûrement (et assez "trivialement" aussi). Je pourrais peut-être donner un exemple de code, mais il n'utilisera pas la trigonométrie, mais plutôt la géométrie (je ne suis donc pas sûr que cela vous aidera du tout. Connaissez-vous du tout le concept de vecteur? Dans votre cas, les positions et les directions pourraient être traitées de la manière la plus simple avec des vecteurs).
manger le

1
atan2(sqrt(a), sqrt(1-a))est le même queasin(sqrt(a))
user102008

Réponses:


241

Voici une version 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
Pourrait utiliser la fonction math.radians () au lieu de multiplier par pi / 180 - même effet, mais un peu plus auto-documenté.
Hugh Bothwell

4
Vous pouvez, mais si vous le dites, import mathvous devez spécifier math.pi, math.sinetc. Avec from math import *vous obtenez un accès direct à tout le contenu du module. Découvrez les «espaces de noms» dans un didacticiel python (comme docs.python.org/tutorial/modules.html )
Michael Dunn

2
Comment se fait-il que vous utilisiez atan2 (sqrt (a), sqrt (1-a)) au lieu de simplement asin (sqrt (a))? Atan2 est-il plus précis dans ce cas?
Eyal

4
Si le rayon moyen de la Terre est défini à 6371 km, cela équivaut à 3959 miles, et non 3956 miles. Voir Rayons moyens globaux pour différentes façons de calculer ces valeurs.
ekhumoro

3
quel est ce retour? Le relèvement ou la distance?
AesculusMaximus

11

La plupart de ces réponses «arrondissent» le rayon de la terre. Si vous les comparez à d'autres calculateurs de distance (tels que la géopie), ces fonctions seront désactivées.

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

2
Celui-ci est beaucoup plus précis que les exemples ci-dessus!
Alex van Es

Cela ne résout pas la variation de la R. 6356,752 km aux pôles à 6378,137 km à l'équateur
ldmtwo

3
Cette erreur est-elle vraiment importante pour votre application? cs.nyu.edu/visual/home/proj/tiger/gisfaq.html
Tejas Kale

8

Il existe également une implémentation vectorisée , qui permet d'utiliser 4 tableaux numpy au lieu de valeurs scalaires pour les coordonnées:

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

5

Le calcul du relèvement est incorrect, vous devez permuter les entrées vers 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

Cela vous donnera le bon roulement.


J'ai en fait du mal à comprendre comment ces équations ont été dérivées pendant que je lis un article. Vous m'avez donné un pointeur: c'est haversine formulala première fois que j'entends cela, merci.
arilwan

c'est correct, et le mod 360 est une belle touche
Marc Compere

4

Vous pouvez essayer ce qui suit:

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

Comment cela peut-il être utilisé dans une requête ORM de Django?
Gocht

3

Voici une implémentation vectorisée numpy de la formule Haversine donnée par @Michael Dunn, qui donne une amélioration de 10 à 50 fois par rapport aux grands vecteurs.

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

Vous pouvez résoudre le problème du roulement négatif en ajoutant 360 °. Malheureusement, cela peut entraîner des roulements supérieurs à 360 ° pour les roulements positifs. C'est un bon candidat pour l'opérateur modulo, donc dans l'ensemble, vous devez ajouter la ligne

Bearing = (Bearing + 360) % 360

à la fin de votre méthode.


1
Je pense que c'est juste: Bearing = Bearing% 360
Holger Bille

1

Le Y dans atan2 est, par défaut, le premier paramètre. Voici la documentation . Vous devrez changer vos entrées pour obtenir le bon angle de relèvement.

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

Voici deux fonctions pour calculer la distance et le relèvement, qui sont basées sur le code des messages précédents et https://gist.github.com/jeromer/2005586 (type de tuple ajouté pour les points géographiques au format lat, lon pour les deux fonctions pour plus de clarté ). J'ai testé les deux fonctions et elles semblent bien fonctionner.

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

cette méthode donne d'autres résultats que toutes les autres méthodes ci-dessus!
basilic
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.