Подтвердить что ты не робот

Использование формулы Хаверсина в Javascript

Я пытаюсь использовать Формулу расстояний Хаверсина (как здесь: 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;
}

Но когда я ставлю это ниже функции, он все равно возвращается с тем же сообщением об ошибке. Как заставить его использовать вспомогательный метод? Или есть альтернативный способ закодировать это, чтобы заставить его работать? Спасибо!

4b9b3361

Ответ 1

Этот код работает:

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

Ответ 2

Здесь реорганизованная функция, основанная на 3 других ответах!

Обратите внимание, что аргументы для координат - это [долгота, широта].

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

Ответ 3

Почему бы не попробовать прямое решение? Вместо расширения прототипа 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()

Ответ 4

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 = (latlngA, latlngB, isMiles) => {
  const toRadian = angle => (Math.PI / 180) * angle;
  const distance = (a, b) => (Math.PI / 180) * (a - b);
  const RADIUS_OF_EARTH_IN_KM = 6371;

  let lat1 = latlngA[0];
  let lat2 = latlngB[0];
  const lon1 = latlngA[1];
  const lon2 = latlngB[1];

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

Ответ 5

Вам необходимо расширить прототип Number, прежде чем вызывать эти расширения в функции.

Поэтому просто убедитесь, что

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

вызывается перед вызовом функции.

Ответ 6

когда я ставлю это ниже функции

Вам нужно только поставить его выше точки, где вы вызываете test(). Там, где объявлена ​​сама функция test, не имеет значения.

Ответ 7

Другой вариант сокращения избыточности, а также совместим с объектами 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));
}

Ответ 8

Это java-реализация решения Talkol выше. Его или ее решение работало очень хорошо для нас. Я не пытаюсь ответить на вопрос, так как исходный вопрос был для 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;
}

Ответ 9

Вот еще один рефакторированный ответ в 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

Ответ 10

Я знаю, что это было не то, что было задано, но это более точная функция, которая выполняет то же самое, что называется формулами Винсенти. Она должна быть точной до 0,5 мм для всего эллипсоида. Haversine может быть около 100 км от 20000 км (0,5%). Я сравнил Vincenty и Haversine, и у него схожая производительность.

Результаты тестов с 10 миллионами пар случайных точек показывают, что производительность не должна быть проблемой здесь:

  • 1650 мс для Haversine, 165 наносекунд на результат
  • 1710 мс для Винсенти, 171 наносекунд на результат
    function vincentyDist(startLat, startLong, endLat, endLong) {
        var earthRadius = 6371000.0; //meters
        var dLat = degToRad(endLat-startLat);
        var dLng = degToRad(endLong-startLong);
        var a = Math.sin(dLat/2) * Math.sin(dLat/2) + Math.cos(degToRad(startLat)) * Math.cos(degToRad(endLat)) * Math.sin(dLng/2) * Math.sin(dLng/2);
        var c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1-a));
        return (earthRadius * c);
    }