在Javascript中使用Haversine公式

41

我正在尝试使用Haversine距离公式(可以在此处找到:http://www.movable-type.co.uk/scripts/latlong.html),但我无法使其工作,请参见以下代码:

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

错误信息如下:

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

我理解这是因为它需要执行以下操作:

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

但是当我将这个方法放在函数下面时,它仍然返回相同的错误消息。如何让它使用帮助方法?或者有其他编码方法可以让它工作吗?谢谢!


这个问题的被接受答案似乎回答得很好,参考了OP给出的同一个链接:https://dev59.com/enRC5IYBdhLWcg3wP-dh - bigsee
9个回答

57

这段代码可以正常工作:

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

注意我如何定义了x1和x2。你可以在此链接中测试: https://tinker.io/3f794


1
我遇到了同样的问题。我定义了一个toRadians()实用函数,并像这样计算dLat和dLon:toRadians(lat1 - lat2)…与先计算差并将其存储在变量中明确计算的答案相比,这给了我错误的答案。为什么会这样? - Parijat Kalia
我也不知道为什么会发生这种情况,@Parijat Kalia,我曾经遇到过同样的问题。 - Porlune

32

这是一个基于其他3个答案进行重构的函数!

请注意,coords参数为[经度,纬度]。

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

你的函数让我有些困惑,因为它期望这样的输入:haversineDistance([lng, lat], [lng, lat], isMiles); - Andy H.
不确定我是否实现有误,但是这个答案给出了一个不准确的结果,而当前被接受的答案,在不进行重构的情况下,给出了与Google Maps相同的结果。请查看我的评论@Harry Mumford-Turner的答案获取更多详细信息。 - bigsee
1
太好了 - 谢谢!供参考,这是相同的函数,但压缩并始终返回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

25

ES6 JavaScript/NodeJS重构版本:

   /**
     * 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;
    };

请查看codepen以进行有关已接受答案的测试:https://codepen.io/harrymt/pen/dyYvLpJ?editors=1011


您正在访问latlngAlatlngB参数的第一个元素来计算纬度差,但是该函数的文档块说明第一个元素是经度。 - marked-down
1
在使用此解决方案时,对于以下坐标,在与 Google 地图进行测试时,我得到了不准确的结果:const latlngA = [52.375603, 4.903206]; const latlngB = [52.366059, 4.926692]; 此解决方案返回 2.8 公里,而当前接受的答案正确返回 1.92 公里(与 Google 地图给出的 1.91 公里非常接近)。 - bigsee
@bigsee 谢谢,我已经修正了公式,使其更加准确和易于理解。 - Harry Mumford-Turner
4
这是我在Stack上看到的最准确的,与Google地图完全匹配,做得很好。 - sasy solutions
1
那是一些非常干净的代码。我期待着回家后测试它。 - Pangamma
@Pangamma,我提供的答案默认为英里,而另一个被接受的答案使用公里。我已将其更新为默认值isMiles为false。 - Harry Mumford-Turner

14

为什么不尝试直接的解决方案呢?不要扩展 Number 原型,只需将 toRad 定义为一个普通函数:

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

然后在各处调用 toRad

var dLat = toRad(lat2-lat1); 

扩展Number原型并不总是按预期工作。例如,调用123.toRad()无法正常工作。我认为如果你这样做var x1 = lat2 - lat1; x1.toRad();比这样做更好(lat2-lat1).toRad()

扩展 Number 原型并不总是按预期工作。例如,调用 123.toRad() 会出现问题。我认为,如果您这样做 var x1 = lat2 - lat1; x1.toRad(); 比这样做更好 (lat2-lat1).toRad()


1

当我把下面这个函数放在下面时

你只需要把它放在调用 test() 的位置之前。 test 函数本身的声明位置并不重要。


0

另一种减少冗余并且与Google LatLng对象兼容的变体:

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

这还是以公里为单位吗?应该加上注释,说明所需的更改以使其返回英里。 - RufusVS

0
在调用函数之前,您需要扩展Number原型。因此,请确保:
Number.prototype.toRad = function() {
  return this * Math.PI / 180;
}

被称为“before”的函数在你的函数被调用之前被调用。

3
不需要将其放在函数定义之前。 - Bergi
@bergi - 对不起,你是正确的 - 在调用函数之前需要定义它 - 我会编辑我的答案。 - DanSingerman

0
这是另一个经过重构的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
这是 talkol 上面解决方案的 Java 实现。他或她的解决方案对我们非常有效。我并不是在回答问题,因为原始问题是关于 JavaScript 的。我只是分享我们的 Java 实现给定 JavaScript 解决方案,以便其他人发现它有用。
// 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;
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接