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

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

В контексте интерактивных карт на основе MapLibre GL JS измерение расстояний реализуется через комбинацию геометрических вычислений, работы с GeoJSON и обработчиков пользовательских событий. Основной подход заключается в построении линии между точками и вычислении её длины с учётом геодезической модели.


Геометрическая модель координат

В MapLibre GL JS координаты представлены в формате [долгота, широта] в системе WGS84. Эти значения:

  • являются угловыми величинами;
  • не образуют линейную метрику в плоском смысле;
  • требуют преобразования для вычисления расстояний.

Расстояние между двумя точками на сфере или эллипсоиде определяется через:

  • формулу гаверсинусов;
  • формулу Винсенти;
  • геодезические библиотеки высокого уровня.

На практике в веб-приложениях чаще используется приближённый, но стабильный вариант — расчёты через специализированные геоинструменты.


Представление линии измерения через GeoJSON

Основной структурой данных для измерений выступает 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

Для вычисления расстояний обычно применяется библиотека Turf.js, обеспечивающая геодезические операции.

Расстояние между двумя точками

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

const distance = turf.distance(from, to, { units: "kilometers" });

Функция turf.distance использует сферическую модель Земли и возвращает результат в выбранных единицах измерения:

  • kilometers;
  • miles;
  • nauticalmiles;
  • degrees.

Инкрементальное измерение длины линии

При интерактивном добавлении точек общая длина вычисляется суммированием сегментов:

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

Такой метод не отражает реальную географическую дистанцию и применяется только для интерфейсных задач.


Построение полноценного инструмента измерений

Комбинированная реализация включает:

  • хранение массива координат;
  • динамическое обновление GeoJSON;
  • вычисление геодезической длины;
  • отображение промежуточных значений;
  • управление режимами редактирования.
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);
}

Ограничения точности

Точность измерений зависит от нескольких факторов:

  • модель Земли (сфера vs эллипсоид);
  • частота сегментации линии;
  • качество исходных координат;
  • метод вычисления расстояний.

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


Оптимизация вычислений

При увеличении числа точек возникает нагрузка на перерасчёт длины. Для оптимизации используется инкрементальный подход:

  • хранится текущая сумма;
  • добавляется только последний сегмент;
  • пересчёт всей линии не выполняется.
let totalDistance = 0;

function addSegment(a, b) {
  totalDistance += turf.distance(a, b, { units: "kilometers" });
}

Обработка редактирования геометрии

При удалении или перемещении точек требуется пересчёт:

  • пересобирается массив координат;
  • полностью пересчитывается длина;
  • обновляется GeoJSON источник.

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


Визуальные маркеры измерений

Для повышения читаемости применяются:

  • точки-узлы;
  • подписи расстояний;
  • направленные линии.

Подписи обычно реализуются через symbol layer:

map.addLayer({
  id: "labels",
  type: "symbol",
  source: "measure-line",
  layout: {
    "text-field": "{distance}",
    "text-size": 12
  }
});

Масштабирование и контекст измерений

Поведение измерений зависит от масштаба карты:

  • на малом масштабе линии сглаживаются;
  • на крупном масштабе увеличивается точность;
  • при зуме изменяется визуальное восприятие длины, но не её геодезическое значение.

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