Векторные линии в Mapbox GL JS представляют собой последовательности координат в формате GeoJSON LineString или MultiLineString. Их геометрия всегда описывается в системе WGS84 (долгота/широта), однако визуализация на карте происходит через проекцию Web Mercator, что напрямую влияет на интерпретацию расстояний.
Ключевая особенность: расстояние между точками на экране не соответствует геодезической длине на поверхности Земли. Поэтому любые вычисления длины линий должны выполняться вне рендеринга карты или с использованием геодезических методов.
Существует два принципиально разных подхода к измерению длины линии:
Проекционная длина
Геодезическая длина
В большинстве задач анализа данных используется именно геодезическая длина, так как она сохраняет физический смысл измерения.
Наиболее распространённый способ расчёта длины линий в 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:
Важно учитывать, что точность зависит от плотности сегментов линии. Чем больше промежуточных точек, тем точнее результат.
При отсутствии сторонних библиотек используется формула Haversine для вычисления расстояния между соседними точками.
a = ^2() + (_1)(_2)^2()
На основе этого выражения вычисляется центральный угол между двумя точками, который затем переводится в расстояние через радиус Земли.
Алгоритм суммарной длины линии:
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;
}
Mapbox GL JS предоставляет встроенный механизм сегментной
нормализации через lineMetrics: true. Он не даёт прямую
длину линии, но позволяет работать с нормализованными значениями вдоль
линии.
map.addSource('route', {
type: 'geojson',
data: routeGeoJSON,
lineMetrics: true
});
После включения становятся доступны выражения:
line-progressline-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.
При работе с динамическими данными часто требуется пересчитывать длину при изменении геометрии.
function getSourceLength(map, sourceId) {
const source = map.getSource(sourceId);
const data = source._data;
return turf.length(data, { units: 'meters' });
}
В реальных приложениях предпочтительнее:
_data)При интерактивном редактировании (например, через Mapbox GL Draw) длина линии пересчитывается на каждом изменении координат.
Типичный цикл обновления:
map.on('draw.update', (e) => {
const feature = e.features[0];
const len = turf.length(feature, { units: 'kilometers' });
console.log(len);
});
Особенность таких сценариев:
При работе с маршрутами, содержащими тысячи координат, прямой пересчёт длины становится затратным.
Практические подходы:
1. Сегментное кэширование
2. Упрощение геометрии
3. Web Worker
Длина линии зависит от частоты дискретизации:
Особенно это заметно на:
Turf.js
Haversine вручную
lineMetrics
Расчёт длины линии используется в различных типах задач:
В интерактивных приложениях длина часто связывается с UI-элементами:
Часто встречающиеся проблемы:
Каждая из этих ошибок приводит к систематическому искажению результатов, особенно на больших расстояниях.