Архитектура MapLibre GL JS строится вокруг унифицированной модели
источников данных (sources), где каждый источник отвечает за получение,
нормализацию и предоставление геоданных в формате, пригодном для
рендеринга через WebGL. Стандартные типы источников —
vector, raster, geojson,
image, video — покрывают большинство
сценариев, однако сложные приложения требуют интеграции нестандартных
протоколов, форматов и потоков данных.
Custom source types предоставляют механизм расширения системы источников без модификации ядра рендерера. Это позволяет внедрять собственные реализации загрузки тайлов, динамической генерации геометрии, потоковой обработки данных и интеграции с внешними системами (IoT, real-time feeds, proprietary GIS backends).
Custom source в MapLibre GL JS представляет собой объект, который реализует набор жизненного цикла источника и контракт взаимодействия с тайловой системой.
Типичный custom source должен обеспечивать:
В отличие от geojson или vector источников,
где логика загрузки фиксирована, custom source полностью определяет
стратегию получения данных.
В расширенных сценариях используется регистрация нового типа источника через API расширения стиля:
map.addSource('custom-data', {
type: 'custom',
data: {}
});
Однако сам по себе параметр type: 'custom' не определяет
поведение. Реальная логика подключается через фабрику источника, которая
регистрируется в стиле.
Внутренне MapLibre использует механизм наподобие:
maplibregl.Style.setSourceType('custom', CustomSourceImplementation);
Где CustomSourceImplementation — класс, реализующий
контракт источника.
Базовая структура пользовательского источника включает следующие методы:
class CustomSource {
constructor(options) {
this.options = options;
}
onAdd(map) {
this.map = map;
this.tileCache = new Map();
}
loadTile(tile, callback) {
// загрузка или генерация данных для конкретного тайла
}
unloadTile(tile) {
// освобождение памяти
}
reload() {
// принудительное обновление всех тайлов
}
onRemove() {
// очистка ресурсов
}
}
Ключевым элементом является метод loadTile, который
привязан к тайловой сетке Spherical Mercator (z/x/y). Именно он
определяет, как данные попадают в рендеринг.
MapLibre использует тайловую пирамиду для оптимизации отображения. Custom source интегрируется в эту систему, получая координаты тайла:
z — уровень масштабированияx, y — координаты тайлаПример обработки тайла:
loadTile(tile, callback) {
const { z, x, y } = tile.tileID.canonical;
fetch(`/tiles/${z}/${x}/${y}.json`)
.then(res => res.json())
.then(data => {
tile.data = this.transform(data);
callback(null);
})
.catch(err => callback(err));
}
Одно из ключевых применений custom source — динамическая генерация геометрии. В отличие от GeoJSON, данные не обязаны существовать заранее.
Сценарии:
Пример генерации точек:
transform(raw) {
return raw.items.map(item => ({
type: 'Feature',
geometry: {
type: 'Point',
coordinates: [item.lng, item.lat]
},
properties: {
value: item.value
}
}));
}
При высоких нагрузках обработка данных в основном потоке становится узким местом. Custom source часто выносится в Web Worker.
Схема взаимодействия:
Пример упрощённого worker-подхода:
// worker.js
self.onmess age = (e) => {
const { z, x, y } = e.data;
const result = generateTile(z, x, y);
self.postMessage({
tileId: `${z}-${x}-${y}`,
data: result
});
};
Custom source обычно требует собственной стратегии кэширования:
Пример простого кэша:
class TileCache {
constructor(limit = 200) {
this.limit = limit;
this.cache = new Map();
}
get(key) {
const value = this.cache.get(key);
if (value) {
this.cache.delete(key);
this.cache.set(key, value);
}
return value;
}
set(key, value) {
if (this.cache.size >= this.limit) {
const firstKey = this.cache.keys().next().value;
this.cache.delete(firstKey);
}
this.cache.set(key, value);
}
}
Custom source обязан корректно работать с асинхронностью MapLibre рендерера. Изменения данных должны инициировать перерасчёт тайлов.
Типовой механизм:
updateData(newData) {
this.data = newData;
this.tileCache.clear();
this.map.triggerRepaint();
}
triggerRepaint() обеспечивает перерисовку без изменения
камеры, что важно для потоковых источников.
Custom source часто используется для streaming-данных:
Архитектура включает буферизацию:
pushStreamPoint(point) {
this.buffer.push(point);
if (this.buffer.length > 1000) {
this.flushBuffer();
}
}
Так как MapLibre работает в Web Mercator (EPSG:3857), custom source часто включает преобразование координат:
Пример:
function lngLatToMercator(lng, lat) {
const x = lng * 20037508.34 / 180;
let y = Math.log(Math.tan((90 + lat) * Math.PI / 360)) / (Math.PI / 180);
y = y * 20037508.34 / 180;
return [x, y];
}
Custom source требует строгой обработки ошибок:
Типовая стратегия:
const controller = new AbortController();
fetch(url, { signal: controller.signal })
.catch(err => {
if (err.name === 'AbortError') return;
console.error(err);
});
Custom source напрямую влияет на:
Критические оптимизации:
Custom source используется в системах, где стандартные источники недостаточны:
Custom source интегрируется в слойную модель MapLibre так же, как стандартные источники:
map.addLayer({
id: 'custom-layer',
type: 'circle',
source: 'custom-data',
paint: {
'circle-radius': 6,
'circle-color': '#ff5500'
}
});
Рендерер не различает тип источника на уровне слоя — важна только структура возвращаемых данных (features).
Custom source API эволюционирует между версиями MapLibre GL JS. В зависимости от версии могут отличаться:
Поэтому реализация обычно изолируется в адаптерном слое, отделяющем бизнес-логику от API MapLibre.