Интеграция с Turf.js

Turf.js — это библиотека пространственного анализа для JavaScript, предназначенная для выполнения геометрических вычислений непосредственно в браузере или на сервере. В связке с MapLibre GL JS она позволяет не только отображать геоданные, но и выполнять над ними сложные операции: измерения, поиск пересечений, буферизацию, кластерный анализ, построение маршрутов, генерацию изолиний и многое другое.

MapLibre GL JS отвечает за визуализацию и взаимодействие с картой, тогда как Turf.js берет на себя вычислительную часть геоинформационной обработки.

Типичная архитектура выглядит следующим образом:

GeoJSON → Turf.js → Обработанный GeoJSON → MapLibre GL JS

Например:

const buffered = turf.buffer(feature, 1, {
    units: 'kilometers'
});

map.getSource('zones').setData(buffered);

В данном случае Turf.js создает буферную зону вокруг объекта, а MapLibre отображает результат на карте.


Подключение Turf.js

Установка через npm:

npm install @turf/turf

Импорт всей библиотеки:

import * as turf from '@turf/turf';

Импорт отдельных модулей:

import buffer from '@turf/buffer';
import distance from '@turf/distance';

В крупных проектах предпочтителен импорт отдельных пакетов, поскольку это уменьшает итоговый размер бандла.


Формат GeoJSON как основа интеграции

MapLibre GL JS и Turf.js используют единый формат данных — GeoJSON.

Пример точки:

const point = {
    type: 'Feature',
    geometry: {
        type: 'Point',
        coordinates: [37.6176, 55.7558]
    },
    properties: {}
};

Пример полигона:

const polygon = {
    type: 'Feature',
    geometry: {
        type: 'Polygon',
        coordinates: [[
            [37.60, 55.75],
            [37.65, 55.75],
            [37.65, 55.78],
            [37.60, 55.78],
            [37.60, 55.75]
        ]]
    },
    properties: {}
};

Любой объект, полученный от Turf.js, может быть напрямую передан в источник данных MapLibre:

map.addSource('data', {
    type: 'geojson',
    data: turfResult
});

Вычисление расстояний между объектами

Одной из наиболее востребованных функций является расчет расстояний.

Создание точек:

const p1 = turf.point([37.6176, 55.7558]);
const p2 = turf.point([30.3141, 59.9386]);

Расчет расстояния:

const distance = turf.distance(p1, p2, {
    units: 'kilometers'
});

console.log(distance);

Поддерживаемые единицы измерения:

  • kilometers
  • meters
  • miles
  • nauticalmiles
  • degrees
  • radians

Отображение результата:

document.getElementById('distance').textContent =
    `${distance.toFixed(2)} км`;

Измерение длины маршрутов

Для линий используется функция length().

Создание маршрута:

const route = turf.lineString([
    [37.61, 55.75],
    [37.65, 55.77],
    [37.72, 55.80]
]);

Вычисление длины:

const routeLength = turf.length(route, {
    units: 'kilometers'
});

console.log(routeLength);

Это особенно полезно для:

  • навигационных приложений;
  • спортивных трекеров;
  • систем мониторинга транспорта;
  • анализа логистических маршрутов.

Вычисление площади полигонов

Площадь рассчитывается функцией area().

const area = turf.area(polygon);

console.log(area);

Результат возвращается в квадратных метрах.

Преобразование в гектары:

const hectares = area / 10000;

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

const km2 = area / 1000000;

Типичный сценарий:

map.on('click', e => {

    const selectedPolygon = getPolygon();

    const area = turf.area(selectedPolygon);

    console.log(`Площадь: ${area} м²`);
});

Построение буферных зон

Буферизация является одной из важнейших операций ГИС.

Буфер создает область вокруг объекта на заданном расстоянии.

Создание буфера вокруг точки:

const point = turf.point([37.6176, 55.7558]);

const buffer = turf.buffer(point, 2, {
    units: 'kilometers'
});

Отображение:

map.addSource('buffer', {
    type: 'geojson',
    data: buffer
});

Добавление слоя:

map.addLayer({
    id: 'buffer-fill',
    type: 'fill',
    source: 'buffer',
    paint: {
        'fill-color': '#0080ff',
        'fill-opacity': 0.3
    }
});

Буферизация применяется для:

  • зон обслуживания объектов;
  • анализа доступности;
  • санитарных зон;
  • радиусов доставки;
  • оценки покрытия сети.

Проверка попадания точки в полигон

Для определения принадлежности объекта определенной территории используется booleanPointInPolygon().

const inside = turf.booleanPointInPolygon(
    point,
    polygon
);

console.log(inside);

Интеграция с кликом по карте:

map.on('click', e => {

    const point = turf.point([
        e.lngLat.lng,
        e.lngLat.lat
    ]);

    const result =
        turf.booleanPointInPolygon(
            point,
            polygon
        );

    console.log(result);
});

Возможные результаты:

true

или

false

Проверка пересечения объектов

Полигоны часто необходимо сравнивать между собой.

Проверка пересечения:

const intersects =
    turf.booleanIntersects(
        polygon1,
        polygon2
    );

Результат:

true

или

false

Это востребовано в системах:

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

Получение области пересечения

Если требуется не просто узнать факт пересечения, а получить саму область пересечения, используется intersect().

const intersection =
    turf.intersect(
        turf.featureCollection([
            polygon1,
            polygon2
        ])
    );

После получения результата его можно сразу отобразить:

map.getSource('intersection')
    .setData(intersection);

Объединение полигонов

Для объединения нескольких территорий применяется union().

const merged =
    turf.union(
        turf.featureCollection([
            polygon1,
            polygon2
        ])
    );

Результатом будет единый полигон.

Пример отображения:

map.getSource('merged')
    .setData(merged);

Вычисление центроида

Центроид представляет геометрический центр объекта.

const center =
    turf.centroid(polygon);

Полученные координаты:

console.log(
    center.geometry.coordinates
);

Отображение маркера:

new maplibregl.Marker()
    .setLngLat(
        center.geometry.coordinates
    )
    .addTo(map);

Использование:

  • автоматическое размещение подписей;
  • создание всплывающих окон;
  • аналитические расчеты.

Построение ограничивающего прямоугольника

Для определения границ объекта используется bbox().

const bbox =
    turf.bbox(feature);

Результат:

[
    minLng,
    minLat,
    maxLng,
    maxLat
]

Автоматическое масштабирование карты:

map.fitBounds([
    [bbox[0], bbox[1]],
    [bbox[2], bbox[3]]
]);

Генерация ограничивающего полигона

Полученный bbox можно преобразовать в геометрию:

const boundsPolygon =
    turf.bboxPolygon(
        turf.bbox(feature)
    );

Отображение:

map.getSource('bounds')
    .setData(boundsPolygon);

Такой подход полезен для отладки и визуального анализа данных.


Поиск ближайшего объекта

Функция nearestPoint() позволяет находить ближайший объект из набора точек.

const nearest =
    turf.nearestPoint(
        targetPoint,
        pointsCollection
    );

Пример:

const nearestHospital =
    turf.nearestPoint(
        userLocation,
        hospitals
    );

Подобная логика используется в:

  • поиске ближайших магазинов;
  • системах навигации;
  • службах доставки;
  • сервисах экстренного реагирования.

Упрощение сложной геометрии

Большие полигоны могут содержать тысячи вершин.

Для оптимизации применяется simplify().

const simplified =
    turf.simplify(
        polygon,
        {
            tolerance: 0.001,
            highQuality: false
        }
    );

Преимущества:

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

Создание выпуклой оболочки

Выпуклая оболочка строится вокруг множества точек.

const hull =
    turf.convex(points);

Пример отображения:

map.getSource('hull')
    .setData(hull);

Типичные области применения:

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

Построение линии между точками

Создание линии:

const line =
    turf.lineString([
        pointA.geometry.coordinates,
        pointB.geometry.coordinates
    ]);

Отображение:

map.getSource('line')
    .setData(line);

Используется для:

  • маршрутов;
  • связей между объектами;
  • визуализации сетей.

Разбиение линии на сегменты

Для анализа маршрутов применяется lineChunk().

const chunks =
    turf.lineChunk(
        route,
        1,
        {
            units: 'kilometers'
        }
    );

Каждый сегмент будет отдельным объектом GeoJSON.

Это удобно для:

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

Получение точки на линии

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

const point =
    turf.along(
        route,
        5,
        {
            units: 'kilometers'
        }
    );

Полученная точка находится на расстоянии 5 километров от начала линии.

Применение:

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

Генерация случайных объектов

Для тестирования часто требуется искусственный набор данных.

Случайные точки:

const randomPoints =
    turf.randomPoint(100);

Случайные полигоны:

const randomPolygons =
    turf.randomPolygon(20);

Отображение:

map.getSource('random')
    .setData(randomPoints);

Такой подход полезен при нагрузочном тестировании интерфейса.


Построение сетки точек

Создание регулярной сетки:

const grid =
    turf.pointGrid(
        [
            37.5,
            55.6,
            37.8,
            55.9
        ],
        1,
        {
            units: 'kilometers'
        }
    );

Полученная коллекция может использоваться для:

  • пространственного моделирования;
  • интерполяции данных;
  • построения тепловых карт;
  • геостатистического анализа.

Анализ данных по клику на карте

Практический сценарий объединяет MapLibre и Turf.js.

map.on('click', e => {

    const point = turf.point([
        e.lngLat.lng,
        e.lngLat.lat
    ]);

    const inside =
        turf.booleanPointInPolygon(
            point,
            cityBoundary
        );

    if (inside) {
        console.log(
            'Точка находится внутри города'
        );
    }
});

Здесь MapLibre предоставляет координаты события, а Turf.js выполняет пространственный анализ.


Динамическое обновление геометрии

После выполнения вычислений данные могут сразу отображаться на карте.

const result =
    turf.buffer(
        selectedObject,
        3,
        {
            units: 'kilometers'
        }
    );

map.getSource('analysis')
    .setData(result);

Преимущество такого подхода заключается в отсутствии необходимости обращаться к серверу после каждого действия пользователя.


Производительность при работе с Turf.js

При работе с большими объемами данных рекомендуется:

  • импортировать только необходимые модули;
  • упрощать геометрию перед анализом;
  • выполнять тяжелые вычисления в Web Worker;
  • кэшировать результаты расчетов;
  • избегать повторного анализа одних и тех же объектов;
  • обновлять только изменившиеся источники MapLibre;
  • использовать FeatureCollection вместо множества отдельных источников.

Пример кэширования:

const cache = new Map();

function getBuffer(id, feature) {

    if (cache.has(id)) {
        return cache.get(id);
    }

    const result =
        turf.buffer(
            feature,
            2,
            {
                units: 'kilometers'
            }
        );

    cache.set(id, result);

    return result;
}

Организация пространственного анализа в приложении

Практика показывает, что наиболее удобной является схема разделения ответственности:

MapLibre GL JS
    ↓
Событие пользователя
    ↓
Turf.js
    ↓
GeoJSON результат
    ↓
Источник данных MapLibre
    ↓
Отрисовка слоя

Такой подход обеспечивает:

  • независимость визуализации от вычислений;
  • простое тестирование аналитической логики;
  • повторное использование геометрических функций;
  • масштабируемость приложения;
  • поддержку сложных геоинформационных сценариев без серверных вычислений.

Совместное использование MapLibre GL JS и Turf.js превращает браузерное картографическое приложение в полноценную геоинформационную систему, способную не только отображать данные, но и выполнять пространственный анализ в режиме реального времени непосредственно на стороне клиента.