При работе с геопространственными данными в веб-клиентах ключевая проблема заключается в балансе между объёмом данных, скоростью отрисовки и ограничениями памяти. Браузерный слой хранения становится критическим компонентом, особенно при использовании Mapbox GL JS, где источником данных часто выступает GeoJSON.
GeoJSON в чистом виде плохо масштабируется при увеличении объёма
объектов: полная перезагрузка источника через setData
приводит к перерасчёту всех фич на стороне рендерера WebGL. В таких
условиях IndexedDB используется как промежуточный слой долговременного
хранения и частичной выборки данных.
IndexedDB представляет собой асинхронное объектное хранилище с поддержкой индексов и транзакций. Для GeoJSON оптимальной считается модель, в которой данные разделяются на:
Feature)Базовая структура объекта:
{
id: "feature-123",
geometry: { type: "Polygon", coordinates: [...] },
properties: {
type: "building",
height: 42
},
bbox: [76.8, 43.2, 76.9, 43.3],
updatedAt: 1710000000000
}
Такой подход позволяет избегать хранения единого монолитного GeoJSON-документа.
Инициализация IndexedDB выполняется через
indexedDB.open, при этом версия базы определяет схему
хранения.
const request = indexedDB.open("geo_db", 1);
request.onupgradenee ded = (event) => {
const db = event.target.result;
const store = db.createObjectStore("features", {
keyPath: "id"
});
store.createIndex("bbox", "bbox", { unique: false });
store.createIndex("type", "properties.type", { unique: false });
store.createIndex("updatedAt", "updatedAt", { unique: false });
};
Ключевой момент — индексация не решает геопространственные запросы напрямую, но позволяет быстро фильтровать набор перед передачей в Mapbox GL JS.
IndexedDB не поддерживает spatial index в нативном виде, поэтому применяется псевдогеопространственное разбиение:
Каждый объект привязывается к тайлу:
tileKey = `${z}/${x}/${y}`;
И хранится как индекс:
store.createIndex("tile", "tileKey", { unique: false });
Использование geohash позволяет группировать объекты по префиксу строки:
u4pruyu4pruЧем короче префикс, тем крупнее область.
Запись выполняется через транзакции в режиме
readwrite:
function saveFeature(db, feature) {
const tx = db.transaction("features", "readwrite");
const store = tx.objectStore("features");
store.put(feature);
return tx.complete;
}
При массовой загрузке важно использовать батчи:
Mapbox GL JS использует GeoJSON Source, который обновляется через
setData.
Базовый поток:
function queryFeatures(db, bbox) {
return new Promise((resolve) => {
const tx = db.transaction("features", "readonly");
const store = tx.objectStore("features");
const index = store.index("bbox");
const results = [];
index.openCursor().onsucc ess = (event) => {
const cursor = event.target.result;
if (!cursor) {
resolve(results);
return;
}
const value = cursor.value;
if (intersects(value.bbox, bbox)) {
results.push(value);
}
cursor.continue();
};
});
}
Mapbox GL JS ожидает структуру:
map.addSource("geo", {
type: "geojson",
data: {
type: "FeatureCollection",
features: []
}
});
Обновление данных:
map.getSource("geo").setData(featureCollection);
Частая проблема — перерисовка слоя при каждом изменении viewport. Решается через:
function debounce(fn, delay) {
let t;
return (...args) => {
clearTimeout(t);
t = setTimeout(() => fn(...args), delay);
};
}
IndexedDB не имеет встроенного diff-механизма, поэтому применяется версия объекта:
{
id: "feature-123",
version: 5,
geometry: {...}
}
Логика обновления:
version > stored.version → overwriteИспользуется стратегия TTL:
store.createIndex("updatedAt", "updatedAt");
Очистка:
Для больших датасетов применяется разбиение:
Структура:
/tiles/14/4823/6160
/tiles/14/4823/6161
Каждый чанк хранится как отдельный объект IndexedDB.
При наличии backend-источника GeoJSON применяется модель:
Несмотря на гибкость, существуют системные ограничения:
Основная ошибка — передача слишком частых setData.
Оптимальная стратегия:
Перед передачей данных в источник применяется агрегация:
Пример логики:
if (zoom < 10) {
return cluster(features);
}
Чтобы избежать блокировки UI:
postMessageЭто особенно важно при объёмах > 50 000 объектов.
Вместо полной выборки:
При длительной работе карты:
Перед записью в IndexedDB часто выполняется нормализация:
Сочетание IndexedDB и Mapbox GL JS позволяет реализовать архитектуру:
Такая модель обеспечивает устойчивую работу при росте данных до сотен тысяч объектов без полной деградации интерактивности карты.