Obtenir la distance entre deux points en fonction de la latitude / longitude


158

J'ai essayé d'implémenter cette formule: http://andrew.hedges.name/experiments/haversine/ L'aplet fait du bien pour les deux points que je teste:

entrez la description de l'image ici

Pourtant mon code ne fonctionne pas.

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

La distance qu'il renvoie est 5447.05546147 . Pourquoi?

Réponses:


207

Edit: Juste une note, si vous avez juste besoin d'un moyen rapide et facile de trouver la distance entre deux points, je recommande fortement d'utiliser l'approche décrite dans la réponse de Kurt ci-dessous au lieu de ré-implémenter Haversine - voir son article pour la justification.

Cette réponse se concentre uniquement sur la réponse au bogue spécifique rencontré par OP.


C'est parce qu'en Python, toutes les fonctions trig utilisent des radians , pas des degrés.

Vous pouvez soit convertir manuellement les nombres en radians, soit utiliser la radiansfonction du module mathématique:

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

La distance renvoie maintenant la valeur correcte de 278.545589351km.


13
cela est vrai dans n'importe quel langage de programmation, et aussi dans le calcul différentiel. l'utilisation de diplômes est l'exception et n'est utilisée que dans la parole humaine.
bluesmoon

11
Parole aux sages, cette formule exige que tous les degrés soient positifs. radians(abs(52.123))devrait faire l'affaire ...
Richard Dunn

1
Êtes-vous sûr que tous les degrés (angles?) Sont positifs? Je pense que cela est faux. Considérez si lat1, lon1 = 10, 10 (degrés) et lat2, lon2 = -10, -10 (degrés). En ajoutant un abs () autour des degrés, la distance serait zéro, ce qui est incorrect. Vous vouliez peut-être prendre la valeur absolue de dlon et / ou dlat, mais si vous regardez les valeurs dlon, dlat dans le calcul de a, le sinus est une fonction paire et le cosinus au carré est une fonction paire, donc je ne le fais pas voir tout avantage à prendre une valeur absolue de dlat ou dlon, non plus.
Dave LeCompte

238

Mise à jour: 04/2018: Notez que Vincenty distance est obsolète depuis la version 1.13 de GeoPy - vous devriez utiliser geopy.distance.distance () à la place!


Les réponses ci-dessus sont basées sur la formule Haversine , qui suppose que la terre est une sphère, ce qui entraîne des erreurs allant jusqu'à environ 0,5% (selon help(geopy.distance)). Vincenty distance utilise des modèles ellipsoïdaux plus précis tels que WGS-84 , et est implémenté en geopy . Par exemple,

import geopy.distance

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

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

imprime la distance en 279.352901604kilomètres en utilisant l'ellipsoïde par défaut WGS-84. (Vous pouvez également choisir .milesou l'une de plusieurs autres unités de distance).


1
Merci. Pouvez-vous s'il vous plaît mettre à jour votre réponse avec les coordonnées que j'ai fournies en question au lieu de Newport et Cleveland. Cela donnera une meilleure compréhension aux futurs lecteurs.
gwaramadze

1
Les emplacements arbitraires de Newport et Cleveland proviennent de l'exemple de documentation Geopy dans la liste PyPI: pypi.python.org/pypi/geopy
Jason Parham

J'ai dû modifier la réponse de Kurt Peek à ceci: Capitalisation requise:print geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604
Jim

4
Vous devriez probablement utiliser geopy.distance.distance(…)dans le code un alias de la meilleure formule de distance actuellement (= la plus précise). (Vincenty pour le moment.)
mbirth

10
Utilisation de geopy.distance.vincenty dans les sorties de geopy-1.18.1: Vincenty est obsolète et va être supprimé dans geopy 2.0. Utilisez plutôt geopy.distance.geodesic(ou la valeur par défaut geopy.distance.distance), qui est plus précise et converge toujours.
juanmah

88

Pour les personnes (comme moi) qui viennent ici via un moteur de recherche et qui cherchent simplement une solution qui fonctionne hors de la boîte, je recommande l'installation mpu. Installez-le via pip install mpu --useret utilisez-le comme ceci pour obtenir la distance 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.

Un autre package est gpxpy.

Si vous ne voulez pas de dépendances, vous pouvez utiliser:

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

L'autre package alternatif est [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

Ils prétendent avoir une optimisation des performances pour les distances entre tous les points dans deux vecteurs

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

Existe-t-il un moyen de modifier le Highet donné de l'un des points?
yovel cohen

Vous pouvez simplement ajouter la différence de hauteur à la distance. Mais je ne ferais pas ça.
Martin Thoma

16

Je suis arrivé à une solution beaucoup plus simple et robuste qui utilise geodesicde geopypackage car vous l'utiliserez très probablement dans votre projet de toute façon, donc aucune installation de package supplémentaire n'est nécessaire.

Voici ma solution:

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


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

Il existe plusieurs façons de calculer la distance en fonction des coordonnées, c'est-à-dire la latitude et la longitude

Installer et importer

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

Définir les coordonnées

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

Utiliser haversine

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)

Utiliser haversine avec sklearn

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

Utilisation d'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)

Utilisation de geopy

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)

Production

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