Turf.js используется как вычислительный слой геоаналитики поверх данных GeoJSON, тогда как Mapbox GL JS отвечает за визуализацию, стилизацию и интерактивное отображение карт в браузере. Их интеграция строится на принципе разделения ответственности: Mapbox GL JS управляет рендерингом и источниками данных, Turf.js — геометрическими вычислениями, фильтрацией и пространственной логикой.
Основой связки выступает формат GeoJSON, который одновременно понимается Mapbox GL JS как источник данных и Turf.js как входной набор геометрий для операций. Типовой поток данных выглядит следующим образом:
sourceКлючевой момент интеграции — иммутабельность данных на уровне
источника. Mapbox GL JS не отслеживает внутренние изменения
объектов GeoJSON, поэтому любые результаты Turf.js требуют явного
обновления source через setData.
Базовая структура включает создание карты и добавление источника типа
geojson.
import mapboxgl from "mapbox-gl";
import * as turf from "@turf/turf";
mapboxgl.accessToken = "YOUR_TOKEN";
const map = new mapboxgl.Map({
container: "map",
style: "mapbox://styles/mapbox/streets-v12",
center: [37.6173, 55.7558],
zoom: 10
});
map.on("load", () => {
map.addSource("points", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
map.addLayer({
id: "points-layer",
type: "circle",
source: "points",
paint: {
"circle-radius": 6,
"circle-color": "#3b82f6"
}
});
});
На этом уровне Mapbox GL JS выступает как контейнер данных, готовый к динамическому обновлению через Turf.js.
Turf.js применяется для трансформации данных перед передачей в слой. Один из базовых сценариев — вычисление буферной зоны вокруг точки или полигона.
const point = turf.point([37.6173, 55.7558]);
const buffer = turf.buffer(point, 5, { units: "kilometers" });
map.getSource("points").setData({
type: "FeatureCollection",
features: [point, buffer]
});
Буферные зоны часто используются для визуализации радиусов влияния, зон поиска или доступности.
Интерактивность достигается через обработчики событий Mapbox GL JS. Координаты клика используются как входные данные Turf.js.
map.on("click", (e) => {
const clickedPoint = turf.point([e.lngLat.lng, e.lngLat.lat]);
const circle = turf.circle(clickedPoint, 10, {
steps: 64,
units: "kilometers"
});
map.getSource("points").setData({
type: "FeatureCollection",
features: [clickedPoint, circle]
});
});
Здесь Turf.js генерирует геометрию круга, а Mapbox GL JS немедленно визуализирует результат через обновление source.
Один из частых сценариев — фильтрация точек внутри заданной области.
const area = turf.polygon([[
[37.0, 55.0],
[38.0, 55.0],
[38.0, 56.0],
[37.0, 56.0],
[37.0, 55.0]
]]);
const points = turf.featureCollection([
turf.point([37.2, 55.2]),
turf.point([37.9, 55.8]),
turf.point([38.5, 55.5])
]);
const filtered = turf.pointsWithinPolygon(points, area);
map.getSource("points").setData(filtered);
Такой подход применяется в задачах геоаналитики, например, при выделении объектов внутри административных границ или пользовательских зон.
Turf.js позволяет вычислять расстояния между объектами и использовать их для стилизации слоёв.
const from = turf.point([37.6173, 55.7558]);
const to = turf.point([37.8, 55.9]);
const distance = turf.distance(from, to, { units: "kilometers" });
Далее результат может использоваться для динамической генерации геометрии:
const line = turf.lineString([
from.geometry.coordinates,
to.geometry.coordinates
]);
map.getSource("points").setData({
type: "FeatureCollection",
features: [from, to, line]
});
При работе с полигонами часто требуется вычислять пересечения или объединения областей.
const poly1 = turf.polygon([[
[37.0, 55.0],
[38.0, 55.0],
[38.0, 56.0],
[37.0, 56.0],
[37.0, 55.0]
]]);
const poly2 = turf.polygon([[
[37.5, 55.5],
[38.5, 55.5],
[38.5, 56.5],
[37.5, 56.5],
[37.5, 55.5]
]]);
const intersection = turf.intersect(poly1, poly2);
map.getSource("points").setData({
type: "FeatureCollection",
features: intersection ? [intersection] : []
});
Такие операции используются в системах территориального анализа, наложения слоёв и определения конфликтующих зон.
При интеграции Turf.js с Mapbox GL JS критически важна производительность, особенно при обработке тысяч объектов.
Основные подходы оптимизации:
bbox фильтров перед точными
вычислениямиsetDataПример предварительного отбора:
const bbox = turf.bboxPolygon([37, 55, 38, 56]);
const filtered = features.filter(f =>
turf.booleanPointInPolygon(f, bbox)
);
Функция поиска ближайшего объекта часто применяется в интерфейсах выбора.
const points = turf.featureCollection([
turf.point([37.6, 55.7]),
turf.point([37.7, 55.8]),
turf.point([37.8, 55.9])
]);
const target = turf.point([37.65, 55.75]);
const nearest = turf.nearestPoint(target, points);
map.getSource("points").setData({
type: "FeatureCollection",
features: [target, nearest]
});
Это позволяет реализовать привязку к ближайшим объектам, маршрутизацию и автоматический выбор элементов.
При постоянном изменении данных Turf.js используется как промежуточный слой трансформации:
function update(features) {
const enriched = features.map(f => {
const buffered = turf.buffer(f, 2, { units: "kilometers" });
return buffered;
});
map.getSource("points").setData({
type: "FeatureCollection",
features: enriched
});
}
Mapbox GL JS использует проекцию Web Mercator, тогда как Turf.js выполняет расчёты в геодезической системе координат WGS84. Это приводит к важному принципу:
[lng, lat]При сложных операциях (например, больших радиусах buffer) необходимо учитывать искажения проекции, особенно в высоких широтах.
Интеграция позволяет строить многослойные аналитические визуализации:
Каждый слой формируется отдельным результатом Turf.js и управляется Mapbox GL JS независимо, но синхронно через общий набор данных или связанные источники.
map.addLayer({
id: "buffer-layer",
type: "fill",
source: "points",
paint: {
"fill-color": "#60a5fa",
"fill-opacity": 0.3
}
});
Turf.js часто используется как движок логики интерфейса:
Mapbox GL JS при этом остаётся слоем представления, не содержащим бизнес-логики, что обеспечивает предсказуемость архитектуры и масштабируемость геоприложений.