Динамическое добавление источников

В MapLibre GL JS источники (sources) являются фундаментальным уровнем архитектуры визуализации. Они определяют, откуда карта получает данные: векторные тайлы, GeoJSON, растровые изображения или видео. Динамическое добавление источников позволяет изменять структуру данных карты во время выполнения приложения без пересоздания стиля и без перезагрузки карты.

Ключевой особенностью является то, что источник не существует сам по себе — он всегда связан с определённым стилем и может быть использован слоями (layers). Это означает, что добавление источника динамически обычно сопровождается последующим добавлением или обновлением слоёв.


Базовый механизм добавления источников

В MapLibre GL JS источники добавляются через метод addSource объекта карты.

map.addSource('cities', {
    type: 'geojson',
    data: {
        type: 'FeatureCollection',
        features: []
    }
});

Основные параметры:

  • id — уникальный идентификатор источника
  • type — тип данных (geojson, vector, raster, image, video)
  • data — объект данных или URL

После добавления источник становится доступен для слоёв:

map.addLayer({
    id: 'cities-layer',
    type: 'circle',
    source: 'cities',
    paint: {
        'circle-radius': 6,
        'circle-color': '#ff0000'
    }
});

Динамическое добавление источника во время выполнения

Динамическое добавление подразумевает, что источник создаётся после инициализации карты, например в ответ на пользовательское действие или загрузку данных с сервера.

function addCitySource(geojson) {
    if (map.getSource('dynamic-cities')) {
        map.getSource('dynamic-cities').setData(geojson);
        return;
    }

    map.addSource('dynamic-cities', {
        type: 'geojson',
        data: geojson
    });

    map.addLayer({
        id: 'dynamic-cities-layer',
        type: 'circle',
        source: 'dynamic-cities',
        paint: {
            'circle-radius': 5,
            'circle-color': '#1e90ff'
        }
    });
}

Ключевой паттерн:

  • проверка существования источника через getSource
  • обновление через setData, если источник уже есть
  • создание нового источника при первом вызове

Обновление данных в источнике

GeoJSON-источники поддерживают динамическое обновление через метод setData.

const source = map.getSource('dynamic-cities');

source.setData({
    type: 'FeatureCollection',
    features: [
        {
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [73.3499, 49.8061]
            },
            properties: {
                name: 'Karaganda'
            }
        }
    ]
});

Особенность поведения:

  • обновление происходит без пересоздания слоя
  • визуализация автоматически пересчитывается
  • перерисовываются только затронутые тайлы

Асинхронная загрузка источников

На практике данные почти всегда приходят с сервера. В этом случае добавление источника происходит после завершения fetch.

async function loadCities() {
    const response = await fetch('/api/cities');
    const geojson = await response.json();

    if (!map.getSource('cities')) {
        map.addSource('cities', {
            type: 'geojson',
            data: geojson
        });

        map.addLayer({
            id: 'cities-layer',
            type: 'circle',
            source: 'cities'
        });
    } else {
        map.getSource('cities').setData(geojson);
    }
}

Управление жизненным циклом источников

Источники нельзя безопасно удалять в любой момент, так как они могут быть связаны со слоями. Удаление выполняется через removeSource, но только после удаления всех зависимых слоёв.

if (map.getLayer('cities-layer')) {
    map.removeLayer('cities-layer');
}

if (map.getSource('cities')) {
    map.removeSource('cities');
}

Порядок имеет критическое значение:

  1. удалить слои
  2. удалить источник

Переключение источников без пересоздания слоёв

В некоторых сценариях требуется заменить источник данных, не трогая слои. Это достигается через setData или замену URL (для тайловых источников через setTiles или обновление стиля).

function switchDataset(url) {
    fetch(url)
        .then(res => res.json())
        .then(data => {
            const source = map.getSource('points');
            if (source) {
                source.setData(data);
            }
        });
}

Динамическое создание множественных источников

При работе с большими наборами данных часто создаются несколько источников, разделённых по категориям.

const categories = ['restaurants', 'schools', 'hospitals'];

categories.forEach(category => {
    map.addSource(category, {
        type: 'geojson',
        data: `/api/${category}.geojson`
    });

    map.addLayer({
        id: `${category}-layer`,
        type: 'circle',
        source: category,
        paint: {
            'circle-radius': 4,
            'circle-color': category === 'restaurants'
                ? '#e74c3c'
                : category === 'schools'
                ? '#2ecc71'
                : '#3498db'
        }
    });
});

Такой подход обеспечивает:

  • изоляцию данных по доменам
  • независимое управление слоями
  • упрощённую фильтрацию и стилизацию

Обновление источников по таймеру

Динамические данные могут обновляться регулярно, например при отображении трекинга объектов.

setInterval(async () => {
    const res = await fetch('/api/live-positions');
    const data = await res.json();

    const source = map.getSource('vehicles');
    if (source) {
        source.setData(data);
    }
}, 5000);

Особенность: MapLibre оптимизирует перерисовку и обновляет только изменённые геометрии.


Условное добавление источников

В сложных приложениях источник добавляется только при выполнении условий, например при активации слоя.

function enableTrafficLayer() {
    if (!map.getSource('traffic')) {
        map.addSource('traffic', {
            type: 'vector',
            url: 'mapbox://traffic.source'
        });

        map.addLayer({
            id: 'traffic-layer',
            type: 'line',
            source: 'traffic',
            'source-layer': 'traffic_flow'
        });
    }
}

Ошибки при динамическом добавлении

Частые проблемы связаны с неправильным порядком операций и повторным добавлением источников.

Типичные ошибки:

  • повторное добавление источника с одинаковым id
  • попытка использовать источник до его создания
  • удаление источника при существующих слоях
  • обновление данных до загрузки стиля карты

Проверка готовности карты

Динамическое добавление источников должно выполняться только после события load.

map.on('load', () => {
    map.addSource('initial-data', {
        type: 'geojson',
        data: '/data/init.geojson'
    });
});

Без этой проверки возможно возникновение ошибок из-за незагруженного стиля.


Работа с временными источниками

Временные источники используются для интерактивных операций, таких как рисование, выделение или измерение.

map.addSource('draw-temp', {
    type: 'geojson',
    data: {
        type: 'FeatureCollection',
        features: []
    }
});

map.on('mousemove', (e) => {
    const source = map.getSource('draw-temp');
    source.setData({
        type: 'FeatureCollection',
        features: [{
            type: 'Feature',
            geometry: {
                type: 'Point',
                coordinates: [e.lngLat.lng, e.lngLat.lat]
            }
        }]
    });
});

Оптимизация динамических источников

При частых обновлениях важно минимизировать нагрузку на рендеринг.

Основные подходы:

  • батчинг обновлений через requestAnimationFrame
  • уменьшение частоты setData
  • использование упрощённых GeoJSON
  • разделение источников по частоте обновления
let pendingData = null;

function scheduleUpdate(data) {
    pendingData = data;

    requestAnimationFrame(() => {
        if (pendingData) {
            map.getSource('dynamic').setData(pendingData);
            pendingData = null;
        }
    });
}

Архитектурные паттерны динамического управления

В крупных приложениях источники управляются централизованно.

Подход через registry:

const sourceRegistry = new Map();

function registerSource(id, definition) {
    if (map.getSource(id)) return;

    map.addSource(id, definition);
    sourceRegistry.set(id, definition);
}

function updateSource(id, data) {
    const source = map.getSource(id);
    if (source) {
        source.setData(data);
    }
}

Такой подход обеспечивает:

  • контроль над состоянием источников
  • предотвращение дублирования
  • единый интерфейс обновления данных

Связь динамических источников и слоёв

Слои в MapLibre GL JS полностью зависят от источников. При динамическом добавлении источника важно учитывать момент создания слоя.

map.addSource('points', {
    type: 'geojson',
    data: emptyGeoJSON
});

map.addLayer({
    id: 'points-layer',
    type: 'circle',
    source: 'points'
});

После этого любые обновления источника автоматически отражаются в слое без дополнительных действий.