Кастомные источники данных

В основе работы картографического приложения на базе OpenLayers лежит слой источников данных (sources), отвечающий за получение пространственной информации, её загрузку, кеширование и передачу в слои отображения. Источник данных в OpenLayers — это абстракция, инкапсулирующая способ получения геометрии, тайлов или изображений из различных систем хранения и сервисов.

Кастомизация источников данных становится ключевым механизмом при интеграции нестандартных API, внутренних корпоративных сервисов, потоковых данных и динамически формируемых пространственных наборов.


Базовые типы источников и точки расширения

OpenLayers предоставляет несколько классов источников:

  • ol/source/Vector — для векторных данных
  • ol/source/TileXYZ — для тайлов по схеме XYZ
  • ol/source/TileWMS — для WMS-сервисов
  • ol/source/VectorTile — для векторных тайлов
  • ol/source/ImageStatic — для одиночных изображений

Каждый из них может быть расширен или заменён кастомной логикой загрузки.

Ключевые точки кастомизации:

  • loader (для VectorSource)
  • tileLoadFunction (для tile sources)
  • url как функция вместо строки
  • переопределение протоколов загрузки
  • собственные форматы парсинга данных

Кастомный VectorSource через loader

Наиболее гибкий способ создания источника — использование ol/source/Vector с пользовательским загрузчиком.

import VectorSource from 'ol/source/Vector';
import GeoJSON from 'ol/format/GeoJSON';

const source = new VectorSource({
  loader: function (extent, resolution, projection) {
    const url = `/api/objects?bbox=${extent.join(',')}`;

    fetch(url)
      .then(response => response.json())
      .then(data => {
        const format = new GeoJSON();

        const features = format.readFeatures(data, {
          featureProjection: projection
        });

        source.addFeatures(features);
      });
  }
});

Ключевые особенности:

  • extent передаёт текущую видимую область карты
  • resolution позволяет адаптировать уровень детализации
  • projection обеспечивает корректную трансформацию координат
  • загрузка выполняется лениво, только при необходимости

Такой подход используется для серверов с поддержкой BBOX-фильтрации.


Управление загрузкой тайлов через tileLoadFunction

Для растровых и тайловых слоёв кастомизация выполняется через tileLoadFunction.

import XYZ from 'ol/source/XYZ';

const source = new XYZ({
  tileLoadFunction: function (tile, src) {
    const img = tile.getImage();

    fetch(src, {
      headers: {
        Authorization: 'Bearer TOKEN'
      }
    })
      .then(response => response.blob())
      .then(blob => {
        img.src = URL.createObjectURL(blob);
      });
  },
  url: 'https://tiles.example.com/{z}/{x}/{y}.png'
});

Возможности кастомного загрузчика:

  • добавление токенов авторизации
  • шифрование запросов
  • подмена URL на лету
  • логирование тайловых запросов
  • реализация оффлайн-кеша

Полностью динамический источник через функцию URL

OpenLayers позволяет задавать url как функцию:

const source = new XYZ({
  url: function (tileCoord) {
    const z = tileCoord[0];
    const x = tileCoord[1];
    const y = tileCoord[2];

    return `/dynamic-tiles/${z}/${x}/${y}?ts=${Date.now()}`;
  }
});

Применение:

  • анти-кеширование
  • балансировка нагрузки
  • A/B тестирование тайлов
  • динамическая генерация слоёв

Интеграция с нестандартным REST API

Часто сервер возвращает данные не в GeoJSON, а в произвольной структуре.

import VectorSource from 'ol/source/Vector';
import Feature from 'ol/Feature';
import Point from 'ol/geom/Point';

const source = new VectorSource({
  loader: function () {
    fetch('/custom/api/points')
      .then(res => res.json())
      .then(data => {
        const features = data.items.map(item => {
          return new Feature({
            geometry: new Point([item.lon, item.lat]),
            name: item.title
          });
        });

        source.addFeatures(features);
      });
  }
});

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

  • ручное создание Feature
  • отсутствие зависимости от форматов OL
  • полный контроль над структурой данных

Использование пользовательских форматов

OpenLayers поддерживает расширение через ol/format.

import TextFormat from 'ol/format/Text';

class CustomFormat {
  readFeatures(response) {
    return response.records.map(r => {
      return {
        type: 'Feature',
        geometry: {
          type: 'Point',
          coordinates: [r.x, r.y]
        },
        properties: r
      };
    });
  }
}

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

const format = new CustomFormat();
const features = format.readFeatures(data);

Практика применения:

  • проприетарные форматы GIS
  • бинарные протоколы
  • сжатые потоки данных
  • CSV/TSV нестандартной структуры

Кеширование и контроль повторных запросов

Кастомные источники часто требуют оптимизации сетевой нагрузки.

In-memory кеширование:

const cache = new Map();

function loadData(url) {
  if (cache.has(url)) {
    return Promise.resolve(cache.get(url));
  }

  return fetch(url)
    .then(r => r.json())
    .then(data => {
      cache.set(url, data);
      return data;
    });
}

Применение:

  • повторное использование тайлов
  • ускорение панорамирования карты
  • снижение нагрузки на API

Потоковые источники данных

Кастомные источники могут работать в режиме стриминга.

const source = new VectorSource({
  loader: function () {
    const ws = new WebSocket('wss://stream.example.com');

    ws.onmess age = function (event) {
      const data = JSON.parse(event.data);

      const feature = new Feature({
        geometry: new Point(data.coords)
      });

      source.addFeature(feature);
    };
  }
});

Характерные сценарии:

  • трекинг транспорта
  • IoT-устройства
  • обновления в реальном времени
  • мониторинг событий

Декартова трансформация координат в кастомных источниках

При интеграции внешних API часто требуется ручная работа с проекциями.

import { transform } from 'ol/proj';

const coords = transform(
  [30.5, 50.4],
  'EPSG:4326',
  'EPSG:3857'
);

Используется внутри loader для нормализации данных.


Комбинированные источники и агрегация

Иногда один слой объединяет несколько источников.

const sourceA = new VectorSource();
const sourceB = new VectorSource();

function mergeSources() {
  return [...sourceA.getFeatures(), ...sourceB.getFeatures()];
}

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

  • объединение API и локальных данных
  • overlay бизнес-логики
  • фильтрация по источникам

Ограничения кастомных источников

При гибкости подхода возникают системные ограничения:

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

Архитектурные паттерны работы с источниками

Lazy-loading

Загрузка данных только при попадании в viewport.

Event-driven loading

Привязка загрузки к событиям карты:

map.on('moveend', function () {
  source.clear();
  source.refresh();
});

Layer-driven sourcing

Источник зависит от состояния слоя:

  • видимость
  • масштаб
  • фильтры пользователя

Интеграция с серверной агрегацией

Кастомные источники часто работают в связке с сервером:

  • кластеризация на backend
  • генерация тайлов на лету
  • динамическая генерация GeoJSON по bbox

Оптимизация производительности

Практические техники:

  • ограничение числа feature в источнике
  • использование spatial indexing на сервере
  • дебаунс запросов при перемещении карты
  • разделение источников по слоям детализации

Расширение поведения источников через наследование

import VectorSource from 'ol/source/Vector';

class CustomSource extends VectorSource {
  refreshCustom() {
    this.clear();
    this.loader();
  }
}

Такой подход применяется для:

  • инкапсуляции бизнес-логики
  • переиспользования загрузчиков
  • создания domain-specific источников