Distance между точками

В веб-картографии расстояние между двумя точками на карте нельзя корректно вычислять через обычную евклидову формулу координат. Причина в том, что координаты долготы и широты задаются на сфере (или эллипсоиде), а не на плоскости. Поэтому прямое применение формулы Пифагора даёт искажённый результат, особенно на больших расстояниях.

В Mapbox GL JS работа с расстояниями обычно строится вокруг геодезических вычислений. Библиотека предоставляет объект LngLat, который уже учитывает географическую природу координат и позволяет вычислять расстояние между точками через метод distanceTo.


Координаты и модель Земли

Любая точка в Mapbox GL JS задаётся в формате:

  • долгота (longitude)
  • широта (latitude)
const point = new mapboxgl.LngLat(37.6173, 55.7558);

Ключевой момент: это не декартова система координат. Даже если визуально карта выглядит плоской, под капотом используется проекция Web Mercator, которая искажает расстояния, особенно ближе к полюсам.

Поэтому вычисление расстояния должно опираться на геодезические формулы.


Метод LngLat.distanceTo

В Mapbox GL JS встроен метод:

const a = new mapboxgl.LngLat(37.6173, 55.7558);
const b = new mapboxgl.LngLat(30.3351, 59.9343);

const distance = a.distanceTo(b);

Результат возвращается в метрах.

Как это работает

Внутри используется сферическая модель Земли и формула, близкая к гаверсинусной:

  • координаты переводятся в радианы
  • вычисляется центральный угол
  • результат умножается на радиус Земли

Приближение достаточно точное для большинства задач интерфейса и аналитики.


Ручной расчёт через формулу гаверсинуса

Иногда требуется полный контроль над вычислением, например при серверной синхронизации или работе вне Mapbox API.

function getDistance(lat1, lon1, lat2, lon2) {
  const R = 6371000; // радиус Земли в метрах

  const toRad = (value) => (value * Math.PI) / 180;

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

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

  const c = 2 * Math.atan2(Math.sqrt(a), Math.sqrt(1 - a));

  return R * c;
}

Этот подход полностью независим от библиотеки и часто используется в связке с геоданными.


Использование Turf.js

В экосистеме геопространственных вычислений широко применяется Turf.js.

Она предоставляет готовую функцию:

import { distance } from '@turf/turf';

const from = [37.6173, 55.7558];
const to = [30.3351, 59.9343];

const result = distance(from, to, { units: 'kilometers' });

Особенности:

  • поддержка различных единиц измерения (км, м, мили)
  • работа с GeoJSON
  • высокая точность для прикладных задач анализа

Интерактивное измерение расстояния на карте

Типовой сценарий в Mapbox GL JS — измерение расстояния между кликами пользователя.

const coordinates = [];

map.on('click', (e) => {
  coordinates.push([e.lngLat.lng, e.lngLat.lat]);

  if (coordinates.length === 2) {
    const start = new mapboxgl.LngLat(coordinates[0][0], coordinates[0][1]);
    const end = new mapboxgl.LngLat(coordinates[1][0], coordinates[1][1]);

    const dist = start.distanceTo(end);

    console.log(`Расстояние: ${dist} м`);
  }
});

Такой подход применяется в:

  • логистических приложениях
  • системах доставки
  • геоаналитических панелях

Отображение линии между точками

Чтобы визуализировать расстояние, используется слой типа line:

map.addSource('route', {
  type: 'geojson',
  data: {
    type: 'Feature',
    geometry: {
      type: 'LineString',
      coordinates: []
    }
  }
});

map.addLayer({
  id: 'route-line',
  type: 'line',
  source: 'route',
  paint: {
    'line-color': '#3b82f6',
    'line-width': 3
  }
});

Обновление данных при выборе точек:

const source = map.getSource('route');

source.setData({
  type: 'Feature',
  geometry: {
    type: 'LineString',
    coordinates
  }
});

Разница между экранным и географическим расстоянием

Важно различать два типа расстояний:

Географическое расстояние

  • учитывает кривизну Земли
  • возвращается в метрах/километрах
  • используется для аналитики

Экранное расстояние

  • измеряется в пикселях
  • зависит от масштаба карты
  • используется для UI (например, кластеризация или hover-зоны)

Mapbox GL JS оперирует обоими типами, но методы вроде distanceTo всегда относятся к географическому пространству.


Ошибки при расчёте расстояний

На практике часто встречаются типичные ошибки:

1. Использование евклидовой формулы

Math.sqrt(dx * dx + dy * dy)

Корректно только для локальных плоскостей.

2. Перепутанный порядок координат

Mapbox использует порядок:

[lng, lat]

а не наоборот.

3. Игнорирование проекции

Web Mercator сильно искажает расстояния ближе к полюсам.


Производительность вычислений

При работе с большим количеством точек:

  • метод distanceTo быстрее и безопаснее ручных реализаций
  • Turf.js удобнее для пакетной обработки GeoJSON
  • кэширование расстояний снижает нагрузку при интерактивных сценариях

Для кластеризации или heatmap лучше заранее считать матрицы расстояний на сервере.


Практический паттерн: динамическое измерение пути

Распространённый кейс — накопление маршрута:

const route = [];

map.on('click', (e) => {
  route.push([e.lngLat.lng, e.lngLat.lat]);

  let total = 0;

  for (let i = 1; i < route.length; i++) {
    const a = new mapboxgl.LngLat(route[i - 1][0], route[i - 1][1]);
    const b = new mapboxgl.LngLat(route[i][0], route[i][1]);

    total += a.distanceTo(b);
  }

  console.log('Общая длина маршрута:', total);
});

Такой подход используется в:

  • трекинге перемещений
  • построении пользовательских маршрутов
  • фитнес-трекерах и outdoor-приложениях