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

Роль мокирования в работе с OpenLayers

При разработке картографических приложений значительная часть логики завязана на источники пространственных данных: векторные слои, тайловые серверы, WMS/WMTS сервисы, внешние API. В реальных условиях такие источники могут быть нестабильными, медленными или недоступными, что делает разработку и тестирование затруднительными.

Мокирование источников данных в OpenLayers позволяет заменить реальные сервисы контролируемыми локальными или программно сгенерированными данными. Это обеспечивает предсказуемость поведения карты, ускоряет разработку и упрощает тестирование логики отображения и взаимодействия.


Базовые подходы к подмене источников

В OpenLayers почти любой источник данных абстрагирован через класс ol/source/*. Это делает возможными несколько стратегий мокирования:

  • замена VectorSource локальными GeoJSON-данными
  • использование кастомных loader функций
  • подмена сетевых запросов (fetch/XHR)
  • генерация тайлов через функции URL
  • использование in-memory источников

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


Мокирование VectorSource через статический GeoJSON

Наиболее простой вариант — использование локального GeoJSON без сетевых запросов.

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

const mockGeojson = {
  type: 'FeatureCollection',
  features: [
    {
      type: 'Feature',
      geometry: {
        type: 'Point',
        coordinates: [30.3141, 59.9386]
      },
      properties: {
        id: 1,
        name: 'Mock Point A'
      }
    },
    {
      type: 'Feature',
      geometry: {
        type: 'LineString',
        coordinates: [
          [30.31, 59.93],
          [30.32, 59.94]
        ]
      },
      properties: {
        id: 2,
        name: 'Mock Line'
      }
    }
  ]
};

const source = new VectorSource({
  features: new GeoJSON().readFeatures(mockGeojson, {
    featureProjection: 'EPSG:3857'
  })
});

Такой подход полностью исключает сетевую зависимость и подходит для UI-тестирования слоёв и стилей.


Использование кастомного loader в VectorSource

Более гибкий способ — управление загрузкой данных через loader. Это позволяет симулировать серверные ответы.

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

const source = new VectorSource({
  loader: function (extent, resolution, projection) {
    const features = [
      new Feature({
        geometry: new Point([0, 0]),
        name: 'Dynamic Mock Point'
      }),
      new Feature({
        geometry: new Point([1000000, 1000000]),
        name: 'Another Mock Point'
      })
    ];

    source.addFeatures(features);
  },
  strategy: function () {
    return [[-180, -90, 180, 90]];
  }
});

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


Мокирование через fetch и XHR перехват

Если источники данных используют url (например, GeoJSON или TileWMS), удобно перехватывать сетевые запросы.

Пример с переопределением fetch:

const originalFetch = window.fetch;

window.fetch = async function (url, options) {
  if (url.includes('/api/features')) {
    return new Response(JSON.stringify({
      type: 'FeatureCollection',
      features: [
        {
          type: 'Feature',
          geometry: {
            type: 'Point',
            coordinates: [10, 10]
          },
          properties: { name: 'Mocked via fetch' }
        }
      ]
    }));
  }

  return originalFetch(url, options);
};

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


Мокирование TileWMS и XYZ источников

Тайловые источники сложнее, поскольку работают с изображениями.

Подмена XYZ через data URL
import XYZ from 'ol/source/XYZ';

const source = new XYZ({
  tileUrlFunction: function (tileCoord) {
    const [z, x, y] = tileCoord;

    const canvas = document.createElement('canvas');
    canvas.width = 256;
    canvas.height = 256;

    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#e0e0e0';
    ctx.fillRect(0, 0, 256, 256);

    ctx.fillStyle = '#000';
    ctx.fillText(`Z:${z} X:${x} Y:${y}`, 20, 130);

    return canvas.toDataURL();
  }
});

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


Мокирование TileWMS через подмену URL
import TileWMS from 'ol/source/TileWMS';

const source = new TileWMS({
  url: '/mock/wms',
  params: {
    LAYERS: 'mock_layer',
    TILED: true
  },
  tileLoadFunction: function (imageTile, src) {
    const canvas = document.createElement('canvas');
    canvas.width = 256;
    canvas.height = 256;

    const ctx = canvas.getContext('2d');
    ctx.fillStyle = '#cfcfcf';
    ctx.fillRect(0, 0, 256, 256);

    ctx.fillStyle = '#333';
    ctx.fillText('Mock WMS Tile', 50, 120);

    imageTile.getImage().src = canvas.toDataURL();
  }
});

Изоляция данных через in-memory источник

Иногда требуется полностью исключить формат GeoJSON и сетевые вызовы.

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

const polygon = new Feature({
  geometry: new Polygon([[
    [0, 0],
    [1000000, 0],
    [1000000, 1000000],
    [0, 1000000],
    [0, 0]
  ]])
});

const source = new VectorSource({
  features: [polygon]
});

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


Мокирование стратегий загрузки

OpenLayers использует стратегии загрузки (strategy) для управления запросами. Их также можно подменять для полного контроля поведения источника.

import { bbox as bboxStrategy } from 'ol/loadingstrategy';

const source = new VectorSource({
  loader: function () {
    // игнорирует extent
  },
  strategy: function () {
    return [[0, 0, 0, 0]];
  }
});

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

  • отсутствие данных
  • постоянную загрузку одного экстента
  • мгновенную загрузку всех данных

Мокирование через MSW (Mock Service Worker)

При работе с REST API, которые возвращают GeoJSON или тайлы, применяется перехват на уровне сети.

import { rest } from 'msw';
import { setupServer } from 'msw/node';

const server = setupServer(
  rest.get('/api/geo', (req, res, ctx) => {
    return res(
      ctx.json({
        type: 'FeatureCollection',
        features: [
          {
            type: 'Feature',
            geometry: {
              type: 'Point',
              coordinates: [20, 20]
            },
            properties: { label: 'MSW Point' }
          }
        ]
      })
    );
  })
);

server.listen();

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


Мокирование стилевых зависимостей данных

Иногда важно проверять не только геометрию, но и поведение стилей, зависящих от свойств объектов.

import Style from 'ol/style/Style';
import Fill from 'ol/style/Fill';
import CircleStyle from 'ol/style/Circle';

function mockStyle(feature) {
  const type = feature.get('type');

  return new Style({
    image: new CircleStyle({
      radius: type === 'critical' ? 10 : 5,
      fill: new Fill({
        color: type === 'critical' ? 'red' : 'blue'
      })
    })
  });
}

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


Генерация больших наборов тестовых данных

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

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

const features = [];

for (let i = 0; i < 10000; i++) {
  features.push(
    new Feature({
      geometry: new Point([
        Math.random() * 1e6,
        Math.random() * 1e6
      ]),
      id: i
    })
  );
}

const source = new VectorSource({
  features
});

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


Комбинирование мок-источников

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

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

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