--- type: trz work_item_id: ET-006 title: "ТЗ: Загрузка и визуализация GPX-треков" version: 2 status: approved created_at: 2026-05-22 updated_at: 2026-05-22 authors: - "agent:analyst" --- # ТЗ — ET-006: Загрузка и визуализация GPX-треков ## 1. Функциональные требования ### REQ-F-01: Кнопка загрузки GPX - В правой панели кнопок карты (`#map-controls-r`) добавляется кнопка «GPX» с иконкой загрузки (стрелка вверх + документ). - Позиция: между кнопкой «Компас» и «Моё местоположение» (верхняя часть панели). - По нажатию открывается системный диалог выбора файла (``). - Допускается множественный выбор (`multiple`). ### REQ-F-02: Парсинг GPX - Парсинг выполняется на клиенте через `DOMParser` (XML → DOM → GeoJSON). - Поддерживается GPX 1.1 (namespace `http://www.topografix.com/GPX/1/1`). - Извлекаются: - `` → массив треков, каждый `` → массив точек `[lon, lat, ele?, time?]` - `` → waypoints `{lon, lat, name?, ele?}` - `` → route points (трактуются как трек) - Если файл содержит несколько ``, каждый трек — отдельная сущность. - При ошибке парсинга — показать toast-уведомление: «Не удалось прочитать GPX-файл». ### REQ-F-03: Валидация - Максимальный размер файла: 50 МБ. При превышении — toast: «Файл слишком большой (макс. 50 МБ)». - Если файл не содержит ни одного трека и ни одного waypoint — toast: «GPX-файл не содержит данных». ### REQ-F-04: Отрисовка трека на карте - Каждый трек отрисовывается как `line` layer в MapLibre. - Source: GeoJSON (`LineString` или `MultiLineString`). - Цвет: из палитры 8 цветов, циклически. Палитра отличается от цветов роутинга (синий/зелёный/оранжевый). - Предлагаемая палитра: `#e6194b`, `#3cb44b`, `#ffe119`, `#4363d8`, `#f58231`, `#911eb4`, `#42d4f4`, `#f032e6`. - Толщина линии: 4px. - Opacity: 0.85. - Z-index: выше базовых слоёв, ниже маршрута OSRM (если активен). ### REQ-F-05: Отображение waypoints - Каждый `` отображается как маркер (circle layer + symbol layer для имени). - Цвет маркера: совпадает с цветом трека из того же файла (или нейтральный, если waypoints без трека). - Имя waypoint (``) отображается как label рядом с маркером. - Если имя отсутствует — маркер без подписи. ### REQ-F-06: Fit bounds - После загрузки файла карта выполняет `fitBounds` по bbox всех точек загруженного файла. - Padding: 50px со всех сторон. - Если загружено несколько файлов подряд — fit bounds только по последнему загруженному. ### REQ-F-07: Множественная загрузка - Треки из разных файлов накапливаются в сессии. - Каждый файл получает следующий цвет из палитры. - Максимальное количество одновременных треков: не ограничено (разумный предел — производительность браузера). ### REQ-F-08: Удаление трека - В панели управления треками (REQ-F-09) у каждого трека есть кнопка удаления (иконка ✕). - При удалении: убирается line layer, source, маркеры waypoints с карты. - Если удалён активный (выбранный) трек — панель профиля высот скрывается. ### REQ-F-09: Панель управления треками (GPX Sheet) - Реализуется как bottom sheet (`#sheet-gpx`), аналогично существующим sheet-route, sheet-recon. - Открывается автоматически при загрузке первого трека. - Содержит: - Заголовок «GPX-треки» с иконкой и кнопкой свернуть. - Список загруженных треков: цветной кружок + имя файла (без расширения) + кнопка удаления. - По тапу на трек в списке — он становится «активным» (выделяется), показывается его статистика и профиль высот. - Кнопка в тулбаре нижнего toolbar (`#toolbar`): «GPX» — переключает видимость sheet. ### REQ-F-10: Профиль высот - Отображается в нижней части sheet-gpx (под списком треков) для активного трека. - График: canvas-элемент, ширина 100% sheet, высота 120px. - Ось X: расстояние от начала трека (км). - Ось Y: высота (м). - Линия графика: цвет трека. - Заливка под линией: цвет трека с opacity 0.2. - Если данные высот отсутствуют (`` нет) — показать текст: «Данные высот отсутствуют». - При наведении/тапе на график — показать tooltip с высотой и расстоянием, и подсветить соответствующую точку на карте (маркер-курсор). ### REQ-F-11: Статистика трека - Отображается над профилем высот в sheet-gpx для активного трека. - Формат: компактная сетка (аналогично recon-grid). - Поля: - Длина (км) — сумма расстояний между точками (Haversine). - Набор высоты (м) — сумма положительных дельт `ele`. - Сброс высоты (м) — сумма отрицательных дельт `ele` (абсолютное значение). - Мин. высота (м). - Макс. высота (м). - Если данные высот отсутствуют — показать только длину, остальные поля: «—». ### REQ-F-12: Интерактивность трека на карте - При клике на линию трека на карте — этот трек становится активным в панели (показывается статистика + профиль). - Курсор при наведении на трек: pointer. ### REQ-F-13: Сохранение треков при переключении стиля карты - При переключении стиля карты (тёмная тема, восстановление слоёв рельефа) вызывается `map.setStyle()`, который удаляет **все** пользовательские source и layer. - После смены стиля все загруженные GPX-треки должны быть автоматически восстановлены: линии треков, source, waypoints-маркеры. - Восстановление выполняется в функции `rebuildMapOverlays()` (`src/web/app.js`) — по аналогии с уже реализованными там маршрутом OSRM, разведкой и scenic-маршрутами. - Данные треков (`window.gpxTracks`) хранятся в памяти и при `setStyle()` не теряются — пересоздаются только объекты карты (source / layer / маркеры). - Активный трек, его статистика и профиль высот должны сохраняться после переключения стиля. - Z-order GPX-слоёв (см. REQ-F-04) корректно восстанавливается и после смены стиля. ## 2. Нефункциональные требования ### REQ-NF-01: Производительность - Парсинг файла 50 МБ: ≤ 5 секунд на устройстве с 4 ГБ RAM. - Рендеринг трека 500K точек: без видимых фризов при pan/zoom (MapLibre оптимизирует GeoJSON line layers). - Во время парсинга показывать индикатор загрузки (spinner или moto-wheel). ### REQ-NF-02: Совместимость - Работает в Chrome 90+, Firefox 90+, Safari 15+. - Работает на мобильных (touch events для профиля высот). ### REQ-NF-03: UX - Кнопка загрузки доступна всегда, независимо от активного режима (роутинг, разведка и т.д.). - GPX-треки не конфликтуют с активным маршрутом OSRM — отображаются одновременно. - При ошибках — toast-уведомления (не alert/confirm). ### REQ-NF-04: Хранение - Данные треков хранятся только в памяти (JS-переменные). - При перезагрузке страницы — все треки теряются. - Не используется localStorage/sessionStorage для данных треков (слишком большие). ## 3. UI-спецификация ### 3.1 Кнопка в правой панели (#map-controls-r) ``` ┌──────────┐ │ ↑ GPX │ ← новая кнопка (между Компас и Геолокация) └──────────┘ ``` - Класс: `map-btn` - ID: `btn-gpx-upload` - Иконка: стрелка вверх из документа (upload file) - Title: «Загрузить GPX» ### 3.2 Кнопка в нижнем тулбаре (#toolbar) ``` [ Маршрут | Связка | Красивый | Разведка | Линейка | Поиск | Метка | GPX ] ``` - Класс: `tb-btn` - ID: `tb-gpx` - Иконка: файл с линией (track) - Label: «GPX» - Действие: `toggleGpxSheet()` ### 3.3 Bottom sheet (#sheet-gpx) ``` ┌─────────────────────────────────────┐ │ ═══ (handle) │ │ 📄 GPX-треки [свернуть]│ ├─────────────────────────────────────┤ │ 🔴 track_morning.gpx [✕] │ │ 🔵 weekend_ride.gpx ✓ [✕] │ ← активный (выделен) │ 🟢 test_route.gpx [✕] │ ├─────────────────────────────────────┤ │ СТАТИСТИКА │ │ ┌────┐ ┌────┐ ┌────┐ ┌────┐ ┌────┐ │ │ │47км│ │820м│ │650м│ │120м│ │980м│ │ │ │длин│ │наб.│ │сбр.│ │мин │ │макс│ │ │ └────┘ └────┘ └────┘ └────┘ └────┘ │ ├─────────────────────────────────────┤ │ ПРОФИЛЬ ВЫСОТ │ │ ┌───────────────────────────────┐ │ │ │ ╱╲ ╱╲╱╲ │ │ │ │╱ ╲╱╲╱ ╲╱╲ │ │ │ └───────────────────────────────┘ │ │ 0 km 23.5 km 47 km │ └─────────────────────────────────────┘ ``` ### 3.4 Toast-уведомления - Позиция: верх экрана, по центру. - Автоскрытие: 4 секунды. - Стиль: аналогично `#ruler-toast`. ## 4. Данные ### Входные данные (GPX 1.1) ```xml Morning Ride 150 ... Кафе 155 ``` ### Внутренняя модель (JS) ```javascript // Массив загруженных GPX-файлов window.gpxTracks = [ { id: 'gpx-1716336000000', // уникальный ID (timestamp) filename: 'morning_ride', // имя файла без расширения color: '#e6194b', // цвет из палитры tracks: [ // массив треков из файла { name: 'Morning Ride', points: [[lon, lat, ele, time], ...], // массив точек stats: { distanceKm, elevGain, elevLoss, eleMin, eleMax } } ], waypoints: [ { lon, lat, name, ele } ], sourceId: 'gpx-source-1716336000000', layerId: 'gpx-layer-1716336000000', waypointLayerId: 'gpx-wpt-1716336000000' } ]; ``` ## 5. Алгоритмы ### 5.1 Расчёт расстояния (Haversine) Сумма расстояний между последовательными точками трека. Формула Haversine для каждой пары. ### 5.2 Расчёт набора/сброса высоты ``` elevGain = Σ max(0, ele[i+1] - ele[i]) для всех i elevLoss = Σ max(0, ele[i] - ele[i+1]) для всех i ``` Фильтрация шума: игнорировать дельты < 2 м (GPS-шум). ### 5.3 Палитра цветов Циклический массив из 8 цветов. Индекс = `gpxTracks.length % 8` на момент добавления. ## 6. Файловая структура изменений ``` src/web/ ├── index.html # + кнопка в #map-controls-r, + sheet-gpx, + tb-btn ├── app.js # + gpx-модуль (парсинг, рендеринг, управление) ├── app.css # + стили sheet-gpx, профиля высот, toast ``` Альтернативно, GPX-логику можно вынести в отдельный файл `gpx.js` (аналогично `units.js`). ## 7. Взаимодействие с существующими режимами - GPX-треки отображаются **параллельно** с любым активным режимом (роутинг, разведка, красивый маршрут). - Z-order: GPX-треки ниже активного маршрута OSRM, но выше базовых слоёв (trails, terrain). - Кнопка загрузки в `#map-controls-r` доступна всегда. - Кнопка «GPX» в toolbar переключает sheet, но не деактивирует другие режимы. - При смене стиля карты (`setStyle` — тёмная тема, слои рельефа) GPX-слои восстанавливаются через `rebuildMapOverlays()` — см. REQ-F-13.