Для вычисления расстояния в километрах между двумя географическими точками, которые заданы парой координат, воспользуемся формулой гаверсинуса. Она точно вычисляет кратчайшее расстояние между двумя точками на поверхности сферы с учетом ее кривизны.

Формула гаверсинуса

Гаверсинус (haversine) – специальная математическая функция, равная sin²(θ/2).

Формула гаверсинуса для расстояния между двумя точками на сфере:

Она же текстом:

a = sin²((φ2 − φ1) / 2) + cos φ1 ⋅ cos φ2 ⋅ sin²((λ2 − λ1) / 2)
c = 2 ⋅ arcsin(√a)
d = r ⋅ c

где φ1, φ2 – координаты по широте в радианах; λ1, λ1 – координаты по долготе в радианах; r – радиус Земли в километрах (в среднем он равен примерно 6371 км).

Вариация с Atan2 и почему

Вместо arcsin для расчетов будем использовать atan2. Оба варианта математически эквивалентны, но atan2 более стабилен численно.

c = 2 ⋅ arcsin(√a)
c = 2 ⋅ atan2(√a, √(1 − a))

Функция arcsin определена только для аргументов в диапазоне [-1, 1]. Из-за ошибок округления с плавающей точкой значение a может стать чуть больше 1, что вызовет ошибку:

// Потенциальная проблема:
const a = 1.0000000000000001; // Из-за ошибок округления
const c = 2 * Math.asin(Math.sqrt(a)); // Ошибка! asin(>1) не определен

atan2(y, x) более устойчив к ошибкам округления. Даже если из-за численных погрешностей a станет чуть больше 1, atan2(y, x) продолжит корректно работать:

// Более устойчивый вариант:
const a = 1.0000000000000001;
const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a)); // Работает!

Реализация на JS

//расстояние между 2 точками на карте по формуле гаверсина
function dist2CoordsInKm(coord1, coord2) {
    const [lat1, lon1] = coord1;
    const [lat2, lon2] = coord2;

    const R = 6371; // Радиус Земли в км

    const dLat = degToRad(lat2 - lat1);
    const dLon = degToRad(lon2 - lon1);

    const a =
        Math.sin(dLat / 2) * Math.sin(dLat / 2) +
        Math.cos(degToRad(lat1)) * Math.cos(degToRad(lat2)) *
        Math.sin(dLon / 2) * Math.sin(dLon / 2);

    return R * 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));
}

//перевод градусов в радианы
function degToRad(deg) {
    return deg * (Math.PI / 180);
}

Функция принимает на вход 2 точки, которые заданы парой координат [lat, lon].

Пример использования (Москва и Санкт-Петербург):

dist2CoordsInKm([55.7540584, 37.62049], [59.9390012, 30.3158184]); //634.4325191115644

Преимущества перед плоскими расчетами

  • Корректно работает на больших расстояниях
  • Учитывает реальную форму Земли
  • Точно вычисляет расстояния между географическими координатами