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

Асинхронная загрузка является одной из ключевых особенностей CesiumJS. Практически все операции, связанные с получением данных из внешних источников, выполняются без блокировки основного потока выполнения JavaScript. Благодаря этому интерфейс приложения остается отзывчивым даже при загрузке крупных наборов геопространственных данных, изображений, моделей и тайлов.

В современных версиях CesiumJS активно используются объекты Promise и синтаксис async/await, что делает работу с асинхронными операциями более удобной и понятной.


Причины использования асинхронной загрузки

Трехмерные геопространственные приложения работают с большими объемами данных:

  • спутниковыми снимками;
  • картографическими тайлами;
  • 3D Tiles;
  • моделями glTF;
  • GeoJSON-файлами;
  • KML-документами;
  • изображениями;
  • данными высот.

Загрузка подобных ресурсов может занимать значительное время. Если бы браузер ожидал завершения каждой операции синхронно, интерфейс приложения постоянно зависал бы.

Асинхронная модель позволяет:

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

Promise в CesiumJS

Большинство методов загрузки возвращают объект Promise.

Пример загрузки GeoJSON:

const promise = Cesium.GeoJsonDataSource.load(
    "data/cities.geojson"
);

console.log(promise);

Пока файл загружается, Promise находится в состоянии ожидания.

После успешного завершения операция переходит в состояние fulfilled.

После возникновения ошибки — в состояние rejected.


Использование then()

Классический способ работы с Promise выглядит следующим образом:

Cesium.GeoJsonDataSource.load(
    "data/cities.geojson"
).then(function(dataSource) {

    viewer.dataSources.add(dataSource);

});

Порядок выполнения:

  1. Отправляется запрос на получение файла.
  2. Основной код продолжает выполняться.
  3. После завершения загрузки вызывается функция внутри then().
  4. Полученный объект добавляется на сцену.

Обработка ошибок через catch()

Любая операция загрузки может завершиться ошибкой:

  • отсутствует файл;
  • сервер недоступен;
  • ошибка CORS;
  • поврежденный формат данных;
  • проблемы сети.

Для обработки подобных ситуаций используется catch().

Cesium.GeoJsonDataSource.load(
    "data/cities.geojson"
)
.then(function(dataSource) {

    viewer.dataSources.add(dataSource);

})
.catch(function(error) {

    console.error(error);

});

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


Использование async/await

Современный подход основан на синтаксисе async/await.

Тот же пример становится более читаемым:

async function loadCities() {

    const dataSource =
        await Cesium.GeoJsonDataSource.load(
            "data/cities.geojson"
        );

    viewer.dataSources.add(dataSource);
}

В момент выполнения оператора await функция временно приостанавливается до получения результата.

Внешний код продолжает работать без блокировок.


Асинхронная загрузка GeoJSON

GeoJSON является одним из наиболее часто используемых форматов в CesiumJS.

async function loadGeoJson() {

    const geojson =
        await Cesium.GeoJsonDataSource.load(
            "data/roads.geojson"
        );

    viewer.dataSources.add(geojson);
}

После завершения загрузки на сцене автоматически создаются соответствующие сущности:

  • точки;
  • линии;
  • полигоны.

Асинхронная загрузка KML

Загрузка KML также выполняется асинхронно.

async function loadKml() {

    const dataSource =
        await Cesium.KmlDataSource.load(
            "data/places.kml"
        );

    viewer.dataSources.add(dataSource);
}

Если документ содержит большое количество объектов, процесс может занимать заметное время.


Асинхронная загрузка CZML

CZML широко используется для временных геопространственных данных.

async function loadCzml() {

    const dataSource =
        await Cesium.CzmlDataSource.load(
            "data/satellite.czml"
        );

    viewer.dataSources.add(dataSource);
}

После завершения загрузки Cesium автоматически создает временную шкалу и анимацию объектов.


Асинхронная загрузка 3D Tiles

Наборы 3D Tiles обычно имеют большой размер и практически всегда загружаются постепенно.

async function loadTileset() {

    const tileset =
        await Cesium.Cesium3DTileset.fromUrl(
            "tileset/tileset.json"
        );

    viewer.scene.primitives.add(
        tileset
    );
}

Загрузка включает несколько этапов:

  1. Получение файла tileset.json.
  2. Анализ структуры тайлов.
  3. Загрузка необходимых тайлов.
  4. Постепенное отображение содержимого.

Даже после завершения метода fromUrl() отдельные тайлы продолжают загружаться динамически по мере перемещения камеры.


Асинхронная загрузка glTF-моделей

Трехмерные модели также получают данные через сеть.

async function loadModel() {

    const model =
        await Cesium.Model.fromGltfAsync({
            url: "models/aircraft.glb"
        });

    viewer.scene.primitives.add(model);
}

Метод возвращает Promise, который завершается после подготовки модели к отображению.


Последовательная загрузка ресурсов

Иногда необходимо дождаться завершения одной операции перед началом другой.

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

async function initializeScene() {

    const dataSource =
        await Cesium.GeoJsonDataSource.load(
            "data/regions.geojson"
        );

    viewer.dataSources.add(dataSource);

    await viewer.flyTo(dataSource);
}

Здесь вторая операция начинается только после завершения первой.


Параллельная загрузка ресурсов

Если данные независимы друг от друга, эффективнее загружать их одновременно.

async function loadData() {

    const results = await Promise.all([
        Cesium.GeoJsonDataSource.load(
            "data/cities.geojson"
        ),
        Cesium.GeoJsonDataSource.load(
            "data/rivers.geojson"
        ),
        Cesium.GeoJsonDataSource.load(
            "data/roads.geojson"
        )
    ]);

    results.forEach(ds => {
        viewer.dataSources.add(ds);
    });
}

Все запросы отправляются параллельно, что существенно ускоряет загрузку.


Promise.all()

Метод Promise.all() завершает работу после успешного выполнения всех Promise.

const [cities, roads] = await Promise.all([
    Cesium.GeoJsonDataSource.load(
        "cities.geojson"
    ),
    Cesium.GeoJsonDataSource.load(
        "roads.geojson"
    )
]);

Если хотя бы один Promise завершится ошибкой, вся операция будет считаться неуспешной.


Promise.allSettled()

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

const results =
    await Promise.allSettled([
        Cesium.GeoJsonDataSource.load(
            "cities.geojson"
        ),
        Cesium.GeoJsonDataSource.load(
            "roads.geojson"
        )
    ]);

console.log(results);

Каждый элемент массива содержит информацию:

{
    status: "fulfilled",
    value: ...
}

или

{
    status: "rejected",
    reason: ...
}

Такой подход особенно полезен при загрузке большого количества независимых ресурсов.


Отображение индикатора загрузки

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

async function loadData() {

    loadingElement.style.display = "block";

    try {

        const dataSource =
            await Cesium.GeoJsonDataSource.load(
                "large.geojson"
            );

        viewer.dataSources.add(dataSource);

    } finally {

        loadingElement.style.display = "none";
    }
}

Независимо от результата индикатор будет скрыт.


Асинхронная загрузка изображений

Многие материалы используют внешние изображения.

const image = await Cesium.Resource.fetchImage({
    url: "images/marker.png"
});

Полученное изображение может использоваться:

  • в билбордах;
  • материалах;
  • пользовательских шейдерах;
  • текстурах моделей.

Использование Resource

Класс Resource предоставляет универсальный механизм работы с удаленными данными.

const resource =
    new Cesium.Resource({
        url: "data/config.json"
    });

Получение JSON:

const json =
    await resource.fetchJson();

Получение текста:

const text =
    await resource.fetchText();

Получение бинарных данных:

const buffer =
    await resource.fetchArrayBuffer();

Асинхронное получение JSON

Часто сервер возвращает настройки или конфигурацию сцены.

async function loadConfig() {

    const config =
        await Cesium.Resource.fetchJson({
            url: "config.json"
        });

    console.log(config);
}

После получения объекта можно динамически создавать сущности или настраивать визуализацию.


Асинхронная инициализация Viewer

Некоторые компоненты необходимо загрузить до создания сцены.

async function initialize() {

    const terrain =
        await Cesium.CesiumTerrainProvider.fromIonAssetId(
            1
        );

    const viewer =
        new Cesium.Viewer("cesiumContainer", {
            terrainProvider: terrain
        });
}

Сначала создается провайдер рельефа, затем Viewer.


Асинхронная загрузка рельефа

Современные версии CesiumJS предоставляют асинхронные методы создания провайдеров.

const terrain =
    await Cesium.createWorldTerrainAsync();

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

viewer.terrainProvider =
    terrain;

Асинхронная работа с Cesium ion

Многие ресурсы Cesium ion получают данные через облачную инфраструктуру.

const tileset =
    await Cesium.Cesium3DTileset.fromIonAssetId(
        12345
    );

Последовательность операций включает:

  • обращение к сервису ion;
  • получение токена доступа;
  • получение URL ресурса;
  • загрузку данных.

Все этапы выполняются асинхронно.


Обработка ошибок через try/catch

Наиболее распространенный шаблон для async/await.

async function loadData() {

    try {

        const dataSource =
            await Cesium.GeoJsonDataSource.load(
                "data.geojson"
            );

        viewer.dataSources.add(
            dataSource
        );

    } catch(error) {

        console.error(
            "Ошибка загрузки:",
            error
        );
    }
}

Такой подход обеспечивает централизованную обработку исключений.


Повторная попытка загрузки

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

async function loadWithRetry(
    url,
    retries = 3
) {

    for(let i = 0; i < retries; i++) {

        try {

            return await Cesium.GeoJsonDataSource.load(
                url
            );

        } catch(error) {

            if(i === retries - 1) {
                throw error;
            }
        }
    }
}

Подобный механизм полезен для мобильных приложений и удаленных серверов.


Отмена загрузки

Для некоторых типов запросов может использоваться механизм отмены через браузерные API и собственную логику управления запросами.

Типичный сценарий:

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

Отмена лишних запросов снижает нагрузку на сеть и повышает производительность приложения.


Управление большим количеством асинхронных операций

В крупных проектах одновременно могут загружаться:

  • десятки GeoJSON-файлов;
  • сотни тайлов;
  • изображения интерфейса;
  • текстуры моделей;
  • данные высот;
  • конфигурационные файлы.

Распространенная практика заключается в создании централизованного менеджера загрузок.

Пример структуры:

class LoadingManager {

    constructor() {
        this.pending = 0;
    }

    start() {
        this.pending++;
    }

    finish() {
        this.pending--;
    }

    get isLoading() {
        return this.pending > 0;
    }
}

Такой объект позволяет отслеживать текущее состояние приложения и отображать общий индикатор загрузки.


Особенности асинхронной загрузки тайлов

Карты и 3D Tiles используют потоковую модель загрузки.

Особенности:

  • данные подгружаются по мере перемещения камеры;
  • загружаются только видимые тайлы;
  • невидимые тайлы выгружаются из памяти;
  • уровень детализации изменяется динамически;
  • запросы выполняются параллельно.

Благодаря этому даже огромные наборы геопространственных данных могут отображаться в браузере без предварительной загрузки всего объема информации.


Рекомендации по организации асинхронного кода

Использование async/await вместо длинных цепочек then()

const ds =
    await Cesium.GeoJsonDataSource.load(url);

Обязательная обработка ошибок

try {
    ...
}
catch(error) {
    ...
}

Параллельная загрузка независимых ресурсов

await Promise.all([...]);

Отображение состояния загрузки

loading = true;

Минимизация количества последовательных запросов

Каждая лишняя последовательная операция увеличивает время ожидания пользователя.

Разделение логики загрузки и логики отображения

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

Асинхронная архитектура CesiumJS позволяет эффективно работать с крупными геопространственными наборами данных, облачными сервисами, потоковой передачей 3D Tiles и удаленными ресурсами, сохраняя высокую отзывчивость интерфейса и обеспечивая масштабируемость веб-ГИС любого уровня сложности.