Turf.js предоставляет набор геопространственных алгоритмов, позволяющих выполнять операции над точками, линиями и полигонами в географических координатах. Привязка точек к линиям (point snapping / map matching упрощённого уровня) — одна из ключевых задач при обработке GPS-треков, построении маршрутов и анализе перемещений.
Привязка точки к линии в геопространственном контексте сводится к поиску проекции точки на ближайший сегмент линии и вычислению координат этой проекции в системе координат исходной геометрии.
Линия в GeoJSON представляется как LineString —
последовательность координат:
{
"type": "LineString",
"coordinates": [
[lng1, lat1],
[lng2, lat2],
[lng3, lat3]
]
}
Точка:
{
"type": "Point",
"coordinates": [lng, lat]
}
Задача заключается в том, чтобы:
Основной инструмент для решения задачи —
nearestPointOnLine.
import * as turf from "@turf/turf";
const point = turf.point([37.6156, 55.7522]);
const line = turf.lineString([
[37.60, 55.75],
[37.62, 55.76],
[37.65, 55.74]
]);
const snapped = turf.nearestPointOnLine(line, point);
Результат содержит расширенную структуру:
{
type: "Feature",
geometry: {
type: "Point",
coordinates: [...]
},
properties: {
index: 1,
dist: 0.42,
location: 12.7
}
}
geometry.coordinates — координаты точки, «прилипшей» к
линииproperties.index — индекс сегмента линии, на котором
найдена проекцияproperties.dist — расстояние от исходной точки до
линииproperties.location — положение вдоль линии в единицах
её длиныПеред выполнением полноценного снаппинга часто вычисляется расстояние до линии:
const dist = turf.pointToLineDistance(point, line, { units: "kilometers" });
Функция возвращает минимальное расстояние от точки до всей полилинии, не вычисляя саму проекцию.
Использование:
const MAX_DISTANCE = 0.2; // км
if (dist < MAX_DISTANCE) {
const snapped = turf.nearestPointOnLine(line, point);
}
Полный процесс привязки обычно включает несколько шагов:
const dist = turf.pointToLineDistance(point, line, { units: "meters" });
if (dist > 100) return null;
Turf.js внутри nearestPointOnLine перебирает сегменты
LineString и вычисляет минимальное расстояние до
каждого.
Для сегмента A-B вычисляется ортогональная проекция точки P:
Если:
t < 0 — ближе к At > 1 — ближе к BРезультат — координата, вычисленная как:
P' = A + t * (B - A)
Для сложных линий важно учитывать индекс сегмента:
const snapped = turf.nearestPointOnLine(line, point);
console.log(snapped.properties.index);
Это позволяет:
Для мультилиний используется та же функция:
const multiLine = turf.multiLineString([
[[0, 0], [1, 1]],
[[1, 1], [2, 2]]
]);
const snapped = turf.nearestPointOnLine(multiLine, point);
Особенность:
Поле location — ключевой элемент для линейной
привязки:
snapped.properties.location
Оно позволяет:
Пример интерполяции:
const progress = snapped.properties.location / turf.length(line, { units: "kilometers" });
При обработке трека алгоритм применяется последовательно:
const snappedTrack = points.map(p =>
turf.nearestPointOnLine(line, p)
);
Дополнительно применяются оптимизации:
Сырые GPS-данные дают «дрожание» точки на линии. Для стабилизации применяются методы:
function smooth(points, windowSize = 3) {
return points.map((p, i, arr) => {
const slice = arr.slice(Math.max(0, i - windowSize), i + windowSize);
const avgLng = slice.reduce((s, x) => s + x.geometry.coordinates[0], 0) / slice.length;
const avgLat = slice.reduce((s, x) => s + x.geometry.coordinates[1], 0) / slice.length;
return turf.point([avgLng, avgLat]);
});
}
Если расстояние между соседними снапами слишком велико, точка считается выбросом.
Если линия состоит из одной точки, проекция невозможна.
На острых углах возможны скачки индекса сегмента.
Turf.js работает в географических координатах, но локальные искажения проекции могут влиять на точность при больших расстояниях.
При пересечениях возможен выбор «неожиданного» сегмента как ближайшего.
При большом количестве точек (десятки тысяч) важно учитывать сложность:
Методы ускорения:
Разделение на чанки снижает количество проверок.
Использование R-tree или grid-индекса вне Turf.js.
Если точки идут последовательно вдоль маршрута:
let lastIndex = 0;
function fastSnap(point, line) {
return turf.nearestPointOnLine(line, point, { units: "meters", closestPoint: true });
}
(в реальных системах дополнительно ограничивают поиск соседними сегментами вручную)
const total = turf.length(line, { units: "kilometers" });
const segment = turf.lineSlice(start, snapped.geometry, line);
const buffer = turf.buffer(line, 0.1, { units: "kilometers" });
Функции поддерживают:
Выбор единиц влияет на:
При вычислениях на сфере возникают:
Turf.js компенсирует часть этих проблем использованием геодезических формул, но при высокоточных задачах требуется дополнительная фильтрация и проверка результатов.