简体   繁体   中英

A More Efficient Haversine Function

In my previous question , I looked to speed up list selection based on a function result. Now, my bottleneck is the function itself.

It's a basic Haversine function, using the code below:

private static double Haversine(double lat1, double lat2, double lon1, double lon2)
{            
    const double r = 6371e3; // meters
    var dlat = (lat2 - lat1)/2;
    var dlon = (lon2 - lon1)/2;

    var q = Math.Pow(Math.Sin(dlat), 2) + Math.Cos(lat1) * Math.Cos(lat2) * Math.Pow(Math.Sin(dlon), 2);
    var c = 2 * Math.Atan2(Math.Sqrt(q), Math.Sqrt(1 - q));

    var d = r * c;
    return d / 1000;
}

So... why does it need to be so fast? The issue is that I'm calling it a lot . Think north of 16,500,000 times.

Obviously, that's a lot . And in my use case I'm passing it objects that it has to get the location data from and then convert Latitude and Longitude to radians, which increases the time further (only by about 15%). I don't know that there's much I can do about that, but I do know that by passing it purely doubles in radians (as above) it takes ~4.5 seconds - which is more than 75% of the processing time in my implementation. The lines assigning values to q and c seems to take up the most time.

As it's being called a lot , I'm looking to make it a bit faster. I'm open to multithreaded solutions (and am currently working on one myself), but it may be a bit more difficult to implement given the use case in my previous question (linked above).

This was as optimized as I could get the answer (and, to my knowledge, this is the most optimized the answer could possibly get without doing some wizard-level optimization on the formula itself):

private static double Haversine(double lat1, double lat2, double lon1, double lon2)
{
    const double r = 6371; // meters

    var sdlat = Math.Sin((lat2 - lat1) / 2);
    var sdlon = Math.Sin((lon2 - lon1) / 2);
    var q = sdlat * sdlat + Math.Cos(lat1) * Math.Cos(lat2) * sdlon * sdlon;
    var d = 2 * r * Math.Asin(Math.Sqrt(q));

    return d;
}

On my machine, this formula, when run 16.5 million times, runs at almost exactly 3 seconds, whereas the above version runs at just shy of 5.

However, I maintain that the biggest optimization could be in the system that actually calls this method. 33,000 times on each of 500 Latitude-Longitude pairs? That's a system that is likely in dire need of optimization itself. For starters, you could first calculate the linear-distance-squared of your pairs and only process pairs that are below a certain threshold. Or you could maintain a look-up table to avoid calculating the same pair more than once. Or, depending on the source of that 33,000 number, you can prioritize so that you don't need to call the method nearly that much.

For me this is more accurate

public static class Haversine {
  public static double calculate(double lat1, double lon1, double lat2, double lon2) {
    var R = 6372.8; // In kilometers
    var dLat = toRadians(lat2 - lat1);
    var dLon = toRadians(lon2 - lon1);
    lat1 = toRadians(lat1);
    lat2 = toRadians(lat2);

    var a = Math.Sin(dLat / 2) * Math.Sin(dLat / 2) + Math.Sin(dLon / 2) * Math.Sin(dLon / 2) * Math.Cos(lat1) * Math.Cos(lat2);
    var c = 2 * Math.Asin(Math.Sqrt(a));
    return R * c;
  }

  public static double toRadians(double angle) {
    return Math.PI * angle / 180.0;
  }
}

void Main() {
  Console.WriteLine(String.Format("The distance between coordinates {0},{1} and {2},{3} is: {4}", 36.12, -86.67, 33.94, -118.40, Haversine.calculate(36.12, -86.67, 33.94, -118.40)));
}

// Returns: The distance between coordinates 36.12,-86.67 and 33.94,-118.4 is: 2887.25995060711

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM