Files
enduro-trails/docs/work-items/ET-006/02-trz.md

290 lines
16 KiB
Markdown
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
---
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» с иконкой загрузки (стрелка вверх + документ).
- Позиция: между кнопкой «Компас» и «Моё местоположение» (верхняя часть панели).
- По нажатию открывается системный диалог выбора файла (`<input type="file" accept=".gpx">`).
- Допускается множественный выбор (`multiple`).
### REQ-F-02: Парсинг GPX
- Парсинг выполняется на клиенте через `DOMParser` (XML → DOM → GeoJSON).
- Поддерживается GPX 1.1 (namespace `http://www.topografix.com/GPX/1/1`).
- Извлекаются:
- `<trk>` → массив треков, каждый `<trkseg>` → массив точек `[lon, lat, ele?, time?]`
- `<wpt>` → waypoints `{lon, lat, name?, ele?}`
- `<rte>` → route points (трактуются как трек)
- Если файл содержит несколько `<trk>`, каждый трек — отдельная сущность.
- При ошибке парсинга — показать 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
- Каждый `<wpt>` отображается как маркер (circle layer + symbol layer для имени).
- Цвет маркера: совпадает с цветом трека из того же файла (или нейтральный, если waypoints без трека).
- Имя waypoint (`<name>`) отображается как 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.
- Если данные высот отсутствуют (`<ele>` нет) — показать текст: «Данные высот отсутствуют».
- При наведении/тапе на график — показать 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
<?xml version="1.0" encoding="UTF-8"?>
<gpx version="1.1" xmlns="http://www.topografix.com/GPX/1/1">
<trk>
<name>Morning Ride</name>
<trkseg>
<trkpt lat="55.7558" lon="37.6173"><ele>150</ele><time>2026-01-01T08:00:00Z</time></trkpt>
...
</trkseg>
</trk>
<wpt lat="55.76" lon="37.62">
<name>Кафе</name>
<ele>155</ele>
</wpt>
</gpx>
```
### Внутренняя модель (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.