Fallback решения

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

В веб-картографии устойчивость приложения играет критически важную роль. Пользователь может работать через медленное соединение, использовать устаревший браузер, находиться за корпоративным прокси-сервером или столкнуться с недоступностью внешних сервисов. Без заранее предусмотренных fallback-механизмов карта может полностью перестать отображаться.

Основные категории fallback-решений:

  • резервные стили карты;
  • резервные источники данных;
  • обработка ошибок загрузки;
  • замена WebGL-карт альтернативными представлениями;
  • кэширование данных;
  • офлайн-режимы;
  • деградация функциональности;
  • переключение между провайдерами карт.

Причины возникновения отказов

Перед проектированием fallback-механизмов необходимо понимать основные точки отказа.

Недоступность стилей

Mapbox GL JS загружает стиль по URL:

style: 'mapbox://styles/mapbox/streets-v12'

Возможные проблемы:

  • неверный Access Token;
  • ошибки DNS;
  • проблемы CDN;
  • блокировка запросов корпоративной сетью;
  • истечение срока действия токена.

Ошибки тайлов

Карта строится на основе множества тайлов.

Причины отказов:

  • отсутствие соединения;
  • превышение квоты запросов;
  • ошибки сервера;
  • повреждённые данные.

Проблемы WebGL

Mapbox GL JS полностью зависит от WebGL.

Некоторые причины недоступности:

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

Ошибки GeoJSON

Динамические данные часто загружаются через API:

map.addSource('cities', {
    type: 'geojson',
    data: '/api/cities'
});

Если API недоступен, соответствующий слой не будет отображён.


Проверка поддержки WebGL

Первым этапом обычно становится определение возможности запуска Mapbox GL JS.

Использование встроенной проверки

Mapbox предоставляет специальный метод:

if (!mapboxgl.supported()) {
    console.error('WebGL не поддерживается');
}

Более практичный вариант:

if (!mapboxgl.supported()) {
    document.getElementById('map').innerHTML =
        '<p>Карта недоступна в данном браузере.</p>';
}

Создание альтернативного интерфейса

При отсутствии WebGL может отображаться:

  • статическая карта;
  • изображение местности;
  • таблица координат;
  • список объектов.

Пример:

if (!mapboxgl.supported()) {

    const image = document.createElement('img');

    image.src = '/static-map.png';
    image.alt = 'Карта';

    document.getElementById('map').appendChild(image);
}

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


Обработка ошибок загрузки карты

Mapbox GL JS генерирует событие ошибок.

Глобальный обработчик

map.on('error', (e) => {
    console.error(e.error);
});

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

map.on('error', (e) => {

    showNotification(
        'Произошла ошибка загрузки карты'
    );

    sendErrorToMonitoring(e.error);
});

Преимущества:

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

Резервный стиль карты

Одним из наиболее распространённых fallback-подходов является автоматическая замена недоступного стиля.

Основной и резервный стиль

const primaryStyle =
    'mapbox://styles/company/main-style';

const fallbackStyle =
    'mapbox://styles/mapbox/streets-v12';

Пример переключения:

const map = new mapboxgl.Map({
    container: 'map',
    style: primaryStyle
});

map.on('error', (e) => {

    if (e.error) {

        map.setStyle(fallbackStyle);
    }
});

Локальный стиль

Особенно надёжным вариантом является размещение JSON-стиля на собственном сервере.

style: '/styles/fallback-style.json'

Преимущества:

  • независимость от внешних сервисов;
  • контроль версий;
  • предсказуемое поведение.

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

GeoJSON часто загружается из API.

Основной источник

fetch('/api/objects')

Резервный источник

fetch('/backup/objects.json')

Реализация:

async function loadData() {

    try {

        const response =
            await fetch('/api/objects');

        return await response.json();

    } catch {

        const response =
            await fetch('/backup/objects.json');

        return await response.json();
    }
}

Данный механизм позволяет сохранять отображение объектов даже при отказе основного API.


Использование локального кэша

Одним из лучших fallback-инструментов является Local Storage.

Сохранение данных

const data = await response.json();

localStorage.setItem(
    'cachedData',
    JSON.stringify(data)
);

Загрузка из кэша

const cached =
    localStorage.getItem('cachedData');

if (cached) {

    source.setData(
        JSON.parse(cached)
    );
}

Комбинированный подход

try {

    const response =
        await fetch('/api/objects');

    const data =
        await response.json();

    localStorage.setItem(
        'cachedData',
        JSON.stringify(data)
    );

    source.setData(data);

} catch {

    const cached =
        localStorage.getItem('cachedData');

    if (cached) {

        source.setData(
            JSON.parse(cached)
        );
    }
}

Такой сценарий позволяет продолжать работу даже после потери соединения.


IndexedDB как продвинутый fallback

Для больших объёмов данных Local Storage становится недостаточным.

Преимущества IndexedDB:

  • хранение десятков мегабайт данных;
  • работа с большими GeoJSON;
  • возможность офлайн-картографии;
  • асинхронный доступ.

Схема работы:

API
 ↓
IndexedDB
 ↓
Mapbox Source

Алгоритм:

  1. Получение данных с сервера.
  2. Сохранение в IndexedDB.
  3. Использование кэша при ошибках сети.
  4. Фоновая синхронизация.

Fallback для изображений

Mapbox использует множество пользовательских иконок.

Основная загрузка

map.loadImage(
    '/icons/shop.png',
    callback
);

Резервная загрузка

map.loadImage(
    '/icons/shop.png',
    (error, image) => {

        if (error) {

            map.loadImage(
                '/icons/default.png',
                fallbackHandler
            );

            return;
        }

        map.addImage('shop', image);
    }
);

Подобный подход предотвращает исчезновение символов на карте.


Fallback для пользовательских слоёв

Некоторые слои могут зависеть от внешних сервисов.

Например:

Weather API
Traffic API
Logistics API

При отказе сервиса слой может быть скрыт.

try {

    await loadTrafficLayer();

} catch {

    map.setLayoutProperty(
        'traffic-layer',
        'visibility',
        'none'
    );
}

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


Постепенная деградация функциональности

Полное отключение карты редко является лучшим решением.

Гораздо эффективнее отключать отдельные возможности.

Уровень 1

Полная интерактивность:

  • 3D;
  • анимации;
  • кластеризация;
  • динамические слои.

Уровень 2

Отключение тяжёлых компонентов:

map.removeLayer('buildings-3d');

Уровень 3

Отключение анимаций:

map.jumpTo({
    center: [30, 50],
    zoom: 8
});

вместо

map.flyTo({
    center: [30, 50],
    zoom: 8
});

Уровень 4

Статическая карта.

Подобная стратегия позволяет сохранить максимальную работоспособность.


Fallback при потере интернет-соединения

Современные браузеры позволяют отслеживать состояние сети.

Проверка подключения

navigator.onLine

Пример:

window.addEventListener(
    'offline',
    () => {

        showOfflineMessage();
    }
);

Восстановление

window.addEventListener(
    'online',
    () => {

        refreshMapData();
    }
);

Автоматические повторные попытки

Многие сетевые ошибки являются временными.

Retry-механизм

async function fetchWithRetry(
    url,
    retries = 3
) {

    for (
        let i = 0;
        i < retries;
        i++
    ) {

        try {

            const response =
                await fetch(url);

            return response.json();

        } catch {

            await new Promise(
                r => setTimeout(r, 1000)
            );
        }
    }

    throw new Error('Ошибка загрузки');
}

Данный механизм значительно повышает устойчивость приложения.


Переключение между провайдерами

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

Архитектура:

Mapbox
  ↓
Ошибка
  ↓
MapLibre
  ↓
Ошибка
  ↓
Статическая карта

Пример конфигурации:

const providers = [
    mapboxStyle,
    backupStyle,
    localStyle
];

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


Резервные геокодеры

Поиск адресов также является потенциальной точкой отказа.

Основной сервис

Mapbox Geocoding API

Резервный сервис

Nominatim

Обёртка:

async function geocode(query) {

    try {

        return await mapboxGeocode(query);

    } catch {

        return await nominatimGeocode(query);
    }
}

Это обеспечивает непрерывность поиска объектов.


Мониторинг отказов

Fallback-механизмы должны сопровождаться системой наблюдения.

Полезно отслеживать:

  • ошибки стилей;
  • ошибки тайлов;
  • ошибки GeoJSON;
  • проблемы WebGL;
  • частоту переходов на резервные сценарии.

Пример:

map.on('error', (e) => {

    analytics.track(
        'map_error',
        {
            message: e.error.message
        }
    );
});

Накапливаемая статистика позволяет выявлять наиболее проблемные компоненты системы.


Архитектура отказоустойчивой карты

Практическая схема может выглядеть следующим образом:

Mapbox GL JS
    │
    ├── Основной стиль
    │       │
    │       └── Fallback стиль
    │
    ├── Основной API
    │       │
    │       └── Cached GeoJSON
    │
    ├── Пользовательские иконки
    │       │
    │       └── Иконки по умолчанию
    │
    ├── Онлайн режим
    │       │
    │       └── IndexedDB
    │
    └── WebGL
            │
            └── Статическая карта

Подобная многоуровневая система обеспечивает высокую устойчивость приложений на базе Mapbox GL JS к сетевым сбоям, отказам внешних сервисов, ошибкам конфигурации и ограничениям клиентской среды, сохраняя доступность картографической информации даже в неблагоприятных условиях эксплуатации.