Оптимизация памяти

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

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

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

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

Особенности хранения данных в GeoJSON

Практически все функции Turf.js работают с объектами GeoJSON.

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

const point = {
  type: "Feature",
  properties: {
    name: "Station A"
  },
  geometry: {
    type: "Point",
    coordinates: [71.4304, 51.1282]
  }
};

Даже такая простая структура содержит несколько вложенных объектов.

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

  • строки ключей;
  • свойства объектов;
  • внутренние структуры JavaScript;
  • ссылки между объектами.

Например:

const points = [];

for (let i = 0; i < 100000; i++) {
  points.push(
    turf.point([70 + Math.random(), 50 + Math.random()])
  );
}

Создание ста тысяч объектов Feature приводит к значительным затратам памяти.


Минимизация свойств объектов

Свойства GeoJSON часто занимают больше памяти, чем геометрия.

Неоптимальный вариант:

const point = turf.point(
  [71.43, 51.12],
  {
    name: "Object",
    description: "Long text...",
    owner: "Department",
    createdAt: "2025-01-01",
    updatedAt: "2025-01-02",
    status: "active"
  }
);

Если свойства не участвуют в вычислениях, их лучше удалить:

const point = turf.point([71.43, 51.12]);

Или оставить только необходимые данные:

const point = turf.point(
  [71.43, 51.12],
  {
    id: 15
  }
);

При обработке сотен тысяч объектов экономия становится существенной.


Избежание ненужного копирования FeatureCollection

Частая ошибка — создание копий коллекций перед каждой операцией.

Неэффективный код:

const copied = JSON.parse(
  JSON.stringify(collection)
);

const result = turf.buffer(copied, 5);

В этом случае весь объект полностью дублируется в памяти.

Лучше использовать исходные данные:

const result = turf.buffer(collection, 5);

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


Контроль промежуточных результатов

Многие функции Turf.js возвращают новые объекты вместо изменения существующих.

Например:

const buffered = turf.buffer(lines, 1);
const dissolved = turf.dissolve(buffered);
const simplified = turf.simplify(dissolved);

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

Менее эффективный вариант:

const step1 = turf.buffer(data, 1);
const step2 = turf.dissolve(step1);
const step3 = turf.simplify(step2);

store.push(step1);
store.push(step2);
store.push(step3);

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

Предпочтительный подход:

let result = turf.buffer(data, 1);

result = turf.dissolve(result);

result = turf.simplify(result);

В этом случае старые ссылки могут быть удалены сборщиком мусора JavaScript.


Освобождение ссылок

Сборщик мусора удаляет объект только тогда, когда на него больше нет ссылок.

Пример:

let polygons = turf.buffer(points, 5);

// работа с данными

polygons = null;

После удаления ссылки объект становится кандидатом на очистку памяти.

То же относится к массивам:

results.length = 0;

или

results = null;

Подход особенно важен в серверных приложениях на Node.js, которые работают длительное время без перезапуска.


Потоковая обработка больших наборов данных

Не всегда необходимо загружать весь набор данных одновременно.

Плохой вариант:

const allFeatures = loadMillionFeatures();

const result = turf.featureCollection(
  allFeatures.map(processFeature)
);

В памяти одновременно находятся:

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

Лучше использовать пакетную обработку:

const batchSize = 1000;

for (let i = 0; i < features.length; i += batchSize) {
  const batch = features.slice(i, i + batchSize);

  processBatch(batch);
}

Такой подход значительно снижает пиковое потребление памяти.


Упрощение геометрии

Сложные полигоны способны занимать огромный объём памяти.

Например, административная граница может содержать десятки тысяч вершин.

Количество координат напрямую влияет на:

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

Для уменьшения объёма данных используется функция simplify.

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

После упрощения:

  • уменьшается число вершин;
  • сокращается объём памяти;
  • ускоряются операции анализа.

Выбор оптимального значения tolerance

Слишком маленькое значение почти не уменьшает размер данных.

turf.simplify(polygon, {
  tolerance: 0.000001
});

Слишком большое значение может привести к потере деталей.

turf.simplify(polygon, {
  tolerance: 0.1
});

На практике значение подбирается экспериментально в зависимости от масштаба карты и требований к точности.


Исключение дублирующихся геометрий

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

Например:

const station = turf.point([71.43, 51.12]);

const groupA = [station];
const groupB = [station];
const groupC = [station];

Такой вариант использует одну геометрию и несколько ссылок.

Менее эффективно:

const groupA = [
  turf.point([71.43, 51.12])
];

const groupB = [
  turf.point([71.43, 51.12])
];

const groupC = [
  turf.point([71.43, 51.12])
];

Здесь создаются три независимых объекта.


Оптимизация работы с координатами

Иногда для вычислений не требуется полный объект GeoJSON.

Неэффективно:

const points = [];

for (let i = 0; i < 100000; i++) {
  points.push(
    turf.point([70 + i, 50 + i])
  );
}

Если необходимы только координаты:

const coordinates = [];

for (let i = 0; i < 100000; i++) {
  coordinates.push([70 + i, 50 + i]);
}

Создание Feature лучше отложить до момента непосредственного вызова функций Turf.js.


Сокращение объёма FeatureCollection

FeatureCollection имеет дополнительную структуру:

{
  type: "FeatureCollection",
  features: [...]
}

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

Например:

const features = collection.features;

Это избавляет от создания лишних обёрток.


Использование bbox вместо полной геометрии

Во многих сценариях не требуется хранить полный полигон.

Если задача состоит только в предварительной фильтрации объектов, достаточно ограничивающего прямоугольника.

Получение границ:

const bbox = turf.bbox(polygon);

Результат:

[
  minX,
  minY,
  maxX,
  maxY
]

Массив из четырёх чисел занимает значительно меньше памяти, чем сложный полигон с тысячами координат.


Ограничение числа одновременно создаваемых буферов

Операция буферизации часто создаёт очень крупные полигоны.

Пример:

const buffers = points.features.map(feature =>
  turf.buffer(feature, 10)
);

При большом количестве объектов память может быстро закончиться.

Более рациональный подход:

for (const feature of points.features) {
  const buffer = turf.buffer(feature, 10);

  save(buffer);
}

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


Избежание глубоких цепочек преобразований

Иногда встречается код вида:

const result =
  turf.simplify(
    turf.dissolve(
      turf.buffer(
        turf.union(
          collection
        ),
        5
      )
    )
  );

Подобные конструкции затрудняют контроль памяти и отладку.

Лучше разбивать вычисления на этапы:

let result = turf.union(collection);

result = turf.buffer(result, 5);

result = turf.dissolve(result);

result = turf.simplify(result);

Так проще контролировать жизненный цикл объектов.


Работа с сериализацией JSON

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

const json = JSON.stringify(collection);

Во время выполнения одновременно существуют:

  1. объект GeoJSON;
  2. строковое представление.

Для очень больших наборов данных это может привести к резкому скачку потребления памяти.

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

writeStream.write(
  JSON.stringify(feature)
);

Использование генераторов для последовательной обработки

Генераторы позволяют не хранить весь набор данных в памяти.

Пример:

function* featureGenerator(features) {
  for (const feature of features) {
    yield feature;
  }
}

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

for (const feature of featureGenerator(data)) {
  processFeature(feature);
}

Подход особенно полезен при обработке больших геопространственных архивов.


Контроль потребления памяти в Node.js

Для анализа использования памяти можно применять встроенные средства платформы.

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

console.log(
  process.memoryUsage()
);

Результат содержит:

  • rss;
  • heapTotal;
  • heapUsed;
  • external;
  • arrayBuffers.

Контроль показателей помогает выявлять участки кода, вызывающие утечки памяти.


Типичные причины утечек памяти

Хранение всех результатов вычислений

const history = [];

while (true) {
  history.push(
    turf.buffer(feature, 1)
  );
}

Массив будет постоянно расти.


Глобальные коллекции

global.cache.push(result);

Если очистка не предусмотрена, память никогда не освободится.


Кэширование без ограничений

cache[key] = result;

Необходимо использовать механизмы удаления устаревших данных:

  • TTL-кэш;
  • LRU-кэш;
  • периодическую очистку.

Практические рекомендации

Для браузера

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

Для Node.js

  • обрабатывать данные потоками;
  • контролировать heap usage;
  • очищать ссылки на промежуточные результаты;
  • ограничивать размер кэша;
  • избегать полного копирования FeatureCollection.

Для больших геоданных

  • использовать bbox вместо полной геометрии там, где это возможно;
  • выполнять предварительное упрощение геометрий;
  • разбивать вычисления на этапы;
  • не создавать лишние объекты GeoJSON;
  • хранить только необходимые свойства и координаты.

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