基于经纬度计算两点之间的距离

278

我尝试实现在基于纬度和经度计算距离中的公式。这个小应用对我测试的这两个点效果很好:

Enter image description here

但是我的代码没有运行。

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

它返回距离5447.05546147。为什么?


这个回答解决了你的问题吗?Python中的Haversine公式(计算两个GPS点之间的方位和距离) - Gonçalo Peres
12个回答

416
Vincenty距离现在已被弃用自GeoPy 1.13版本 - 您应该使用geopy.distance.distance()代替!
上面的答案基于 haversine公式,它假设地球是一个球体,这会导致误差高达约0.5%(根据help(geopy.distance))。 Vincenty距离使用更精确的椭球模型,例如WGS-84,并在geopy中实现。例如,
import geopy.distance

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

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

将使用默认的WGS-84椭球体打印出距离为279.352901604公里的距离。(您还可以选择.miles或其他几种距离单位。)


2
谢谢。您能否更新您在答案中提供的坐标,而不是使用纽波特和克利夫兰?这将更好地帮助未来的读者理解。 - gwaramadze
2
Newport和Cleveland的任意位置来自PyPI列表中geopy文档示例:https://pypi.python.org/pypi/geopy - Jason Parham
5
我需要修改Kurt Peek的回答: 需要大写字母:print geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604 修改结果:print Geopy.distance.VincentyDistance(coords_1, coords_2).km 279.352901604 - Jim
8
你应该在代码中使用geopy.distance.distance(...),它是当前最佳(即最准确)距离公式的别名。(目前是Vincenty。) - mbirth
40
在geopy-1.18.1中使用geopy.distance.vincenty会输出以下信息:Vincenty已被弃用并将在geopy 2.0中删除。请改用更为精确且始终收敛的geopy.distance.geodesic(或默认的geopy.distance.distance)。 - juanmah
显示剩余6条评论

303

需要注意的是,如果你只需要快速简单地找到两个点之间的距离,我强烈建议使用下面Kurt的回答中所描述的方法,而不是重新实现 Haversine,详细信息请参见他的帖子。

这个答案的重点是解决 OP 遇到的具体问题。


这是因为在 Python 中,所有三角函数都使用弧度制,而不是角度制。

你可以手动将数字转换为弧度,或者使用math模块中的radians函数:

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

距离现在返回了正确的值:278.545589351 公里。


15
这一点在任何编程语言和微积分中都是正确的。使用度数是个例外,只有在人类语言中才会用到。 - bluesmoon
14
这个公式需要确保所有角度都是正数。使用radians(abs(52.123))就能满足要求了。 - Richard Dunn
7
您确定所有角度(度数)都是正数吗? 我认为这是错误的。考虑如果lat1,lon1 = 10, 10(度数),lat2,lon2 = -10,-10(度数)。通过在度数周围添加abs()函数,距离将为零,这是不正确的。也许您打算取dlon和/或dlat的绝对值,但是如果您查看a的计算中的dlon,dlat的值,正弦是一个偶函数,余弦平方也是一个偶函数,因此我不认为取dlat或dlon的绝对值会有任何好处。 - Dave LeCompte
请问上面提到的距离是两个位置之间的弧长还是平面距离? - Punita Ojha
有一个重大变更。移除了geopy.distance.vincenty,请改用geopy.distance.geodesic。您能更新您的答案吗? - ethergeist

125

对于像我这样通过搜索引擎来到这里的人,只是想要一个开箱即用的解决方案,我建议安装 mpu。使用 pip install mpu --user 进行安装,并像这样使用它来获取 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.

另一个选择是gpxpy包。

如果您不想使用依赖项,可以使用:

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

另一个可选的包是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

他们声称对于两个向量中所有点之间的距离进行了性能优化:

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

有没有办法更改其中一个点的给定高度? - user12177026
你可以简单地将高度差加到距离上。不过我不会这样做。 - Martin Thoma
里昂、巴黎,392.2172595594006 公里,哇,最后一位数字甚至不及氢原子的大小。非常精确! - mins
哇,你能帮我吗?在地图上的自定义点上是否可以获取相应的十进制度距离?例如:获取点x,y的十进制度,如距离为300米。 - Cristián Vargas Acevedo

36

我找到了一种更简单且稳定的解决方案,即使用geopy包中的geodesic,因为你很可能在项目中已经使用了它,所以不需要安装额外的包。

以下是我的解决方案:

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


谢谢你提醒纬度在前,经度在后。干杯! - Jakub

20

根据坐标(即纬度和经度)有多种方法可以计算距离。

安装和导入

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

定义坐标

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

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

使用haversine与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))

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

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

输出

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

11

您可以使用Uber的H3point_dist()函数来计算两个(纬度、经度)点之间的球面距离。我们可以设置返回单位('km','m'或'rads')。默认单位是km。

示例:

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

1
你得到了什么结果?问题是:“它返回距离5447.05546147。为什么?” - Peter Mortensen
3
@PeterMortensen,我认为回答这个特定的问题没有意义,因为我是在将近9年后回答的。我旨在为这个帖子提供有价值的信息,因为当有人在Google上搜索“使用Python获取两点之间距离”时,它似乎是最顶部的结果。 - Ransaka Ravihara

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)

你好,你认为有没有一种方法可以直接从模板中获取数据进行计算? - Louis
需要进行一些解释。例如,问题是“它返回距离5447.05546147的距离。为什么?”。这个回答如何回答那个问题?主意/要领是什么?你得到了什么结果?根据帮助中心:“…始终解释为什么您提出的解决方案是恰当的以及它的工作原理”。请通过编辑(更改)您的答案来回应,而不是在此处发表评论(无需“编辑:”,“更新:”或类似内容 - 答案应该看起来像今天写的)。 - Peter Mortensen

2
在2022年,人们可以在网页上发布混合的JavaScript和Python代码来解决这个问题。使用更近期的Python库——geographiclib会让解决问题变得更加简单。一般来说,用户可以在现代设备上运行并查看网页上的结果。

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>


使用 Pyodide,大概是这样。 - Peter Mortensen

1

(2022年,JavaScript语言在线版本。)以下是使用最新版本的JavaScript库解决问题的代码。一般的好处是用户可以在现代设备上运行并查看网页上的结果。

// 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/geographiclib-geodesic@2.0.0/geographiclib-geodesic.min.js">
</script>


1
是的,但问题标记为Python。 - Peter Mortensen

0

最简单的方法是使用 haversine 包。

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

你得到了什么结果?问题是:“它返回距离 5447.05546147。为什么?” - Peter Mortensen

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接