Интерактивные картографические приложения редко работают только со статическими наборами данных. Координаты транспорта изменяются каждую секунду, показания датчиков поступают непрерывным потоком, маршруты перестраиваются в режиме реального времени, а результаты поиска обновляются после каждого пользовательского действия.
В MapLibre GL JS динамическое обновление данных реализуется через изменение источников данных (sources), обновление геометрии объектов, изменение свойств объектов и обновление параметров визуализации слоёв без полной перезагрузки карты.
Основным механизмом является изменение содержимого источника GeoJSON
через метод setData(), а также работа с состоянием объектов
через feature-state.
Отображение данных в MapLibre GL JS строится по следующей схеме:
Источник данных (Source)
↓
Слой (Layer)
↓
Визуализация на карте
Источник содержит географические данные:
map.addSource('vehicles', {
type: 'geojson',
data: {
type: 'FeatureCollection',
features: []
}
});
Слой использует этот источник:
map.addLayer({
id: 'vehicles-layer',
type: 'circle',
source: 'vehicles',
paint: {
'circle-radius': 8,
'circle-color': '#ff0000'
}
});
При обновлении данных источник остаётся тем же самым, а его содержимое заменяется.
Главный инструмент динамического обновления GeoJSON-источников —
метод setData().
Получение источника:
const source = map.getSource('vehicles');
Обновление данных:
source.setData(newGeoJson);
Полный пример:
const geojson = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [37.6176, 55.7558]
}
}
]
};
map.getSource('vehicles').setData(geojson);
После вызова метода карта автоматически выполняет:
Дополнительных действий не требуется.
Наиболее распространённый сценарий — получение новых данных через фиксированные интервалы времени.
Пример обновления каждые пять секунд:
setInterval(async () => {
const response = await fetch('/api/vehicles');
const data = await response.json();
map.getSource('vehicles').setData(data);
}, 5000);
Схема работы:
Сервер
↓
fetch()
↓
JSON
↓
setData()
↓
Обновление карты
Такой подход широко используется для:
Нередко данные обновляются не по таймеру, а в ответ на действия пользователя.
Пример поиска объектов:
async function search(query) {
const response = await fetch(
`/api/search?q=${encodeURIComponent(query)}`
);
const data = await response.json();
map.getSource('search-results').setData(data);
}
Обработчик поля ввода:
input.addEventListener('input', event => {
search(event.target.value);
});
Каждый новый запрос заменяет содержимое источника.
Метод setData() всегда заменяет весь
GeoJSON-документ.
Если требуется изменить один объект, обычно выполняются следующие действия:
const sourceData = map
.getSource('vehicles')
._data;
Поиск объекта:
const vehicle = sourceData.features.find(
item => item.properties.id === 25
);
Изменение координат:
vehicle.geometry.coordinates = [
37.61,
55.75
];
Обновление:
map
.getSource('vehicles')
.setData(sourceData);
Несмотря на изменение одного объекта, источник будет пересчитан целиком.
Часто необходимо отображать движение объекта по карте.
Простейший пример:
let lng = 37.6176;
setInterval(() => {
lng += 0.001;
const data = {
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [lng, 55.7558]
}
}
]
};
map.getSource('vehicle').setData(data);
}, 100);
Объект будет плавно перемещаться вдоль оси долготы.
Для более плавного движения используется механизм браузера
requestAnimationFrame.
let position = 37.6176;
function animate() {
position += 0.00005;
map.getSource('vehicle').setData({
type: 'FeatureCollection',
features: [
{
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [
position,
55.7558
]
}
}
]
});
requestAnimationFrame(animate);
}
animate();
Преимущества:
Не всегда требуется обновлять геометрию объекта. Иногда необходимо изменить только его состояние.
Для таких задач используется механизм feature-state.
Источник:
map.addSource('cities', {
type: 'geojson',
data: citiesData
});
Слой:
map.addLayer({
id: 'cities-layer',
type: 'circle',
source: 'cities',
paint: {
'circle-color': [
'case',
['boolean', ['feature-state', 'selected'], false],
'#ff0000',
'#0080ff'
]
}
});
Изменение состояния:
map.setFeatureState(
{
source: 'cities',
id: 15
},
{
selected: true
}
);
После этого цвет объекта изменится без обновления GeoJSON.
Сброс состояния:
map.removeFeatureState({
source: 'cities',
id: 15
});
Удаление конкретного свойства:
map.removeFeatureState(
{
source: 'cities',
id: 15
},
'selected'
);
Использование feature-state значительно эффективнее
обновления всего источника.
Сравнение:
| Операция | Производительность |
|---|---|
| setData() | Ниже |
| feature-state | Выше |
Причины:
Особенно заметна разница на слоях с тысячами объектов.
Обновляться могут не только данные, но и параметры отображения.
Изменение цвета:
map.setPaintProperty(
'vehicles-layer',
'circle-color',
'#00ff00'
);
Изменение размера:
map.setPaintProperty(
'vehicles-layer',
'circle-radius',
12
);
Изменение прозрачности:
map.setPaintProperty(
'vehicles-layer',
'circle-opacity',
0.5
);
Все изменения отображаются немедленно.
Для свойств секции layout используется метод
setLayoutProperty().
Скрытие слоя:
map.setLayoutProperty(
'vehicles-layer',
'visibility',
'none'
);
Показ слоя:
map.setLayoutProperty(
'vehicles-layer',
'visibility',
'visible'
);
Изменение текста подписи:
map.setLayoutProperty(
'labels',
'text-field',
['get', 'name']
);
Для потоковых данных чаще используется WebSocket.
Подключение:
const socket = new WebSocket(
'wss://example.com/ws'
);
Получение сообщений:
socket.onmess age = event => {
const geojson =
JSON.parse(event.data);
map
.getSource('vehicles')
.setData(geojson);
};
Преимущества:
Такой подход применяется для:
При больших объёмах данных нежелательно постоянно передавать полный GeoJSON.
Вместо этого сервер может отправлять только изменения.
Пример сообщения:
{
"id": 25,
"lng": 37.62,
"lat": 55.76
}
Клиент обновляет существующий объект:
socket.onmess age = event => {
const update =
JSON.parse(event.data);
const feature =
geojson.features.find(
f => f.properties.id === update.id
);
feature.geometry.coordinates = [
update.lng,
update.lat
];
map
.getSource('vehicles')
.setData(geojson);
};
Объём сетевого трафика существенно уменьшается.
Если используется кластеризация:
map.addSource('earthquakes', {
type: 'geojson',
data: data,
cluster: true
});
После вызова:
source.setData(newData);
кластеры пересчитываются автоматически.
Дополнительная настройка не требуется.
Иногда данные загружаются только для текущей области просмотра.
Получение границ:
const bounds = map.getBounds();
Загрузка данных:
async function loadVisibleData() {
const bounds =
map.getBounds();
const response =
await fetch(
`/api/data?bbox=${
bounds.getWest()
},${
bounds.getSouth()
},${
bounds.getEast()
},${
bounds.getNorth()
}`
);
const data =
await response.json();
map
.getSource('objects')
.setData(data);
}
Обновление после перемещения карты:
map.on('moveend', loadVisibleData);
Преимущества:
MapLibre предоставляет события, позволяющие отслеживать обновление источников.
Событие начала загрузки:
map.on('dataloading', event => {
console.log(event);
});
Событие завершения:
map.on('data', event => {
console.log(event);
});
Проверка полной загрузки:
if (map.isSourceLoaded('vehicles')) {
console.log('Источник загружен');
}
Подобные механизмы полезны для отображения индикаторов загрузки.
Чрезмерно частые вызовы setData() могут снижать
производительность.
Неудачный пример:
setInterval(updateData, 10);
Такой код создаёт сотни обновлений в секунду.
Более разумный вариант:
setInterval(updateData, 1000);
или
requestAnimationFrame(updateData);
Частота обновления должна соответствовать характеру данных:
| Тип данных | Частота |
|---|---|
| Погода | 1–10 минут |
| Датчики | 1–10 секунд |
| Транспорт | 1–5 секунд |
| Анимация | 60 FPS |
| Финансовые данные | по событию |
Наиболее важные практики повышения производительности:
Использование feature-state вместо setData(), когда изменяются только визуальные параметры объектов.
Загрузка данных по области видимости карты, а не всего набора данных.
Использование WebSocket вместо частого опроса сервера.
Минимизация размера GeoJSON, исключение ненужных свойств.
Снижение частоты обновлений, если данные не требуют мгновенного отображения.
Группировка изменений, когда несколько обновлений
объединяются в одну операцию setData().
Использование кластеризации для больших наборов точечных объектов.
Отказ от полной пересборки объектов GeoJSON, если возможно обновление существующих структур данных.
Грамотно реализованное динамическое обновление позволяет MapLibre GL JS эффективно отображать десятки тысяч объектов и обрабатывать потоки данных в реальном времени без заметного снижения производительности интерфейса.