Геопространственные вычисления нередко связаны с обработкой больших объёмов данных. Операции поиска ближайших объектов, проверки пересечений, буферизации, кластеризации или расчёта расстояний могут выполняться тысячи раз подряд. При повторном выполнении одинаковых вычислений происходит бессмысленная трата процессорного времени.
Кэширование результатов представляет собой механизм сохранения ранее вычисленных данных для их повторного использования без запуска дорогостоящих операций.
Для проектов на основе Turf.js кэширование особенно актуально в следующих случаях:
Не каждая операция требует сохранения результатов.
Например, если вычисление выполняется единственный раз:
const distance = turf.distance(point1, point2);
создание кэша не имеет смысла.
Однако ситуация меняется при регулярном повторении:
for (let i = 0; i < 10000; i++) {
turf.distance(point1, point2);
}
Здесь вычисляется одно и то же значение десять тысяч раз.
Гораздо эффективнее:
const distance = turf.distance(point1, point2);
for (let i = 0; i < 10000; i++) {
console.log(distance);
}
Кэширование полезно тогда, когда одновременно выполняются два условия:
Наиболее распространённый вариант хранения вычисленных данных в
JavaScript — объект Map.
Предположим, необходимо многократно вычислять расстояния между одинаковыми координатами.
import * as turf fr om "@turf/turf";
const distanceCache = new Map();
function getDistance(point1, point2) {
const key = JSON.stringify([point1, point2]);
if (distanceCache.has(key)) {
return distanceCache.get(key);
}
const result = turf.distance(point1, point2);
distanceCache.set(key, result);
return result;
}
Использование:
const result = getDistance(pointA, pointB);
При повторном вызове значение будет получено из памяти без запуска
функции turf.distance().
Операция буферизации считается одной из самых ресурсоёмких в Turf.js.
Пример без кэширования:
const buffer = turf.buffer(feature, 5, {
units: "kilometers"
});
Если буфер строится для одного и того же объекта многократно, разумно сохранять результат.
const bufferCache = new Map();
function getBuffer(feature, radius) {
const key = JSON.stringify({
geometry: feature.geometry,
radius
});
if (bufferCache.has(key)) {
return bufferCache.get(key);
}
const result = turf.buffer(feature, radius, {
units: "kilometers"
});
bufferCache.set(key, result);
return result;
}
Такой подход особенно эффективен при работе с картами, где пользователь постоянно включает и отключает отображение зон покрытия.
Визуализация больших наборов данных часто требует вычисления центров объектов.
Без кэша:
const center = turf.centroid(feature);
С кэшем:
const centroidCache = new Map();
function getCentroid(feature) {
const id = feature.properties.id;
if (centroidCache.has(id)) {
return centroidCache.get(id);
}
const result = turf.centroid(feature);
centroidCache.set(id, result);
return result;
}
При наличии уникального идентификатора в свойствах объекта
использование id значительно быстрее сериализации всего
GeoJSON.
Если объект GeoJSON существует в памяти как объект JavaScript,
удобным решением становится WeakMap.
Преимущество заключается в автоматическом удалении записей после уничтожения объекта.
const cache = new WeakMap();
function getArea(feature) {
if (cache.has(feature)) {
return cache.get(feature);
}
const area = turf.area(feature);
cache.set(feature, area);
return area;
}
Преимущества:
Недостаток:
Проверки пространственных отношений часто используются при фильтрации данных.
Пример:
turf.booleanContains(polygon, point);
При обработке тысяч точек возникают серьёзные затраты времени.
Можно сохранить результаты:
const containsCache = new Map();
function contains(polygon, point) {
const key = JSON.stringify([
polygon.geometry,
point.geometry
]);
if (containsCache.has(key)) {
return containsCache.get(key);
}
const result = turf.booleanContains(
polygon,
point
);
containsCache.set(key, result);
return result;
}
Подобный подход широко применяется при анализе территорий доставки, административных зон и областей покрытия мобильной связи.
Кластеризация большого количества точек может выполняться достаточно долго.
Например:
const clusters = turf.clustersDbscan(
points,
5
);
Если исходный набор данных не изменяется, результат можно сохранить.
const clusterCache = new Map();
function getClusters(points, radius) {
const key = JSON.stringify({
points,
radius
});
if (clusterCache.has(key)) {
return clusterCache.get(key);
}
const result = turf.clustersDbscan(
points,
radius
);
clusterCache.set(key, result);
return result;
}
Повторный расчёт не потребуется до изменения входных данных.
В серверных приложениях Turf.js часто используется внутри API.
Пример:
app.get("/route/:id", (req, res) => {
const result = calculateRoute(req.params.id);
res.json(result);
});
Если маршрут рассчитывается дорогостоящими алгоритмами, результат можно сохранять.
const routeCache = new Map();
app.get("/route/:id", (req, res) => {
const id = req.params.id;
if (routeCache.has(id)) {
return res.json(routeCache.get(id));
}
const route = calculateRoute(id);
routeCache.set(id, route);
res.json(route);
});
Такой подход значительно уменьшает нагрузку на процессор.
Бесконечное накопление данных приводит к росту потребления памяти.
Простейшее решение:
const MAX_CACHE_SIZE = 1000;
function addToCache(cache, key, value) {
if (cache.size >= MAX_CACHE_SIZE) {
const firstKey =
cache.keys().next().value;
cache.delete(firstKey);
}
cache.set(key, value);
}
После достижения лимита старые записи автоматически удаляются.
LRU (Least Recently Used) удаляет редко используемые элементы.
Пример реализации:
class LRUCache {
constructor(lim it = 100) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
if (!this.cache.has(key)) {
return null;
}
const value = this.cache.get(key);
this.cache.delete(key);
this.cache.set(key, value);
return value;
}
set(key, value) {
if (this.cache.has(key)) {
this.cache.delete(key);
}
this.cache.set(key, value);
if (this.cache.size > this.limit) {
const oldest =
this.cache.keys().next().value;
this.cache.delete(oldest);
}
}
}
Использование:
const cache = new LRUCache(500);
Подобная схема хорошо подходит для веб-карт с большим количеством пространственных запросов.
Операция:
turf.nearestPoint(target, points);
может часто повторяться при движении объекта по карте.
const nearestCache = new Map();
function getNearestPoint(target, points) {
const key = JSON.stringify({
target,
count: points.features.length
});
if (nearestCache.has(key)) {
return nearestCache.get(key);
}
const result = turf.nearestPoint(
target,
points
);
nearestCache.set(key, result);
return result;
}
Особенно заметный выигрыш достигается при работе с транспортными системами и GPS-трекингом.
Иногда данные должны автоматически обновляться.
Для этого используется TTL (Time To Live).
const cache = new Map();
function setCache(key, value, ttl) {
cache.set(key, {
value,
expires: Date.now() + ttl
});
}
function getCache(key) {
const item = cache.get(key);
if (!item) {
return null;
}
if (Date.now() > item.expires) {
cache.delete(key);
return null;
}
return item.value;
}
Использование:
setCache("buffer", result, 60000);
Значение будет действительно одну минуту.
Для долговременного хранения результатов возможно использование браузерного хранилища.
const area = turf.area(feature);
localStorage.setItem(
"feature-area",
JSON.stringify(area)
);
Получение:
const area = JSON.parse(
localStorage.getItem("feature-area")
);
Преимущества:
Недостатки:
Если объём кэшируемых результатов измеряется десятками мегабайт, предпочтительнее использовать IndexedDB.
Пример сохранения:
const request = indexedDB.open(
"geo-cache",
1
);
В IndexedDB можно хранить:
Такой подход часто применяется в офлайн-картах.
Самая сложная задача кэширования — определение момента, когда данные становятся неактуальными.
Распространённые стратегии:
cache.delete(featureId);
cache.clear();
if (Date.now() > expires) {
cache.delete(key);
}
const key = `${version}:${id}`;
При изменении версии автоматически формируется новый набор записей.
Не стоит кэшировать элементарные вычисления:
turf.getCoord(point);
Накладные расходы могут оказаться выше выигрыша.
cache.set(key, value);
Без удаления старых записей память будет постоянно расти.
JSON.stringify(featureCollection);
Для больших наборов данных операция может занимать значительное время.
В таких случаях лучше использовать:
feature.properties.id
или другой компактный идентификатор.
Если объект после сохранения меняется:
cache.set(id, feature);
кэш может содержать устаревшее состояние данных.
Для критически важных сценариев используются копии:
cache.set(
id,
structuredClone(feature)
);
Для крупных приложений обычно применяется многоуровневая схема:
Такой подход позволяет существенно сократить количество повторных вызовов функций Turf.js, уменьшить нагрузку на процессор и обеспечить высокую скорость работы геоинформационных приложений даже при обработке больших объёмов пространственных данных.