Fetch API для загрузки данных

Механизм получения данных в OpenLayers строится вокруг асинхронных источников, где Fetch API выступает базовым инструментом для взаимодействия с сервером. Основная идея заключается в разделении ответственности: карта и слои не знают ничего о транспортном уровне, а получают уже подготовленные геоданные через функции загрузки.

Fetch API используется векторными источниками, пользовательскими загрузчиками, а также в интеграции с REST-сервисами, выдающими GeoJSON, TopoJSON, MVT или произвольные JSON-структуры.


Базовая модель загрузки через fetch

Fetch представляет собой Promise-ориентированный интерфейс HTTP-запросов. В контексте OpenLayers он применяется внутри загрузчиков источников (loader), где результат преобразуется в объекты геометрии.

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

  • запрос к API
  • проверку статуса ответа
  • преобразование в JSON
  • парсинг через формат OpenLayers
  • добавление объектов в источник
fetch('https://example.com/data.geojson')
  .then(response => response.json())
  .then(data => {
    // обработка GeoJSON
  });

В OpenLayers этот процесс чаще инкапсулируется внутри ol/source/Vector, где fetch используется как низкоуровневый транспорт.


Vector source с пользовательским loader

Наиболее гибкая схема загрузки данных реализуется через ol/source/Vector с кастомной функцией loader.

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

const source = new VectorSource({
  loader: function(extent, resolution, projection) {
    fetch(`https://example.com/api/features?bbox=${extent.join(',')}`)
      .then(response => response.json())
      .then(json => {
        const features = new GeoJSON().readFeatures(json, {
          featureProjection: projection
        });

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

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


Работа с экстентом и пространственная фильтрация

Extent передается в loader как массив координат [minX, minY, maxX, maxY]. Этот диапазон используется для серверной фильтрации.

Часто применяется следующая схема:

  • преобразование координат в целевую проекцию
  • передача bbox в API
  • серверная генерация выборки
const bbox = extent.join(',');

const url = new URL('https://example.com/features');
url.searchParams.set('bbox', bbox);
url.searchParams.set('srid', projection.getCode());

fetch(url.toString())

Такой подход снижает объем передаваемых данных и ускоряет отрисовку.


AbortController и управление жизненным циклом запросов

При интерактивном перемещении карты множество запросов могут устаревать. Для их контроля используется AbortController.

let controller;

const source = new VectorSource({
  loader: function(extent, resolution, projection) {
    if (controller) {
      controller.abort();
    }

    controller = new AbortController();

    fetch('https://example.com/data', {
      signal: controller.signal
    })
      .then(r => r.json())
      .then(json => {
        const features = new GeoJSON().readFeatures(json, {
          featureProjection: projection
        });
        source.addFeatures(features);
      })
      .catch(e => {
        if (e.name !== 'AbortError') {
          console.error(e);
        }
      });
  }
});

Такой механизм предотвращает накопление устаревших сетевых операций и снижает нагрузку на клиент.


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

Сетевые данные требуют строгой обработки ошибок:

  • таймауты
  • 500/404 ответы
  • некорректный JSON
  • частичная загрузка

Fetch не выбрасывает ошибку на HTTP-статус, поэтому проверка выполняется вручную:

fetch(url)
  .then(response => {
    if (!response.ok) {
      throw new Error(`HTTP error: ${response.status}`);
    }
    return response.json();
  })

Для повышения устойчивости применяется повторная отправка запросов:

function fetchWithRetry(url, retries = 3) {
  return fetch(url).then(response => {
    if (!response.ok && retries > 0) {
      return fetchWithRetry(url, retries - 1);
    }
    return response.json();
  });
}

Интеграция с форматами OpenLayers

После получения данных fetch не участвует в дальнейшей логике. Его результат передается в парсеры:

  • ol/format/GeoJSON
  • ol/format/TopoJSON
  • ol/format/WKT

GeoJSON остается наиболее распространенным форматом.

const format = new GeoJSON();

fetch(url)
  .then(r => r.json())
  .then(json => {
    const features = format.readFeatures(json, {
      featureProjection: 'EPSG:3857'
    });
  });

Преобразование проекций является обязательным этапом при работе с веб-картографией.


Заголовки, токены и авторизация

Fetch позволяет задавать HTTP-заголовки, что используется для доступа к защищенным API:

fetch('https://api.example.com/data', {
  headers: {
    'Authorization': 'Bearer TOKEN',
    'Accept': 'application/json'
  }
})

В OpenLayers это особенно важно при работе с приватными геосервисами, где каждый запрос должен содержать токен доступа.


Кэширование и оптимизация сетевых запросов

При работе с картой часто повторяются идентичные запросы. Оптимизация включает:

  • мемоизацию URL
  • серверное кэширование (ETag, Cache-Control)
  • клиентское хранение

Простейшая форма кэша:

const cache = new Map();

function cachedFetch(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;
    });
}

Стриминг и работа с большими данными

При больших объемах геоданных загрузка может происходить по частям. Хотя Fetch API поддерживает streaming, OpenLayers чаще работает с полными JSON-ответами, однако возможно использование потоковой обработки через ReadableStream.

fetch(url).then(response => {
  const reader = response.body.getReader();
  // обработка чанков
});

Такой подход применяется при нестандартных API или бинарных форматах.


Пагинация и инкрементальная загрузка

REST-сервисы часто ограничивают размер ответа. Векторный источник может реализовать постраничную загрузку:

function loadPage(page) {
  return fetch(`https://api.example.com/features?page=${page}`)
    .then(r => r.json());
}

let page = 1;

function loader(extent, resolution, projection) {
  loadPage(page).then(json => {
    const features = new GeoJSON().readFeatures(json, {
      featureProjection: projection
    });

    source.addFeatures(features);
    page += 1;
  });
}

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

После получения данных через fetch часто выполняется преобразование координат:

  • EPSG:4326 → EPSG:3857
  • локальные системы координат → Web Mercator

OpenLayers выполняет это через featureProjection, но иногда требуется предварительная трансформация на уровне данных.


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

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

const urls = [...];

async function loadAll() {
  for (const url of urls) {
    const res = await fetch(url);
    const json = await res.json();
  }
}

Для ускорения применяется Promise.all, однако это увеличивает нагрузку:

Promise.all(urls.map(u => fetch(u).then(r => r.json())));

Выбор стратегии зависит от объема данных и ограничений сервера.


Fetch в кастомных слоях Tile и Image

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

Пример кастомного tile load function:

tileLoadFunction: function(tile, src) {
  fetch(src)
    .then(response => response.blob())
    .then(blob => {
      const url = URL.createObjectURL(blob);
      tile.getImage().src = url;
    });
}

Объединение REST API и картографической логики

Fetch становится связующим звеном между серверной геоаналитикой и клиентской визуализацией. Сервер может возвращать:

  • агрегированные данные
  • кластеризованные точки
  • отсеченные по bbox геометрии
  • статистические атрибуты

OpenLayers в этом случае выполняет роль визуального рендера, а fetch — транспорта данных между слоями системы.