41

I'm trying to use the Haversine Distance Formula (as found here: http://www.movable-type.co.uk/scripts/latlong.html) but I can't get it to work, please see the following code

    function test() { 
    var lat2 = 42.741; 
    var lon2 = -71.3161; 
    var lat1 = 42.806911; 
    var lon1 = -71.290611; 

    var R = 6371; // km 
    //has a problem with the .toRad() method below.
    var dLat = (lat2-lat1).toRad();  
    var dLon = (lon2-lon1).toRad();  
    var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
                    Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * 
                    Math.sin(dLon/2) * Math.sin(dLon/2);  
    var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
    var d = R * c; 

    alert(d); 
}

And the error is:

Uncaught TypeError: Object -0.06591099999999983 has no method 'toRad' 

Which I understand to be because it needs to do the following:

Number.prototype.toRad = function() {
return this * Math.PI / 180;
}

But when I put this below the function, it still comes back with the same error message. How do I make it use the helper method? Or is there an alternative way to code this to get it to work? Thanks!

Creights
  • 907
  • 1
  • 12
  • 25
  • The accepted answer to this question seems to answer this well, with reference to the same link given by the OP: https://stackoverflow.com/questions/365826/calculate-distance-between-2-gps-coordinates – bigsee Oct 20 '18 at 23:58

9 Answers9

57

This code is working:

Number.prototype.toRad = function() {
   return this * Math.PI / 180;
}

var lat2 = 42.741; 
var lon2 = -71.3161; 
var lat1 = 42.806911; 
var lon1 = -71.290611; 

var R = 6371; // km 
//has a problem with the .toRad() method below.
var x1 = lat2-lat1;
var dLat = x1.toRad();  
var x2 = lon2-lon1;
var dLon = x2.toRad();  
var a = Math.sin(dLat/2) * Math.sin(dLat/2) + 
                Math.cos(lat1.toRad()) * Math.cos(lat2.toRad()) * 
                Math.sin(dLon/2) * Math.sin(dLon/2);  
var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a)); 
var d = R * c; 

alert(d);

Notice how I defined x1 and x2. Play with it at: https://tinker.io/3f794

talkol
  • 12,564
  • 11
  • 54
  • 64
  • 1
    I encountered the same problem. I defined a toRadians() as a utility function and computed dLat and dLon like this: toRadians(lat1 - lat2) ... This gave me the wrong answer vs explicitly computing the difference first up and storing it in a variable. Why is that? – Parijat Kalia Aug 20 '14 at 17:56
  • I don't know why that happens either @Parijat Kalia, I had the same problem. – Porlune Dec 11 '15 at 14:17
32

Here's a refactored function based on 3 of the other answers!

Please note that the coords arguments are [longitude, latitude].

function haversineDistance(coords1, coords2, isMiles) {
  function toRad(x) {
    return x * Math.PI / 180;
  }

  var lon1 = coords1[0];
  var lat1 = coords1[1];

  var lon2 = coords2[0];
  var lat2 = coords2[1];

  var R = 6371; // km

  var x1 = lat2 - lat1;
  var dLat = toRad(x1);
  var x2 = lon2 - lon1;
  var dLon = toRad(x2)
  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
    Math.cos(toRad(lat1)) * Math.cos(toRad(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;

  if(isMiles) d /= 1.60934;

  return d;
}
Nathan Lippi
  • 4,967
  • 5
  • 25
  • 29
  • one thing that tripped me up with your function, you are expecting: `haversineDistance([lng, lat], [lng, lat], isMiles);` – Andy H. Mar 22 '17 at 21:33
  • Again, not sure if I've implemented something wrong but this answer gives an inaccurate result whereas the currently accepted answer, without refactoring, gives the same result as Google Maps. See my comment on @Harry Mumford-Turner's answer for more detail. – bigsee Oct 20 '18 at 23:56
  • 1
    Great -- thanks! For reference, here's the **same function, except compressed** and always returning KM: `function distKM(lat1,lon1,lat2,lon2){var a=Math,r=(lat2-lat1)*a.PI/180,c=(lon2-lon1)*a.PI/180,e=a.sin(r/2)*a.sin(r/2)+a.cos(lat1*a.PI/180)*a.cos(lat2*a.PI/180)*a.sin(c/2)*a.sin(c/2);return d=2*a.atan2(a.sqrt(e),a.sqrt(1-e))*6371}` – ashleedawg Sep 23 '21 at 01:52
25

ES6 JavaScript/NodeJS refactored version:

   /**
     * Calculates the haversine distance between point A, and B.
     * @param {number[]} latlngA [lat, lng] point A
     * @param {number[]} latlngB [lat, lng] point B
     * @param {boolean} isMiles If we are using miles, else km.
     */
    const haversineDistance = ([lat1, lon1], [lat2, lon2], isMiles = false) => {
      const toRadian = angle => (Math.PI / 180) * angle;
      const distance = (a, b) => (Math.PI / 180) * (a - b);
      const RADIUS_OF_EARTH_IN_KM = 6371;

      const dLat = distance(lat2, lat1);
      const dLon = distance(lon2, lon1);

      lat1 = toRadian(lat1);
      lat2 = toRadian(lat2);

      // Haversine Formula
      const a =
        Math.pow(Math.sin(dLat / 2), 2) +
        Math.pow(Math.sin(dLon / 2), 2) * Math.cos(lat1) * Math.cos(lat2);
      const c = 2 * Math.asin(Math.sqrt(a));

      let finalDistance = RADIUS_OF_EARTH_IN_KM * c;

      if (isMiles) {
        finalDistance /= 1.60934;
      }

      return finalDistance;
    };

See codepen for tests against accepted answer: https://codepen.io/harrymt/pen/dyYvLpJ?editors=1011

  • You're accessing the 1st element of the `latlngA` and `latlngB` parameters to calculate the delta of the latitude, but the docblock for the function states that the 1st element is the longitude. – marked-down Oct 20 '18 at 02:18
  • 1
    I got an inaccurate result using this solution when testing against Google Maps for the following coords: const latlngA = [52.375603, 4.903206]; const latlngB = [52.366059, 4.926692]; This solution returns 2.8km whereas the currently accepted answer correctly returns 1.92km (which closely matches the 1.91km given by Google Maps). – bigsee Oct 20 '18 at 23:54
  • @bigsee thank you, I have fixed the formula to be more accurate and understandable – Harry Mumford-Turner Aug 28 '19 at 14:23
  • 4
    this is the most accurate I have seen on stack and matches up with googlemaps perfectly, well done – sasy solutions Sep 15 '19 at 18:00
  • 1
    That is some nice clean code right there. I look forward to testing it when I get home – Pangamma Feb 05 '20 at 22:20
  • @Pangamma the answer I provided defaulted to miles, the other and accepted answer used KM. I have updated it to default isMiles to false – Harry Mumford-Turner Apr 25 '20 at 12:38
14

Why not try the straight forward solution? Instead of extending Number prototype, just define toRad as a regular function:

function toRad(x) {
   return x * Math.PI / 180;
}

and then call toRad everywhere:

var dLat = toRad(lat2-lat1); 

Extending the Number prototype does not always work as expected. For example calling 123.toRad() does not work. I think that if you do var x1 = lat2 - lat1; x1.toRad(); works better than doing (lat2-lat1).toRad()

talkol
  • 12,564
  • 11
  • 54
  • 64
1

You need to extend the Number prototype, before calling those extensions in a function.

So just ensure

Number.prototype.toRad = function() {
  return this * Math.PI / 180;
}

is called before your function is called.

DanSingerman
  • 36,066
  • 13
  • 81
  • 92
1

when I put this below the function

You only need to put it above the point where you call test(). Where the test function itself is declared does not matter.

Bergi
  • 630,263
  • 148
  • 957
  • 1,375
0

Another variant to reduce redundancy and also compatible with Google LatLng objects:

  function haversine_distance(coords1, coords2) {

     function toRad(x) {
         return x * Math.PI / 180;
    }

  var dLat = toRad(coords2.latitude - coords1.latitude);
  var dLon = toRad(coords2.longitude - coords1.longitude)

  var a = Math.sin(dLat / 2) * Math.sin(dLat / 2) +
          Math.cos(toRad(coords1.latitude)) * 
          Math.cos(toRad(coords2.latitude)) *
          Math.sin(dLon / 2) * Math.sin(dLon / 2);

  return 12742 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}
candidJ
  • 4,289
  • 1
  • 22
  • 32
Dillon
  • 79
  • 1
  • 4
  • Is this still in km? It should be commented as such, as well as the change required to make it return miles. – RufusVS Feb 14 '18 at 03:41
0

Here's another refactored answer in JavaScript:

getHaversineDistance = (firstLocation, secondLocation) => {
    const earthRadius = 6371; // km 

    const diffLat = (secondLocation.lat-firstLocation.lat) * Math.PI / 180;  
    const diffLng = (secondLocation.lng-firstLocation.lng) * Math.PI / 180;  

    const arc = Math.cos(
                    firstLocation.lat * Math.PI / 180) * Math.cos(secondLocation.lat * Math.PI / 180) 
                    * Math.sin(diffLng/2) * Math.sin(diffLng/2)
                    + Math.sin(diffLat/2) * Math.sin(diffLat/2);
    const line = 2 * Math.atan2(Math.sqrt(arc), Math.sqrt(1-arc));

    const distance = earthRadius * line; 

    return distance;
}

const philly = { lat: 39.9526, lng: -75.1652 }
const nyc = { lat: 40.7128, lng: -74.0060 }
const losAngeles = { lat: 34.0522, lng: -118.2437 }

console.log(getHaversineDistance(philly, nyc)) //129.61277152662188
console.log(getHaversineDistance(philly, losAngeles)) //3843.4534005980404
-1

This is a java implemetation of talkol's solution above. His or her solution worked very well for us. I'm not trying to answer the question, since the original question was for javascript. I'm just sharing our java implementation of the given javascript solution in case others find it of use.

// this was a pojo class we used internally...
public class GisPostalCode {

    private String country;
    private String postalCode;
    private double latitude;
    private double longitude;

    // getters/setters, etc.
}


public static double distanceBetweenCoordinatesInMiles2(GisPostalCode c1, GisPostalCode c2) {

    double lat2 = c2.getLatitude();
    double lon2 = c2.getLongitude();
    double lat1 = c1.getLatitude();
    double lon1 = c1.getLongitude();

    double R = 6371; // km
    double x1 = lat2 - lat1;
    double dLat = x1 * Math.PI / 180;
    double x2 = lon2 - lon1;
    double dLon = x2 * Math.PI / 180;

    double a = Math.sin(dLat/2) * Math.sin(dLat/2) +
        Math.cos(lat1*Math.PI/180) * Math.cos(lat2*Math.PI/180) *
        Math.sin(dLon/2) * Math.sin(dLon/2);

    double c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
    double d = R * c;

    // convert to miles
    return d / 1.60934;
}
Perry Tew
  • 2,632
  • 3
  • 22
  • 25