计算两个经纬度地理坐标之间的距离

我在计算两个地理坐标之间的距离。我正在测试我的应用程序对3-4其他应用程序。当我计算距离时,我倾向于得到平均3.3英里,而其他应用程序得到的是3.5英里。对于我要进行的计算来说,这是一个很大的区别。有什么好的类库可以用来计算距离吗?我在 C # 中是这样计算的:

public static double Calculate(double sLatitude,double sLongitude, double eLatitude,
double eLongitude)
{
var radiansOverDegrees = (Math.PI / 180.0);


var sLatitudeRadians = sLatitude * radiansOverDegrees;
var sLongitudeRadians = sLongitude * radiansOverDegrees;
var eLatitudeRadians = eLatitude * radiansOverDegrees;
var eLongitudeRadians = eLongitude * radiansOverDegrees;


var dLongitude = eLongitudeRadians - sLongitudeRadians;
var dLatitude = eLatitudeRadians - sLatitudeRadians;


var result1 = Math.Pow(Math.Sin(dLatitude / 2.0), 2.0) +
Math.Cos(sLatitudeRadians) * Math.Cos(eLatitudeRadians) *
Math.Pow(Math.Sin(dLongitude / 2.0), 2.0);


// Using 3956 as the number of miles around the earth
var result2 = 3956.0 * 2.0 *
Math.Atan2(Math.Sqrt(result1), Math.Sqrt(1.0 - result1));


return result2;
}

我能做错什么呢? 我应该先计算公里数,然后再转换成英里数吗?

194786 次浏览

地理坐标类(.NETFramework4及更高版本)已经具有 GetDistanceTo方法。

var sCoord = new GeoCoordinate(sLatitude, sLongitude);
var eCoord = new GeoCoordinate(eLatitude, eLongitude);


return sCoord.GetDistanceTo(eCoord);

距离以米为单位。

您需要参考 System.Device。

这里是 JavaScript 版本的男孩和女孩

function distanceTo(lat1, lon1, lat2, lon2, unit) {
var rlat1 = Math.PI * lat1/180
var rlat2 = Math.PI * lat2/180
var rlon1 = Math.PI * lon1/180
var rlon2 = Math.PI * lon2/180
var theta = lon1-lon2
var rtheta = Math.PI * theta/180
var dist = Math.sin(rlat1) * Math.sin(rlat2) + Math.cos(rlat1) * Math.cos(rlat2) * Math.cos(rtheta);
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
}

试试这个:

    public double getDistance(GeoCoordinate p1, GeoCoordinate p2)
{
double d = p1.Latitude * 0.017453292519943295;
double num3 = p1.Longitude * 0.017453292519943295;
double num4 = p2.Latitude * 0.017453292519943295;
double num5 = p2.Longitude * 0.017453292519943295;
double num6 = num5 - num3;
double num7 = num4 - d;
double num8 = Math.Pow(Math.Sin(num7 / 2.0), 2.0) + ((Math.Cos(d) * Math.Cos(num4)) * Math.Pow(Math.Sin(num6 / 2.0), 2.0));
double num9 = 2.0 * Math.Atan2(Math.Sqrt(num8), Math.Sqrt(1.0 - num8));
return (6376500.0 * num9);
}

基于埃利奥特 · 伍德的函数如果有人对 C 函数感兴趣的话这个函数正在工作。

#define SIM_Degree_to_Radian(x) ((float)x * 0.017453292F)
#define SIM_PI_VALUE                         (3.14159265359)


float GPS_Distance(float lat1, float lon1, float lat2, float lon2)
{
float theta;
float dist;


theta = lon1 - lon2;


lat1 = SIM_Degree_to_Radian(lat1);
lat2 = SIM_Degree_to_Radian(lat2);
theta = SIM_Degree_to_Radian(theta);


dist = (sin(lat1) * sin(lat2)) + (cos(lat1) * cos(lat2) * cos(theta));
dist = acos(dist);


//   dist = dist * 180.0 / SIM_PI_VALUE;
//   dist = dist * 60.0 * 1.1515;
//   /* Convert to km */
//   dist = dist * 1.609344;


dist *= 6370.693486F;


return (dist);
}

你可以把它改成 双倍,它返回的值以 km 为单位。

GetRange 是最好的解决方案 ,但在许多情况下,我们 不能用 this Method (例如 Universal App)

  • 计算距离到坐标之间的算法伪码 :

    public static double DistanceTo(double lat1, double lon1, double lat2, double lon2, char unit = 'K')
    {
    double rlat1 = Math.PI*lat1/180;
    double rlat2 = Math.PI*lat2/180;
    double theta = lon1 - lon2;
    double rtheta = Math.PI*theta/180;
    double dist =
    Math.Sin(rlat1)*Math.Sin(rlat2) + Math.Cos(rlat1)*
    Math.Cos(rlat2)*Math.Cos(rtheta);
    dist = Math.Acos(dist);
    dist = dist*180/Math.PI;
    dist = dist*60*1.1515;
    
    
    switch (unit)
    {
    case 'K': //Kilometers -> default
    return dist*1.609344;
    case 'N': //Nautical Miles
    return dist*0.8684;
    case 'M': //Miles
    return dist;
    }
    
    
    return dist;
    }
    
  • Real World C# Implementation, which makes use of an Extension Methods

    Usage:

    var distance = new Coordinates(48.672309, 15.695585)
    .DistanceTo(
    new Coordinates(48.237867, 16.389477),
    UnitOfLength.Kilometers
    );
    

    实施方法:

    public class Coordinates
    {
    public double Latitude { get; private set; }
    public double Longitude { get; private set; }
    
    
    public Coordinates(double latitude, double longitude)
    {
    Latitude = latitude;
    Longitude = longitude;
    }
    }
    public static class CoordinatesDistanceExtensions
    {
    public static double DistanceTo(this Coordinates baseCoordinates, Coordinates targetCoordinates)
    {
    return DistanceTo(baseCoordinates, targetCoordinates, UnitOfLength.Kilometers);
    }
    
    
    public static double DistanceTo(this Coordinates baseCoordinates, Coordinates targetCoordinates, UnitOfLength unitOfLength)
    {
    var baseRad = Math.PI * baseCoordinates.Latitude / 180;
    var targetRad = Math.PI * targetCoordinates.Latitude/ 180;
    var theta = baseCoordinates.Longitude - targetCoordinates.Longitude;
    var thetaRad = Math.PI * theta / 180;
    
    
    double dist =
    Math.Sin(baseRad) * Math.Sin(targetRad) + Math.Cos(baseRad) *
    Math.Cos(targetRad) * Math.Cos(thetaRad);
    dist = Math.Acos(dist);
    
    
    dist = dist * 180 / Math.PI;
    dist = dist * 60 * 1.1515;
    
    
    return unitOfLength.ConvertFromMiles(dist);
    }
    }
    
    
    public class UnitOfLength
    {
    public static UnitOfLength Kilometers = new UnitOfLength(1.609344);
    public static UnitOfLength NauticalMiles = new UnitOfLength(0.8684);
    public static UnitOfLength Miles = new UnitOfLength(1);
    
    
    private readonly double _fromMilesFactor;
    
    
    private UnitOfLength(double fromMilesFactor)
    {
    _fromMilesFactor = fromMilesFactor;
    }
    
    
    public double ConvertFromMiles(double input)
    {
    return input*_fromMilesFactor;
    }
    }
    

计算经纬度之间的距离。

        double Lat1 = Convert.ToDouble(latitude);
double Long1 = Convert.ToDouble(longitude);


double Lat2 = 30.678;
double Long2 = 45.786;
double circumference = 40000.0; // Earth's circumference at the equator in km
double distance = 0.0;
double latitude1Rad = DegreesToRadians(Lat1);
double latititude2Rad = DegreesToRadians(Lat2);
double longitude1Rad = DegreesToRadians(Long1);
double longitude2Rad = DegreesToRadians(Long2);
double logitudeDiff = Math.Abs(longitude1Rad - longitude2Rad);
if (logitudeDiff > Math.PI)
{
logitudeDiff = 2.0 * Math.PI - logitudeDiff;
}
double angleCalculation =
Math.Acos(
Math.Sin(latititude2Rad) * Math.Sin(latitude1Rad) +
Math.Cos(latititude2Rad) * Math.Cos(latitude1Rad) * Math.Cos(logitudeDiff));
distance = circumference * angleCalculation / (2.0 * Math.PI);
return distance;

你可以使用 System.device.Location:

System.device.Location.GeoCoordinate gc = new System.device.Location.GeoCoordinate(){
Latitude = yourLatitudePt1,
Longitude = yourLongitudePt1
};


System.device.Location.GeoCoordinate gc2 = new System.device.Location.GeoCoordinate(){
Latitude = yourLatitudePt2,
Longitude = yourLongitudePt2
};


Double distance = gc2.getDistanceTo(gc);

祝你好运

对于那些正在使用 Xamarin 并且没有访问 Geo坐标类的用户,你可以使用 Android Location 类来代替:

public static double GetDistanceBetweenCoordinates (double lat1, double lng1, double lat2, double lng2) {
var coords1 = new Location ("");
coords1.Latitude = lat1;
coords1.Longitude = lng1;
var coords2 = new Location ("");
coords2.Latitude = lat2;
coords2.Longitude = lng2;
return coords1.DistanceTo (coords2);
}

而在这里,对于那些仍然不满意(像我) ,原来的代码从。NET 框架 GeoCoordinate类,重构成一个独立的方法:

public double GetDistance(double longitude, double latitude, double otherLongitude, double otherLatitude)
{
var d1 = latitude * (Math.PI / 180.0);
var num1 = longitude * (Math.PI / 180.0);
var d2 = otherLatitude * (Math.PI / 180.0);
var num2 = otherLongitude * (Math.PI / 180.0) - num1;
var d3 = Math.Pow(Math.Sin((d2 - d1) / 2.0), 2.0) + Math.Cos(d1) * Math.Cos(d2) * Math.Pow(Math.Sin(num2 / 2.0), 2.0);
    

return 6376500.0 * (2.0 * Math.Atan2(Math.Sqrt(d3), Math.Sqrt(1.0 - d3)));
}

你可以使用这个函数:

资料来源: https://www.geodatasource.com/developers/c-sharp

private double distance(double lat1, double lon1, double lat2, double lon2, char unit) {
if ((lat1 == lat2) && (lon1 == lon2)) {
return 0;
}
else {
double theta = lon1 - lon2;
double dist = Math.Sin(deg2rad(lat1)) * Math.Sin(deg2rad(lat2)) + Math.Cos(deg2rad(lat1)) * Math.Cos(deg2rad(lat2)) * Math.Cos(deg2rad(theta));
dist = Math.Acos(dist);
dist = rad2deg(dist);
dist = dist * 60 * 1.1515;
if (unit == 'K') {
dist = dist * 1.609344;
} else if (unit == 'N') {
dist = dist * 0.8684;
}
return (dist);
}
}


//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::  This function converts decimal degrees to radians             :::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private double deg2rad(double deg) {
return (deg * Math.PI / 180.0);
}


//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
//::  This function converts radians to decimal degrees             :::
//:::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::::
private double rad2deg(double rad) {
return (rad / Math.PI * 180.0);
}


Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "M"));
Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "K"));
Console.WriteLine(distance(32.9697, -96.80322, 29.46786, -98.53506, "N"));

对于这些平台,有这样一个 地理坐标库:

  • 单核细胞增多症
  • .NET 4.5
  • .NET 核心
  • Windows Phone 8. x 操作系统
  • 通用视窗平台
  • Xamarin iOS
  • Xamarin 机器人

安装是通过 NuGet 完成的:

PM > 安装-软件包地理坐标

用法

GeoCoordinate pin1 = new GeoCoordinate(lat, lng);
GeoCoordinate pin2 = new GeoCoordinate(lat, lng);


double distanceBetween = pin1.GetDistanceTo(pin2);

两个坐标之间的距离,以 电表表示。

这是一个老问题,然而在性能和优化方面,答案并不令我满意。

这里我优化的 C # 变量(以公里为单位的距离,没有变量和冗余计算,非常接近 Haversine 公式 https://en.wikipedia.org/wiki/Haversine_formula的数学表达式)。

灵感来自: Https://rosettacode.org/wiki/haversine_formula#c.23

public static class Haversine
{
public static double Calculate(double lat1, double lon1, double lat2, double lon2)
{
double rad(double angle) => angle * 0.017453292519943295769236907684886127d; // = angle * Math.Pi / 180.0d
double havf(double diff) => Math.Pow(Math.Sin(rad(diff) / 2d), 2); // = sin²(diff / 2)
return 12745.6 * Math.Asin(Math.Sqrt(havf(lat2 - lat1) + Math.Cos(rad(lat1)) * Math.Cos(rad(lat2)) * havf(lon2 - lon1))); // earth radius 6.372,8‬km x 2 = 12745.6
}
}

Haversine Formular from Wikipedia

当 CPU/数学计算能力有限时:

有时候(比如在我的工作中)计算能力不足(比如没有浮点处理器,使用小型微控制器) ,一些三角函数可能会占用过多的 CPU 时间(比如3000 + 时钟周期) ,所以当我只需要一个近似值时,尤其是如果 CPU 不能长时间占用,我会用这个来最小化 CPU 开销:

/**------------------------------------------------------------------------
* \brief  Great Circle distance approximation in km over short distances.
*
* Can be off by as much as 10%.
*
* approx_distance_in_mi = sqrt(x * x + y * y)
*
* where x = 69.1 * (lat2 - lat1)
* and y = 69.1 * (lon2 - lon1) * cos(lat1/57.3)
*//*----------------------------------------------------------------------*/
double    ApproximateDisatanceBetweenTwoLatLonsInKm(
double lat1, double lon1,
double lat2, double lon2
) {
double  ldRadians, ldCosR, x, y;


ldRadians = (lat1 / 57.3) * 0.017453292519943295769236907684886;
ldCosR = cos(ldRadians);
x = 69.1 * (lat2 - lat1);
y = 69.1 * (lon2 - lon1) * ldCosR;


return sqrt(x * x + y * y) * 1.609344;  /* Converts mi to km. */
}

这要归功于 https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt

结果与基准点的比较

您确实需要关注结果中的变化,并且需要基准测试。

我在这个页面上找到了几个答案,加上一个未知数,并将它们与我20年前写的代码进行了比较。

我在测试中使用了这些坐标: 32.9697、 -96.80322和29.46786、 -98.53506

我的方法是:

 public static double CalculateDistanceBetweenCoordinates(double fromLatitude, double fromLongitude,
double toLatitude, double toLongitude)
{
double x = 69.1 * (toLatitude - fromLatitude);
double y = 69.1 * (toLongitude - fromLongitude) * Math.Cos(fromLatitude / 57.3);


// Convert to KM by multiplying 1.609344
return (Math.Sqrt(x * x + y * y) * 1.609344);
}

以下是知识管理的结果:

  • 422.73893139401383//我的代码 * 我的代码和扬加产生相同的结果。
  • 421.6152868008663//by Leitner
  • 422.8787129776151//By JanW
  • 422.73893139401383//作者: 扬加
  • 422.7592707099537//未知

你可以看到他们都非常接近,除了莱特纳的答案是大约1公里。我还检查了两个在线计算器,他们的答案分别是421.8和422.8,相差1公里。

下面是使用 BenchmarkDotNet 0.13.2运行1,000,000次迭代的 Benchmarks (注意,我漏掉了 Marc 的答案) :

BenchmarkDotNet=v0.13.2, OS=Windows 11 (10.0.22000.978/21H2)

Intel Core i7-8700 CPU 3.20 GHz (Coffee Lake) ,1个 CPU,12个逻辑内核和6个物理内核 . NET SDK = 6.0.401 [主机] : . NET 6.0.9(6.0.922.41905) ,X64 RyuJIT AVX2 默认工作: . NET 6.0.9(6.0.922.41905) ,X64 RyuJIT AVX2

方法 刻薄 错误 性发展 比率 比率 军衔 分配 分配比率
MyCode 239.6美元 2.56美元 2.14美元 一点 0.00 1 - 匿名
其他 34,014.2美元 405.67美元 379.46美元 142.25 1.69 2 32B 匿名
莱特纳 46749.4美元 390.52美元 346.19美元 195.23 2.50 3 44B 匿名
扬加 48,360.2美元 955.85美元 1062.43美元 202.75 4.24 4 44B 匿名
JanW 97,399.6美元 708.25美元 627.84美元 406.54 4.79元 5 592B 匿名

以下是我发现的另一种方法,以供参考:

 return 12742 * Math.Asin(Math.Sqrt(0.5 - Math.Cos((lat2 - lat1) * 0.017453292519943295) / 2 + Math.Cos(lat1 * 0.017453292519943295) * Math.Cos(lat2 * 0.017453292519943295) * (1 - Math.Cos((lon2 - lon1) * 0.017453292519943295)) / 2));

总而言之,根据距离的计算方法,总会有一些变化。但就性能而言,我的方法是迄今为止最快的,而且没有分配任何内存。