diff --git a/docs/work-items/ET-006/00-business-request.md b/docs/work-items/ET-006/00-business-request.md new file mode 100644 index 0000000..9f7f369 --- /dev/null +++ b/docs/work-items/ET-006/00-business-request.md @@ -0,0 +1,28 @@ +--- +type: business-request +work_item_id: ET-006 +title: "Загрузка и визуализация GPX-треков на карте" +created_at: 2026-05-22 +source: telegram +requester: Слава +--- + +# Бизнес-запрос — ET-006 + +## Исходная формулировка + +> Нужно добавить опцию загрузки GPX-треков. Я хочу нажать на кнопку в интерфейсе, выбрать файл с треком и далее увидеть его визуализацию на карте. + +## Уточнения (из диалога) + +1. Формат: GPX 1.1 стандартный. Возможны несколько треков в одном файле. +2. Визуализация: линия трека + waypoints как маркеры + профиль высот + статистика (длина, набор/сброс высоты). +3. Кнопка: отдельная в интерфейсе. +4. После загрузки: карта центрируется на треке (fit bounds). +5. Можно загрузить несколько треков одновременно. +6. Можно удалить загруженный трек. +7. Хранение: только на время сессии (в памяти браузера), без сохранения на сервер. +8. Каждый трек — свой цвет (для различения). +9. Лимит файла: до 50 МБ, без упрощения (simplify) точек. +10. Waypoints из GPX: показывать как маркеры на карте с именами. +11. UX профиля высот и статистики — на усмотрение аналитика. diff --git a/docs/work-items/ET-006/01-brd.md b/docs/work-items/ET-006/01-brd.md new file mode 100644 index 0000000..af9de27 --- /dev/null +++ b/docs/work-items/ET-006/01-brd.md @@ -0,0 +1,81 @@ +--- +type: brd +work_item_id: ET-006 +title: "BRD: Загрузка и визуализация GPX-треков" +version: 1 +status: draft +created_at: 2026-05-22 +authors: + - "agent:analyst" +--- + +# BRD — ET-006: Загрузка и визуализация GPX-треков + +## 1. Цель + +Дать пользователю возможность загрузить GPX-файл с треком и увидеть его на карте: линию маршрута, waypoints, профиль высот и статистику. Это позволяет визуально оценить чужой или ранее записанный трек перед поездкой. + +## 2. Контекст + +- Приложение уже умеет строить маршруты через OSRM и экспортировать их в GPX (кнопка «Скачать GPX» в sheet-route). +- Обратная операция — импорт GPX — отсутствует. +- Фаза PH-3 (Smart Route) в roadmap включает работу с GPX. +- Фронтенд: MapLibre GL JS + vanilla JS, без фреймворков. +- Backend-изменения не требуются — парсинг GPX происходит на клиенте. + +## 3. Scope + +### In scope + +| # | Функция | +|---|---------| +| F-01 | Кнопка загрузки GPX в тулбаре карты | +| F-02 | Парсинг GPX 1.1 на клиенте (XML → GeoJSON) | +| F-03 | Поддержка нескольких треков в одном файле | +| F-04 | Отрисовка линии трека на карте (каждый трек — свой цвет) | +| F-05 | Отображение waypoints из GPX как маркеров с именами | +| F-06 | Fit bounds — карта центрируется на загруженном треке | +| F-07 | Загрузка нескольких файлов (треки накапливаются) | +| F-08 | Удаление отдельного трека | +| F-09 | Панель управления треками (список, цвет, удаление) | +| F-10 | Профиль высот выбранного трека | +| F-11 | Статистика трека: длина, набор высоты, сброс высоты, мин/макс высота | +| F-12 | Лимит размера файла: 50 МБ | + +### Out of scope + +- Сохранение треков на сервер / в БД +- Редактирование трека (обрезка, склейка) +- Конвертация из других форматов (KML, FIT, TCX) +- Упрощение (simplify) точек трека +- Экспорт загруженного трека обратно в GPX +- Роутинг по загруженному треку (snap to road) + +## 4. Метрики успеха + +| Метрика | Критерий | +|---------|----------| +| Загрузка файла | Файл до 50 МБ загружается и парсится без ошибок за ≤ 3 сек (на среднем устройстве) | +| Визуализация | Трек отображается на карте как цветная линия | +| Waypoints | Маркеры с именами видны на карте | +| Fit bounds | Карта автоматически подстраивает zoom/center под трек | +| Множественные треки | 5+ треков отображаются одновременно, различимы по цвету | +| Удаление | Удалённый трек исчезает с карты и из панели | +| Профиль высот | Отображается корректный график высот для выбранного трека | +| Статистика | Длина, набор/сброс высоты отображаются корректно | +| Не ломает существующий функционал | Роутинг, рельеф, POI, линейка работают как прежде | + +## 5. Риски + +| Риск | Вероятность | Влияние | Митигация | +|------|-------------|---------|-----------| +| Большой GPX (50 МБ, 500K+ точек) тормозит рендеринг | Средняя | Среднее | Использовать GeoJSON source + line layer (MapLibre оптимизирует); при необходимости — Web Worker для парсинга | +| GPX без данных высот → профиль пустой | Средняя | Низкое | Показать сообщение «Данные высот отсутствуют» | +| Невалидный GPX → ошибка парсинга | Низкая | Низкое | Показать пользователю понятное сообщение об ошибке | +| Конфликт цветов треков с цветами маршрута OSRM | Низкая | Низкое | Использовать отдельную палитру, отличную от цветов роутинга | + +## 6. Зависимости + +- Нет внешних зависимостей +- Только фронтенд (vanilla JS + MapLibre GL JS) +- Парсинг XML: нативный DOMParser браузера diff --git a/docs/work-items/ET-006/02-trz.md b/docs/work-items/ET-006/02-trz.md new file mode 100644 index 0000000..5ffa5c0 --- /dev/null +++ b/docs/work-items/ET-006/02-trz.md @@ -0,0 +1,278 @@ +--- +type: trz +work_item_id: ET-006 +title: "ТЗ: Загрузка и визуализация GPX-треков" +version: 1 +status: draft +created_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. + +## 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, но не деактивирует другие режимы. diff --git a/docs/work-items/ET-006/03-acceptance-criteria.md b/docs/work-items/ET-006/03-acceptance-criteria.md new file mode 100644 index 0000000..e986575 --- /dev/null +++ b/docs/work-items/ET-006/03-acceptance-criteria.md @@ -0,0 +1,234 @@ +--- +type: acceptance-criteria +work_item_id: ET-006 +title: "AC: Загрузка и визуализация GPX-треков" +version: 1 +status: draft +created_at: 2026-05-22 +authors: + - "agent:analyst" +--- + +# Acceptance Criteria — ET-006: Загрузка и визуализация GPX-треков + +## AC-01: Загрузка файла через кнопку + +```gherkin +Feature: Загрузка GPX-файла + + Scenario: Успешная загрузка одного файла + Given пользователь находится на карте + When пользователь нажимает кнопку «Загрузить GPX» в правой панели + And выбирает валидный GPX-файл размером < 50 МБ + Then файл парсится без ошибок + And трек отображается на карте цветной линией + And карта выполняет fit bounds по треку + And в панели GPX-треков появляется запись с именем файла + + Scenario: Файл превышает лимит + Given пользователь находится на карте + When пользователь выбирает GPX-файл размером > 50 МБ + Then показывается toast «Файл слишком большой (макс. 50 МБ)» + And трек не загружается + + Scenario: Невалидный файл + Given пользователь находится на карте + When пользователь выбирает файл с невалидным XML + Then показывается toast «Не удалось прочитать GPX-файл» + And трек не загружается + + Scenario: Пустой GPX (без треков и waypoints) + Given пользователь находится на карте + When пользователь выбирает GPX-файл без и без + Then показывается toast «GPX-файл не содержит данных» +``` + +## AC-02: Визуализация трека + +```gherkin +Feature: Отрисовка трека на карте + + Scenario: Один трек в файле + Given загружен GPX-файл с одним треком + Then на карте отображается линия трека + And линия имеет цвет из палитры + And толщина линии 4px + + Scenario: Несколько треков в одном файле + Given загружен GPX-файл с 3 треками + Then на карте отображаются 3 линии + And все линии одного цвета (цвет файла) + And в панели — одна запись (имя файла) с 3 треками внутри + + Scenario: Несколько файлов + Given загружены 3 GPX-файла + Then на карте отображаются треки из всех файлов + And каждый файл имеет свой цвет из палитры + And в панели — 3 записи +``` + +## AC-03: Waypoints + +```gherkin +Feature: Отображение waypoints + + Scenario: Waypoints с именами + Given загружен GPX-файл с waypoints, у которых есть + Then на карте отображаются маркеры в позициях waypoints + And рядом с каждым маркером отображается имя + + Scenario: Waypoints без имён + Given загружен GPX-файл с waypoints без + Then на карте отображаются маркеры без подписей + + Scenario: Файл без waypoints + Given загружен GPX-файл без + Then маркеры waypoints не отображаются + And трек отображается нормально +``` + +## AC-04: Fit bounds + +```gherkin +Feature: Автоцентрирование карты + + Scenario: Карта центрируется на загруженном треке + Given карта показывает произвольную область + When пользователь загружает GPX-файл + Then карта выполняет fit bounds по всем точкам файла + And padding составляет 50px со всех сторон + + Scenario: Загрузка второго файла + Given на карте уже есть загруженный трек + When пользователь загружает второй GPX-файл + Then карта выполняет fit bounds только по второму файлу +``` + +## AC-05: Удаление трека + +```gherkin +Feature: Удаление загруженного трека + + Scenario: Удаление трека из панели + Given загружены 2 GPX-файла + When пользователь нажимает кнопку удаления (✕) у первого трека + Then первый трек исчезает с карты (линия + waypoints) + And первый трек исчезает из панели + And второй трек остаётся на карте + + Scenario: Удаление активного трека + Given загружены 2 GPX-файла, второй — активный (показана статистика) + When пользователь удаляет второй трек + Then профиль высот и статистика скрываются + And первый трек остаётся на карте + And первый трек не становится автоматически активным + + Scenario: Удаление последнего трека + Given загружен 1 GPX-файл + When пользователь удаляет его + Then карта пуста (нет GPX-слоёв) + And панель GPX показывает пустое состояние +``` + +## AC-06: Панель управления (sheet-gpx) + +```gherkin +Feature: Панель GPX-треков + + Scenario: Открытие панели при загрузке + Given панель GPX закрыта + When пользователь загружает первый GPX-файл + Then панель GPX открывается автоматически + + Scenario: Переключение через toolbar + Given панель GPX закрыта + When пользователь нажимает кнопку «GPX» в нижнем тулбаре + Then панель GPX открывается + + Scenario: Выбор активного трека + Given загружены 3 GPX-файла + When пользователь тапает на второй трек в списке + Then второй трек выделяется визуально + And показывается его статистика и профиль высот +``` + +## AC-07: Профиль высот + +```gherkin +Feature: Профиль высот + + Scenario: Трек с данными высот + Given выбран активный трек с данными + Then в панели отображается график профиля высот + And ось X — расстояние (км) + And ось Y — высота (м) + And линия графика — цвет трека + + Scenario: Трек без данных высот + Given выбран активный трек без данных + Then вместо графика отображается текст «Данные высот отсутствуют» + + Scenario: Интерактивность профиля + Given отображается профиль высот + When пользователь наводит курсор (или тапает) на точку графика + Then показывается tooltip с высотой и расстоянием + And на карте подсвечивается соответствующая точка трека +``` + +## AC-08: Статистика трека + +```gherkin +Feature: Статистика трека + + Scenario: Полная статистика (с высотами) + Given выбран активный трек с данными + Then отображаются: длина (км), набор высоты (м), сброс высоты (м), мин. высота (м), макс. высота (м) + + Scenario: Частичная статистика (без высот) + Given выбран активный трек без данных + Then отображается только длина (км) + And остальные поля показывают «—» +``` + +## AC-09: Интерактивность на карте + +```gherkin +Feature: Клик по треку на карте + + Scenario: Выбор трека кликом + Given на карте отображаются 3 трека из разных файлов + When пользователь кликает на линию второго трека + Then второй трек становится активным в панели + And показывается его статистика и профиль высот +``` + +## AC-10: Совместимость с другими режимами + +```gherkin +Feature: Параллельная работа с роутингом + + Scenario: GPX + активный маршрут + Given пользователь построил маршрут через OSRM + And загрузил GPX-файл + Then оба отображаются на карте одновременно + And маршрут OSRM визуально выше GPX-трека + And оба интерактивны + + Scenario: GPX + режим разведки + Given пользователь в режиме разведки + When загружает GPX-файл + Then трек отображается на карте + And режим разведки продолжает работать +``` + +## AC-11: Индикатор загрузки + +```gherkin +Feature: Индикатор при парсинге + + Scenario: Большой файл + Given пользователь выбирает GPX-файл > 10 МБ + Then показывается индикатор загрузки (spinner) + And после завершения парсинга индикатор скрывается + And трек отображается на карте +``` diff --git a/docs/work-items/ET-006/04-test-plan.yaml b/docs/work-items/ET-006/04-test-plan.yaml new file mode 100644 index 0000000..ff6c45d --- /dev/null +++ b/docs/work-items/ET-006/04-test-plan.yaml @@ -0,0 +1,248 @@ +--- +type: test-plan +work_item_id: ET-006 +title: "Test Plan: Загрузка и визуализация GPX-треков" +version: 1 +status: draft +created_at: 2026-05-22 +authors: + - "agent:analyst" + +test_suites: + + - name: unit-gpx-parser + type: unit + description: "Парсинг GPX XML → внутренняя модель" + cases: + - id: U-01 + name: "Парсинг валидного GPX 1.1 с одним треком" + input: "GPX-файл с 1 trk, 1 trkseg, 10 trkpt (lat, lon, ele, time)" + expected: "Возвращает объект с 1 треком, 10 точками, корректными координатами и высотами" + + - id: U-02 + name: "Парсинг GPX с несколькими треками" + input: "GPX-файл с 3 trk" + expected: "Возвращает массив из 3 треков" + + - id: U-03 + name: "Парсинг waypoints" + input: "GPX-файл с 5 wpt (lat, lon, name, ele)" + expected: "Возвращает массив из 5 waypoints с именами и координатами" + + - id: U-04 + name: "Парсинг route (rte)" + input: "GPX-файл с 1 rte, 20 rtept" + expected: "Возвращает как трек с 20 точками" + + - id: U-05 + name: "GPX без данных высот" + input: "GPX-файл с trkpt без " + expected: "Точки имеют ele=null, stats.elevGain=null" + + - id: U-06 + name: "Невалидный XML" + input: "Файл с битым XML" + expected: "Выбрасывает ошибку с сообщением" + + - id: U-07 + name: "Пустой GPX (нет trk, wpt, rte)" + input: "Валидный XML, но без данных" + expected: "Выбрасывает ошибку 'no data'" + + - id: U-08 + name: "GPX с namespace и без" + input: "GPX без xmlns атрибута" + expected: "Парсится корректно (fallback без namespace)" + + - name: unit-gpx-stats + type: unit + description: "Расчёт статистики трека" + cases: + - id: U-10 + name: "Расчёт длины (Haversine)" + input: "Трек из 3 точек: [37.6, 55.7], [37.7, 55.8], [37.8, 55.9]" + expected: "Длина ≈ 28.3 км (±0.5 км)" + + - id: U-11 + name: "Расчёт набора высоты" + input: "Точки с ele: [100, 150, 120, 200, 180]" + expected: "elevGain = 130 м (50 + 80), elevLoss = 70 м (30 + 20)" + + - id: U-12 + name: "Фильтрация шума высот (дельта < 2м)" + input: "Точки с ele: [100, 101, 100, 101, 150]" + expected: "elevGain = 50 м (только 100→150), мелкие колебания игнорируются" + + - id: U-13 + name: "Мин/макс высота" + input: "Точки с ele: [100, 250, 80, 300, 150]" + expected: "eleMin=80, eleMax=300" + + - id: U-14 + name: "Статистика без данных высот" + input: "Точки без ele" + expected: "distanceKm рассчитан, elevGain/elevLoss/eleMin/eleMax = null" + + - name: unit-gpx-colors + type: unit + description: "Назначение цветов из палитры" + cases: + - id: U-20 + name: "Первый файл получает первый цвет" + input: "Загрузка первого файла" + expected: "Цвет = #e6194b" + + - id: U-21 + name: "Девятый файл получает первый цвет (цикл)" + input: "Загрузка 9-го файла" + expected: "Цвет = #e6194b (индекс 8 % 8 = 0)" + + - name: integration-gpx-map + type: integration + description: "Интеграция GPX с MapLibre" + cases: + - id: I-01 + name: "Добавление source и layer при загрузке" + input: "Загрузка валидного GPX" + expected: "map.getSource(sourceId) !== null, map.getLayer(layerId) !== null" + + - id: I-02 + name: "Удаление source и layer при удалении трека" + input: "Удаление загруженного трека" + expected: "map.getSource(sourceId) === null, map.getLayer(layerId) === null" + + - id: I-03 + name: "Fit bounds после загрузки" + input: "Загрузка GPX с bbox [37.5, 55.6, 37.9, 55.9]" + expected: "map.getBounds() содержит указанный bbox" + + - id: I-04 + name: "Waypoints как маркеры" + input: "GPX с 3 waypoints" + expected: "На карте 3 маркера с подписями" + + - id: I-05 + name: "Клик по треку активирует его" + input: "Клик на линию трека" + expected: "Трек становится активным, показывается статистика" + + - id: I-06 + name: "GPX-слои ниже маршрута OSRM" + input: "Загружен GPX + построен маршрут" + expected: "Layer order: gpx-layer before route-layer" + + - name: integration-gpx-elevation + type: integration + description: "Профиль высот" + cases: + - id: I-10 + name: "Рендеринг canvas профиля" + input: "Активный трек с 100 точками и ele" + expected: "Canvas отрисован, ширина = ширина контейнера, высота = 120px" + + - id: I-11 + name: "Tooltip при наведении" + input: "Mousemove по canvas на позиции 50%" + expected: "Tooltip показывает высоту и расстояние средней точки" + + - id: I-12 + name: "Маркер-курсор на карте при наведении на профиль" + input: "Mousemove по canvas" + expected: "На карте появляется маркер в соответствующей точке трека" + + - name: e2e-gpx-workflow + type: e2e + description: "Полный пользовательский сценарий" + cases: + - id: E-01 + name: "Загрузка → визуализация → статистика → удаление" + steps: + - "Открыть приложение" + - "Нажать кнопку GPX в правой панели" + - "Выбрать файл test-track.gpx (1 трек, 500 точек, с ele)" + - "Убедиться: трек на карте, панель открыта, статистика показана" + - "Проверить профиль высот" + - "Удалить трек" + - "Убедиться: карта пуста, панель пуста" + + - id: E-02 + name: "Множественная загрузка и различение цветов" + steps: + - "Загрузить 3 GPX-файла последовательно" + - "Убедиться: 3 трека на карте разных цветов" + - "Убедиться: 3 записи в панели" + - "Кликнуть на второй трек в панели" + - "Убедиться: показана статистика второго трека" + + - id: E-03 + name: "Большой файл (50 МБ)" + steps: + - "Загрузить GPX-файл ~50 МБ" + - "Убедиться: показан индикатор загрузки" + - "Убедиться: трек отображается после парсинга" + - "Убедиться: pan/zoom работают без фризов" + + - id: E-04 + name: "Файл с waypoints" + steps: + - "Загрузить GPX с 5 waypoints" + - "Убедиться: 5 маркеров на карте с подписями" + - "Удалить трек" + - "Убедиться: маркеры исчезли" + + - id: E-05 + name: "GPX параллельно с роутингом" + steps: + - "Построить маршрут через OSRM" + - "Загрузить GPX-файл" + - "Убедиться: оба отображаются, маршрут выше GPX" + - "Удалить GPX" + - "Убедиться: маршрут не затронут" + + - id: E-06 + name: "Ошибки: невалидный файл, превышение лимита" + steps: + - "Попытаться загрузить .txt файл переименованный в .gpx" + - "Убедиться: toast с ошибкой" + - "Попытаться загрузить файл > 50 МБ" + - "Убедиться: toast с ошибкой" + - "Убедиться: предыдущие треки не затронуты" + + - id: E-07 + name: "Мобильное устройство (touch)" + steps: + - "Открыть на мобильном (или эмуляция)" + - "Загрузить GPX" + - "Тапнуть на трек в панели" + - "Тапнуть на профиль высот" + - "Убедиться: tooltip и маркер-курсор работают" + + - name: e2e-gpx-toolbar + type: e2e + description: "Кнопка GPX в toolbar" + cases: + - id: E-10 + name: "Переключение панели через toolbar" + steps: + - "Нажать кнопку GPX в нижнем тулбаре" + - "Убедиться: панель GPX открылась" + - "Нажать ещё раз" + - "Убедиться: панель свернулась" + +test_data: + - name: "test-track-simple.gpx" + description: "1 трек, 10 точек, с ele и time" + - name: "test-track-multi.gpx" + description: "3 трека в одном файле" + - name: "test-track-waypoints.gpx" + description: "1 трек + 5 waypoints с именами" + - name: "test-track-no-ele.gpx" + description: "1 трек без данных высот" + - name: "test-track-large.gpx" + description: "~50 МБ, 500K+ точек" + - name: "test-track-invalid.gpx" + description: "Битый XML" + - name: "test-track-empty.gpx" + description: "Валидный GPX без trk/wpt/rte" + - name: "test-track-route.gpx" + description: "GPX с вместо "