Пользовательские провайдеры

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

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

  • информацию о тайлах (Tile Availability)
  • геометрию и текстуры
  • метаданные экстента
  • правила масштабирования и уровни детализации

Ключевая идея — ленивое получение данных. Сцена не загружает весь слой целиком, а обращается к провайдеру по мере необходимости, запрашивая только видимые тайлы.

Провайдеры делятся на несколько категорий:

  • ImageryProvider — растровые подложки
  • TerrainProvider — цифровые модели рельефа
  • DataSource — объекты сцены (CZML, GeoJSON и др.)
  • 3D Tiles — потоковые 3D-структуры

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

Контракт ImageryProvider

ImageryProvider представляет собой интерфейс, определяющий доступ к растровым тайлам. Он не навязывает конкретный формат, но требует соблюдения логики тайловой пирамиды.

Основные методы и свойства:

  • requestImage(x, y, level) — загрузка изображения тайла
  • pickFeatures(x, y, level, longitude, latitude) — выбор объектов под курсором
  • tileWidth / tileHeight — размеры тайла
  • maximumLevel / minimumLevel — диапазон детализации
  • rectangle — географические границы покрытия
  • tilingScheme — схема разбиения (обычно WebMercatorTilingScheme или GeographicTilingScheme)

Вся система построена вокруг координат (x, y, level), где level соответствует zoom-уровню.

Создание пользовательского ImageryProvider

Пользовательский провайдер создаётся через реализацию интерфейса объекта. CesiumJS допускает как классовую реализацию, так и объект с функциями.

Базовая структура:

class CustomImageryProvider {
  constructor(options) {
    this._url = options.url;
    this._tileWidth = 256;
    this._tileHeight = 256;
    this._maximumLevel = options.maximumLevel || 18;
    this._minimumLevel = 0;
    this._tilingScheme = new Cesium.WebMercatorTilingScheme();
    this._rectangle = this._tilingScheme.rectangle;
    this._credit = options.credit;
    this._ready = true;
  }

  get url() {
    return this._url;
  }

  get tileWidth() {
    return this._tileWidth;
  }

  get tileHeight() {
    return this._tileHeight;
  }

  get maximumLevel() {
    return this._maximumLevel;
  }

  get minimumLevel() {
    return this._minimumLevel;
  }

  get tilingScheme() {
    return this._tilingScheme;
  }

  get rectangle() {
    return this._rectangle;
  }

  get ready() {
    return this._ready;
  }

  requestImage(x, y, level) {
    const url = `${this._url}/${level}/${x}/${y}.png`;
    return Cesium.ImageryProvider.loadImage(this, url);
  }
}

Такой подход позволяет подключать любой REST-сервис, который следует tile XYZ-схеме.

Схемы тайлинга и координатная модель

Ключевая сложность пользовательских провайдеров — соответствие координатной системы сервера и CesiumJS.

Используются две основные схемы:

  • Web Mercator (EPSG:3857)
  • Geographic (EPSG:4326)

WebMercatorTilingScheme применяется по умолчанию для большинства интернет-картографических сервисов. Он оперирует проекцией, где меридианы и параллели трансформируются в квадратную сетку.

При несовпадении схемы необходимо либо:

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

Пример кастомной трансформации уровня:

const longitude = Cesium.Math.lerp(west, east, x / Math.pow(2, level));
const latitude = Cesium.Math.lerp(south, north, y / Math.pow(2, level));

Ошибки в этом слое приводят к смещению тайлов, инверсии оси Y или разрыву карты.

Кастомные источники тайлов

Частый сценарий — подключение сервиса, возвращающего бинарные изображения или динамически генерируемые тайлы.

Поддерживаются форматы:

  • PNG / JPEG
  • WebP (в зависимости от браузера)
  • Canvas ImageBitmap
  • Blob URLs

Пример обработки бинарного ответа:

requestImage(x, y, level) {
  const url = `${this._url}?z=${level}&x=${x}&y=${y}`;

  return fetch(url)
    .then(response => response.blob())
    .then(blob => Cesium.loadImageFromBlob(blob));
}

Такая схема полезна для серверов, генерирующих изображения на лету (например, тепловые карты или аналитические слои).

Кеширование и повторное использование тайлов

CesiumJS уже содержит внутренний механизм кеширования, однако пользовательские провайдеры могут дополнительно оптимизировать сеть:

  • memoization запросов
  • IndexedDB для офлайн-режима
  • LRU-кеш в памяти

Пример простого кеша:

const cache = new Map();

requestImage(x, y, level) {
  const key = `${level}/${x}/${y}`;

  if (cache.has(key)) {
    return cache.get(key);
  }

  const promise = Cesium.ImageryProvider.loadImage(
    this,
    `${this._url}/${key}.png`
  );

  cache.set(key, promise);
  return promise;
}

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

TerrainProvider и кастомный рельеф

TerrainProvider отвечает за геометрию земной поверхности. В отличие от ImageryProvider, он работает с высотными данными и геометрическими мешами.

Ключевые методы:

  • requestTileGeometry(x, y, level)
  • getLevelMaximumGeometricError
  • hasWaterMask
  • childTileMask

Пример минимальной реализации:

class CustomTerrainProvider {
  constructor(url) {
    this._url = url;
    this._tilingScheme = new Cesium.WebMercatorTilingScheme();
    this._errorEvent = new Cesium.Event();
    this._ready = true;
  }

  requestTileGeometry(x, y, level) {
    const url = `${this._url}/terrain/${level}/${x}/${y}.bin`;

    return fetch(url)
      .then(r => r.arrayBuffer())
      .then(buffer => Cesium.TerrainData.createTerrainMesh(buffer));
  }

  get tilingScheme() {
    return this._tilingScheme;
  }

  get errorEvent() {
    return this._errorEvent;
  }
}

При создании кастомного TerrainProvider критично учитывать:

  • формат высот (quantized-mesh или heightmap)
  • порядок байтов
  • масштабирование высот
  • наличие skirt-геометрии

Производительность пользовательских провайдеров

Основные узкие места:

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

Оптимизационные подходы:

  • объединение тайлов (tile batching)
  • gzip/brotli на сервере
  • переход на WebP вместо PNG
  • использование ArrayBuffer вместо JSON

CesiumJS активно использует Web Workers, поэтому предпочтительно возвращать бинарные данные, чтобы минимизировать блокировку основного потока.

Обработка ошибок и устойчивость

Провайдеры должны корректно обрабатывать:

  • отсутствие тайла (404)
  • таймауты сети
  • некорректные координаты
  • частично загруженные данные

Пример fallback-логики:

requestImage(x, y, level) {
  const url = buildUrl(x, y, level);

  return Cesium.ImageryProvider.loadImage(this, url)
    .catch(() => Cesium.ImageryProvider.loadImage(this, this._fallbackUrl));
}

При отсутствии fallback слоя сцена может отображать “дырки”, что ухудшает визуальную целостность.

Интеграция с 3D Tiles

Хотя 3D Tiles не является классическим провайдером, он часто взаимодействует с пользовательскими источниками данных. При потоковой загрузке моделей:

  • тайлы содержат батчи геометрии
  • используется иерархия уровней детализации
  • провайдер может подменять URL tileset.json

Пример динамической подмены источника:

const tileset = new Cesium.Cesium3DTileset({
  url: new Cesium.Resource({
    url: customEndpoint,
    requestHeaders: {
      Authorization: token
    }
  })
});

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

Совместимость и расширяемость интерфейсов

Пользовательские провайдеры должны учитывать версионные изменения CesiumJS:

  • изменения сигнатур requestImage
  • переход на Promise-based API
  • поддержка async/await
  • обновления TilingScheme

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

Типовые ошибки реализации

  • перепутанные координаты X/Y (особенно в TMS схемах)
  • игнорирование уровня zoom
  • отсутствие обработки null-ответов
  • возврат DOM-элементов вместо Promise/Image
  • неправильная проекция географических границ

Наиболее критическая ошибка — несоответствие tilingScheme серверу, приводящее к систематическому смещению всех тайлов.

Расширенные сценарии

Пользовательские провайдеры часто используются для:

  • визуализации данных IoT в реальном времени
  • отображения спутниковых снимков
  • тепловых карт городской аналитики
  • интеграции CAD/GIS систем
  • потоковой отрисовки симуляций

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