Архитектура расширений

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

Модульная структура и организация пакетов

OpenLayers использует модульную структуру ES Modules, где каждый функциональный блок находится в отдельном пространстве имён:

  • ol/Map — контейнер карты и координатор рендеринга
  • ol/layer/* — слои различных типов
  • ol/source/* — источники данных
  • ol/interaction/* — взаимодействия пользователя
  • ol/control/* — UI-элементы управления
  • ol/geom/* — геометрические примитивы
  • ol/format/* — парсинг геоданных

Такое разделение обеспечивает возможность точечного расширения функциональности без затрагивания остальной системы. Tree-shaking в сборщиках позволяет включать только используемые модули, что критично для производительности в веб-приложениях.

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

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

Ключевые свойства архитектуры:

  • реактивные свойства (set/get)
  • подписка на изменения состояния
  • единый механизм событий

Пример создания расширяемого объекта:

import BaseObject from 'ol/Object.js';

class CustomModel extends BaseObject {
  constructor(options) {
    super();
    this.set('value', options.value || 0);
  }

  increment() {
    const current = this.get('value');
    this.set('value', current + 1);
  }
}

Механизм set/get встроен в систему уведомлений, что позволяет автоматически синхронизировать состояние карты и представления.

Событийная модель расширений

Система событий основана на паттерне Observer. Практически любой объект может генерировать события:

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

Каждое событие имеет тип и контекст:

map.on('moveend', function (event) {
  const center = map.getView().getCenter();
});

Расширения используют этот механизм для интеграции с ядром без необходимости модификации внутренних компонентов.

Слои как точка расширения рендеринга

Слои (ol/layer) являются ключевым расширяемым уровнем системы. Базовый класс BaseLayer определяет контракт:

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

Основные типы слоёв:

  • TileLayer — тайловые данные
  • VectorLayer — векторная графика
  • ImageLayer — растровые изображения
  • VectorTileLayer — векторные тайлы

Создание собственного слоя возможно через наследование:

import Layer from 'ol/layer/Layer.js';

class CustomLayer extends Layer {
  constructor(options) {
    super(options);
  }

  render(frameState) {
    const context = frameState.context;
    // пользовательская логика отрисовки
  }
}

Расширение слоя чаще всего связано с переопределением метода render, который получает состояние текущего кадра карты.

Источники данных и расширяемость input-слоя

Источники (ol/source) определяют способ получения данных:

  • HTTP-запросы (XYZ, WMS, WMTS)
  • локальные данные (GeoJSON, KML)
  • динамические источники (функции, API)

Базовый класс Source реализует события загрузки и обновления. Расширение источника позволяет интегрировать нестандартные API.

Пример кастомного источника:

import Source from 'ol/source/Source.js';

class CustomSource extends Source {
  constructor(options) {
    super(options);
    this.url = options.url;
  }

  load() {
    fetch(this.url)
      .then(r => r.json())
      .then(data => {
        this.dispatchEvent({ type: 'load', data });
      });
  }
}

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

Интеракции как расширяемый слой поведения

Интеракции (ol/interaction) отвечают за поведение пользователя:

  • масштабирование
  • панорамирование
  • выделение объектов
  • редактирование геометрии

Каждая интеракция реализует набор методов:

  • handleDownEvent
  • handleDragEvent
  • handleUpEvent
  • setActive

Пример кастомной интеракции:

import Interaction from 'ol/interaction/Interaction.js';

class CustomClickInteraction extends Interaction {
  constructor() {
    super();
  }

  handleDownEvent(evt) {
    const coordinate = evt.coordinate;
    console.log('Click at:', coordinate);
    return true;
  }
}

Интеракции добавляются в карту как независимые модули поведения:

map.addInteraction(new CustomClickInteraction());

Контролы и расширение UI-слоя

Контролы (ol/control) отделены от логики карты и предназначены для интерфейса:

  • масштабирование
  • полноэкранный режим
  • переключение слоёв
  • пользовательские панели

Базовый класс Control предоставляет контейнер DOM и методы жизненного цикла:

  • render
  • setMap

Пример кастомного контроля:

import Control from 'ol/control/Control.js';

class CustomControl extends Control {
  constructor() {
    const button = document.createElement('button');
    button.innerHTML = 'Action';

    const element = document.createElement('div');
    element.className = 'custom-control';
    element.appendChild(button);

    super({ element });

    button.addEventListener('click', () => {
      console.log('Control triggered');
    });
  }
}

Система рендеринга и frame state

Рендеринг карты основан на концепции frameState. Это объект, содержащий:

  • текущий центр и масштаб
  • разрешение
  • размер viewport
  • список активных слоёв
  • временные параметры анимации

Каждый кадр строится заново, что позволяет реализовать:

  • плавные анимации
  • синхронизацию слоёв
  • динамическое обновление данных

Слои получают frameState и решают, нужно ли перерисовываться.

Форматы данных как расширяемый слой парсинга

Модуль ol/format отвечает за преобразование геоданных:

  • GeoJSON
  • KML
  • GPX
  • WKT

Каждый формат реализует интерфейс:

  • readFeatures
  • writeFeatures

Пример расширения формата:

import TextFeature from 'ol/format/TextFeature.js';

class CustomFormat extends TextFeature {
  readFeatures(text) {
    const parsed = JSON.parse(text);
    return parsed.features;
  }
}

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

Архитектурная роль Observable и реактивности

Ключевой механизм расширяемости — Observable. Он обеспечивает:

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

Любое изменение свойства автоматически может инициировать перерасчёт рендеринга.

Плагинный подход и композиция модулей

OpenLayers не использует единый плагинный реестр. Вместо этого расширения строятся через композицию:

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

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

Типовая композиция расширений:

map.addLayer(customLayer);
map.addInteraction(customInteraction);
customLayer.setSource(customSource);

Сборка, tree-shaking и избирательное расширение

Архитектура модулей ориентирована на статический анализ импортов. Это позволяет:

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

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

Кастомные расширения на уровне рендера и данных

Наиболее глубокий уровень расширения связан с:

  • переопределением render
  • созданием новых типов слоёв
  • внедрением кастомных источников WebGL/Canvas
  • интеграцией внешних GIS-сервисов

Рендеринг может быть полностью заменён для реализации специализированных визуализаций:

  • тепловые карты
  • 3D-проекции
  • временные анимации
  • потоковые данные

Такие расширения опираются на frameState и низкоуровневый доступ к canvas/WebGL контексту, что позволяет обходить стандартные слои отображения и реализовывать собственные графические пайплайны.