Files
enduro-trails/docs/work-items/ET-006/01-brd.md
claude-bot 6edf97fe79
Some checks failed
CI / lint (push) Failing after 4s
CI / test (push) Successful in 6s
CI / build (push) Has been skipped
analyst(ET): auto-commit from analyst run_id=59
2026-06-03 17:33:09 +00:00

9.1 KiB
Raw Blame History

type, work_item_id, title, version, status, created_at, updated_at, authors
type work_item_id title version status created_at updated_at authors
brd ET-006 BRD: Скачивание трека из popup на карте 1 approved 2026-06-03 2026-06-03
agent:analyst

BRD — ET-006: Скачивание трека из popup на карте

1. Цель

Дать пользователю возможность сохранить публичный GPS-трек (источники OSM, EnduroRussia, Wikiloc, ttrails — слой ET-008) к себе на устройство в виде GPX-файла прямо из popup, который открывается по клику на трек на карте. Это закрывает базовый use case «увидел чужой интересный трек → забрал к себе → запланировал поездку».

2. Контекст

  • Слой публичных GPS-треков реализован в ET-008 (src/web/gps_tracks.js, src/api/gps_tracks/). При клике на трек открывается MapLibre popup с метаданными (имя, активность, длина, дата, пользователь, источники).
  • Геометрия треков хранится на сервере в SQLite: 2D WKB LineString (tracks.geom, см. src/api/gps_tracks/db.py и mvt.py). Высот по точкам в БД нет.
  • На клиенте геометрия доступна только при zoom ≥ 12 (режим GeoJSON через GET /api/gps-tracks?bbox=...). При zoom 811 popup открывается над MVT-фичей, у которой геометрия по тайлу упрощена/обрезана и непригодна для повторного экспорта.
  • Существующий GPX в проекте умеет только парсить локальный файл и отдаёт OSRM-маршрут (кнопка «Скачать GPX» в sheet-route). Экспорт публичного трека из БД в GPX отсутствует.
  • Backend — FastAPI; новый эндпоинт добавляется в существующий router /api/gps-tracks (src/api/gps_tracks/endpoint.py).

3. Scope

In scope

# Функция
F-01 Кнопка/ссылка «Скачать GPX» в popup'е публичного трека (_renderTrackPopupHtml в gps_tracks.js)
F-02 Backend-эндпоинт GET /api/gps-tracks/{id}.gpx — возвращает корректный GPX 1.1 для трека по его БД-id
F-03 Имя файла: <sanitized-name>.gpx если есть имя, иначе track-{id}.gpx; передаётся в Content-Disposition: attachment; filename=...
F-04 Тело GPX: <gpx version="1.1"> с <metadata> (name, desc, time, link на первый external_url) и одним <trk> с одним <trkseg>, заполненным точками из tracks.geom
F-05 MIME-тип ответа: application/gpx+xml; charset=utf-8
F-06 Работа единообразна на обоих zoom-режимах: при MVT (z 811) и при GeoJSON (z ≥ 12) popup использует один и тот же id трека и один и тот же URL — клиент НЕ собирает GPX из MVT-геометрии
F-07 Ошибки: 404 для несуществующего id, 500 — через стандартный HTTPException; на клиенте: ошибка качания → toast «Не удалось скачать трек», popup не закрывается
F-08 Атрибуция источников сохранена в <metadata>: <copyright> и/или <desc> содержат source_id'ы и внешние ссылки (ODbL/Wikiloc TOS — обязательно)
F-09 Мобильный UX: кнопка тапабельна (≥ 36 px высота), не ломает раскладку popup'а на узких экранах
F-10 Аналитика клика: событие в консоль через существующий механизм console.log / showToast (телеметрия не вводится)

Out of scope

  • Скачивание сразу нескольких треков (batch).
  • Конвертация в KML / TCX / FIT / KMZ.
  • Шаринг ссылки на скачивание (короткая ссылка / OG-карточка).
  • Сохранение в личную «библиотеку» пользователя (нет аккаунтов).
  • Загрузка скачанного файла обратно на карту — уже покрыто текущим gpx.js (UI «Загрузить GPX»), и пересечения функциональностей нет.
  • Авторизация / rate-limit на загрузку — публичные данные, тот же cors-разрешённый эндпоинт что и GeoJSON.
  • Восстановление высот по DEM для GPX (трек не имеет <ele>).
  • Изменение схемы БД gps_tracks.sqlite.

4. Метрики успеха

Метрика Критерий
Доступность кнопки Кнопка «Скачать GPX» видна в каждом popup'е публичного трека на desktop и mobile, на обоих zoom-режимах (z = 9 и z = 14)
Корректность файла Скачанный файл валидируется парсером gpx.js (drag-and-drop в приложение) и рисуется на карте без ошибок
Корректность GPX 1.1 Файл проходит XSD-валидацию http://www.topografix.com/GPX/1/1/gpx.xsd (online valdiator или xmllint --schema)
Совместимость Файл открывается в OsmAnd / Locus / gpx.studio без ошибок
Имя файла Имя файла читаемое, не содержит запрещённых на Windows/Android символов (`/:*?"<>
Атрибуция В <metadata> явно перечислены все source_id и external_urls трека
Производительность Эндпоинт отвечает ≤ 500 ms p95 для трека до 50 000 точек (среднестатистический эндуро-трек < 5000 точек)
Размер ответа Для трека 5000 точек тело GPX ≤ 600 КБ (без gzip), ≤ 150 КБ (с gzip)
Регрессии Существующий popup, MVT-/GeoJSON-режимы слоя, фильтры активностей/источников, halo на спутнике — не сломаны

5. Риски

Риск Вероятность Влияние Митигация
Геометрия трека отсутствует (row.geom is None) Низкая Низкое 404 + toast «Геометрия трека недоступна»; такие записи редки (защита уже в _wkb_to_coords)
Очень длинный трек (десятки тысяч точек) → ответ > 5 МБ Низкая Среднее Без упрощения геометрии. Документировать лимит. При необходимости — gzip через nginx (уже включён)
Wikiloc TOS требует ссылки на оригинал Высокая Высокое F-08: <metadata><link> на оригинальную страницу для каждого external_url
ODbL атрибуция в GPX Высокая Высокое F-08: <copyright author="OpenStreetMap contributors" license="https://opendatacommons.org/licenses/odbl/"> для треков с source = osm
Опасный символ в name → XML-инъекция Низкая Высокое Эскейп через стандартный XML-сериализатор (ElementTree / минимальный helper)
Имя файла с кириллицей не сохраняется в Safari Средняя Низкое RFC 5987 — filename*=UTF-8''<percent-encoded> + ASCII-fallback filename=...
Кнопка обрезается на узком экране (≤ 360 px) Средняя Низкое Кнопка на отдельной строке, flex-wrap, тестируется в UI-тестах TC-UI-02

6. Зависимости

  • Бэкенд: существующий router /api/gps-tracks (src/api/gps_tracks/endpoint.py), функции _wkb_to_coords и БД-слой. Изменения чисто аддитивные.
  • Фронтенд: модуль src/web/gps_tracks.js, функция _renderTrackPopupHtml. Точечная правка HTML-шаблона.
  • Никаких внешних сервисов / новых данных / миграций БД.
  • Никаких новых npm/pip-зависимостей: GPX генерируется штатным xml.etree.ElementTree (есть в stdlib Python 3.12).