# Архитектура Enduro Trails

## Обзор
Веб-приложение для планирования эндуро-маршрутов с визуализацией рельефа.

## Компоненты
- **Frontend** — MapLibre GL JS, vanilla JS (ES modules)
- **Backend API** — FastAPI (Python 3.12), uvicorn
- **Tile Server** — статические raster tiles (PNG), раздаются через FastAPI/nginx
- **Routing Engine** — OSRM с кастомным эндуро-профилем
- **Database** — SQLite + Spatialite (точки интереса, маршруты, публичные GPS-треки)
- **GPS Tracks Pipeline** — `gps-collector` (docker-compose service, `profiles: [batch]`), запускается host cron'ом 1–2 раза в неделю; собирает публичные GPS-треки с внешних платформ в `data/gps_tracks.sqlite` (ET-008 / ADR-007)

## Слои карты
- Base map: **Схема** (OpenStreetMap raster) либо **Спутник** (Esri World Imagery raster) — переключается в UI (ET-007 / ADR-004)
- Hillshade (рельеф с тенями)
- TRI (Terrain Ruggedness Index — сложность рельефа)
- Hypsometric (высотная раскраска)
- Trails (маршруты из OSM)

## Внешние тайл-провайдеры

Клиент (браузер) обращается напрямую к двум внешним raster-tile сервисам.
Сервер mva154 эти тайлы не проксирует и не кэширует.

| Провайдер | Назначение | URL | Активация | API-ключ |
|-----------|-----------|-----|-----------|----------|
| OpenStreetMap | Базовый слой «Схема» | `https://tile.openstreetmap.org/{z}/{x}/{y}.png` | всегда (default подложка) | нет |
| Esri World Imagery | Базовый слой «Спутник» | `https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}` | лениво — только при включении «Спутник» пользователем (ET-007) | нет |

Атрибуция обоих провайдеров выводится MapLibre автоматически при
активном source.

## GPS Tracks Pipeline (ET-008)

Серверный офлайн-pipeline сбора публичных GPS-треков. Не часть runtime
API, изолирован отдельным docker-compose service'ом и отдельной БД.

### Компонент

- Сервис: `gps-collector` в `docker-compose.yml`, `profiles: ["batch"]`,
  тот же образ что `app`, не стартует при `docker compose up -d`.
- Точка входа: `scripts/gps_collect.py` (см. `src/api/gps_tracks/`).
- Расписание: cron на mva154, Mon + Thu 03:00 UTC; + ежемесячный GC.
- БД: `data/gps_tracks.sqlite` (SQLite + Spatialite, отдельный файл от
  `centralfederal.sqlite`).

### Внешние источники pipeline

Скрейпинг/API только из контейнера `gps-collector`, при наличии
accepted-ADR на источник.

| Источник | Доступ | Лицензия | ADR | MVP |
|---|---|---|---|---|
| OSM Public GPS Traces | API `api.openstreetmap.org/api/0.6/trackpoints` | ODbL | ADR-009 (accepted) | да |
| EnduroRussia.ru | публичный JSON API `endurorussia.ru/api/tracks` | публичная, обезличенно (без user) | ADR-010 (accepted; активирован в ET-009) | да |
| Wikiloc | HTML-парсинг `www.wikiloc.com` + downloadTrail.do | proprietary, некоммерческое использование, обезличенно | ADR-012 (accepted; активирован в ET-009) | да |
| ttrails.ru / Тропинки.ру | HTML + GPX-ссылки | требует review | ADR-011 (proposed/blocked) | условно |

Источник без `status: accepted` в ADR pipeline'ом **пропускается** (см.
ADR-007 §6 licensing guard).

### Клиентский слой публичных треков

Двухрежимная отдача (см. ADR-008):
- z=8..11 — MVT через `GET /api/gps-tracks/tiles/{z}/{x}/{y}.mvt` + сервер-LRU.
- z≥12 — GeoJSON через `GET /api/gps-tracks?bbox=...&activity=...&source=...`.
- z<8 — слой скрыт (защита от шторма запросов).

Скачивание одного трека из popup карты (ET-011):
`GET /api/gps-tracks/{track_id}/download` — отдаёт GPX 1.1 с
правильным `Content-Disposition` и UTF-8 именем по RFC 5987. Разрешено
только для источников с `download_allowed: true` в
`config/gps_sources.yaml` (MVP: только `osm`). Cap 200000 точек →
413 Payload Too Large. См. ADR-014 / ADR-015.

Health/observability: `GET /api/gps-tracks/health` — состояние БД,
число треков по источникам, последний прогон.

## Деплой
Один Docker Compose на mva154. Nginx проксирует /enduro/ на контейнер.

## Клиентские модули (src/web/)

| Модуль | Описание | Work Item |
|--------|----------|-----------|
| `app.js` | Главный модуль: MapLibre, роутинг, UI, тёмная тема | PH-1..PH-6 |
| `units.js` | Централизованный форматтер расстояний (км/мили), localStorage, событие `unitchange` | ET-005 |
| `gpx.js` | GPX 1.1 парсер (DOMParser), рендеринг треков/waypoints, canvas-профиль высот, `rebuildMapOverlays()` | ET-006 |
| `gps_tracks.js` | Слой публичных GPS-треков (MVT + GeoJSON гибрид по zoom), фильтры по активности/источнику, popup с метаданными, halo на спутнике, `restorePublicTracksState()` | ET-008 |
| `style.json` | MapLibre стиль (светлая тема) | PH-1/PH-5 |
| `style-dark.json` | MapLibre стиль (тёмная тема) | PH-5 |

