Heatmap визуализация

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

Основой тепловой карты выступает набор точек, каждая из которых содержит:

  • координаты (долгота, широта, высота опционально)
  • интенсивность (weight / value)
  • радиус влияния (в пикселях или метрах)
  • цветовое распределение (градиент или функция)

Типичная структура данных:

const points = [
  { lon: 71.4304, lat: 51.1282, value: 0.8 },
  { lon: 71.4491, lat: 51.1605, value: 0.4 },
  { lon: 71.4700, lat: 51.1400, value: 1.0 }
];

Для дальнейшей обработки данные преобразуются в координаты сцены:

const cartesian = Cesium.Cartesian3.fromDegrees(lon, lat, height);

Базовый подход: экранный heatmap через canvas

Один из наиболее распространённых способов — генерация heatmap в 2D canvas с последующей проекцией на globe как текстуры.

Генерация тепловой карты в Canvas

Используется промежуточный offscreen canvas:

function createHeatmapCanvas(points) {
  const canvas = document.createElement("canvas");
  canvas.width = 1024;
  canvas.height = 1024;

  const ctx = canvas.getContext("2d");

  points.forEach(p => {
    const x = p.x;
    const y = p.y;

    const gradient = ctx.createRadialGradient(x, y, 0, x, y, 50);
    gradient.addColorStop(0, "rgba(255,0,0,0.8)");
    gradient.addColorStop(1, "rgba(255,0,0,0)");

    ctx.fillStyle = gradient;
    ctx.fillRect(x - 50, y - 50, 100, 100);
  });

  return canvas;
}

Привязка canvas к поверхности Земли

Canvas используется как текстура для GroundPrimitive или ImageryLayer.

const layer = new Cesium.ImageryLayer(
  new Cesium.SingleTileImageryProvider({
    url: canvas.toDataURL(),
    rectangle: Cesium.Rectangle.fromDegrees(70, 50, 75, 53)
  })
);

viewer.imageryLayers.add(layer);

Ключевое ограничение этого подхода — статичность и зависимость от масштаба.

Heatmap через Billboard clustering и влияние радиуса

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

Построение влияния точки

Каждая точка представляется полупрозрачным кругом:

viewer.entities.add({
  position: Cesium.Cartesian3.fromDegrees(lon, lat),
  ellipse: {
    semiMinorAxis: 5000.0,
    semiMajorAxis: 5000.0,
    material: Cesium.Color.RED.withAlpha(0.3)
  }
});

При большом количестве точек используется кластеризация:

const dataSource = new Cesium.CustomDataSource();

points.forEach(p => {
  dataSource.entities.add({
    position: Cesium.Cartesian3.fromDegrees(p.lon, p.lat),
    point: {
      pixelSize: 10,
      color: Cesium.Color.YELLOW.withAlpha(p.value)
    }
  });
});

viewer.dataSources.add(dataSource);

dataSource.clustering.enabled = true;
dataSource.clustering.pixelRange = 50;

Недостаток метода — отсутствие непрерывного градиента плотности.

GPU-heatmap через fragment shader

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

Идея

Каждая точка передаётся в виде uniform массива или texture buffer, а вычисление плотности происходит в fragment shader.

Пример структуры

const fs = `
uniform vec3 u_points[100];
uniform float u_values[100];

void main() {
  float intensity = 0.0;

  for (int i = 0; i < 100; i++) {
    float d = distance(gl_FragCoord.xy, u_points[i].xy);
    intensity += u_values[i] / (d * d);
  }

  gl_FragColor = vec4(vec3(intensity), 1.0);
}
`;

В реальных системах используется texture-based storage вместо uniform массивов.

Интеграция через PostProcessStage

viewer.scene.postProcessStages.add(
  new Cesium.PostProcessStage({
    fragmentShader: fs
  })
);

Этот метод позволяет строить динамические heatmap в реальном времени.

Heatmap через Ground Primitive

Для географически точной визуализации применяется GroundPrimitive с кастомной геометрией.

Подготовка геометрии

Создаётся сетка, где каждая вершина соответствует точке влияния:

const geometry = new Cesium.RectangleGeometry({
  rectangle: Cesium.Rectangle.fromDegrees(70, 50, 75, 53),
  vertexFormat: Cesium.VertexFormat.ALL
});

Применение материала heatmap

const material = new Cesium.Material({
  fabric: {
    type: "Heatmap",
    uniforms: {
      intensity: 1.0
    }
  }
});

Material appearance может быть расширен через MaterialProperty.

Интерполяция плотности (kernel density estimation)

Heatmap часто строится на основе KDE (Kernel Density Estimation).

Формула:

f(x)=_{i=1}^{n} K()

Где:

    1. — ядровая функция (Gaussian, Epanechnikov)
    1. — bandwidth (радиус сглаживания)
  • (x_i) — точки данных

Gaussian kernel:

K(r)=e^{-}

Эта модель позволяет получить непрерывное распределение плотности вместо дискретных пятен.

Преобразование географических координат в экранные

Для screen-space heatmap необходимо преобразование:

const scene = viewer.scene;
const coord = Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, cartesian);

Далее координаты используются для canvas-рендеринга.

Проблема возникает при:

  • зуме камеры
  • перспективных искажениях
  • пересечении горизонта

Динамическое обновление heatmap

При изменении данных или камеры heatmap пересчитывается:

viewer.scene.preRender.addEventListener(() => {
  updateHeatmap();
});

Оптимизация включает:

  • spatial indexing (R-tree / grid)
  • downsampling точек
  • caching промежуточных текстур

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

Ключевые методы оптимизации:

Снижение числа точек

  • агрегация по тайлам
  • кластеризация
  • grid binning

GPU batching

  • использование texture atlas
  • instanced rendering

Ограничение радиуса влияния

Большие радиусы экспоненциально увеличивают стоимость вычислений.

Многоуровневая heatmap (LOD)

Для масштабируемости применяется LOD:

  • уровень 0: глобальная агрегация
  • уровень 1: региональная плотность
  • уровень 2: точечная детализация

При зуме переключается слой данных.

Цветовые схемы

Heatmap визуально кодируется через градиенты:

  • синий → зелёный → жёлтый → красный
  • чёрный → красный → белый (высокая интенсивность)

Применение:

function getColor(value) {
  return Cesium.Color.fromHsl(
    (1.0 - value) * 0.6,
    1.0,
    0.5
  );
}

Проблемы точности и искажений

При глобальной визуализации возникают:

  • искажение площадей на полюсах
  • неравномерная плотность пикселей
  • ошибка проекции при large-scale datasets

Решения:

  • tile-based rendering
  • equal-area projection
  • локальные heatmap слои

Смешанные подходы

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

  • GPU heatmap для динамики
  • canvas для статических слоёв
  • clustering для упрощения
  • imagery layer для глобального покрытия

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