Геопространственные приложения нередко работают с тысячами и даже миллионами объектов. По мере роста объёма данных возрастает нагрузка на процессор, увеличивается потребление памяти и ухудшается отзывчивость интерфейса. В таких условиях эффективность алгоритмов становится не менее важной, чем корректность вычислений.
Библиотека Turf.js предоставляет широкий набор инструментов для пространственного анализа непосредственно в JavaScript. Большинство функций выполняются на стороне клиента или в среде Node.js, поэтому производительность напрямую зависит от объёма геометрии, сложности алгоритмов и особенностей реализации приложения.
Анализ производительности позволяет:
Наиболее очевидный фактор — число геометрических объектов.
Например, вычисление площади для десяти полигонов выполняется практически мгновенно:
import * as turf from "@turf/turf";
const areas = polygons.features.map(feature =>
turf.area(feature)
);
Однако при обработке десятков тысяч объектов время выполнения начинает существенно расти.
Пример:
for (const feature of largeCollection.features) {
turf.area(feature);
}
Если коллекция содержит 100 000 полигонов, количество операций возрастает пропорционально размеру массива.
Не менее важным фактором является число вершин.
Два полигона могут занимать одинаковую площадь, но содержать разное количество координат:
const simplePolygon = turf.polygon([
[
[0,0],
[10,0],
[10,10],
[0,10],
[0,0]
]
]);
const complexPolygon = turf.polygon([
generateThousandsOfVertices()
]);
Многие алгоритмы Turf.js обходят все вершины геометрии.
Следовательно:
Разные функции имеют различную вычислительную сложность.
Сравнение нескольких распространённых операций:
| Операция | Относительная стоимость |
|---|---|
| area | Низкая |
| length | Низкая |
| centroid | Низкая |
| bbox | Очень низкая |
| buffer | Высокая |
| union | Очень высокая |
| intersect | Высокая |
| difference | Высокая |
| dissolve | Очень высокая |
Операции топологической обработки обычно требуют значительно больше ресурсов, чем простые измерения.
Наиболее точный способ измерения времени в браузере:
const start = performance.now();
turf.buffer(feature, 5);
const end = performance.now();
console.log(`Время: ${end - start} мс`);
Преимущества:
Для быстрого анализа подходит встроенный механизм консоли.
console.time("buffer");
turf.buffer(feature, 5);
console.timeEnd("buffer");
Результат:
buffer: 12.5 ms
Метод удобен при отладке и сравнении различных реализаций.
Одна операция может выполняться быстрее или медленнее из-за:
Поэтому следует выполнять серию измерений.
Неправильный подход:
const start = performance.now();
turf.centroid(feature);
console.log(performance.now() - start);
Правильный подход:
const iterations = 1000;
const start = performance.now();
for (let i = 0; i < iterations; i++) {
turf.centroid(feature);
}
const end = performance.now();
console.log((end - start) / iterations);
Среднее значение гораздо лучше отражает реальную производительность.
Современные движки используют JIT-компиляцию.
Во время первых запусков функция может выполняться медленнее:
for (let i = 0; i < 1000; i++) {
turf.area(feature);
}
После разогрева оптимизированный код начинает работать быстрее.
При построении бенчмарков рекомендуется:
Функция area() относится к наиболее быстрым операциям
Turf.js.
Пример тестирования:
const start = performance.now();
for (const polygon of polygons.features) {
turf.area(polygon);
}
const end = performance.now();
console.log(end - start);
Сложность зависит преимущественно от числа вершин полигона.
Функция length() проходит по сегментам линии.
const distance = turf.length(line);
Чем больше сегментов содержит маршрут, тем больше времени потребуется на вычисления.
Для GPS-треков из тысяч точек стоимость операции становится заметной.
Операции:
turf.centroid(feature);
и
turf.center(feature);
обычно выполняются быстро даже на крупных наборах данных.
Они подходят для массовой обработки коллекций.
Одна из самых популярных операций:
turf.booleanPointInPolygon(point, polygon);
При небольшом количестве объектов нагрузка минимальна.
Проблемы появляются при массовых проверках:
for (const point of points.features) {
turf.booleanPointInPolygon(point, polygon);
}
Если выполняется:
затраты становятся существенными.
Наивный вариант:
for (const point of points.features) {
for (const polygon of polygons.features) {
turf.booleanPointInPolygon(point, polygon);
}
}
При:
получается 10 миллионов проверок.
Такой подход быстро становится непрактичным.
Перед выполнением дорогих пространственных операций полезно использовать BBOX.
Получение границ:
const bbox = turf.bbox(polygon);
Проверка попадания точки в прямоугольник значительно дешевле полноценного анализа полигона.
Схема работы:
if (
pointLng >= bbox[0] &&
pointLat >= bbox[1] &&
pointLng <= bbox[2] &&
pointLat <= bbox[3]
) {
turf.booleanPointInPolygon(point, polygon);
}
Такой подход способен уменьшить количество дорогостоящих вычислений в десятки раз.
Буферизация относится к ресурсоёмким операциям.
Пример:
const buffered = turf.buffer(line, 5);
Во время работы происходит:
Количество точек результата зачастую значительно превышает количество исходных координат.
Функция позволяет задавать детализацию окружностей.
turf.buffer(feature, 5, {
steps: 64
});
Чем выше значение steps, тем более гладкой становится
геометрия.
Однако увеличиваются:
Сравнение:
| Steps | Скорость |
|---|---|
| 8 | Очень высокая |
| 16 | Высокая |
| 32 | Средняя |
| 64 | Низкая |
| 128 | Очень низкая |
Для большинства задач достаточно значений 8–16.
Функция объединения полигонов считается одной из наиболее тяжёлых.
const merged = turf.union(
turf.featureCollection([
polygon1,
polygon2
])
);
Алгоритм должен:
При сложных геометриях время выполнения может увеличиваться экспоненциально.
Растворение границ между объектами:
turf.dissolve(polygons);
Особенно дорого обходится для больших наборов данных.
Например:
Многие функции Turf.js не изменяют исходную геометрию.
Вместо этого создаётся новый объект:
const result = turf.buffer(feature, 10);
Исходный объект остаётся в памяти.
Результат также занимает память.
При обработке больших коллекций это приводит к значительному росту использования RAM.
Неэффективный пример:
const results = [];
for (const feature of features.features) {
results.push(
turf.buffer(feature, 10)
);
}
При тысячах крупных объектов память может быстро исчерпываться.
Иногда эффективнее обрабатывать данные пакетами.
Вместо обработки всего массива одновременно:
processAllFeatures(features);
используется разбиение:
const chunkSize = 1000;
for (
let i = 0;
i < features.length;
i += chunkSize
) {
const chunk = features.slice(
i,
i + chunkSize
);
processChunk(chunk);
}
Преимущества:
Тяжёлые операции Turf.js способны блокировать главный поток браузера.
Например:
turf.union(bigCollection);
Во время вычислений интерфейс может перестать реагировать.
Решение — Web Worker.
Главный поток:
worker.postMessage(data);
Worker:
importScripts("turf.min.js");
const result = turf.buffer(feature, 20);
Преимущества:
Инструмент позволяет:
Типичный процесс анализа:
Вкладка Memory помогает определить:
Особенно полезна при обработке больших пространственных наборов данных.
Неэффективно:
for (const item of data) {
const polygon = turf.polygon(
item.coordinates
);
turf.area(polygon);
}
Лучше создавать геометрию заранее:
const polygons = data.map(item =>
turf.polygon(item.coordinates)
);
for (const polygon of polygons) {
turf.area(polygon);
}
Плохой вариант:
for (const point of points) {
const bbox = turf.bbox(polygon);
check(point, bbox);
}
Оптимизированный вариант:
const bbox = turf.bbox(polygon);
for (const point of points) {
check(point, bbox);
}
Нередко геометрии содержат намного больше координат, чем требуется.
Например:
[
[10.123456789, 20.123456789]
]
Для отображения на карте такая точность зачастую избыточна.
Уменьшение количества вершин перед сложными операциями способно значительно ускорить обработку.
Последовательность анализа производительности обычно выглядит следующим образом:
performance.now().Такой подход позволяет поддерживать высокую скорость работы приложений даже при обработке крупных наборов геопространственных данных средствами Turf.js.