Length линий

Векторные линии в Mapbox GL JS представляют собой последовательности координат в формате GeoJSON LineString или MultiLineString. Их геометрия всегда описывается в системе WGS84 (долгота/широта), однако визуализация на карте происходит через проекцию Web Mercator, что напрямую влияет на интерпретацию расстояний.

Ключевая особенность: расстояние между точками на экране не соответствует геодезической длине на поверхности Земли. Поэтому любые вычисления длины линий должны выполняться вне рендеринга карты или с использованием геодезических методов.


Геодезическая и проекционная длина

Существует два принципиально разных подхода к измерению длины линии:

Проекционная длина

  • вычисляется в координатах Web Mercator
  • зависит от масштаба и широты
  • используется только для экранных операций (например, hit-testing)

Геодезическая длина

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

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


Использование Turf.js для вычисления длины

Наиболее распространённый способ расчёта длины линий в Mapbox GL JS — библиотека Turf.js.

import * as turf from '@turf/turf';

const line = {
  type: 'Feature',
  geometry: {
    type: 'LineString',
    coordinates: [
      [37.6173, 55.7558],
      [30.3141, 59.9386]
    ]
  }
};

const length = turf.length(line, { units: 'kilometers' });

Функция turf.length:

  • принимает GeoJSON Feature или Geometry
  • автоматически применяет геодезическую модель
  • возвращает длину в заданных единицах (kilometers, miles, meters)

Важно учитывать, что точность зависит от плотности сегментов линии. Чем больше промежуточных точек, тем точнее результат.


Расчёт длины вручную через Haversine

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

a = ^2() + (_1)(_2)^2()

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

Алгоритм суммарной длины линии:

  • проход по всем сегментам LineString
  • вычисление расстояния между соседними координатами
  • суммирование результатов
function toRad(v) {
  return (v * Math.PI) / 180;
}

function segmentLength([lng1, lat1], [lng2, lat2]) {
  const R = 6371000;

  const dLat = toRad(lat2 - lat1);
  const dLng = toRad(lng2 - lng1);

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

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

  return R * c;
}

function lineLength(coords) {
  let sum = 0;

  for (let i = 1; i < coords.length; i++) {
    sum += segmentLength(coords[i - 1], coords[i]);
  }

  return sum;
}

Включение lineMetrics в Mapbox GL JS

Mapbox GL JS предоставляет встроенный механизм сегментной нормализации через lineMetrics: true. Он не даёт прямую длину линии, но позволяет работать с нормализованными значениями вдоль линии.

map.addSource('route', {
  type: 'geojson',
  data: routeGeoJSON,
  lineMetrics: true
});

После включения становятся доступны выражения:

  • line-progress
  • line-distance

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

Пример градиента вдоль линии:

map.addLayer({
  id: 'route-line',
  type: 'line',
  source: 'route',
  paint: {
    'line-width': 4,
    'line-gradient': [
      'interpolate',
      ['linear'],
      ['line-progress'],
      0, 'blue',
      1, 'red'
    ]
  }
});

Хотя это не даёт абсолютной длины, оно позволяет косвенно работать с нормализованной длиной 0–1.


Получение длины из GeoJSON источника

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

function getSourceLength(map, sourceId) {
  const source = map.getSource(sourceId);
  const data = source._data;

  return turf.length(data, { units: 'meters' });
}

В реальных приложениях предпочтительнее:

  • хранить GeoJSON отдельно от Mapbox Source
  • пересчитывать длину при обновлении данных
  • избегать доступа к приватным полям (_data)

Длина линий при редактировании геометрии

При интерактивном редактировании (например, через Mapbox GL Draw) длина линии пересчитывается на каждом изменении координат.

Типичный цикл обновления:

map.on('draw.update', (e) => {
  const feature = e.features[0];
  const len = turf.length(feature, { units: 'kilometers' });

  console.log(len);
});

Особенность таких сценариев:

  • высокая частота пересчётов
  • необходимость оптимизации (debounce/throttle)
  • возможное кэширование промежуточных значений

Оптимизация вычислений на больших линиях

При работе с маршрутами, содержащими тысячи координат, прямой пересчёт длины становится затратным.

Практические подходы:

1. Сегментное кэширование

  • хранение длины каждого сегмента отдельно
  • пересчёт только изменённого участка

2. Упрощение геометрии

  • применение алгоритма Douglas–Peucker
  • уменьшение количества точек без потери формы

3. Web Worker

  • перенос вычислений вне основного потока
  • предотвращение блокировки UI

Влияние плотности координат на точность

Длина линии зависит от частоты дискретизации:

  • редкие точки → заниженная длина (срезание дуг)
  • избыточные точки → точнее, но дороже вычисления

Особенно это заметно на:

  • длинных географических маршрутах
  • линиях вдоль кривых траекторий (реки, дороги)

Сравнение подходов вычисления длины

Turf.js

  • геодезическая точность
  • удобный API
  • зависимость от библиотеки

Haversine вручную

  • полный контроль
  • отсутствие зависимостей
  • больше кода и ответственности

lineMetrics

  • не даёт абсолютную длину
  • полезен для стилизации
  • интегрирован в рендер Mapbox GL JS

Использование длины в прикладных сценариях

Расчёт длины линии используется в различных типах задач:

  • маршрутизация и навигация
  • оценка протяжённости инфраструктуры
  • анализ логистических путей
  • измерение трасс в GIS-системах
  • динамическое отображение прогресса движения вдоль маршрута

В интерактивных приложениях длина часто связывается с UI-элементами:

  • индикаторы прогресса
  • расчёт времени в пути
  • визуализация этапов маршрута

Ошибки при работе с длиной линий

Часто встречающиеся проблемы:

  • использование экранных координат вместо географических
  • игнорирование геодезической модели
  • неправильные единицы измерения
  • отсутствие пересчёта при редактировании геометрии
  • работа с упрощёнными данными без учёта погрешности

Каждая из этих ошибок приводит к систематическому искажению результатов, особенно на больших расстояниях.