Кэширование результатов

Геопространственные вычисления нередко связаны с обработкой больших объёмов данных. Операции поиска ближайших объектов, проверки пересечений, буферизации, кластеризации или расчёта расстояний могут выполняться тысячи раз подряд. При повторном выполнении одинаковых вычислений происходит бессмысленная трата процессорного времени.

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

Для проектов на основе Turf.js кэширование особенно актуально в следующих случаях:

  • многократная обработка одних и тех же GeoJSON-объектов;
  • интерактивные карты с постоянным изменением масштаба;
  • серверные API геоаналитики;
  • приложения мониторинга транспорта;
  • системы логистики;
  • геоинформационные панели (dashboard).

Когда кэширование действительно необходимо

Не каждая операция требует сохранения результатов.

Например, если вычисление выполняется единственный раз:

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);
}

Кэширование полезно тогда, когда одновременно выполняются два условия:

  1. Результат операции не меняется.
  2. Вычисление повторяется много раз.

Простое кэширование через Map

Наиболее распространённый вариант хранения вычисленных данных в 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().


Кэширование результатов buffer()

Операция буферизации считается одной из самых ресурсоёмких в 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.


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

Если объект 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;
}

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

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

Недостаток:

  • ключом может быть только объект.

Кэширование операций booleanContains()

Проверки пространственных отношений часто используются при фильтрации данных.

Пример:

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-кэша

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)

Иногда данные должны автоматически обновляться.

Для этого используется 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);

Значение будет действительно одну минуту.


Кэширование в браузере через localStorage

Для долговременного хранения результатов возможно использование браузерного хранилища.

const area = turf.area(feature);

localStorage.setItem(
    "feature-area",
    JSON.stringify(area)
);

Получение:

const area = JSON.parse(
    localStorage.getItem("feature-area")
);

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

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

Недостатки:

  • ограниченный объём памяти;
  • необходимость сериализации.

IndexedDB для крупных геоданных

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

Пример сохранения:

const request = indexedDB.open(
    "geo-cache",
    1
);

В IndexedDB можно хранить:

  • GeoJSON FeatureCollection;
  • результаты кластеризации;
  • буферы;
  • полигоны;
  • маршруты;
  • вычисленные аналитические показатели.

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


Инвалидация кэша

Самая сложная задача кэширования — определение момента, когда данные становятся неактуальными.

Распространённые стратегии:

По изменению объекта

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

Для крупных приложений обычно применяется многоуровневая схема:

  1. WeakMap для объектов в оперативной памяти.
  2. LRU-кэш для часто используемых вычислений.
  3. TTL-кэш для временных данных.
  4. localStorage для небольших постоянных результатов.
  5. IndexedDB для крупных GeoJSON-наборов.
  6. Серверный кэш для тяжёлых пространственных операций.

Такой подход позволяет существенно сократить количество повторных вызовов функций Turf.js, уменьшить нагрузку на процессор и обеспечить высокую скорость работы геоинформационных приложений даже при обработке больших объёмов пространственных данных.