Calculating Distance between two Latitude and Longitude GeoCoordinates

Viewed 190126

I'm calculating the distance between two GeoCoordinates. I'm testing my app against 3-4 other apps. When I'm calculating distance, I tend to get an average of 3.3 miles for my calculation whereas other apps are getting 3.5 miles. It's a big difference for the calculation I'm trying to perform. Are there any good class libraries out there for calculating distance? I'm calculating it like this in 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;
}

What could I be doing wrong? Should I calculate it in km first and then convert to miles?

14 Answers

And here, for those still not satisfied (like me), the original code from .NET-Frameworks GeoCoordinate class, refactored into a standalone method:

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

This is an old question, nevertheless the answers did not satisfy me regarding to performance and optimization.

Here my optimized C# variant (distance in km, without variables and redundant calculations, very close to mathematical expression of Haversine Formular https://en.wikipedia.org/wiki/Haversine_formula).

Inspired by: 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

There's this library GeoCoordinate for these platforms:

  • Mono
  • .NET 4.5
  • .NET Core
  • Windows Phone 8.x
  • Universal Windows Platform
  • Xamarin iOS
  • Xamarin Android

Installation is done via NuGet:

PM> Install-Package GeoCoordinate

Usage

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

double distanceBetween = pin1.GetDistanceTo(pin2);

The distance between the two coordinates, in meters.

You can use this function :

Source : 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"));

When CPU/math computing power is limited:

There are times (such as in my work) when computing power is scarce (e.g. no floating point processor, working with small microcontrollers) where some trig functions can take an exorbitant amount of CPU time (e.g. 3000+ clock cycles), so when I only need an approximation, especially if if the CPU must not be tied up for a long time, I use this to minimize CPU overhead:

/**------------------------------------------------------------------------
 * \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. */
}

Credit goes to https://github.com/kristianmandrup/geo_vectors/blob/master/Distance%20calc%20notes.txt.

Results comparison with Bench Marks

You really need to look at the variations in results and you need benchmarks.

I took several answers on this page plus an unknown and compared them to the code that I had written 20 years ago.

I used these coordinates for the test: 32.9697, -96.80322 and 29.46786, -98.53506

Here is my method:

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

Here are the results in KM:

  • 422.73893139401383 // My Code *My code and Yanga produce identical results.
  • 421.6152868008663 // by Leitner
  • 199.54410262484785 // by Marc ? Not sure what is going on here.
  • 422.8787129776151 // By JanW
  • 422.73893139401383 // By Yanga
  • 422.7592707099537 // Unknown

You can see that with the exception of the answer by Marc, they are all very close except the answer by Leitner which is off about 1km. I also checked two on-line calculators and their answers were 421.8 and 422.8 so off by 1km.

And here are the Benchmarks running 1,000,000 iterations using BenchmarkDotNet 0.13.2 (note that I left out the answer by Marc):

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

Intel Core i7-8700 CPU 3.20GHz (Coffee Lake), 1 CPU, 12 logical and 6 physical cores .NET SDK=6.0.401 [Host] : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT AVX2 DefaultJob : .NET 6.0.9 (6.0.922.41905), X64 RyuJIT AVX2

Method Mean Error StdDev Ratio RatioSD Rank Allocated Alloc Ratio
MyCode 239.6 us 2.56 us 2.14 us 1.00 0.00 1 - NA
Other 34,014.2 us 405.67 us 379.46 us 142.25 1.69 2 32 B NA
Leitner 46,749.4 us 390.52 us 346.19 us 195.23 2.50 3 44 B NA
Yanga 48,360.2 us 955.85 us 1,062.43 us 202.75 4.24 4 44 B NA
JanW 97,399.6 us 708.25 us 627.84 us 406.54 4.79 5 592 B NA

For reference here is the other method I found:

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

In summary, there will always be variations depending on how the distance is calculated. But for performance my method is by far the fastest and with zero allocated memory.

Related