--- type: brd work_item_id: ET-008 title: "BRD: GPS-треки с публичных платформ на карте" version: 2 status: draft created_at: 2026-06-01 updated_at: 2026-06-01 changelog: - "v2 (2026-06-01): полная переработка под реальный business request — серверная агрегация из ≥3 источников по региону, дедупликация, фильтры по активности и источнику, расширяемость на регионы. Предыдущая v1 трактовала задачу как URL-импорт + OSM live-поиск, что не соответствовало бизнес-цели." authors: - "agent:analyst" --- # BRD — ET-008: GPS-треки с публичных платформ на карте ## 1. Цель Показать пользователю Enduro Trails реальные GPS-треки, **заранее собранные с публичных платформ** (Wikiloc, Offmaps.ru, Тропинки.ру, EnduroRussia.ru, OSM Public GPS Traces, Nakarte.me, Komoot и т.п.) и сохранённые на сервере. Цель — три практические задачи мотоэндуриста: 1. **Видеть реальные дороги/тропы, которых нет в OSM.** Vector-тайлы `trails` показывают только OSM-данные; реальные грунтовки/тропы из GPS-логов дают информацию, которой в OSM никогда не было. 2. **Понимать, где реально ездят.** Плотность публичных треков на участке — прямая прокси-метрика популярности и проходимости. 3. **Выявлять «мёртвые» дороги.** OSM-грунтовка, не покрытая ни одним публичным треком за последние N лет — кандидат на «давно никто не ездит, может быть заросла». ET-008 даёт **новый отдельный слой** (поверх `trails`, ниже маршрута OSRM) с отдельными линиями (не heatmap), цветом по источнику или типу активности, с UI-фильтрами. ## 2. Контекст - Vector-тайлы из OSM (`/api/tiles/{z}/{x}/{y}.mvt`) уже отдают грунтовки/тропы/POI. ET-008 их **не заменяет** — добавляет параллельный слой публичных GPS-треков. - ET-006 реализовал клиентский импорт GPX-файлов пользователем (`window.gpxTracks`, `#sheet-gpx`). Это **другой сценарий**: ET-006 — «мой трек в памяти браузера», ET-008 — «треки сообщества с сервера». Модели данных не пересекаются. - Стек БД: SQLite + Spatialite. Для ET-008 заводится **отдельная** БД `data/gps_tracks.sqlite` — чтобы не смешивать данные с основной `centralfederal.sqlite` и иметь независимый цикл обновления / бэкапа. - Pipeline сбора — **офлайн-скрипт на cron**, не runtime. На запрос пользователя сервер отдаёт уже собранные данные. - Регион MVP: **ЦФО + Чувашия** (18 субъектов ЦФО + Чувашская Республика, площадь ≈ 670 тыс. км²). Расширение на другие регионы — через конфиг-файл. ## 3. Scope ### In scope | # | Функция | | ----- | ----------------------------------------------------------------------------- | | F-01 | Pipeline сбора GPX-треков с ≥ 3 публичных источников | | F-02 | Хранение треков в SQLite + Spatialite: геометрия + метаданные | | F-03 | Дедупликация: один реальный трек = одна запись, даже если найден в N источниках | | F-04 | Метаданные трека: источник, URL, тип активности, дата, длина, кол-во точек, автор (если публичен) | | F-05 | API endpoint `GET /api/gps-tracks?bbox=…&activity=…&source=…` для отдачи треков клиенту | | F-06 | Векторные тайлы публичных треков `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` для эффективной отдачи на низких зумах | | F-07 | Визуализация **отдельными линиями** (не heatmap) на карте | | F-08 | Цветовая дифференциация: палитра по источнику (default) с возможностью переключения на палитру по типу активности | | F-09 | UI-чекбокс «Публичные треки» в `#terrain-popup`: включить/выключить весь слой | | F-10 | UI-фильтр по типу активности (enduro / moto / offroad / bicycle / hike / other), multi-select | | F-11 | UI-фильтр по источнику, multi-select | | F-12 | Конфиг-файл регионов: bbox + название + список активных источников | | F-13 | MVP-датасет: ЦФО + Чувашия, ≥ 5000 треков | | F-14 | Совместимость со сменой стиля карты (через `rebuildMapOverlays()` по аналогии с ET-006 REQ-F-13 и ET-007 REQ-F-06) | | F-15 | Совместимость со спутниковой подложкой (ET-007): треки видны на спутнике с halo для контраста | | F-16 | Клик по треку → popup с метаданными: имя/тип активности/источник/дата/длина и ссылка на оригинал | | F-17 | Health-эндпоинт `/api/gps-tracks/health`: дата последнего сбора, кол-во треков по источникам, ошибки последнего прогона | ### Out of scope - **Real-time сбор**: только периодический офлайн (cron, 1–2 раза в неделю). - **Wikiloc Premium / Komoot Premium / любые платные API**: используем только бесплатные публичные endpoints и публичные HTML-страницы там, где это разрешено ToS источника. - **Strava Metro как источник линий**: это heatmap, не отдельные треки — не соответствует бизнес-требованию «отдельные линии». Опционально в будущем — как метрика популярности для валидации, не для MVP. - **OAuth-интеграции** (вход пользователя в Strava/Komoot со своим аккаунтом): отдельный work item. - **Загрузка пользователем своих треков в общую базу**: отдельный work item. - **Редактирование/обрезка треков на стороне сервера**. - **Конвертация из KML/FIT/TCX**: pipeline принимает только GPX. - **Snap-to-road** для треков (выравнивание под дороги OSM). - **Учёт сложности (drag-level) внутри трека**: фильтр только по типу активности; сложность — отдельная задача (требует анализа геометрии и скорости). ## 4. Источники (с оценкой реализуемости в MVP) Анализ каждого источника из business request с честной оценкой доступности и юридических условий: | # | Источник | Тип доступа | MVP | Комментарий | | - | ------------------------- | ------------------------ | ------ | ---------------------------------------------------------------------------------------------------------------------- | | 1 | OSM Public GPS Traces | Документированный API | **да** | `/api/0.6/trackpoints?bbox=…&page=…`. Лицензия ODbL, атрибуция OSM. Объём для ЦФО оценочно ≈ 50–100K точек, тыс. треков. | | 2 | EnduroRussia.ru | Web (HTML + GPX-ссылки) | **да** | По регионам, есть прямые GPX-ссылки. Лицензия и условия скрейпинга — фиксируются в ADR `06-adr/source-licensing.md` до начала разработки. | | 3 | Тропинки.ру / ttrails.ru | Web (GPX/KML) | **да** | Эндуро-категория, GPX доступен без авторизации. Условия скрейпинга — то же ADR. | | 4 | Offmaps.ru | Web | пилот | Требует ревью формата выдачи и лицензии. Подключаем в пилот-режим если ADR разрешает. | | 5 | Nakarte.me | Public layers + JSON | пилот | Агрегатор: содержит ссылки на треки внешних источников. Может быть «бесплатным» путём к Wikiloc/Strava-treki косвенно. Требует ревью лицензии. | | 6 | Wikiloc | API (премиум) | нет | Бесплатный публичный API не отдаёт GPX. Без премиума — невозможно. **Откладываем.** | | 7 | Komoot | API (партнёрский) | нет | Публичный API ограничен, нет публичной выдачи GPX по bbox. **Откладываем.** | | 8 | Strava Metro | API (исследовательский) | нет | Heatmap, не отдельные треки → не соответствует бизнес-требованию. **Out of scope.** | **MVP-минимум: 3 источника живут в продакшне** — обязательно OSM (гарантированно доступен), плюс минимум 2 из (2)–(5) по результатам ADR-ревью лицензий. ### Юридический минимум Перед началом разработки каждого источника (2)–(5) — **обязательный ADR** `docs/work-items/ET-008/06-adr/-licensing.md`: 1. Что говорит ToS источника о скрейпинге / массовой загрузке GPX. 2. Что говорит robots.txt. 3. На каких условиях разрешена публикация чужих треков (имя/анонимизация/атрибуция). 4. Rate-limit, который мы будем соблюдать (default: 1 req / 5 sec, с корректным `User-Agent: enduro-trails/ (+contact)`). 5. Список метаданных, которые **нельзя** сохранять/публиковать (личные адреса, имена при отсутствии явного согласия). Источник без явного зелёного света в ADR — **не включается** в pipeline. ## 5. Метрики успеха | Метрика | Критерий MVP | | ------------------------------------------------ | --------------------------------------------------------------------------------------------------------- | | Покрытие региона | ≥ 5000 уникальных треков для ЦФО + Чувашии после первого полного прогона pipeline | | Источники в продакшне | ≥ 3 источника, отдающих данные в БД | | Дедупликация | < 5% дублей (один реальный трек — одна запись). Метрика: руками отсэмплировать 100 треков, посчитать дубли. | | Производительность отдачи bbox | `GET /api/gps-tracks?bbox=…` ≤ 300 мс p95 на z ≥ 10 (≤ 500 треков в видимой области) | | Производительность отдачи тайлов | `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` ≤ 200 мс p95 на z = 8–11 | | Производительность отрисовки | При включённом слое pan/zoom без видимых фризов на десктопе и мобильных с 4 ГБ RAM | | Расширяемость на регион | Добавить новый регион (bbox + название + список источников) — ≤ 30 строк YAML-конфига, без правки кода | | Скорость UI-фильтров | Переключение фильтра по активности/источнику меняет видимую выборку за ≤ 200 мс (фильтрация на клиенте) | | Сохранение слоя при `setStyle()` | Слой не теряется при переключении тёмной темы / спутника / hillshade — восстанавливается через `rebuildMapOverlays()` | | Pipeline стабильность | Падение парсера одного источника не валит остальных; лог + алерт в `/api/gps-tracks/health` | | Атрибуция | На карте видна атрибуция каждого активного источника; в popup трека — ссылка на оригинал | ## 6. Риски | Риск | Вероятность | Влияние | Митигация | | ----------------------------------------------------------------------------------- | ----------- | -------- | ------------------------------------------------------------------------------------------------------------------------------------------------------ | | Источник меняет HTML → парсер ломается | Высокая | Среднее | Каждый источник в отдельном модуле, изолированная ошибка. Pipeline пишет статус по источнику в health-эндпоинт. Алерт при 2 неудачных прогонах подряд. | | ToS источника запрещает скрейпинг | Средняя | Высокое | Обязательный ADR с фиксацией лицензии до подключения источника. Источник без явного разрешения — не включается. | | Дубли треков из разных источников (один и тот же трек выкладывают на 2 платформах) | Высокая | Среднее | Spatial+temporal hash: bbox округлённый до 0.01° + длина ± 5% + дата ± 1 день → одна запись. Алгоритм в TRZ §6. | | Перегрузка карты на низких зумах (10K+ треков в видимой области) | Высокая | Высокое | На клиенте: на z < 10 — отдача через MVT-тайлы с упрощением геометрии (как `simplify_coords` для `trails`). На z ≥ 10 — JSON с лимитом 500 треков. | | Размер БД растёт неконтролируемо (миллионы треков при расширении на РФ) | Низкая | Среднее | Отдельная `gps_tracks.sqlite`. Ротация: треки старше N лет (по конфигу, default 5) удаляются. Метрика размера БД в health. | | Скрейпер банится по IP | Средняя | Среднее | Rate-limit + backoff + `User-Agent` с контактом. Сбор по cron 1–2 раза в неделю, не чаще. Per-source конфигурируемый delay. | | Персональные данные в треках (точки «дом», имена) | Низкая | Высокое | Не сохраняем waypoint без явного публичного флага. Не сохраняем `author` если ToS требует анонимизации. Список запрещённых полей — в `08-data-requirements.md`. | | Лицензия источника обязывает менять/удалять данные по требованию автора | Средняя | Среднее | Сохраняем `external_id` и `external_url` — можем удалить точечно по запросу. Pipeline уважает «удалённое на источнике» → удалять и у нас. | | Pipeline ест слишком много трафика mva154 | Средняя | Низкое | Per-source лимит на прогон (например, max 1000 новых треков за прогон). Метрики в health. | | Отдача больших MVT тайлов медленная | Средняя | Среднее | Серверный кэш тайлов (LRU 1024 записи, как уже сделано для `trails`). Упрощение геометрии по зуму. | ## 7. Зависимости ### Backend - Новый пакет `src/api/gps_tracks/` с подмодулями: - `models.py` — Pydantic + SQL schema - `sources/.py` — модули per-source (OSM, EnduroRussia, ttrails, …) - `dedup.py` — алгоритм дедупликации - `db.py` — обвязка SQLite + Spatialite - `endpoint.py` — FastAPI routes - `mvt.py` — генерация MVT-тайлов - Зависимости Python: `httpx` (есть), `lxml` или `defusedxml` (новая — для безопасного парсинга XML на сервере), `shapely` (есть). ### Pipeline - Скрипт `scripts/gps_collect.py` — точка входа. - Конфиг `config/gps_sources.yaml` — список источников и параметры. - Конфиг `config/gps_regions.yaml` — список регионов (bbox + список активных источников per-region). - Cron на mva154: `0 3 * * 1,4 /usr/local/bin/python /opt/enduro-trails/scripts/gps_collect.py` (Mon + Thu, 03:00 UTC). - Логи: `/var/log/enduro-trails/gps-collect.log`. ### Frontend - Новый модуль `src/web/gps_tracks.js` — слой, фильтры, popup, по аналогии с `gpx.js`. - Расширение `index.html`: - Чекбокс «Публичные треки» и кнопка «Фильтры» в `#terrain-popup`. - Bottom sheet `#sheet-gps-filters` с фильтрами по активности и источнику. - Расширение `style.json` / `style-dark.json`: layer + halo-layer для спутника (по аналогии с `trails-track-halo-satellite` из ET-007). - Интеграция с `rebuildMapOverlays()` в `app.js`. ### Инфра - Файловая: `data/gps_tracks.sqlite` на mva154, права чтения для FastAPI, права записи только для pipeline. Бэкап в общий backup-стек проекта. - Сетевая: исходящие HTTPS к источникам с mva154 (уже разрешено). ### Документация - `06-adr/source-licensing.md` — лицензии всех источников. - `06-adr/dedup-algorithm.md` — обоснование выбора алгоритма дедупликации. - `06-adr/storage-schema.md` — обоснование отдельной БД vs единой. - `07-infra-requirements.md` — cron, бэкапы, ротация, мониторинг. - `08-data-requirements.md` — схема БД, поля, ограничения, политика персональных данных. - `10-tech-risks.md` — расширенный риск-реестр (расширяет §6 BRD). ### Связи с другими work items - **ET-006** — модель `window.gpxTracks` живёт параллельно. ET-008 не трогает её, использует свою модель `window.gpsTracksLayer`. - **ET-007** — спутниковая подложка. ET-008 добавляет halo-слой для публичных треков в режиме «Спутник» по тому же паттерну. - **PH-3 Smart Route** — публичные треки в будущем могут стать входом для построения «реально-езженого» маршрута. Не в scope ET-008. - **PH-9 PWA** — слой публичных треков должен корректно работать в офлайне (через cached MVT). Учитывается в TRZ, но реализация офлайна — задача PH-9.