Современные веб-картографические приложения редко ограничиваются отображением одного источника данных. Карта может одновременно загружать геометрию объектов, стили, изображения, статистику, информацию о пробках, погодных условиях, пользовательских данных и результаты пространственных запросов. При большом количестве отдельных HTTP-запросов возрастает нагрузка на сеть, увеличивается задержка отображения данных и ухудшается пользовательский опыт.
Пакетные запросы позволяют объединять несколько операций в одну сетевую транзакцию, снижая количество обращений к серверу и повышая эффективность загрузки данных. Хотя Mapbox GL JS не содержит встроенного механизма Batch API, библиотека активно используется совместно с серверными решениями и внешними API, поддерживающими пакетную обработку запросов.
При работе с интерактивными картами часто возникает ситуация, когда необходимо одновременно получить данные из нескольких источников.
Например:
Без пакетирования приложение может выполнять десятки запросов:
fetch('/api/object/1');
fetch('/api/object/2');
fetch('/api/object/3');
fetch('/api/object/4');
fetch('/api/object/5');
Подобный подход создает:
Вместо этого используется единый пакетный запрос:
fetch('/api/batch', {
method: 'POST',
body: JSON.stringify({
ids: [1, 2, 3, 4, 5]
})
});
Сервер возвращает данные сразу по всем объектам.
Типичная схема выглядит следующим образом:
Mapbox GL JS
│
▼
Batch API
│
┌─────┼─────┐
▼ ▼ ▼
DB Cache GIS
Карта отправляет один запрос.
Сервер:
Часто карта отображает несколько слоев, использующих разные наборы данных.
Вместо последовательной загрузки:
const roads = await fetch('/roads');
const buildings = await fetch('/buildings');
const parks = await fetch('/parks');
используется единый запрос:
const response = await fetch('/api/map-data');
const data = await response.json();
Ответ сервера:
{
"roads": {},
"buildings": {},
"parks": {}
}
После получения данные добавляются в карту:
map.addSource('roads', {
type: 'geojson',
data: data.roads
});
map.addSource('buildings', {
type: 'geojson',
data: data.buildings
});
map.addSource('parks', {
type: 'geojson',
data: data.parks
});
Одним из наиболее распространенных сценариев является получение данных только для видимой области.
Получение границ карты:
const bounds = map.getBounds();
Формирование пакетного запроса:
const response = await fetch('/api/batch-extent', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
bbox: [
bounds.getWest(),
bounds.getSouth(),
bounds.getEast(),
bounds.getNorth()
]
})
});
Ответ может содержать сразу несколько наборов объектов:
{
"restaurants": {},
"hotels": {},
"stations": {}
}
Обновление данных обычно происходит после завершения перемещения карты.
Событие:
map.on('moveend', loadData);
Функция загрузки:
async function loadData() {
const bounds = map.getBounds();
const response = await fetch('/api/batch-map-data', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
west: bounds.getWest(),
south: bounds.getSouth(),
east: bounds.getEast(),
north: bounds.getNorth()
})
});
const data = await response.json();
map.getSource('places')
.setData(data.places);
map.getSource('roads')
.setData(data.roads);
}
Такой подход особенно эффективен для больших картографических систем.
При отображении тысяч точек может потребоваться дополнительная информация для каждого кластера.
Получение видимых кластеров:
const features = map.queryRenderedFeatures({
layers: ['clusters']
});
Формирование списка идентификаторов:
const clusterIds = features.map(
feature => feature.properties.cluster_id
);
Отправка единого запроса:
const response = await fetch('/api/clusters/info', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
clusters: clusterIds
})
});
Ответ:
{
"101": {
"sales": 1000
},
"102": {
"sales": 2500
}
}
После выбора нескольких объектов пользовательского интерфейса может потребоваться дополнительная информация.
Сбор идентификаторов:
const selectedIds = features.map(
feature => feature.properties.id
);
Пакетный запрос:
const response = await fetch('/api/features/details', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
ids: selectedIds
})
});
Сервер возвращает данные одним ответом.
Многие карты используют кастомные маркеры.
Неэффективный вариант:
fetch('/icon1.png');
fetch('/icon2.png');
fetch('/icon3.png');
Пакетный вариант:
const response = await fetch('/api/icons');
Ответ:
{
"hospital": "...",
"school": "...",
"airport": "..."
}
После декодирования изображения могут быть зарегистрированы в карте:
map.addImage('hospital', imageData);
Даже если сервер не поддерживает Batch API, можно организовать клиентское пакетирование.
Пример:
const requests = [
fetch('/roads'),
fetch('/buildings'),
fetch('/parks')
];
const responses = await Promise.all(requests);
Получение данных:
const datasets = await Promise.all(
responses.map(item => item.json())
);
Подобный подход не уменьшает количество HTTP-запросов, однако позволяет выполнять их одновременно.
В крупных приложениях Mapbox GL JS часто работает вместе с GraphQL.
Пример запроса:
const query = `
{
roads {
id
geometry
}
buildings {
id
geometry
}
parks {
id
geometry
}
}
`;
Отправка:
const response = await fetch('/graphql', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({ query })
});
В результате один запрос возвращает сразу несколько наборов пространственных данных.
При использовании собственных серверов тайлов возможна агрегация нескольких тайлов в одном ответе.
Стандартная схема:
/z/x/y.pbf
/z/x/y.pbf
/z/x/y.pbf
Пакетная схема:
/api/tiles
Тело запроса:
{
"tiles": [
"10/523/344",
"10/524/344",
"10/525/344"
]
}
Сервер возвращает набор тайлов единой структурой.
Важно помнить, что ошибка одного элемента не должна приводить к потере всех данных.
Неудачный вариант:
{
"success": false
}
Предпочтительный вариант:
{
"roads": {
"success": true
},
"buildings": {
"success": false,
"error": "Database unavailable"
},
"parks": {
"success": true
}
}
Клиент может обработать каждую часть ответа независимо.
Пример:
if (data.buildings.success) {
updateBuildings(data.buildings.data);
}
Чрезмерно большие запросы способны ухудшить производительность.
Например:
{
ids: [1 ... 50000]
}
Лучше разбивать данные на части.
Функция разбиения массива:
function chunk(array, size) {
const result = [];
for (let i = 0; i < array.length; i += size) {
result.push(
array.slice(i, i + size)
);
}
return result;
}
Использование:
const batches = chunk(ids, 500);
Для повторно используемых данных эффективным решением становится кэширование.
Пример:
const cache = new Map();
Проверка:
if (cache.has(key)) {
return cache.get(key);
}
Сохранение:
cache.set(key, response);
В картографических приложениях это особенно полезно при повторном просмотре одних и тех же областей карты.
Большие объемы данных могут блокировать основной поток браузера.
Обработка ответа в Worker:
worker.postMessage(data);
Получение результата:
worker.onmess age = event => {
updateMap(event.data);
};
Такой подход снижает нагрузку на интерфейс и делает работу карты более плавной.
Наиболее эффективная стратегия включает несколько компонентов одновременно:
В высоконагруженных приложениях на базе Mapbox GL JS пакетные запросы являются одним из ключевых инструментов повышения производительности. Они уменьшают количество сетевых операций, сокращают время ожидания данных, упрощают взаимодействие клиента с сервером и позволяют эффективно масштабировать картографические системы с большими объемами пространственной информации.