Визуализация плотности данных в браузерных картах в Google Maps
JavaScript API реализуется через слой тепловой карты, основанный на
классе HeatmapLayer. Этот механизм предназначен для
отображения концентрации точек в пространстве с использованием цветовой
градации и размытия, что позволяет выявлять зоны повышенной активности.
При переходе к динамическим данным появляется необходимость анимации
тепловой карты — изменения её состояния во времени, в зависимости от
поступающих или фильтруемых данных.
Тепловая карта строится на наборе точек, представленных объектами
LatLng или взвешенными значениями:
Базовая структура данных:
const heatmapData = [
new google.maps.LatLng(55.751244, 37.618423),
new google.maps.LatLng(55.761244, 37.628423),
];
Взвешенные точки:
const heatmapData = [
{ location: new google.maps.LatLng(55.751244, 37.618423), weight: 2 },
{ location: new google.maps.LatLng(55.761244, 37.628423), weight: 5 }
];
Инициализация слоя:
const heatmap = new google.maps.visualization.HeatmapLayer({
data: heatmapData,
radius: 20,
opacity: 0.6,
dissipating: true
});
heatmap.setMap(map);
Анимация тепловой карты в рамках API не является встроенной функцией, но достигается через программное изменение данных слоя и его визуальных параметров.
Основные подходы:
Ключевой метод управления динамикой — setData():
heatmap.setData(new google.maps.MVCArray(updatedPoints));
При каждом вызове слой полностью пересчитывает визуализацию, что позволяет создавать эффект движения плотности.
Пример пошагового обновления:
let frameIndex = 0;
function animate() {
const frameData = datasets[frameIndex];
heatmap.setData(new google.maps.MVCArray(frameData));
frameIndex = (frameIndex + 1) % datasets.length;
setTimeout(animate, 200);
}
animate();
Такой подход формирует дискретную анимацию, основанную на последовательности состояний.
Для более гладких переходов используется интерполяция между наборами точек.
function interpolatePoints(a, b, t) {
return a.map((point, i) => {
const lat = point.lat() + (b[i].lat() - point.lat()) * t;
const lng = point.lng() + (b[i].lng() - point.lng()) * t;
return new google.maps.LatLng(lat, lng);
});
}
Цикл анимации:
let t = 0;
function animate() {
const interpolated = interpolatePoints(frameA, frameB, t);
heatmap.setData(new google.maps.MVCArray(interpolated));
t += 0.02;
if (t >= 1) {
t = 0;
swapFrames();
}
requestAnimationFrame(animate);
}
animate();
Такой метод позволяет добиться непрерывного движения плотности, а не резкой смены кадров.
Помимо координат, изменяется интенсивность влияния каждой точки.
const animatedData = baseData.map(p => ({
location: p.location,
weight: Math.sin(Date.now() * 0.001 + p.phase) * 5 + 5
}));
Обновление слоя:
heatmap.setData(new google.maps.MVCArray(animatedData));
Изменение весов позволяет создавать эффект пульсации и “дыхания” тепловой карты без изменения географии точек.
Анимационный эффект усиливается изменением параметров слоя:
heatmap.setOptions({
radius: currentRadius
});
Динамическое изменение:
currentRadius = 10 + Math.sin(Date.now() * 0.002) * 20;
heatmap.setOptions({
opacity: currentOpacity
});
currentOpacity = 0.4 + Math.abs(Math.sin(Date.now() * 0.001));
Изменение цветовой шкалы позволяет визуализировать “перетекание” интенсивности:
heatmap.setOptions({
gradient: [
"rgba(0, 0, 255, 0)",
"rgba(0, 255, 255, 1)",
"rgba(0, 255, 0, 1)",
"rgba(255, 255, 0, 1)",
"rgba(255, 0, 0, 1)"
]
});
Переключение градиентов во времени:
heatmap.setOptions({
gradient: gradients[index]
});
При работе с событийными данными используется модель временного окна.
function getWindowedData(allData, timestamp, windowSize) {
return allData.filter(point =>
point.time >= timestamp - windowSize &&
point.time <= timestamp
);
}
Анимация по времени:
let time = startTime;
function tick() {
const currentData = getWindowedData(allPoints, time, 60000);
heatmap.setData(new google.maps.MVCArray(currentData));
time += 10000;
setTimeout(tick, 300);
}
tick();
Такой подход применяется для отображения миграции активности, дорожного трафика или пользовательских событий.
Частое обновление тепловой карты приводит к нагрузке на рендеринг. Для стабилизации используются:
setTimeoutMVCArrayОптимизированный цикл:
const dataArray = new google.maps.MVCArray();
heatmap.setData(dataArray);
function update(frame) {
dataArray.clear();
frame.forEach(p => dataArray.push(p));
}
Такой подход снижает количество пересозданий структуры данных.
Наиболее выразительные эффекты достигаются при одновременном изменении нескольких параметров:
Пример синхронного обновления:
function animateFrame(frame) {
heatmap.setOptions({
radius: frame.radius,
opacity: frame.opacity,
gradient: frame.gradient
});
heatmap.setData(new google.maps.MVCArray(frame.points));
}
При изменении масштаба (zoom) тепловая карта может
требовать адаптации плотности отображения.
google.maps.event.addListener(map, "zoom_changed", () => {
const zoom = map.getZoom();
heatmap.setOptions({
radius: Math.max(5, zoom * 2)
});
});
Зависимость параметров от масштаба позволяет сохранить читаемость структуры распределения.
Для сложных сценариев анимации используется предварительная генерация кадров:
const frames = precomputeFrames(rawData);
function precomputeFrames(data) {
return data.map(step => ({
points: transform(step),
radius: computeRadius(step),
opacity: computeOpacity(step)
}));
}
Это снижает нагрузку в момент выполнения и переносит вычисления в этап подготовки данных.
При подключении потоковых данных (WebSocket, polling API) анимация становится непрерывной:
socket.onmess age = (event) => {
const newPoints = JSON.parse(event.data);
heatmap.setData(new google.maps.MVCArray(newPoints));
};
В таких системах визуализация представляет собой постоянно обновляемую поверхность плотности, отражающую текущее состояние системы.