В веб-картографии расстояния между точками на поверхности Земли не совпадают с евклидовой метрикой на плоскости. При работе с интерактивными картами требуется учитывать кривизну эллипсоида, использовать геодезические формулы и корректно преобразовывать координаты из проекций в пространственные величины.
В контексте интерактивных карт на основе MapLibre GL JS измерение расстояний реализуется через комбинацию геометрических вычислений, работы с GeoJSON и обработчиков пользовательских событий. Основной подход заключается в построении линии между точками и вычислении её длины с учётом геодезической модели.
В MapLibre GL JS координаты представлены в формате
[долгота, широта] в системе WGS84. Эти значения:
Расстояние между двумя точками на сфере или эллипсоиде определяется через:
На практике в веб-приложениях чаще используется приближённый, но стабильный вариант — расчёты через специализированные геоинструменты.
Основной структурой данных для измерений выступает
LineString.
const line = {
type: "Feature",
geometry: {
type: "LineString",
coordinates: [
[37.6173, 55.7558],
[30.3351, 59.9343]
]
}
};
Такой объект интерпретируется как ломаная линия, на основе которой вычисляется длина.
MapLibre GL JS использует GeoJSON-источники для отображения геометрии:
map.addSource("measure-line", {
type: "geojson",
data: line
});
map.addLayer({
id: "measure-line-layer",
type: "line",
source: "measure-line",
paint: {
"line-color": "#ff0000",
"line-width": 2
}
});
Для вычисления расстояний обычно применяется библиотека Turf.js, обеспечивающая геодезические операции.
const from = [37.6173, 55.7558];
const to = [30.3351, 59.9343];
const distance = turf.distance(from, to, { units: "kilometers" });
Функция turf.distance использует сферическую модель
Земли и возвращает результат в выбранных единицах измерения:
При интерактивном добавлении точек общая длина вычисляется суммированием сегментов:
function calculateLength(coordinates) {
let total = 0;
for (let i = 1; i < coordinates.length; i++) {
total += turf.distance(
coordinates[i - 1],
coordinates[i],
{ units: "kilometers" }
);
}
return total;
}
Такой подход используется в инструментах измерения на карте, где каждая новая точка добавляет сегмент.
Интерактивность реализуется через события карты.
const coordinates = [];
map.on("click", (e) => {
const point = [e.lngLat.lng, e.lngLat.lat];
coordinates.push(point);
const line = {
type: "Feature",
geometry: {
type: "LineString",
coordinates
}
};
map.getSource("measure-line").setData(line);
const length = calculateLength(coordinates);
console.log("Длина:", length);
});
Каждое событие клика:
Для повышения информативности вычисляется длина каждого сегмента:
function segmentInfo(coords) {
const segments = [];
for (let i = 1; i < coords.length; i++) {
const dist = turf.distance(coords[i - 1], coords[i], {
units: "kilometers"
});
segments.push({
from: coords[i - 1],
to: coords[i],
distance: dist
});
}
return segments;
}
Полученные значения могут использоваться для:
Геодезические расстояния чувствительны к выбранной системе единиц. Приведение результата к удобному виду выполняется через постобработку:
function formatDistance(km) {
if (km < 1) {
return `${(km * 1000).toFixed(0)} м`;
}
return `${km.toFixed(2)} км`;
}
Такой формат позволяет адаптировать вывод под масштаб карты.
Измерительная линия обычно реализуется как отдельный слой поверх базовой карты.
map.addLayer({
id: "measure-points",
type: "circle",
source: "measure-line",
paint: {
"circle-radius": 5,
"circle-color": "#0000ff"
},
filter: ["in", "$type", "Point"]
});
Хотя GeoJSON содержит только LineString, отдельные точки
могут быть преобразованы в FeatureCollection для
отображения узлов.
MapLibre GL JS использует проекцию Web Mercator для визуализации, однако координаты измерений остаются в WGS84.
Ключевой момент:
Ошибка возникает при попытке использовать экранные координаты
(project, unproject) для измерений без
обратного преобразования.
Иногда требуется измерение в пикселях, например для UI-элементов:
const p1 = map.project([37.6173, 55.7558]);
const p2 = map.project([30.3351, 59.9343]);
const dx = p2.x - p1.x;
const dy = p2.y - p1.y;
const pixelDistance = Math.sqrt(dx * dx + dy * dy);
Такой метод не отражает реальную географическую дистанцию и применяется только для интерфейсных задач.
Комбинированная реализация включает:
let measuring = false;
let coords = [];
function startMeasure() {
measuring = true;
coords = [];
}
function addPoint(lngLat) {
if (!measuring) return;
coords.push([lngLat.lng, lngLat.lat]);
map.getSource("measure-line").setData({
type: "Feature",
geometry: {
type: "LineString",
coordinates: coords
}
});
const length = calculateLength(coords);
updateUI(length);
}
Точность измерений зависит от нескольких факторов:
При больших расстояниях расхождение между приближённой сферической моделью и эллипсоидом может становиться заметным.
При увеличении числа точек возникает нагрузка на перерасчёт длины. Для оптимизации используется инкрементальный подход:
let totalDistance = 0;
function addSegment(a, b) {
totalDistance += turf.distance(a, b, { units: "kilometers" });
}
При удалении или перемещении точек требуется пересчёт:
Это обеспечивает согласованность данных при интерактивном редактировании.
Для повышения читаемости применяются:
Подписи обычно реализуются через symbol layer:
map.addLayer({
id: "labels",
type: "symbol",
source: "measure-line",
layout: {
"text-field": "{distance}",
"text-size": 12
}
});
Поведение измерений зависит от масштаба карты:
Разделение визуального и математического представления остаётся ключевым принципом архитектуры измерительных инструментов.