CesiumJS не предоставляет встроенного high-level heatmap-слоя, поэтому тепловые карты в нём реализуются через комбинацию геометрий, шейдеров, текстурных оверлеев и постобработки. Выбор подхода зависит от плотности данных, требований к производительности и уровня детализации визуализации.
Основой тепловой карты выступает набор точек, каждая из которых содержит:
Типичная структура данных:
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 в 2D canvas с последующей проекцией на globe как текстуры.
Используется промежуточный 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 используется как текстура для 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);
Ключевое ограничение этого подхода — статичность и зависимость от масштаба.
Другой подход — визуализация точек с радиусом влияния через билборды или круговые примитивы.
Каждая точка представляется полупрозрачным кругом:
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;
Недостаток метода — отсутствие непрерывного градиента плотности.
Наиболее производительный и гибкий способ — использование пользовательских шейдеров.
Каждая точка передаётся в виде 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 массивов.
viewer.scene.postProcessStages.add(
new Cesium.PostProcessStage({
fragmentShader: fs
})
);
Этот метод позволяет строить динамические heatmap в реальном времени.
Для географически точной визуализации применяется
GroundPrimitive с кастомной геометрией.
Создаётся сетка, где каждая вершина соответствует точке влияния:
const geometry = new Cesium.RectangleGeometry({
rectangle: Cesium.Rectangle.fromDegrees(70, 50, 75, 53),
vertexFormat: Cesium.VertexFormat.ALL
});
const material = new Cesium.Material({
fabric: {
type: "Heatmap",
uniforms: {
intensity: 1.0
}
}
});
Material appearance может быть расширен через MaterialProperty.
Heatmap часто строится на основе KDE (Kernel Density Estimation).
Формула:
f(x)=_{i=1}^{n} K()
Где:
Gaussian kernel:
K(r)=e^{-}
Эта модель позволяет получить непрерывное распределение плотности вместо дискретных пятен.
Для screen-space heatmap необходимо преобразование:
const scene = viewer.scene;
const coord = Cesium.SceneTransforms.wgs84ToWindowCoordinates(scene, cartesian);
Далее координаты используются для canvas-рендеринга.
Проблема возникает при:
При изменении данных или камеры heatmap пересчитывается:
viewer.scene.preRender.addEventListener(() => {
updateHeatmap();
});
Оптимизация включает:
Ключевые методы оптимизации:
Большие радиусы экспоненциально увеличивают стоимость вычислений.
Для масштабируемости применяется LOD:
При зуме переключается слой данных.
Heatmap визуально кодируется через градиенты:
Применение:
function getColor(value) {
return Cesium.Color.fromHsl(
(1.0 - value) * 0.6,
1.0,
0.5
);
}
При глобальной визуализации возникают:
Решения:
Практически всегда используется комбинация:
Такой гибрид обеспечивает баланс между точностью, производительностью и гибкостью визуализации.