2D-режим на Three.js и всплывающий список авиакомпаний

22.03.2026
2D-режим на Three.js и всплывающий список авиакомпаний

Изначально 2D-карта работала на Canvas API с Web Worker-ом для оффскрин-рендеринга. Схема была рабочей — тайлы, кэш, dirty-флаги для отдельных слоёв. Но при активном зуме или выборе новой авиакомпании всё равно чувствовались подтормаживания: Canvas приходилось перерисовывать слои заново, пусть и по частям.

Решение напрашивалось само: раз уж 3D-режим уже работает на Three.js и летает, логично было перевести и 2D на тот же движок. В итоге — получилось существенно быстрее.

Проекция и камера

Для плоской карты выбрал проекцию Миллера вместо Меркатора. Меркатор даёт бесконечность на полюсах, Миллер — нет:

Copy
x = lon / 180
y = 1.25 × ln(tan(π/4 + 0.4 × latRad)) / π

Карта охватывает диапазон от 78°N до 60°S — достаточно, чтобы вместить все коммерческие аэропорты и крупные хабы.

Камера — ортографическая. Никакой перспективной дисторсии, честная плоская проекция. Зум ограничен снизу, чтобы карта не уходила за границы видимости. Плавный влёт к выбранному аэропорту реализован через линейную интерполяцию (LERP) каждый кадр — ощущение мягкого «подлёта» без скачков.

Слои сцены

Сцена строится из нескольких слоёв с явным порядком рендеринга:

  1. Суша — меш из полигонов TopoJSON, треугольники через Earcut, opacity 0.04
  2. Границы стран — линии без дублирования рёбер, opacity 0.09
  3. Маршруты — квадратичные кривые Безье, 36 сегментов на маршрут, opacity 0.16
  4. Выделенные маршруты — толстые линии с анимированным свечением, opacity 0.95
  5. Точки аэропортов — инстансированный меш, один draw call на всё множество точек

Для суши и границ действует LOD: при зуме меньше 2.5 используется упрощённый датасет (countries-110m), при приближении автоматически подгружается детальный (countries-50m). Тяжёлый датасет строится в requestIdleCallback, чтобы не блокировать UI при старте.

Отдельно пришлось решить проблему антимеридиана — разрыв полигонов на ±180° даёт артефакты при триангуляции. Лечится размоткой долгот до непрерывного диапазона перед построением меша.

Маршруты и параллельные дуги

Маршруты — квадратичные дуги Безье. Для пар аэропортов, между которыми летает несколько авиакомпаний одновременно, реализовано lane-разнесение: нулевой лейн идёт по прямой хорде, остальные смещаются перпендикулярно на величину N × min(distance × 0.08, 0.06) единиц мировых координат. Небольшой сдвиг вдоль хорды предотвращает пиксельное слияние параллельных маршрутов.

Анимация выделения

Для выделенных маршрутов — кастомный GLSL-шейдер с анимацией бегущего свечения:

glsl Copy
float wave = fract(uTime * speed - vProgress);

Переменная vProgress интерполируется от 0 до 1 вдоль кривой. Результат — бегущий гребень свечения с хвостом длиной 0.35 от длины маршрута. Цвет смешивается между базовым оранжевым (#eb610b) и жёлтым пиком (#ffe757).

Прирост производительности

Переход на Three.js дал несколько ощутимых преимуществ:

  • GPU-рендеринг вместо CPU Canvas: геометрия отправляется на видеокарту один раз, дальше только трансформации матрицей
  • Инстансированный меш для точек аэропортов: сотни точек — один draw call
  • Dirty-флаг анимационного цикла: если нет активного лёта и нет анимации — рендер пропускается полностью
  • LOD с deferred-загрузкой: тяжёлый датасет не блокирует первый фрейм

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


Список авиакомпаний по аэропорту

Параллельно добавил функцию, которой мне самому давно не хватало при работе с картой.

Раньше, чтобы посмотреть маршруты авиакомпании из конкретного аэропорта, нужно было знать, кто вообще там летает — и искать в списке. Неудобно, особенно если аэропорт незнакомый.

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

Карточка аэропорта — IATA/ICAO-коды, координаты, город и страна.

Список авиакомпаний — все перевозчики, которые выполняют рейсы из этого аэропорта. Для каждой показывается IATA-код, название, количество направлений и частота рейсов. Клик на авиакомпанию — и карта сразу переключается на её маршрутную сеть с автоматической подгонкой камеры под охват сети.

Реализация

Данные подтягиваются двумя параллельными запросами:

Copy
GET /airlines/airports/iata/{code}?lang=ru   — детали аэропорта
GET /airlines/routes/airport/{code}          — авиакомпании из аэропорта

Результаты кэшируются локально, повторный клик на тот же аэропорт не делает новых запросов.

Позиционирование панели — с логикой умного размещения: сначала считается доступное пространство справа и слева от точки привязки, панель открывается там, где места больше. Вертикальная позиция прижата к краям вьюпорта с отступом 8px. Стрелка-указатель выравнивается по Y-координате аэропорта через CSS-переменную --arrow-offset.

Закрытие — клик вне панели или скролл карты.


TODO

Следующий крупный блок, который хочу реализовать — интерфейс редактирования данных. Сейчас вся информация об аэропортах, авиакомпаниях и маршрутных сетях поступает из внешних источников и обновляется автоматически. Но автоматика не всегда точна: бывают устаревшие маршруты, отсутствующие названия на русском, неверные координаты.

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

  • Аэропорты — коррекция названий, координат, привязки к городу
  • Авиакомпании — обновление названий, логотипов, статуса активности
  • Маршрутные сети — добавление и удаление маршрутов, корректировка частоты рейсов

Это даст возможность поддерживать актуальность данных независимо от внешних источников.

Читать далее

22.03.2026

Автоматизация деплоя: от пуша до прогретого кеша

Ручной деплой — это всегда лотерея. Забытый npm run build перед копированием файлов, кеш старой версии на сервере, тесты которые «ну и так понятно что работает». В какой-то момент надоело играть в эту игру и я собрал нормальный пайплайн на GitHub Actions.

Метки
CI/CDGitHub ActionsDevOpsавтоматизациядеплой
23.03.2026

Каталог авиакомпаний и аэропортов: модальное окно с навигацией по маршрутной сети

Новый каталог авиакомпаний и аэропортов позволяет исследовать маршрутные сети без необходимости знать IATA-код — достаточно выбрать букву, кликнуть на перевозчика и увидеть все его направления прямо на карте.

Метки
vue3three.jspostgresqlавиациявизуализация-данных
23.03.2026

Переход на модели Airport/Company и кеширование данных на клиенте

Обновил клиентскую архитектуру карты: перешёл с «сырых» API-объектов на модели Airport и Company, а также внедрил многоуровневое кеширование данных. В статье разобрал, как это упростило код, ускорило интерфейс и позволило безопасно кешировать каталог авиакомпаний/аэропортов с автоматической инвалидиацией по версии данных (newest_update). Есть практические фрагменты кода: нормализация моделей, lazy enrich и версионирование кеша.

Метки
vue3typescriptfrontend-architecturedata-modelingcachingperformance