docs(ET-006): BRD, ТЗ, AC, Test Plan — загрузка GPX-треков

This commit is contained in:
2026-05-22 03:16:20 +03:00
parent 6effac9cce
commit dcf3d244f7
5 changed files with 869 additions and 0 deletions

View File

@@ -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 профиля высот и статистики — на усмотрение аналитика.

View File

@@ -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 браузера

View File

@@ -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» с иконкой загрузки (стрелка вверх + документ).
- Позиция: между кнопкой «Компас» и «Моё местоположение» (верхняя часть панели).
- По нажатию открывается системный диалог выбора файла (`<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.
## 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, но не деактивирует другие режимы.

View File

@@ -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-файл без <trk> и без <wpt>
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, у которых есть <name>
Then на карте отображаются маркеры в позициях waypoints
And рядом с каждым маркером отображается имя
Scenario: Waypoints без имён
Given загружен GPX-файл с waypoints без <name>
Then на карте отображаются маркеры без подписей
Scenario: Файл без waypoints
Given загружен GPX-файл без <wpt>
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 выбран активный трек с данными <ele>
Then в панели отображается график профиля высот
And ось X расстояние (км)
And ось Y высота (м)
And линия графика цвет трека
Scenario: Трек без данных высот
Given выбран активный трек без данных <ele>
Then вместо графика отображается текст «Данные высот отсутствуют»
Scenario: Интерактивность профиля
Given отображается профиль высот
When пользователь наводит курсор (или тапает) на точку графика
Then показывается tooltip с высотой и расстоянием
And на карте подсвечивается соответствующая точка трека
```
## AC-08: Статистика трека
```gherkin
Feature: Статистика трека
Scenario: Полная статистика (с высотами)
Given выбран активный трек с данными <ele>
Then отображаются: длина (км), набор высоты (м), сброс высоты (м), мин. высота (м), макс. высота (м)
Scenario: Частичная статистика (без высот)
Given выбран активный трек без данных <ele>
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 трек отображается на карте
```

View File

@@ -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 без <ele>"
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 с <rte> вместо <trk>"