CPUGridLayer

CPU-агрегирующий слой строится вокруг идеи предварительной обработки данных на центральном процессоре с последующей отрисовкой уже агрегированных ячеек. В случае данного слоя исходный набор точек не передаётся напрямую в рендеринг как независимые объекты. Вместо этого пространство разбивается на регулярную сетку, после чего каждая точка попадает в соответствующую ячейку, где происходит накопление метрик: количества объектов, суммы значений, минимумы, максимумы или пользовательские показатели.

Ключевая особенность подхода заключается в том, что вычислительная нагрузка смещается в JavaScript-слой, а WebGL используется преимущественно для визуализации уже готового результата. Это делает поведение предсказуемым и упрощает реализацию сложной агрегационной логики.

Разбиение пространства на сетку

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

Каждая точка данных проецируется в координаты сетки:

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

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

Агрегационные функции

Слой поддерживает расширяемую систему агрегирования. Внутри каждой ячейки могут вычисляться:

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

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

Типичная структура аккумулятора:

{
  count: 0,
  sum: 0,
  min: Infinity,
  max: -Infinity
}

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

function accumulate(cell, point) {
  cell.count += 1;
  cell.sum += point.value;
  cell.min = Math.min(cell.min, point.value);
  cell.max = Math.max(cell.max, point.value);
}

Преобразование агрегированных данных в визуальные элементы

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

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

Часто используется нормализация:

  • значение приводится к диапазону 0–1;
  • затем применяется цветовая шкала;
  • либо высотная шкала для 3D-режима.

Пример преобразования:

const normalized = (cell.sum - min) / (max - min);
const color = getColorFromGradient(normalized);

Структура входных данных

Данные представляют собой массив объектов с координатами и дополнительными атрибутами. Минимальная форма:

[
  { position: [lng, lat], value: 10 },
  { position: [lng, lat], value: 5 }
]

Координаты обязательно задаются в виде долгота–широта, так как слой опирается на географическую проекцию.

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

Управление размером ячейки и уровнем детализации

Размер сетки напрямую влияет на характер визуализации. Малые ячейки дают высокую детализацию, но увеличивают количество вычисляемых элементов. Большие ячейки уменьшают нагрузку, но сглаживают распределение.

Типичная зависимость:

  • уменьшение размера ячейки → рост числа кластеров;
  • увеличение размера ячейки → рост плотности внутри каждой ячейки.

Выбор параметра обычно зависит от масштаба карты и плотности данных.

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

Оптимизация производительности

Основная нагрузка возникает при пересчёте сетки. Оптимизация достигается несколькими методами:

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

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

Поведение при изменении входных данных

При обновлении набора данных выполняется полная переработка сетки. Алгоритм повторно:

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

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

Визуальное кодирование значений

Отображение агрегированных значений может происходить через несколько каналов:

  • цвет (основной способ);
  • прозрачность;
  • высота в 3D-режиме;
  • комбинированные градиенты.

Часто применяется логарифмическая шкала для компенсации сильного разброса значений плотности.

const scaled = Math.log(cell.count + 1);

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

Связь с экранным пространством

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

Буферы вершин содержат:

  • координаты углов;
  • цветовые атрибуты;
  • дополнительные параметры, например интенсивность.

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

Отличие от точечных слоёв

В отличие от визуализации каждого объекта отдельно, сеточный подход уменьшает количество примитивов с O(n) до O(k), где k — число ячеек.

Это приводит к следующим эффектам:

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

Использование пользовательских атрибутов

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

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

Пример раздельного накопления категорий:

if (point.type === 'A') cell.aCount += 1;
if (point.type === 'B') cell.bCount += 1;

Ограничения подхода

Сеточная агрегация вводит ряд ограничений:

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

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

Поведение при масштабировании карты

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

В других вариантах сетка привязана к географическим координатам, и при зуме изменяется плотность точек внутри ячеек, что даёт более «картографическое» поведение.

Внутренняя структура данных слоя

Внутреннее представление обычно включает:

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

Хэширование ячеек выполняется через комбинацию индексов строки и столбца, что обеспечивает быстрый доступ без сложных пространственных структур.

const key = row * numCols + col;

Роль CPU-агрегации в архитектуре визуализации

Смещение агрегации на CPU позволяет:

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

При этом основным ограничением остаётся пропускная способность JavaScript-цикла при очень больших объёмах данных, где GPU-агрегация или гибридные методы могут быть более эффективными.