Динамические визуализации в Mapbox GL JS строятся на принципе реактивного обновления источников данных (sources), слоёв (layers) и выражений (expressions), которые пересчитываются в рендеринг-пайплайне WebGL в реальном времени. В отличие от статических карт, где геометрия и стили фиксируются при инициализации, динамические карты предполагают непрерывное изменение состояния данных без полного перерисовывания сцены.
Ключевая особенность архитектуры заключается в разделении:
Это позволяет обновлять только затронутые части сцены, минимизируя нагрузку на GPU.
Наиболее распространённый механизм динамики — обновление
GeoJSON-источника через метод setData. Он позволяет
изменять геометрию или свойства объектов без пересоздания слоя.
map.addSource('vehicles', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
map.addLayer({
id: 'vehicles-layer',
type: 'circle',
source: 'vehicles',
paint: {
'circle-radius': 6,
'circle-color': '#ff3b30'
}
});
Дальнейшие обновления выполняются точечно:
function updateVehicles(newData) {
const source = map.getSource('vehicles');
source.setData({
type: 'FeatureCollection',
features: newData
});
}
Такой подход используется в задачах:
Для создания плавных визуальных эффектов используется синхронизация с циклом рендеринга браузера:
function animate(timestamp) {
const progress = (timestamp % 2000) / 2000;
const pulseRadius = 5 + Math.sin(progress * Math.PI * 2) * 3;
map.setPaintProperty('vehicles-layer', 'circle-radius', pulseRadius);
requestAnimationFrame(animate);
}
requestAnimationFrame(animate);
Такой метод применим для:
Однако постоянное обновление стилей требует осторожности, так как каждое изменение инициирует перерасчёт слоя.
Внутренний язык выражений позволяет переносить вычисления в GPU-пайплайн, избегая JavaScript-циклов.
Пример зависимости цвета от свойства скорости:
paint: {
'circle-color': [
'interpolate',
['linear'],
['get', 'speed'],
0, '#2ecc71',
50, '#f1c40f',
100, '#e74c3c'
]
}
Преимущество заключается в том, что обновление данных не требует изменения стиля — достаточно изменить свойства feature.
Mapbox GL JS поддерживает transition-параметры для анимации изменений стиля:
map.setPaintProperty('vehicles-layer', 'circle-radius', 10, {
duration: 800
});
Либо глобально:
paint: {
'circle-radius': 6,
'circle-radius-transition': {
duration: 500,
delay: 0
}
}
Это используется для:
При работе с потоковыми данными часто требуется изменять координаты объектов:
const features = vehicles.map(v => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [v.lon, v.lat]
},
properties: {
speed: v.speed
}
}));
map.getSource('vehicles').setData({
type: 'FeatureCollection',
features
});
Такой подход характерен для систем мониторинга в реальном времени, где данные поступают с высокой частотой.
Фильтрация слоёв позволяет управлять видимостью объектов без изменения источника данных:
map.setFilter('vehicles-layer', [
'>=',
['get', 'speed'],
30
]);
Фильтры применяются на уровне рендеринга и эффективно исключают ненужные объекты из сцены.
Для больших наборов точек используется кластеризация:
map.addSource('points', {
type: 'geojson',
data: data,
cluster: true,
clusterMaxZoom: 14,
clusterRadius: 50
});
Отображение кластеров меняется в зависимости от масштаба:
map.addLayer({
id: 'clusters',
type: 'circle',
source: 'points',
filter: ['has', 'point_count'],
paint: {
'circle-radius': [
'step',
['get', 'point_count'],
10,
100, 20,
1000, 30
]
}
});
Кластеризация снижает нагрузку и обеспечивает стабильную визуализацию при тысячах объектов.
Динамические карты часто подключаются к потокам данных:
const socket = new WebSocket('wss://example.com/stream');
socket.onmess age = (event) => {
const data = JSON.parse(event.data);
map.getSource('vehicles').setData({
type: 'FeatureCollection',
features: data.features
});
};
Такая архитектура применяется в:
Для временных визуализаций используется интерполяция значений:
paint: {
'circle-opacity': [
'interpolate',
['linear'],
['get', 'timestamp'],
1700000000, 0.2,
1700003600, 1
]
}
Это позволяет визуализировать:
Heatmap-слои автоматически перерасчитываются при изменении данных:
map.addLayer({
id: 'heatmap',
type: 'heatmap',
source: 'points',
paint: {
'heatmap-intensity': 1,
'heatmap-radius': 20,
'heatmap-color': [
'interpolate',
['linear'],
['heatmap-density'],
0, 'rgba(0,0,255,0)',
0.5, 'rgba(0,255,0,0.5)',
1, 'rgba(255,0,0,1)'
]
}
});
Такие слои реагируют на масштаб и плотность данных без ручного пересчёта.
Динамические визуализации часто требуют двусторонней синхронизации:
map.on('move', () => {
const center = map.getCenter();
updateExternalState(center);
});
Это позволяет:
При интенсивных обновлениях критично соблюдать баланс между частотой обновления и нагрузкой на рендер:
setDatarequestAnimationFrame вместо
таймеровMapbox проектировала рендеринг таким образом, чтобы обновления данных проходили через оптимизированный WebGL-пайплайн, где пересчитываются только изменённые буферы, а не вся сцена целиком.
Сложные динамические визуализации часто используют несколько источников одновременно:
map.addSource('traffic', { type: 'vector', url: 'mapbox://traffic' });
map.addSource('devices', { type: 'geojson', data: geojsonData });
map.addSource('weather', { type: 'raster', url: 'mapbox://weather' });
Комбинация слоёв позволяет создавать:
Mapbox GL JS предоставляет события для отслеживания изменений сцены:
map.on('data', (e) => {
if (e.sourceId === 'vehicles') {
console.log('Данные обновлены');
}
});
Эта модель используется для построения реактивных интерфейсов, где карта является источником состояния, а не просто визуализацией.