Getting distance between two points based on latitude/longitude
Asked Answered
S

13

284

I tried implementing the formula in Finding distances based on Latitude and Longitude. The applet does good for the two points I am testing:

Enter image description here

Yet my code is not working.

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

It returns the distance 5447.05546147. Why?

Schweitzer answered 16/10, 2013 at 19:49 Comment(1)
Does this answer your question? Haversine Formula in Python (Bearing and Distance between two GPS points)Transduction
H
307

Just as a note, if you just need a quick and easy way of finding the distance between two points, I strongly recommend using the approach described in Kurt's answer below instead of reimplementing Haversine—see his post for rationale.

This answer focuses just on answering the specific bug the OP ran into.


It's because in Python, all the trigonometry functions use radians, not degrees.

You can either convert the numbers manually to radians, or use the radians function from the math module:

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

The distance is now returning the correct value of 278.545589351 km.

Haroldson answered 16/10, 2013 at 19:55 Comment(5)
this is true in any programming language, and also in differential calculus. using degrees is the exception, and only used in human speech.Mazurek
Word to the wise, this formula requires all degrees be positive. radians(abs(52.123)) should do the trick...Sensitometer
Are you sure about all degrees (angles?) being positive? I think this is wrong. Consider if lat1, lon1 = 10, 10 (degrees) and lat2, lon2 = -10, -10 (degrees). By adding an abs() around the degrees, the distance would be zero, which is incorrect. Perhaps you meant to take the absolute value of dlon and/or dlat, but if you look at the dlon, dlat values in the calculation of a, sine is an even function, and cosine squared is an even function, so I don't see any benefit to taking an absolute value of dlat or dlon, either.Skurnik
Just wondering if the distance above is the arc distance or plane distance between two locations?Swirl
There was a breaking change Removed geopy.distance.vincenty, use geopy.distance.geodesic instead. Would you update your answer?Anderer
N
425

The Vincenty distance is now deprecated since GeoPy version 1.13 - you should use geopy.distance.distance() instead!


Some previous answers were based on the haversine formula, which assumes the earth is a sphere, which results in errors of up to about 0.5% (according to help(geopy.distance)). The Vincenty distance uses more accurate ellipsoidal models, such as WGS-84, and is implemented in geopy. For example,

import geopy.distance

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

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

will print the distance of 279.352901604 kilometers using the default ellipsoid WGS-84. (You can also choose .miles or one of several other distance units.)

Nathalia answered 4/4, 2017 at 15:17 Comment(11)
Thanks. Can you please update your answer with coordinates I provided in question instead of Newport and Cleveland. It will give a better understanding to future readers.Schweitzer
The arbitrary locations of Newport and Cleveland come from the example geopy documentation in the PyPI listing: pypi.python.org/pypi/geopyTheoretics
I had to modify Kurt Peek's answer to this: Capitalization required: print geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604Felicity
You should probably use geopy.distance.distance(…) in code which is an alias of the currently best (=most accurate) distance formula. (Vincenty at the moment.)Bonnett
You should probably use geopy.distance.geodesic since the distance will raise ValueError if c1=(1, 179) and c2=(0,0).Merle
Does this give distance or displacement? I mean does it take into account any vertical axis?Fugleman
Using geopy.distance.vincenty in geopy-1.18.1 outputs: Vincenty is deprecated and is going to be removed in geopy 2.0. Use geopy.distance.geodesic (or the default geopy.distance.distance) instead, which is more accurate and always converges.Permissible
geopy.distance.great_circle will run twice as fastSegal
DeprecationWarning: Vincenty is deprecated and is going to be removed in geopy 2.0. Use geopy.distance.geodesic (or the default geopy.distance.distance) instead, which is more accurate and always converges. #FYINl
Update geopy 2.0.0: from geopy import distance and then use dist = distance.distance(coords_1, coords_2).km which defaults to obtaining the geodesic distance.Nodarse
Use conda install geopy -y / pip install geopy to install the packageInextinguishable
H
307

Just as a note, if you just need a quick and easy way of finding the distance between two points, I strongly recommend using the approach described in Kurt's answer below instead of reimplementing Haversine—see his post for rationale.

This answer focuses just on answering the specific bug the OP ran into.


It's because in Python, all the trigonometry functions use radians, not degrees.

You can either convert the numbers manually to radians, or use the radians function from the math module:

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

The distance is now returning the correct value of 278.545589351 km.

Haroldson answered 16/10, 2013 at 19:55 Comment(5)
this is true in any programming language, and also in differential calculus. using degrees is the exception, and only used in human speech.Mazurek
Word to the wise, this formula requires all degrees be positive. radians(abs(52.123)) should do the trick...Sensitometer
Are you sure about all degrees (angles?) being positive? I think this is wrong. Consider if lat1, lon1 = 10, 10 (degrees) and lat2, lon2 = -10, -10 (degrees). By adding an abs() around the degrees, the distance would be zero, which is incorrect. Perhaps you meant to take the absolute value of dlon and/or dlat, but if you look at the dlon, dlat values in the calculation of a, sine is an even function, and cosine squared is an even function, so I don't see any benefit to taking an absolute value of dlat or dlon, either.Skurnik
Just wondering if the distance above is the arc distance or plane distance between two locations?Swirl
There was a breaking change Removed geopy.distance.vincenty, use geopy.distance.geodesic instead. Would you update your answer?Anderer
D
126

For people (like me) coming here via a search engine, and who are just looking for a solution which works out of the box, I recommend installing mpu. Install it via pip install mpu --user and use it like this to get the haversine distance:

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.

An alternative package is gpxpy.

If you don't want dependencies, you can use:

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

The other alternative package is haversine:

from haversine import haversine, Unit

lyon = (45.7597, 4.8422) # (latitude, longitude)
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

They claim to have performance optimization for distances between all points in two vectors:

from haversine import haversine_vector, Unit

lyon = (45.7597, 4.8422) # (latitude, longitude)
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])
Dwarfism answered 4/7, 2016 at 14:55 Comment(4)
Is there a way to change the given Highet of one of the points?Silvia
You could simply add the height difference to the distance. I would not do that, though.Dwarfism
"Lyon, Paris, 392.2172595594006 km", wow the last digit is not even the size of an atom of hydrogen. Very accurate!Kinchinjunga
wow can you hep me ? , is posible obtain the corresponding distance in decimal degrees over a custom point in map ?, ex : get the decimal degree for point x, y like tha distance in meters is 300 mtsEcclesiology
A
36

I arrived at a much simpler and robust solution which is using geodesic from geopy package since you'll be highly likely using it in your project anyways so no extra package installation needed.

Here is my 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

Adigun answered 10/8, 2019 at 21:11 Comment(1)
Thanks buddy for mentioning that latitude is first then longitude. Cheers!Burley
B
20

There are multiple ways to calculate the distance based on the coordinates i.e latitude and longitude

Install and import

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

Define coordinates

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

Using haversine

dlon = radians(lon2) - radians(lon1)
dlat = radians(lat2) - radians(lat1)
    
a = sin(dlat / 2)**2 + cos(radians(lat1)) * cos(radians(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)

Using haversine with 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))

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

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

Output

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
Barbet answered 26/7, 2020 at 6:56 Comment(0)
C
12

You can use Uber's H3,point_dist() function to compute the spherical distance between two (latitude, longitude) points. We can set the return units ('km', 'm', or 'rads'). The default unit is km.

Example:

import h3

coords_1 = (52.2296756, 21.0122287)
coords_2 = (52.406374, 16.9251681)
distance = h3.point_dist(coords_1, coords_2, unit='m') # To get distance in meters
Cottingham answered 23/2, 2021 at 12:19 Comment(2)
What result do you get? The question was: "It returns the distance 5447.05546147. Why?"Pascal
@PeterMortensen, I think it doesn't make sense to answer this particular question because I answered that almost 9 years later. I aimed to offer valuable information to this thread since it appears as the top result when someone searches for getting distance between two points using Python on Google.Cottingham
E
6
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)
Envision answered 26/6, 2019 at 9:24 Comment(2)
Hi do you think there is a way to do the calcul in getting data directly from the template ?Attila
An explanation would be in order. For instance, the question was "It returns the distance 5447.05546147. Why?". How does this answer that question? What is the idea/gist? What result do you get? From the Help Center: "...always explain why the solution you're presenting is appropriate and how it works". Please respond by editing (changing) your answer, not here in comments (without "Edit:", "Update:", or similar - the answer should appear as if it was written today).Pascal
M
2

In the year 2022, one can post mixed JavaScript and Python code that solves this problem using more recent Python library, namely, geographiclib. The general benefit is that the users can run and see the result on the web page that runs on modern devices.

async function main(){
  let pyodide = await loadPyodide();
  await pyodide.loadPackage(["micropip"]);

  console.log(pyodide.runPythonAsync(`
    import micropip
    await micropip.install('geographiclib')
    from geographiclib.geodesic import Geodesic
    lat1 = 52.2296756;
    lon1 = 21.0122287;
    lat2 = 52.406374;
    lon2 = 16.9251681;
    ans = Geodesic.WGS84.Inverse(lat1, lon1, lat2, lon2)
    dkm = ans["s12"] / 1000
    print("Geodesic solution", ans)
    print(f"Distance = {dkm:.4f} km.")
  `));
}

main();
<script src="https://cdn.jsdelivr.net/pyodide/v0.21.0/full/pyodide.js"></script>
Mansized answered 14/5, 2022 at 0:12 Comment(1)
Using Pyodide, presumably.Pascal
M
1

(Year 2022, live JavaScript version.) Here is the code that solves this problem using a more recent JavaScript library. The general benefit is that the users can run and see the result on the web page that runs on modern devices.

// Using the WGS84 ellipsoid model for computation
var geod84 = geodesic.Geodesic.WGS84;
// Input data
lat1 = 52.2296756;
lon1 = 21.0122287;
lat2 = 52.406374;
lon2 = 16.9251681;
// Do the classic `geodetic inversion` computation
geod84inv = geod84.Inverse(lat1, lon1, lat2, lon2);
// Present the solution (only the geodetic distance)
console.log("The distance is " + (geod84inv.s12/1000).toFixed(5) + " km.");
<script type="text/javascript" src="https://cdn.jsdelivr.net/npm/[email protected]/geographiclib-geodesic.min.js">
</script>
Mansized answered 13/5, 2022 at 0:3 Comment(1)
Yes, but the question is tagged with Python.Pascal
B
0

The simplest way is with the haversine package.

import haversine as hs

coord_1 = (lat, lon)
coord_2 = (lat, lon)
x = hs.haversine(coord_1, coord_2)
print(f'The distance is {x} km')
Biogeography answered 30/8, 2021 at 15:34 Comment(1)
What result do you get? The question was: "It returns the distance 5447.05546147. Why?"Pascal
M
0

Another interesting use of mixed JavaScript and Python through a Pyodide and WebAssembly implementation to obtain the solution using Python's libraries Pandas and geographiclib is also feasible.

I made extra effort using Pandas to prep the input data and when output was available, appended them to the solution column. Pandas provides many useful features for input/output for common needs. Its method toHtml is handy to present the final solution on the web page.

I found that the execution of the code in this answer is not successful on some iPhone and iPad devices. But on newer midrange Android devices it will run fine.

async function main(){
let pyodide = await loadPyodide();
await pyodide.loadPackage(["pandas", "micropip"]);
console.log(pyodide.runPythonAsync(`
import micropip
import pandas as pd
import js
print("Pandas version: " + pd.__version__)
await micropip.install('geographiclib')
from geographiclib.geodesic import Geodesic
import geographiclib as gl
print("Geographiclib version: " + gl.__version__)
data = {'Description': ['Answer to the question', 'Bangkok to Tokyo'],
      'From_long': [21.0122287, 100.6],
      'From_lat': [52.2296756, 13.8],
      'To_long': [16.9251681, 139.76],
      'To_lat': [52.406374, 35.69],
      'Distance_km': [0, 0]}
df1 = pd.DataFrame(data)
collist = ['Description','From_long','From_lat','To_long','To_lat']
div2 = js.document.createElement("div")
div2content = df1.to_html(buf=None, columns=collist, col_space=None, header=True, index=True)
div2.innerHTML = div2content
js.document.body.append(div2)
arr="<i>by Swatchai</i>"

def dkm(frLat,frLon,toLat,toLon):
    print("frLon,frLat,toLon,toLat:", frLon, "|", frLat, "|", toLon, "|", toLat)
    dist = Geodesic.WGS84.Inverse(frLat, frLon, toLat, toLon)
    return dist["s12"] / 1000

collist = ['Description','From_long','From_lat','To_long','To_lat','Distance_km']
dist = []
for ea in zip(df1['From_lat'].values, df1['From_long'].values, df1['To_lat'].values, df1['To_long'].values):
  ans = dkm(*ea)
  print("ans=", ans)
  dist.append(ans)

df1['Distance_km'] = dist
# Update content
div2content = df1.to_html(buf=None, columns=collist, col_space=None, header=True, index=False)
div2.innerHTML = div2content
js.document.body.append(div2)

# Using the haversine formula
from math import sin, cos, sqrt, atan2, radians, asin
# Approximate radius of earth in km from Wikipedia
R = 6371
lat1 = radians(52.2296756)
lon1 = radians(21.0122287)
lat2 = radians(52.406374)
lon2 = radians(16.9251681)
# https://en.wikipedia.org/wiki/Haversine_formula
def hav(angrad):
    return (1-cos(angrad))/2
h = hav(lat2-lat1)+cos(lat2)*cos(lat1)*hav(lon2-lon1)
dist2 = 2*R*asin(sqrt(h))
print(f"Distance by haversine formula = {dist2:8.6f} km.")
`));


}
main();
<script src="https://cdn.jsdelivr.net/pyodide/v0.21.0/full/pyodide.js"></script>
Pyodide implementation<br>
Mansized answered 14/5, 2022 at 8:58 Comment(0)
L
0

Here's what I use thanks to Sviatoslav Oleksiv.

   earth_radius = {"km": 6371.0087714, "mile": 3959}

   earth_radius['km'] *
   acos(cos((math.radians(p1['latitude']))) *
   cos(math.radians(p2['latitude'])) *
   cos(math.radians(p2['longitude']) -
   math.radians(p1['longitude'])) +
   sin(math.radians(p1['latitude'])) *
   sin(math.radians(p2['latitude'])))
Latria answered 18/3, 2023 at 21:9 Comment(0)
L
0

Since we are in geographical territory (!), here is a solution using cartopy.geodesic.Geodesic.inverse(). I believe you could also use pyproj.Geod.inv() to achieve something similar.

Note: cartopy uses lon-lat pairs, contrary to most other packages mentioned here.

import cartopy.geodesic as cgeo

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

globe = cgeo.Geodesic()

# Distance between two points
inv = globe.inverse(coords_1[::-1], coords_2[::-1])
print(inv.T[0]/1000)
# [279.3529016]

# Distances from one point to a list of other points
inv_2 = globe.inverse(
    coords_1[::-1],
    [coords_2[::-1], coords_3[::-1]],
)
print(inv_2.T[0]/1000)
# [279.3529016  750.45799898]
Leonleona answered 22/3 at 15:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.