# Dev Task: Enduro Trails — Фаза 4 «Продвинутый роутинг»

**Приоритет:** HIGH  
**Проект:** enduro-trails  
**BRD:** `BRD_PHASE4.md`

---

## Контекст

Фаза 3 реализована: роутинг A→B с альтернативами, статистика, GPX, флажки.
Фаза 4 — 3 новых режима роутинга: Разведка, Связка, Красивый маршрут.

## Деплой

SSH из контейнера НЕ работает. Используй Node.js ssh2:
```js
const { Client } = require('/tmp/node_modules/ssh2');
```

Шаблон деплоя: `/tmp/deploy_app2.js` (уже работает).
Для статики (app.js, app.css, index.html) — SFTP upload в `/home/slin/enduro-trails/prototype/static/`, потом `docker cp` в контейнер + `docker restart`.

---

## Задача 1: «Разведка» (F-14) — ПЕРВАЯ

### Бэкенд: новый endpoint POST `/api/recon`

**Запрос:**
```json
{ "lon": 37.6, "lat": 55.7, "radius_km": 20 }
```

**Ответ:**
```json
{
  "center": { "lon": 37.6, "lat": 55.7 },
  "radius_km": 20,
  "trails": {
    "total_count": 124,
    "total_km": 380,
    "lev12_count": 45, "lev12_km": 120,
    "lev345_count": 68, "lev345_km": 210,
    "path_count": 11, "path_km": 50
  },
  "poi": {
    "natural=water": 3, "tourism=viewpoint": 2,
    "historic=ruins": 1, "ford=yes": 12,
    "natural=peak": 0, "natural=cave_entrance": 0
  }
}
```

**SQL логика:**
```python
import math
lat_rad = math.radians(lat)
delta_lat = radius_km / 111.0
delta_lon = radius_km / (111.0 * math.cos(lat_rad))

# Trails — агрегация по типам
cur.execute("""
    SELECT highway_type, track_type, SUM(length_m) as total_m, COUNT(*) as cnt
    FROM trails
    WHERE min_lon <= ? AND max_lon >= ?
      AND min_lat <= ? AND max_lat >= ?
      AND length_m >= 100
    GROUP BY highway_type, track_type
""", (lon + delta_lon, lon - delta_lon, lat + delta_lat, lat - delta_lat))

# POI — подсчёт по типам
cur.execute("""
    SELECT poi_type, COUNT(*) as cnt
    FROM poi
    WHERE lon >= ? AND lon <= ? AND lat >= ? AND lat <= ?
    GROUP BY poi_type
""", (lon - delta_lon, lon + delta_lon, lat - delta_lat, lat + delta_lat))
```

Агрегация trail типов:
- `lev12`: highway_type='track' AND track_type IN ('grade1','grade2')
- `lev345`: highway_type='track' AND (track_type IN ('grade3','grade4','grade5') OR track_type IS NULL)
- `path`: highway_type IN ('path','bridleway')

### Фронт: кнопка 📍 и UI

**Кнопка в `#map-controls-br`:**
```html
<button id="btn-recon" class="map-ctrl-btn" title="Разведка" onclick="toggleReconMode()">📍</button>
```

⚠️ Конфликт: текущая кнопка геолокации тоже 📍. Заменить геолокацию на иконку 🎯.

**Логика:**
1. Нажал 📍 → `reconMode = true`, курсор crosshair, деактивировать другие режимы
2. Клик на карту → POST `/api/recon`, нарисовать круг + показать попап
3. Круг: GeoJSON Polygon (64 точки) с полупрозрачной заливкой `#ff6600`, opacity 0.1
4. Попап: статистика
5. Кнопки радиуса [20] [50] [100] — перезапрос API
6. Выход: повторный клик на 📍 или другой режим

**Попап (HTML div):**
```html
<div id="recon-panel" style="display:none; position:absolute; bottom:40px; left:12px;
     background:rgba(255,255,255,0.97); border:1px solid #ddd; border-radius:8px;
     padding:12px; font-size:13px; z-index:5; width:240px;
     box-shadow:0 2px 12px rgba(0,0,0,0.15);">
  <div style="font-weight:600; color:#e07b00; margin-bottom:8px;">📍 Разведка</div>
  <div id="recon-stats"></div>
  <div style="margin-top:8px; display:flex; gap:4px;">
    <button onclick="setReconRadius(20)" class="recon-radius-btn active">20 км</button>
    <button onclick="setReconRadius(50)" class="recon-radius-btn">50 км</button>
    <button onclick="setReconRadius(100)" class="recon-radius-btn">100 км</button>
  </div>
</div>
```

**Круг на карте (GeoJSON Polygon):**
```js
function makeCircleGeoJSON(lon, lat, radiusKm) {
  const coords = [];
  for (let i = 0; i <= 64; i++) {
    const a = (2 * Math.PI * i) / 64;
    const dlat = (radiusKm / 111) * Math.cos(a);
    const dlon = (radiusKm / (111 * Math.cos(lat * Math.PI / 180))) * Math.sin(a);
    coords.push([lon + dlon, lat + dlat]);
  }
  return { type: 'Feature', geometry: { type: 'Polygon', coordinates: [coords] } };
}
```

---

## Задача 2: «Связка» (F-13) — ВТОРАЯ

### UI

**Кнопка:**
```html
<button id="btn-link" class="map-ctrl-btn" title="Связка" onclick="toggleLinkMode()">🔗</button>
```

**Логика:** переиспользует POST `/api/route` с `alternatives=3`.
1. Нажал 🔗 → `linkMode = true`, курсор crosshair
2. Клик → маркер «1️⃣» (оранжевый)
3. Клик → маркер «2️⃣» (оранжевый)
4. Автоматический POST `/api/route` с этими двумя точками
5. Карточки маршрутов (как «Дикий путь»)

**Отличия от «Дикого пути»:**
- Заголовок панели: «🔗 Связка»
- Маркеры: оранжевые с цифрами 1️⃣ 2️⃣ (не зелёный/красный A/B)
- В карточке акцент: «грунтовая связка N% грунта»
- Нет промежуточных точек (не нужно для связки)

---

## Задача 3: «Красивый маршрут» (F-11) — ПОСЛЕДНЕЙ

### Бэкенд: новый endpoint POST `/api/scenic`

**Запрос:**
```json
{ "lon": 37.6, "lat": 55.7, "target_km": 150 }
```

**Ответ:**
```json
{
  "routes": [
    {
      "name": "Северный маршрут",
      "waypoints": [
        {"lon": 37.6, "lat": 55.7, "label": "Старт"},
        {"lon": 36.5, "lat": 56.1, "label": "💧 Озеро Сенеж"},
        {"lon": 36.8, "lat": 56.3, "label": "👁 Смотровая"},
        {"lon": 37.6, "lat": 55.7, "label": "Финиш"}
      ],
      "geometry": {"type": "LineString", "coordinates": [...]},
      "distance_m": 142000,
      "duration_s": 17100,
      "stats": { ... },
      "scenic_score": 35,
      "scenic_pois": [
        {"type": "natural=water", "name": "Озеро Сенеж", "lon": 36.5, "lat": 56.1},
        {"type": "tourism=viewpoint", "name": "Смотровая", "lon": 36.8, "lat": 56.3}
      ]
    }
  ]
}
```

### Алгоритм `find_scenic_route()`

1. **Радиус поиска POI** = target_km × 0.6
2. **Запрос POI из БД** с score:
   - water=10, viewpoint=15, ruins=10, peak=12, cave=8, ford=5
3. **Жадный выбор POI:**
   - Начать со старта
   - На каждом шаге: выбрать POI с max(score / distance)
   - Условие: расстояние до POI < remaining_km × 0.5 и > 3 км
   - Добавить в маршрут, уменьшить remaining_km
   - Максимум 5 POI
4. **Построить маршрут** через все выбранные POI + возврат к старту
   - Один OSRM запрос со всеми waypoints
5. **Проверить дистанцию:**
   - Если > target_km × 1.3 — убрать последний POI, перестроить
   - Если < target_km × 0.5 — добавить POI, перестроить
6. **Альтернативы (до 3):**
   - Разделить POI на кластеры по азимуту от старта (4 сектора по 90°)
   - Построить маршрут для каждого кластера с POI
   - Имена: «Северный», «Восточный», «Южный», «Западный»

### Фронт: кнопка 🎨 и UI

**Кнопка:**
```html
<button id="btn-scenic" class="map-ctrl-btn" title="Красивый маршрут" onclick="toggleScenicMode()">🎨</button>
```

**Панель:**
```
┌─────────────────────────────────┐
│ 🎨 Красивый маршрут             │
│ 📍 Точка старта: кликни на карте│
│ 📏 Дистанция:                   │
│   [50] [100] [150] [200]  [___] км│
│                                 │
│ [Построить маршрут]             │
└─────────────────────────────────┘
```

**Карточка маршрута** — стандартная + дополнительно:
- `scenic_pois` с иконками (💧👁🏚🔺)
- Имя маршрута по направлению

---

## Общие требования

### Взаимоисключающие режимы
Активация одного режима деактивирует все остальные:
```js
function deactivateAllModes() {
  if (routeMode) toggleRouteMode();
  if (rulerMode) toggleRuler();
  if (markerMode) toggleMarkerMode();
  if (reconMode) toggleReconMode();
  if (linkMode) toggleLinkMode();
  if (scenicMode) toggleScenicMode();
}
```

### Кнопки
Добавить в `#map-controls-br` (в index.html):
```html
<button id="btn-scenic" class="map-ctrl-btn" title="Красивый маршрут" onclick="toggleScenicMode()">🎨</button>
<button id="btn-link" class="map-ctrl-btn" title="Связка" onclick="toggleLinkMode()">🔗</button>
<button id="btn-recon" class="map-ctrl-btn" title="Разведка" onclick="toggleReconMode()">📍</button>
```

Заменить иконку геолокации: 📍 → 🎯 (чтобы не конфликтовала с Разведкой).

---

## Порядок реализации

1. **Разведка** (F-14) — бэкенд `/api/recon` + фронт 📍
2. **Связка** (F-13) — фронт 🔗 (переиспользует `/api/route`)
3. **Красивый маршрут** (F-11) — бэкенд `/api/scenic` + фронт 🎨
4. Деплой + тест

## Definition of Done

- [ ] POST `/api/recon` возвращает статистику грунтовок и POI в радиусе
- [ ] Круг на карте + попап со статистикой
- [ ] Кнопки радиуса 20/50/100 км работают
- [ ] 🔗 «Связка» строит маршрут между двумя точками с оранжевыми маркерами
- [ ] 🎨 «Красивый маршрут» строит кольцевой маршрут через POI
- [ ] Пресеты дистанции 50/100/150/200 км + ручной ввод
- [ ] Режимы взаимоисключающие
- [ ] Иконка геолокации заменена 📍→🎯
- [ ] Деплой на сервер, health OK
