如何计算由经纬度指定的两点之间的距离?

为了澄清,我想用千米来表示距离;这些点使用WGS84系统,我想了解可用方法的相对准确性。


当前回答

由于这是关于这个话题最受欢迎的讨论,我将在这里补充我从2019年底到2020年初的经验。为了补充现有的答案-我的重点是找到一个准确和快速(即向量化)的解决方案。

让我们从这里最常用的答案——哈弗辛方法开始。向量化是很简单的,参见下面python中的例子:

def haversine(lat1, lon1, lat2, lon2):
    """
    Calculate the great circle distance between two points
    on the earth (specified in decimal degrees)

    All args must be of equal length.
    Distances are in meters.
    
    Ref:
    https://stackoverflow.com/questions/29545704/fast-haversine-approximation-python-pandas
    https://ipython.readthedocs.io/en/stable/interactive/magics.html
    """
    Radius = 6.371e6
    lon1, lat1, lon2, lat2 = map(np.radians, [lon1, lat1, lon2, lat2])

    dlon = lon2 - lon1
    dlat = lat2 - lat1

    a = np.sin(dlat/2.0)**2 + np.cos(lat1) * np.cos(lat2) * np.sin(dlon/2.0)**2

    c = 2 * np.arcsin(np.sqrt(a))
    s12 = Radius * c
    
    # initial azimuth in degrees
    y = np.sin(lon2-lon1) * np.cos(lat2)
    x = np.cos(lat1)*np.sin(lat2) - np.sin(lat1)*np.cos(lat2)*np.cos(dlon)
    azi1 = np.arctan2(y, x)*180./math.pi

    return {'s12':s12, 'azi1': azi1}

就精确度而言,它是最不准确的。维基百科在没有任何来源的情况下表示相对偏差平均为0.5%。我的实验显示偏差较小。以下是10万个随机点与我的库的比较,应该精确到毫米级:

np.random.seed(42)
lats1 = np.random.uniform(-90,90,100000)
lons1 = np.random.uniform(-180,180,100000)
lats2 = np.random.uniform(-90,90,100000)
lons2 = np.random.uniform(-180,180,100000)
r1 = inverse(lats1, lons1, lats2, lons2)
r2 = haversine(lats1, lons1, lats2, lons2)
print("Max absolute error: {:4.2f}m".format(np.max(r1['s12']-r2['s12'])))
print("Mean absolute error: {:4.2f}m".format(np.mean(r1['s12']-r2['s12'])))
print("Max relative error: {:4.2f}%".format(np.max((r2['s12']/r1['s12']-1)*100)))
print("Mean relative error: {:4.2f}%".format(np.mean((r2['s12']/r1['s12']-1)*100)))

输出:

Max absolute error: 26671.47m
Mean absolute error: -2499.84m
Max relative error: 0.55%
Mean relative error: -0.02%

因此,在10万对随机坐标上,平均偏差为2.5km,这可能对大多数情况都是好的。

下一个选择是Vincenty公式,精确到毫米,这取决于收敛标准,也可以向量化。它确实有在对跖点附近收敛的问题。你可以通过放宽收敛标准使其收敛于这些点,但准确度会下降到0.25%甚至更多。在对映点之外,Vincenty将提供与地理库相近的结果,相对误差小于1。平均是E-6。

这里提到的Geographiclib实际上是当前的黄金标准。它有几个实现,而且相当快,特别是如果你使用的是c++版本。

Now, if you are planning to use Python for anything above 10k points I'd suggest to consider my vectorized implementation. I created a geovectorslib library with vectorized Vincenty routine for my own needs, which uses Geographiclib as fallback for near antipodal points. Below is the comparison vs Geographiclib for 100k points. As you can see it provides up to 20x improvement for inverse and 100x for direct methods for 100k points and the gap will grow with number of points. Accuracy-wise it will be within 1.e-5 rtol of Georgraphiclib.

Direct method for 100,000 points
94.9 ms ± 25 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
9.79 s ± 1.4 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

Inverse method for 100,000 points
1.5 s ± 504 ms per loop (mean ± std. dev. of 7 runs, 1 loop each)
24.2 s ± 3.91 s per loop (mean ± std. dev. of 7 runs, 1 loop each)

其他回答

这个链接可能对你有帮助,因为它详细介绍了使用哈弗辛公式来计算距离。

摘录:

这个脚本计算两点之间的大圆距离 也就是说,在地球表面上的最短距离-使用 “半正矢”公式。

function getDistanceFromLatLonInKm(lat1,lon1,lat2,lon2) {
  var R = 6371; // Radius of the earth in km
  var dLat = deg2rad(lat2-lat1);  // deg2rad below
  var dLon = deg2rad(lon2-lon1); 
  var a = 
    Math.sin(dLat/2) * Math.sin(dLat/2) +
    Math.cos(deg2rad(lat1)) * Math.cos(deg2rad(lat2)) * 
    Math.sin(dLon/2) * Math.sin(dLon/2)
    ; 
  var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
  var d = R * c; // Distance in km
  return d;
}

function deg2rad(deg) {
  return deg * (Math.PI/180)
}

下面是移植到Java的已接受的答案实现,以备任何人需要。

package com.project529.garage.util;


/**
 * Mean radius.
 */
private static double EARTH_RADIUS = 6371;

/**
 * Returns the distance between two sets of latitudes and longitudes in meters.
 * <p/>
 * Based from the following JavaScript SO answer:
 * http://stackoverflow.com/questions/27928/calculate-distance-between-two-latitude-longitude-points-haversine-formula,
 * which is based on https://en.wikipedia.org/wiki/Haversine_formula (error rate: ~0.55%).
 */
public double getDistanceBetween(double lat1, double lon1, double lat2, double lon2) {
    double dLat = toRadians(lat2 - lat1);
    double dLon = toRadians(lon2 - lon1);

    double a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
            Math.cos(toRadians(lat1)) * Math.cos(toRadians(lat2)) *
                    Math.sin(dLon / 2) * Math.sin(dLon / 2);
    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
    double d = EARTH_RADIUS * c;

    return d;
}

public double toRadians(double degrees) {
    return degrees * (Math.PI / 180);
}

如果你正在使用python; PIP安装地质

from geopy.distance import geodesic


origin = (30.172705, 31.526725)  # (latitude, longitude) don't confuse
destination = (30.288281, 31.732326)

print(geodesic(origin, destination).meters)  # 23576.805481751613
print(geodesic(origin, destination).kilometers)  # 23.576805481751613
print(geodesic(origin, destination).miles)  # 14.64994773134371

计算距离——尤其是大距离——的主要挑战之一是解释地球的曲率。如果地球是平的,计算两点之间的距离就会像计算直线一样简单!哈弗辛公式包括一个常数(下面是R变量),它表示地球的半径。根据你是用英里还是公里来测量,它分别等于3956英里或6367公里。 基本公式是:

Dlon = lon2 - lon1 dat = lat2 - lat1 = (sin (dlat / 2)) ^ 2 + cos (lat1) * cos (lat2) * (sin (dlon / 2)) ^ 2 C = 2 * atan2(√(a),√(1-a)) distance = R * c(其中R为地球半径) R = 6367公里OR 3956英里

     lat1, lon1: The Latitude and Longitude of point 1 (in decimal degrees)
     lat2, lon2: The Latitude and Longitude of point 2 (in decimal degrees)
     unit: The unit of measurement in which to calculate the results where:
     'M' is statute miles (default)
     'K' is kilometers
     'N' is nautical miles

样本

function distance(lat1, lon1, lat2, lon2, unit) {
    try {
        var radlat1 = Math.PI * lat1 / 180
        var radlat2 = Math.PI * lat2 / 180
        var theta = lon1 - lon2
        var radtheta = Math.PI * theta / 180
        var dist = Math.sin(radlat1) * Math.sin(radlat2) + Math.cos(radlat1) * Math.cos(radlat2) * Math.cos(radtheta);
        dist = Math.acos(dist)
        dist = dist * 180 / Math.PI
        dist = dist * 60 * 1.1515
        if (unit == "K") {
            dist = dist * 1.609344
        }
        if (unit == "N") {
            dist = dist * 0.8684
        }
        return dist
    } catch (err) {
        console.log(err);
    }
}

Java实现在根据哈弗辛公式

double calculateDistance(double latPoint1, double lngPoint1, 
                         double latPoint2, double lngPoint2) {
    if(latPoint1 == latPoint2 && lngPoint1 == lngPoint2) {
        return 0d;
    }

    final double EARTH_RADIUS = 6371.0; //km value;

    //converting to radians
    latPoint1 = Math.toRadians(latPoint1);
    lngPoint1 = Math.toRadians(lngPoint1);
    latPoint2 = Math.toRadians(latPoint2);
    lngPoint2 = Math.toRadians(lngPoint2);

    double distance = Math.pow(Math.sin((latPoint2 - latPoint1) / 2.0), 2) 
            + Math.cos(latPoint1) * Math.cos(latPoint2)
            * Math.pow(Math.sin((lngPoint2 - lngPoint1) / 2.0), 2);
    distance = 2.0 * EARTH_RADIUS * Math.asin(Math.sqrt(distance));

    return distance; //km value
}