---
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/<source>-licensing.md`:

1. Что говорит ToS источника о скрейпинге / массовой загрузке GPX.
2. Что говорит robots.txt.
3. На каких условиях разрешена публикация чужих треков
   (имя/анонимизация/атрибуция).
4. Rate-limit, который мы будем соблюдать (default: 1 req / 5 sec, с
   корректным `User-Agent: enduro-trails/<v> (+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/<source>.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.
