Vincenty vs Haversine vs Sferical Law of Cosines Distance Calculations (v11)

Revision 11 of this benchmark created by norric on


Description

This test compares JavaScript implementations of the three main different point-to-point distance calculation equations, Haversine, Vincenty and Spherical Law of Cosines.

Among geodetics enthusiasts, Haversine is perhaps slightly looked-down upon because its accuracy can be out by 0.3% or so, whereas Vincenty is apparently accurate to 0.5mm. Of course, both methods use a theoretical spheroid, and because no-one can actually travel on either theoretical spheroid, both are only an approximation :-)

The Spherical Law of Cosines has the same accuracy as Haversine. And is nowadays due to high computational precision an equivalent alternative.

More information on Haversine

More information on Vincenty

More information on Spherical Law of Cosines

More information on Spherical Law of Cosines

Setup

/** Converts numeric degrees to radians */
    if (typeof(Number.prototype.toRad) === "undefined") {
      Number.prototype.toRad = function() {
        return this * Math.PI / 180;
      }
    }
    
    /**
     * Calculates geodetic distance between two points specified by latitude/longitude using 
     * Vincenty inverse formula for ellipsoids
     *
     * @param   {Number} lat1, lon1: first point in decimal degrees
     * @param   {Number} lat2, lon2: second point in decimal degrees
     * @returns (Number} distance in metres between points
     */
    
    function distVincenty(lat1, lon1, lat2, lon2) {
      var a = 6378137,
          b = 6356752.314245,
          f = 1 / 298.257223563; // WGS-84 ellipsoid params
      var L = (lon2 - lon1).toRad();
      var U1 = Math.atan((1 - f) * Math.tan(lat1.toRad()));
      var U2 = Math.atan((1 - f) * Math.tan(lat2.toRad()));
      var sinU1 = Math.sin(U1),
          cosU1 = Math.cos(U1);
      var sinU2 = Math.sin(U2),
          cosU2 = Math.cos(U2);
    
      var lambda = L,
          lambdaP, iterLimit = 100;
      do {
        var sinLambda = Math.sin(lambda),
            cosLambda = Math.cos(lambda);
        var sinSigma = Math.sqrt((cosU2 * sinLambda) * (cosU2 * sinLambda) + (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda) * (cosU1 * sinU2 - sinU1 * cosU2 * cosLambda));
        if (sinSigma == 0) return 0; // co-incident points
        var cosSigma = sinU1 * sinU2 + cosU1 * cosU2 * cosLambda;
        var sigma = Math.atan2(sinSigma, cosSigma);
        var sinAlpha = cosU1 * cosU2 * sinLambda / sinSigma;
        var cosSqAlpha = 1 - sinAlpha * sinAlpha;
        var cos2SigmaM = cosSigma - 2 * sinU1 * sinU2 / cosSqAlpha;
        if (isNaN(cos2SigmaM)) cos2SigmaM = 0; // equatorial line: cosSqAlpha=0 (§6)
        var C = f / 16 * cosSqAlpha * (4 + f * (4 - 3 * cosSqAlpha));
        lambdaP = lambda;
        lambda = L + (1 - C) * f * sinAlpha * (sigma + C * sinSigma * (cos2SigmaM + C * cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM)));
      } while (Math.abs(lambda - lambdaP) > 1e-12 && --iterLimit > 0);
    
      if (iterLimit == 0) return NaN // formula failed to converge
      var uSq = cosSqAlpha * (a * a - b * b) / (b * b);
      var A = 1 + uSq / 16384 * (4096 + uSq * (-768 + uSq * (320 - 175 * uSq)));
      var B = uSq / 1024 * (256 + uSq * (-128 + uSq * (74 - 47 * uSq)));
      var deltaSigma = B * sinSigma * (cos2SigmaM + B / 4 * (cosSigma * (-1 + 2 * cos2SigmaM * cos2SigmaM) - B / 6 * cos2SigmaM * (-3 + 4 * sinSigma * sinSigma) * (-3 + 4 * cos2SigmaM * cos2SigmaM)));
      var s = b * A * (sigma - deltaSigma);
    
      return Math.round(s);
    }
    
    // Distance in kilometers between two points using the Haversine algo.
    function haversine(lat1, lon1, lat2, lon2) {
      var R = 6371;
      var dLat = (lat2 - lat1).toRad();
      var dLong = (lon2 - lon1).toRad();
    
      var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) + Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * Math.sin(dLong / 2) * Math.sin(dLong / 2);
      var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
      var d = R * c;
    
      return Math.round(d);
    }
    
    
    //The Spherical Law of Cosines
    //Original source: "http://www.movable-type.co.uk/scripts/latlong.html"
    function SphericalCosinus(lat1, lon1, lat2, lon2) {
        var R = 6371; // km
        var dLon = (lon2-lon1).toRad(); 
        var lat1 = lat1.toRad();
        var lat2 = lat2.toRad();
        var d = Math.acos(Math.sin(lat1)*Math.sin(lat2) + 
                                                Math.cos(lat1)*Math.cos(lat2) *
                                                Math.cos(dLon)) * R;
    
        return Math.round(d);
    }
    
    //Equirectangular approximation
    function eqiRec(lat1, lon1, lat2, lon2) {
      var R = 6371;
      var x = (lon2-lon1) * Math.cos((lat1+lat2)/2);
      var y = (lat2-lat1);
      var d = Math.sqrt(x*x + y*y) * R;
    
      return Math.round(d);
    }

Test runner

Ready to run.

Testing in
TestOps/sec
Vincenty formula
distVincenty(-31.965379, 115.822077, -33.85789, 151.214806);
ready
Haversine formula
haversine(-31.965379, 115.822077, -33.85789, 151.214806);
ready
Spherical Law of Cosines
SphericalCosinus(-31.965379, 115.822077, -33.85789, 151.214806);
ready
Equirectangular approximation
eqiRec(-31.965379, 115.822077, -33.85789, 151.214806)
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.