Web Workers для обработки данных

Интерактивные карты требуют постоянной синхронизации множества процессов: от отрисовки тайлов до обработки пользовательских событий и управления большим количеством геообъектов. Основная проблема клиентского кода заключается в том, что главный поток JavaScript одновременно отвечает за UI и за вычисления. При росте объёма данных (маршруты, кластеры маркеров, GeoJSON, тепловые карты) это приводит к деградации отзывчивости интерфейса.

Web Workers позволяют вынести тяжёлые операции из main thread, сохраняя плавность работы карты даже при обработке тысяч объектов.


Модель потоков и ограничения основного потока карты

JavaScript в браузере традиционно выполняется в одном потоке. В контексте карт это означает:

  • отрисовка карты и слоёв выполняется на main thread;
  • обработка событий (drag, zoom, click) также там же;
  • вычисление геометрии, кластеризация и фильтрация данных конкурируют за CPU.

Даже кратковременная блокировка приводит к заметным лагам при панорамировании или масштабировании.

Web Workers вводят модель параллельного выполнения:

  • отдельный поток без доступа к DOM;
  • обмен данными через postMessage;
  • независимое выполнение вычислений.

Вынос гео-вычислений в Web Worker

Типичный сценарий — обработка массивов координат перед отображением на карте. Например:

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

Пример архитектуры

Главный поток:

const worker = new Worker('geo-worker.js');

worker.postMessage({
  type: 'CLUSTER_POINTS',
  points: largeDataset,
  zoom: map.getZoom()
});

worker.onmess age = (e) => {
  const clusters = e.data;

  clusters.forEach(cluster => {
    new google.maps.Marker({
      position: cluster.center,
      map
    });
  });
};

Worker:

self.onmess age = (e) => {
  const { type, points, zoom } = e.data;

  if (type === 'CLUSTER_POINTS') {
    const result = clusterPoints(points, zoom);
    self.postMessage(result);
  }
};

Кластеризация как вычислительно тяжёлая задача

При отображении тысяч точек стандартный рендеринг маркеров приводит к перегрузке DOM и Canvas. Перенос логики кластеризации в worker позволяет:

  • снизить нагрузку на main thread;
  • заранее агрегировать данные;
  • уменьшить количество объектов google.maps.Marker.

Алгоритмически используются:

  • grid-based clustering;
  • k-means (для плотных распределений);
  • distance threshold clustering.

Worker возвращает уже агрегированные объекты:

[
  {
    "center": { "lat": 51.5, "lng": 30.5 },
    "count": 120
  }
]

Обработка GeoJSON вне основного потока

Google Maps JavaScript API поддерживает отображение GeoJSON через map.data.addGeoJson, но предварительная обработка больших файлов может занимать значительное время.

Web Worker используется для:

  • фильтрации слоёв по атрибутам;
  • упрощения геометрии (Douglas-Peucker);
  • разбиения данных на тайлы;
  • преобразования CRS.

Пример упрощения геометрии:

importScripts('simplify.js');

self.onmess age = (e) => {
  const simplified = simplifyGeoJSON(e.data, 0.0001);
  self.postMessage(simplified);
};

После обработки данные передаются в Data Layer:

worker.onmess age = (e) => {
  map.data.addGeoJson(e.data);
};

Потоковая обработка маршрутов

При работе с маршрутами часто требуется:

  • декодирование polyline;
  • построение альтернативных путей;
  • анализ дорожных сегментов;
  • расчёт статистики (время, расстояние, уклоны).

Эти операции эффективно выносятся в worker:

self.onmess age = (e) => {
  const route = decodePolyline(e.data.polyline);
  const stats = analyzeRoute(route);

  self.postMessage({ route, stats });
};

Основной поток получает уже готовую структуру для визуализации:

  • линия маршрута (google.maps.Polyline);
  • маркеры начала/конца;
  • информационные панели.

Передача данных и стоимость сериализации

Web Workers используют структурированное клонирование. Это создаёт важное ограничение:

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

Оптимизация:

Transferable Objects

worker.postMessage(buffer, [buffer]);

Используется для:

  • ArrayBuffer (например, бинарные геоданные);
  • ImageBitmap (при OffscreenCanvas рендеринге).

Использование OffscreenCanvas для рендеринга слоёв

В сочетании с worker возможно выполнение отрисовки вне main thread:

  • heatmaps;
  • кастомные тайлы;
  • WebGL-слои.

Worker:

const canvas = new OffscreenCanvas(800, 600);
const ctx = canvas.getContext('2d');

drawHeatmap(ctx, data);

self.postMessage(canvas.transferToImageBitmap(), [canvas]);

Главный поток:

overlay.on Add = function () {
  this.img = document.createElement('img');

  worker.onmess age = (e) => {
    this.img.src = e.data;
  };
};

Геопространственная фильтрация в worker

Типичный кейс — фильтрация точек по видимой области карты:

self.onmess age = (e) => {
  const { bounds, points } = e.data;

  const filtered = points.filter(p =>
    p.lat >= bounds.south &&
    p.lat <= bounds.north &&
    p.lng >= bounds.west &&
    p.lng <= bounds.east
  );

  self.postMessage(filtered);
};

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

  • событие idle инициирует пересчёт;
  • worker возвращает только релевантные точки;
  • минимизируется количество DOM-объектов.

Управление жизненным циклом worker в картографических приложениях

При работе с картой важно избегать утечек памяти:

  • worker должен создаваться один раз;
  • пересоздание допустимо только при смене набора данных;
  • требуется явное завершение:
worker.terminate();

Типичная схема:

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

Многовекторная обработка данных

В сложных системах применяется пул workers:

  • один worker для кластеризации;
  • другой для маршрутов;
  • третий для фильтрации GeoJSON.

Распределение задач:

clusterWorker.postMessage(data);
routeWorker.postMessage(data);
filterWorker.postMessage(data);

Это снижает конкуренцию за CPU и улучшает параллелизм.


Синхронизация состояния карты и worker

Основная сложность — согласование асинхронных результатов с текущим состоянием карты:

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

Решение — версии запросов:

let requestId = 0;

function update() {
  const id = ++requestId;

  worker.postMessage({ id, data });

  worker.onmess age = (e) => {
    if (e.data.id !== requestId) return;
    render(e.data.result);
  };
}

Ограничения и архитектурные последствия

Использование Web Workers в картографических приложениях приводит к изменению структуры приложения:

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

При этом достигается ключевой эффект — стабильная отзывчивость интерфейса при росте объёма геоданных и сложности визуализации.