docs(ET-007): analyst artifacts - BRD, TRZ, AC, TestPlan, UI tests
All checks were successful
CI / lint (push) Successful in 4s
CI / test (push) Successful in 6s
CI / build (push) Successful in 1s

This commit is contained in:
2026-05-31 18:28:31 +00:00
parent 5bb2fa96d7
commit d7d06bb046
5 changed files with 1195 additions and 0 deletions

View File

@@ -0,0 +1,98 @@
---
type: brd
work_item_id: ET-007
title: "BRD: Спутниковая карта (Схема / Спутник)"
version: 1
status: draft
created_at: 2026-05-31
updated_at: 2026-05-31
authors:
- "agent:analyst"
---
# BRD — ET-007: Спутниковая карта (Схема / Спутник)
## 1. Цель
Дать пользователю возможность одним кликом переключать подложку карты
между «Схемой» (текущая OSM-схема) и «Спутник» (растровые снимки
поверхности Земли). Спутниковая подложка помогает увидеть реальный
рельеф и поверхность маршрута — лес/поле/брод/каменистый участок — до
выезда.
## 2. Контекст
- Сейчас в приложении используется единственная подложка — OSM-растр,
стилизованный для «Схемы» в двух темах (`style.json`,
`style-dark.json`). Спутникового слоя нет.
- В фазе PH-5 Redesign уже была введена тёмная/светлая тема — но
«тема» относится к стилизации (контрасты, насыщенность), а не к
природе подложки.
- Эндуро-маршруты часто проходят вне дорог OSM (бездорожье, броды,
лесные участки). Спутник критичен для разведки.
- Все клиентские модули (`app.js`, `units.js`, `gpx.js`) уже умеют
переживать `map.setStyle()` через `rebuildMapOverlays()` — это
опорная точка для будущей реализации.
## 3. Scope
### In scope
| # | Функция |
| ----- | ------------------------------------------------------------------------------------ |
| F-01 | Переключатель «Схема / Спутник» в UI (segmented control) |
| F-02 | Спутниковая подложка как новый raster-источник (бесплатный, без API-ключа) |
| F-03 | В режиме «Спутник» — скрыта OSM-схема, показаны спутниковые тайлы |
| F-04 | Все надстройки (грунтовки, тропы, POI, hillshade, TRI, маршрут, GPX) поверх спутника |
| F-05 | Сохранение выбора в `localStorage` (ключ `map-base-layer`) |
| F-06 | Восстановление выбора при загрузке страницы и при смене темы |
| F-07 | Корректное отображение атрибуции спутниковых тайлов |
| F-08 | Сохранение всех пользовательских слоёв (роутинг, GPX, recon) при переключении |
### Out of scope
- Кэширование спутниковых тайлов (offline / PWA — это PH-9).
- Динамический выбор провайдера спутниковых тайлов в UI.
- Гибридный режим «Спутник + подписи дорог OSM поверх».
- Самостоятельный хостинг спутниковых тайлов (юридические/трафик-риски).
- Изменение базовой карты для расчёта маршрутов (роутинг по-прежнему OSRM).
- Авто-переключение Схема/Спутник в зависимости от зума.
## 4. Метрики успеха
| Метрика | Критерий |
| ------------------------ | ------------------------------------------------------------------------------------- |
| Время переключения | ≤ 500 мс от клика до первой видимой спутниковой плитки |
| Сохранение состояния | Выбор подложки сохраняется после reload, смены темы, смены слоёв terrain |
| Совместимость со слоями | Грунтовки, тропы, POI, маршрут OSRM, GPX-треки, hillshade, TRI видны и поверх спутника |
| Совместимость с темой | Переключение тёмной/светлой темы не сбрасывает режим «Спутник» |
| Атрибуция | На карте видна корректная атрибуция провайдера спутника |
| Не ломает существующее | Все режимы (роутинг, разведка, красивый маршрут, GPX, линейка) работают как прежде |
## 5. Риски
| Риск | Вероятность | Влияние | Митигация |
| ------------------------------------------------------------------------------------------------- | ----------- | ------- | -------------------------------------------------------------------------------------------------------------------------- |
| Провайдер спутниковых тайлов закроет доступ / введёт лимит / потребует API-ключ | Средняя | Высокое | Зафиксировать конкретного провайдера в ADR; предусмотреть точку расширения для альтернативного провайдера (несколько URL) |
| Спутниковая подложка медленно грузится → пользователь видит «дыры» | Высокая | Среднее | Использовать background-цвет (тёмно-серый) под спутником; OSM-схема остаётся как fallback в случае ошибки загрузки тайлов |
| Цвет грунтовок и троп плохо виден на спутниковой подложке | Высокая | Среднее | TRZ: на режиме «Спутник» включается обводка (halo) у линий грунтовок и троп — по аналогии с подписями POI |
| Hillshade поверх спутника даёт некрасивое наложение (двойное затенение рельефа) | Средняя | Низкое | По умолчанию hillshade отключается при включении спутника — поведение фиксируется в TRZ |
| Юридические ограничения на использование стороннего провайдера спутниковых тайлов | Низкая | Высокое | В ADR указать выбранного провайдера с лицензией, разрешающей использование без API-ключа (Esri World Imagery, ArcGIS) |
| Регресс UI на мобильных устройствах из-за нового переключателя | Низкая | Среднее | UI-тест-кейсы (04b) для desktop и mobile viewport |
| Конфликт с уже сохранёнными localStorage-значениями старых версий | Низкая | Низкое | Использовать новый ключ `map-base-layer`, default = `schematic` |
## 6. Зависимости
- Только фронтенд — backend изменений не требуется.
- MapLibre GL JS 4.7.0 (уже подключен).
- Внешний провайдер спутниковых тайлов (выбор и фиксация — в ADR).
- Сетевое подключение клиента к серверу провайдера.
## 7. Связь с roadmap
- Фаза PH-5 Redesign — тёмная тема и mobile UI уже сделаны; ET-007
встраивается в эту же панель «Рельеф / Слои» (одна точка управления
визуальными слоями карты).
- Фаза PH-9 PWA — кэширование спутниковых тайлов оффлайн — будет
планироваться отдельно, ET-007 закладывает архитектурную основу
(источник тайлов, точка переключения).

View File

@@ -0,0 +1,385 @@
---
type: trz
work_item_id: ET-007
title: "ТЗ: Спутниковая карта (Схема / Спутник)"
version: 1
status: draft
created_at: 2026-05-31
updated_at: 2026-05-31
authors:
- "agent:analyst"
---
# ТЗ — ET-007: Спутниковая карта (Схема / Спутник)
## 1. Функциональные требования
### REQ-F-01: Переключатель «Схема / Спутник»
- В попап-панели слоёв (`#terrain-popup`, открывается кнопкой
`#terrain-toggle`) добавляется новая секция в самом верху панели —
«Подложка».
- Реализация — segmented-control (`.seg-control` / `.seg-btn`) с двумя
кнопками:
- «Схема» (`data-base="schematic"`, ID `base-btn-schematic`) —
активна по умолчанию.
- «Спутник» (`data-base="satellite"`, ID `base-btn-satellite`).
- Активная кнопка визуально выделяется (`.active` — оранжевый фон, по
аналогии с переключателем единиц измерения, ET-005).
- Обработчик: `onBaseLayerToggle(base)` в `src/web/app.js`.
- Под переключателем — горизонтальная линия-разделитель (`<hr>`),
как уже сделано между секциями попапа.
### REQ-F-02: Спутниковый растровый источник
- Используется растровый тайл-сервер Esri World Imagery (см. ADR в
`docs/work-items/ET-007/06-adr/`):
- URL-шаблон: `https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}`.
- `tileSize: 256`, `minzoom: 0`, `maxzoom: 19`.
- Атрибуция: «Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community».
- Источник добавляется на карту лениво: при первом включении режима
«Спутник», а не на старте приложения.
- ID источника: `satellite-raster`.
- ID слоя: `satellite-base`.
### REQ-F-03: Поведение в режиме «Спутник»
- При включении «Спутник»:
- Если ещё не добавлен — добавить source `satellite-raster` и layer
`satellite-base` сразу после слоя `background` (т.е. ниже всех
остальных слоёв).
- Слой `osm-base` (существующий) скрывается (`visibility: none`).
- Слой `background` остаётся (показывает «дыры» если тайлы ещё не
загрузились) — цвет фона `#2a2a2a` для тёмной темы и `#1a1a1a` для
светлой темы в режиме «Спутник» (чтобы белый фон не «бликовал» под
тёмными снимками).
- При возврате на «Схема»:
- `osm-base` снова видим (`visibility: visible`).
- `satellite-base` скрывается (`visibility: none`), но не удаляется
из стиля (быстрое повторное переключение).
### REQ-F-04: Совместимость со слоями приложения
Все клиентские слои должны корректно отображаться поверх спутника:
| Слой | Z-order над спутником | Доп. правила в режиме «Спутник» |
| ----------------------------- | --------------------- | ------------------------------------------------------------------------------ |
| Hillshade (`terrain-hillshade`) | поверх спутника | Включается/выключается чекбоксом как раньше; по умолчанию НЕ авто-выключается |
| TRI (`terrain-tri`) | поверх спутника | Аналогично hillshade |
| Trails (grade1..5) | поверх terrain | Линия получает halo (line-gap-width + полупрозрачная обводка) для контраста |
| Paths/bridleway | поверх trails | Аналогично — halo для контраста |
| POI circles | поверх trails | Обводка `circle-stroke-color: #ffffff`, толщина 2 px |
| POI labels | поверх POI | `text-halo-color: #000000`, `text-halo-width: 2px` для читаемости на спутнике |
| Route / Scenic / Link / Ruler | поверх POI | Без изменений |
| GPX-треки и waypoints | поверх Route | Без изменений (ET-006 уже совместим) |
Реализация:
- Для halo у линий грунтовок/троп добавить отдельные «underlay»-слои с
более широкой полупрозрачной белой линией; включать их через
`visibility` только в режиме «Спутник».
- Стили POI на спутнике задаются динамически через `setPaintProperty`
при переключении режима.
### REQ-F-05: Сохранение состояния (localStorage)
- Ключ: `map-base-layer`.
- Значения: `"schematic"` (default) | `"satellite"`.
- При `onBaseLayerToggle()` — запись.
- При старте приложения — чтение и применение через
`restoreBaseLayerState()` (по аналогии с `restoreTerrainState()`).
### REQ-F-06: Восстановление после смены стиля карты
- При вызове `map.setStyle()` (переключение тёмной/светлой темы, см.
`switchMapStyle()` в `app.js`) спутниковый source/layer удаляются
вместе со стилем.
- В функции `rebuildMapOverlays()` добавляется вызов
`restoreBaseLayerState()` — это пересоздаёт source/layer спутника и
выставляет видимость по сохранённому состоянию.
- Порядок вызовов в `rebuildMapOverlays()`: `restoreBaseLayerState()`
вызывается **до** `restoreTerrainState()` — чтобы hillshade/TRI
оказались выше спутника, но ниже trails (тот же подход, что и для
schematic-режима).
### REQ-F-07: Атрибуция
- При создании source `satellite-raster` передаётся свойство
`attribution: "Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community"`.
- MapLibre автоматически отображает атрибуцию в правом нижнем углу
карты, когда соответствующий source активен.
- Атрибуция OSM остаётся видимой в обоих режимах (vector-источник
`trails-tiles` всегда активен).
### REQ-F-08: Fallback при ошибке загрузки тайлов
- Если спутниковые тайлы не загружаются (network error / 4xx / 5xx),
MapLibre сам показывает прозрачную плитку — под ней видим `background`.
- Логика fallback на схему не предусмотрена (пользователь сам
переключит, если нужно).
## 2. Нефункциональные требования
### REQ-NF-01: Производительность
- Время переключения «Схема → Спутник» (до первой видимой спутниковой
плитки): ≤ 500 мс при скорости сети ≥ 5 Мбит/с.
- Переключение обратно «Спутник → Схема» — мгновенное (источник
остаётся в стиле, меняется только visibility).
- В момент переключения не должно быть «прыжков» камеры — `center`,
`zoom`, `bearing`, `pitch` сохраняются.
### REQ-NF-02: Совместимость
- Браузеры: Chrome 90+, Firefox 90+, Safari 15+.
- Мобильные: iOS Safari 15+, Chrome для Android.
- MapLibre GL JS 4.7.0 (уже подключен).
### REQ-NF-03: UX
- Текущая активная подложка визуально видна в UI всегда (в попапе
слоёв).
- Переключение происходит без перезагрузки страницы и без потери
пользовательского состояния (маршрута, GPX, точек разведки).
### REQ-NF-04: Хранение
- localStorage ключ `map-base-layer`, размер ≤ 16 байт.
- Никаких других данных приложение для этой фичи не хранит.
### REQ-NF-05: Безопасность
- Запросы к Esri World Imagery идут по HTTPS.
- Никаких персональных данных пользователя в URL запросов не
передаётся.
- Атрибуция выводится в соответствии с лицензией провайдера (см. ADR).
## 3. UI-спецификация
### 3.1 Изменения в `#terrain-popup`
Сейчас:
```
┌────────────────────────────┐
│ Эндуро │
│ ☐ Тени рельефа │
│ ☐ Перепады │
│ ─────── │
│ ☑ Грунтовки │
│ ☑ Тропы │
│ ─────── │
│ ☑ POI │
│ ─────── │
│ Единицы [км][мили] │
└────────────────────────────┘
```
После:
```
┌────────────────────────────┐
│ Подложка [Схема][Спутник] │ ← новая секция
│ ─────── │
│ Эндуро │
│ ☐ Тени рельефа │
│ ☐ Перепады │
│ ─────── │
│ ☑ Грунтовки │
│ ☑ Тропы │
│ ─────── │
│ ☑ POI │
│ ─────── │
│ Единицы [км][мили] │
└────────────────────────────┘
```
### 3.2 Разметка HTML
В `src/web/index.html`, в начале `#terrain-popup` (сразу после
`<div class="terrain-popup-title">Эндуро</div>` ИЛИ выше него — по
выбору разработчика; рекомендуется в самом верху для большей
заметности):
```html
<!-- ET-007: переключатель подложки (Схема / Спутник) -->
<div class="terrain-base-row">
<span class="terrain-base-label">Подложка</span>
<div class="seg-control base-seg" id="base-seg">
<button type="button" class="seg-btn active" id="base-btn-schematic"
data-base="schematic" onclick="onBaseLayerToggle('schematic')">Схема</button>
<button type="button" class="seg-btn" id="base-btn-satellite"
data-base="satellite" onclick="onBaseLayerToggle('satellite')">Спутник</button>
</div>
</div>
<hr style="margin:6px 0;border-color:rgba(128,128,128,0.3)">
```
### 3.3 CSS
В `src/web/app.css` — добавить стили (по аналогии с `.terrain-unit-row`):
```css
.terrain-base-row {
display: flex;
align-items: center;
gap: 8px;
padding: 4px 0;
}
.terrain-base-label {
font-size: 12px;
color: var(--text2);
flex-shrink: 0;
}
.terrain-base-row .seg-control {
flex: 1;
margin-bottom: 0;
}
.base-seg .seg-btn {
font-size: 12px;
}
```
### 3.4 Поведение на мобильных устройствах
- Попап `#terrain-popup` уже адаптирован под мобильные (ET-005). Новая
строка не должна нарушать ширину попапа.
- Высота кнопок `.seg-btn` остаётся 34px (как у переключателя единиц).
## 4. Данные
### 4.1 Спутниковый источник (MapLibre source spec)
```js
{
type: 'raster',
tiles: [
'https://server.arcgisonline.com/ArcGIS/rest/services/World_Imagery/MapServer/tile/{z}/{y}/{x}'
],
tileSize: 256,
minzoom: 0,
maxzoom: 19,
attribution: 'Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community'
}
```
### 4.2 Спутниковый слой (MapLibre layer spec)
```js
{
id: 'satellite-base',
type: 'raster',
source: 'satellite-raster',
paint: {
'raster-opacity': 1.0,
'raster-resampling': 'linear'
},
layout: { visibility: 'none' } // включается при переключении
}
```
Вставляется в стиль сразу после слоя `background`.
### 4.3 localStorage
| Ключ | Значения | Default |
| ----------------- | ------------------------------ | ------------- |
| `map-base-layer` | `"schematic"` \| `"satellite"` | `"schematic"` |
## 5. Алгоритмы
### 5.1 `onBaseLayerToggle(base)`
```
1. Если base === текущий — return.
2. Сохранить в localStorage('map-base-layer', base).
3. Применить applyBaseLayer(base).
4. syncBaseLayerUI(base).
```
### 5.2 `applyBaseLayer(base)`
```
1. map = window._map; если нет — return.
2. Если base === 'satellite':
2.1. Если source 'satellite-raster' отсутствует — addSource (см. 4.1).
2.2. Если layer 'satellite-base' отсутствует — addLayer (см. 4.2),
вставлять beforeId = id первого слоя trails-* или terrain-*
(первый из существующих) — чтобы спутник оказался под terrain
и trails.
2.3. setLayoutProperty('satellite-base', 'visibility', 'visible').
2.4. setLayoutProperty('osm-base', 'visibility', 'none').
2.5. Применить «спутниковые» правки к слоям trails/path/poi:
- усилить halo у line-слоёв (через setPaintProperty);
- сделать POI text-halo чёрным.
2.6. Сменить background-color на тёмно-серый (#2a2a2a).
3. Иначе (base === 'schematic'):
3.1. setLayoutProperty('osm-base', 'visibility', 'visible').
3.2. setLayoutProperty('satellite-base', 'visibility', 'none')
(если слой существует).
3.3. Вернуть halo trails / POI к дефолтным значениям из текущего стиля.
3.4. Background-color — из исходного стиля (не трогать,
он восстанавливается при setStyle).
```
### 5.3 `restoreBaseLayerState()`
```
1. base = localStorage.getItem('map-base-layer') || 'schematic'.
2. syncBaseLayerUI(base).
3. applyBaseLayer(base).
```
### 5.4 `syncBaseLayerUI(base)`
```
1. schematicBtn.classList.toggle('active', base === 'schematic').
2. satelliteBtn.classList.toggle('active', base === 'satellite').
```
### 5.5 Интеграция с `rebuildMapOverlays()` (`app.js`)
В существующей функции (см. `app.js`, ~строка 127) добавить вызов
**первым**:
```js
function rebuildMapOverlays() {
// ET-007: восстановить выбранную подложку первой —
// чтобы terrain/trails/POI применили свои overlays поверх неё
if (typeof restoreBaseLayerState === 'function') {
restoreBaseLayerState();
}
// ── далее без изменений ──
restoreTerrainState();
restoreTrailsState();
// ...
}
```
## 6. Файловая структура изменений
```
src/web/
├── index.html # + блок переключателя в #terrain-popup
├── app.css # + стили .terrain-base-row, .base-seg
├── app.js # + onBaseLayerToggle, applyBaseLayer,
# restoreBaseLayerState, syncBaseLayerUI,
# правка rebuildMapOverlays
```
Backend изменений нет.
## 7. Взаимодействие с существующими режимами
- Все режимы тулбара (Маршрут, Связка, Красивый, Разведка, Линейка,
Поиск, Метка, GPX) работают независимо от выбранной подложки.
- Переключение подложки **не сбрасывает** состояние режимов: маршруты,
GPX-треки, точки разведки, линейка, метки — остаются.
- Переключение темы (тёмная/светлая) **не сбрасывает** выбор подложки.
- При вызове `map.setStyle()` (тема, восстановление стиля)
спутниковый слой пересоздаётся в `rebuildMapOverlays()`.
## 8. Открытые вопросы для ADR
- Выбор провайдера спутниковых тайлов (Esri / Mapbox / Bing / OpenAerialMap).
- Решение по halo для POI/trails на спутнике: статические правки в
`style.json` через `visibility` или динамические `setPaintProperty`.
- Поведение hillshade при включении спутника: оставить как есть (по
выбору пользователя) — зафиксировано в REQ-F-04 как «оставить».

View File

@@ -0,0 +1,207 @@
---
type: acceptance-criteria
work_item_id: ET-007
title: "AC: Спутниковая карта (Схема / Спутник)"
version: 1
status: draft
created_at: 2026-05-31
updated_at: 2026-05-31
authors:
- "agent:analyst"
---
# Acceptance Criteria — ET-007: Спутниковая карта (Схема / Спутник)
## AC-01: UI переключателя
```gherkin
Feature: Переключатель подложки в попапе слоёв
Scenario: Открытие попапа показывает переключатель
Given пользователь находится на карте
When пользователь нажимает кнопку «Рельеф» (#terrain-toggle)
Then открывается попап #terrain-popup
And в попапе виден segmented-control «Подложка» с кнопками «Схема» и «Спутник»
And одна из кнопок имеет класс .active
Scenario: Default — Схема
Given localStorage пуст (или ключ 'map-base-layer' не задан)
When пользователь открывает попап слоёв
Then активна кнопка «Схема» (#base-btn-schematic)
And не активна кнопка «Спутник» (#base-btn-satellite)
```
## AC-02: Переключение на «Спутник»
```gherkin
Feature: Переключение Схема → Спутник
Scenario: Базовое переключение
Given активна подложка «Схема»
When пользователь нажимает «Спутник» в попапе слоёв
Then кнопка «Спутник» получает класс .active
And кнопка «Схема» теряет класс .active
And на карте слой osm-base скрыт (visibility=none)
And на карте появляется слой satellite-base (visibility=visible)
And положение карты (center, zoom, bearing, pitch) не изменилось
Scenario: Атрибуция Esri отображается
Given пользователь включил режим «Спутник»
Then в нижнем правом углу карты видна атрибуция «Source: Esri, Maxar, Earthstar Geographics, and the GIS User Community»
```
## AC-03: Переключение на «Схема»
```gherkin
Feature: Переключение Спутник → Схема
Scenario: Возврат на схему
Given активна подложка «Спутник»
When пользователь нажимает «Схема» в попапе слоёв
Then кнопка «Схема» получает класс .active
And слой osm-base снова виден (visibility=visible)
And слой satellite-base скрыт (visibility=none), но source остаётся в стиле
And положение карты не изменилось
```
## AC-04: Совместимость со слоями приложения
```gherkin
Feature: Слои поверх спутника
Scenario: Грунтовки и тропы видны на спутнике
Given активна подложка «Спутник»
And в попапе включены «Грунтовки» и «Тропы»
Then на карте видны линии грунтовок и троп поверх спутника
And линии имеют визуально различимую обводку (halo) для контраста
Scenario: POI видны на спутнике
Given активна подложка «Спутник»
And в попапе включён «POI»
Then на карте видны маркеры POI поверх спутника
And подписи POI читаемы (имеют тёмный halo)
Scenario: Hillshade поверх спутника
Given активна подложка «Спутник»
When пользователь включает «Тени рельефа»
Then на карте видны и спутник, и hillshade (hillshade поверх спутника)
Scenario: Маршрут OSRM поверх спутника
Given пользователь построил маршрут через OSRM
When пользователь переключает подложку на «Спутник»
Then маршрут остаётся виден поверх спутника
And статистика маршрута сохранена
Scenario: GPX-треки поверх спутника
Given пользователь загрузил GPX-трек
When пользователь переключает подложку на «Спутник»
Then GPX-линии и waypoints остаются видны поверх спутника
```
## AC-05: Сохранение в localStorage
```gherkin
Feature: Persistence выбора подложки
Scenario: Сохранение при переключении
Given активна подложка «Схема»
When пользователь нажимает «Спутник»
Then localStorage['map-base-layer'] === 'satellite'
Scenario: Восстановление после reload
Given localStorage['map-base-layer'] === 'satellite'
When пользователь перезагружает страницу
Then после загрузки карты активна подложка «Спутник»
And кнопка «Спутник» имеет класс .active
```
## AC-06: Восстановление при смене темы
```gherkin
Feature: Подложка переживает смену темы
Scenario: Переключение тёмной/светлой темы в режиме «Спутник»
Given активна подложка «Спутник»
When пользователь переключает тему (тёмная светлая)
Then после завершения map.setStyle() спутниковый слой восстановлен
And подложка «Спутник» остаётся активной
And все слои поверх (trails, POI, маршрут, GPX) восстановлены
Scenario: Переключение слоёв terrain в режиме «Спутник»
Given активна подложка «Спутник»
When пользователь включает или выключает «Тени рельефа» / «Перепады»
Then подложка «Спутник» остаётся активной
```
## AC-07: Совместимость с режимами тулбара
```gherkin
Feature: Подложка не мешает другим режимам
Scenario: Режим «Маршрут» на спутнике
Given активна подложка «Спутник»
When пользователь активирует режим «Маршрут»
And тапает 2 точки на карте
Then маршрут строится корректно
And линия маршрута видна на спутнике
Scenario: Режим «Разведка» на спутнике
Given активна подложка «Спутник»
When пользователь активирует режим «Разведка» и тапает на карту
Then круг радиуса разведки видим
And статистика разведки отображается
Scenario: Линейка на спутнике
Given активна подложка «Спутник»
When пользователь активирует «Линейка» и расставляет точки
Then линия линейки видна
And расстояние отображается
Scenario: Поиск на спутнике
Given активна подложка «Спутник»
When пользователь нажимает «Поиск» и вводит запрос
Then результаты поиска отображаются
And карта корректно центрируется на найденной точке
```
## AC-08: Производительность
```gherkin
Feature: Скорость переключения
Scenario: Переключение Схема → Спутник
Given активна подложка «Схема» и сеть 5 Мбит/с
When пользователь нажимает «Спутник»
Then первая спутниковая плитка отображается в течение 500 мс
Scenario: Переключение Спутник → Схема
Given активна подложка «Спутник» (тайлы уже подгружены)
When пользователь нажимает «Схема»
Then смена визуально мгновенная ( 100 мс)
```
## AC-09: Mobile UI
```gherkin
Feature: Переключатель на мобильных устройствах
Scenario: Попап слоёв на мобильном
Given пользователь открыл приложение на мобильном устройстве (виртуальный viewport 375×812)
When пользователь открывает попап слоёв
Then переключатель «Подложка» виден полностью
And обе кнопки нажимаемы (touch target 34px)
And не перекрывает другие элементы попапа
```
## AC-10: Не ломает существующий функционал
```gherkin
Feature: Регресс-проверка
Scenario: Все режимы работают как в режиме «Схема», так и в «Спутник»
Given пользователь использует приложение
Then режимы Маршрут, Связка, Красивый, Разведка, Линейка, Поиск, Метка, GPX
работают одинаково в обеих подложках
And переключение единиц измерения (км/мили) работает в обеих подложках
And переключение темы работает в обеих подложках
```

View File

@@ -0,0 +1,231 @@
---
type: test-plan
work_item_id: ET-007
title: "Test Plan: Спутниковая карта (Схема / Спутник)"
version: 1
status: draft
created_at: 2026-05-31
updated_at: 2026-05-31
authors:
- "agent:analyst"
test_suites:
- name: unit-base-layer-state
type: unit
description: "Чтение/запись/восстановление выбора подложки"
cases:
- id: U-01
name: "Default — Схема, если localStorage пуст"
input: "localStorage без ключа 'map-base-layer'"
expected: "restoreBaseLayerState() выставляет base='schematic'"
- id: U-02
name: "Чтение значения 'satellite' из localStorage"
input: "localStorage['map-base-layer'] = 'satellite'"
expected: "restoreBaseLayerState() выставляет base='satellite'"
- id: U-03
name: "Запись значения при переключении"
input: "onBaseLayerToggle('satellite')"
expected: "localStorage['map-base-layer'] === 'satellite'"
- id: U-04
name: "Игнор некорректного значения в localStorage"
input: "localStorage['map-base-layer'] = 'unknown'"
expected: "restoreBaseLayerState() fallback на 'schematic'"
- id: U-05
name: "Toggle на уже активный режим — no-op"
input: "active=schematic; onBaseLayerToggle('schematic')"
expected: "Никаких изменений в стиле, localStorage не записывается повторно"
- name: unit-ui-sync
type: unit
description: "Синхронизация .active у кнопок переключателя"
cases:
- id: U-10
name: "syncBaseLayerUI('satellite')"
input: "DOM с #base-btn-schematic.active и #base-btn-satellite без класса"
expected: "После: #base-btn-satellite.active=true, #base-btn-schematic.active=false"
- id: U-11
name: "syncBaseLayerUI('schematic')"
input: "DOM с #base-btn-satellite.active"
expected: "После: #base-btn-schematic.active=true, #base-btn-satellite.active=false"
- name: integration-maplibre-layers
type: integration
description: "Взаимодействие с MapLibre source/layer"
cases:
- id: I-01
name: "Добавление спутникового source при первом включении"
input: "applyBaseLayer('satellite') впервые"
expected: "map.getSource('satellite-raster') !== undefined; URL содержит arcgisonline.com"
- id: I-02
name: "Добавление спутникового layer при первом включении"
input: "applyBaseLayer('satellite') впервые"
expected: "map.getLayer('satellite-base') !== undefined; type='raster'"
- id: I-03
name: "Visibility OSM-base после переключения на спутник"
input: "applyBaseLayer('satellite')"
expected: "map.getLayoutProperty('osm-base', 'visibility') === 'none'"
- id: I-04
name: "Visibility satellite-base после переключения на схему"
input: "applyBaseLayer('satellite') → applyBaseLayer('schematic')"
expected: "satellite-base.visibility==='none', osm-base.visibility==='visible'"
- id: I-05
name: "Z-order: satellite ниже terrain и trails"
input: "applyBaseLayer('satellite'); включены hillshade и trails"
expected: "Layer index(satellite-base) < index(terrain-hillshade) < index(trails-track)"
- id: I-06
name: "Position карты сохраняется при переключении"
input: "center=[37.6,55.75], zoom=10; applyBaseLayer('satellite')"
expected: "После: getCenter() == [37.6,55.75], getZoom() == 10"
- id: I-07
name: "Атрибуция Esri зарегистрирована"
input: "applyBaseLayer('satellite')"
expected: "source 'satellite-raster' содержит attribution с упоминанием Esri"
- name: integration-style-switch
type: integration
description: "Поведение при map.setStyle (смена темы)"
cases:
- id: I-10
name: "Спутник восстанавливается после setStyle (тёмная → светлая)"
input: "active='satellite'; вызывается switchMapStyle()"
expected: "После idle: layer 'satellite-base' существует; visibility='visible'; osm-base.visibility='none'"
- id: I-11
name: "Сохранённое состояние читается из localStorage в rebuildMapOverlays"
input: "localStorage='satellite'; rebuildMapOverlays() вручную"
expected: "applyBaseLayer вызван с 'satellite'"
- id: I-12
name: "Восстановление выполняется до restoreTerrainState"
input: "rebuildMapOverlays() с заглушками-shpions"
expected: "Порядок вызовов: restoreBaseLayerState → restoreTerrainState"
- name: integration-other-layers
type: integration
description: "Совместимость со всеми клиентскими слоями"
cases:
- id: I-20
name: "Маршрут OSRM не теряется при переключении"
input: "Построен маршрут; applyBaseLayer('satellite')"
expected: "Layer маршрута существует, координаты не изменились"
- id: I-21
name: "GPX-трек не теряется при переключении"
input: "Загружен GPX; applyBaseLayer('satellite')"
expected: "Layer gpx-* существует, source.data не изменён"
- id: I-22
name: "Recon-круг не теряется при переключении"
input: "Активен recon; applyBaseLayer('satellite')"
expected: "Recon-круг отображается на карте"
- id: I-23
name: "Hillshade поверх спутника"
input: "applyBaseLayer('satellite'); включить hillshade"
expected: "Оба слоя видимы; hillshade выше satellite-base в стиле"
- id: I-24
name: "POI halo чёрный на спутнике"
input: "applyBaseLayer('satellite')"
expected: "map.getPaintProperty('poi-labels','text-halo-color') === '#000000' (или эквивалент)"
- id: I-25
name: "POI halo дефолтный на схеме"
input: "applyBaseLayer('schematic') после спутника"
expected: "POI labels вернули halo цвет схемы (#ffffff)"
- name: e2e-base-layer-workflow
type: e2e
description: "Полный пользовательский сценарий"
cases:
- id: E-01
name: "Открыть попап → включить спутник → сохранилось"
steps:
- "Открыть приложение (default — Схема)"
- "Нажать кнопку «Рельеф» в правой панели"
- "Убедиться: переключатель «Подложка» виден"
- "Нажать «Спутник»"
- "Убедиться: спутниковые тайлы загрузились"
- "Убедиться: атрибуция Esri видна"
- "Перезагрузить страницу"
- "Убедиться: после загрузки активен «Спутник»"
- id: E-02
name: "Переключение туда-обратно без потери маршрута"
steps:
- "Построить маршрут через OSRM (2 точки)"
- "Переключить на «Спутник»"
- "Убедиться: маршрут виден на спутнике, статистика сохранена"
- "Переключить на «Схема»"
- "Убедиться: маршрут виден на схеме, статистика та же"
- id: E-03
name: "Спутник + загрузка GPX"
steps:
- "Переключить на «Спутник»"
- "Загрузить GPX-файл"
- "Убедиться: трек отрисован поверх спутника"
- "Убедиться: цвет трека различим"
- id: E-04
name: "Спутник + смена темы"
steps:
- "Переключить на «Спутник»"
- "Переключить тёмную тему на светлую"
- "Дождаться idle"
- "Убедиться: подложка осталась «Спутник»"
- "Убедиться: все остальные слои восстановились"
- id: E-05
name: "Спутник + переключение единиц измерения"
steps:
- "Переключить на «Спутник»"
- "Открыть попап слоёв и переключить «мили»"
- "Убедиться: единицы переключились, подложка не сбросилась"
- id: E-06
name: "Спутник + hillshade"
steps:
- "Переключить на «Спутник»"
- "Включить «Тени рельефа»"
- "Убедиться: видны спутник и тени одновременно"
- id: E-07
name: "Линейка на спутнике"
steps:
- "Переключить на «Спутник»"
- "Активировать линейку"
- "Поставить 3 точки на карте"
- "Убедиться: линия линейки видна на спутнике"
- "Убедиться: расстояния отображаются"
- name: e2e-error-handling
type: e2e
description: "Поведение при сетевых ошибках"
cases:
- id: E-10
name: "Спутниковые тайлы недоступны (offline)"
steps:
- "Включить «Спутник»"
- "Симулировать offline (DevTools throttling: Offline)"
- "Сдвинуть карту в новую область"
- "Убедиться: приложение не падает; видим фон background"
- "Восстановить сеть → тайлы догружаются"
test_data:
- name: "test-track-simple.gpx"
description: "1 трек, 10 точек — для проверки совместимости с GPX"
- name: "Тестовый OSRM-маршрут"
description: "2 waypoint в районе [37.6,55.75] → [37.7,55.8]"

View File

@@ -0,0 +1,274 @@
---
type: ui-test-cases
work_item_id: ET-007
title: "UI Test Cases: Спутниковая карта (Схема / Спутник)"
version: 1
status: draft
created_at: 2026-05-31
updated_at: 2026-05-31
authors:
- "agent:analyst"
---
# UI Test Cases — ET-007: Спутниковая карта (Схема / Спутник)
Базовый URL: `https://openclaw.mva154.duckdns.org/enduro/`
Все тесты проверяют появление и поведение переключателя «Подложка» в
попапе слоёв, а также корректное отображение спутниковой подложки
поверх существующих UI-элементов.
---
### TC-UI-01 — Переключатель «Подложка» виден в попапе
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. screenshot: "01-popup-with-base-toggle"
6. check-visual: "В открывшемся попапе #terrain-popup видна строка «Подложка» с двумя кнопками: «Схема» (активна, оранжевый фон) и «Спутник» (неактивна)"
---
### TC-UI-02 — Активация «Спутник» меняет подложку
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. screenshot: "02-satellite-active"
8. check-visual: "Карта показывает спутниковые снимки (зелёные/коричневые поля, реальный рельеф). В попапе кнопка «Спутник» подсвечена оранжевым, «Схема» — нет"
---
### TC-UI-03 — Атрибуция Esri видна
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#terrain-toggle"
8. wait: 500
9. screenshot: "03-attribution-esri"
10. check-visual: "В правом нижнем углу карты видна атрибуция со словом «Esri» (или иконка info, при клике на которую разворачивается полный текст)"
---
### TC-UI-04 — Возврат на «Схема»
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#base-btn-schematic"
8. wait: 2000
9. screenshot: "04-schematic-restored"
10. check-visual: "Карта снова показывает схему OSM (бежевый/серый фон, дороги). В попапе кнопка «Схема» подсвечена оранжевым"
---
### TC-UI-05 — Грунтовки и тропы видны на спутнике
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. screenshot: "05-trails-on-satellite"
8. check-visual: "На спутниковой подложке отчётливо видны линии грунтовок (золотые/красные) и троп (красные пунктирные). Линии имеют светлую обводку (halo) для контраста с тёмным спутником"
---
### TC-UI-06 — POI и подписи на спутнике читаемы
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. screenshot: "06-poi-on-satellite"
8. check-visual: "POI-маркеры (цветные кружки) видны на спутнике. Подписи POI имеют тёмный halo, читаемы на любом фоне"
---
### TC-UI-07 — Спутник переживает смену темы
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#btn-theme"
8. wait: 3000
9. screenshot: "07-satellite-after-theme-switch"
10. check-visual: "После переключения темы карта по-прежнему показывает спутниковую подложку (а не схему)"
---
### TC-UI-08 — Hillshade поверх спутника
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#terrain-hillshade-cb"
8. wait: 3000
9. screenshot: "08-hillshade-on-satellite"
10. check-visual: "Виден спутник + затенение рельефа поверх (тёмные тени по склонам, рельеф «выпуклый»). Слои не перекрывают друг друга полностью"
---
### TC-UI-09 — Маршрут OSRM на спутнике
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#tb-route"
4. wait: 1000
5. click: "#map"
6. wait: 2000
7. scroll: 100
8. click: "#map"
9. wait: 5000
10. click: "#terrain-toggle"
11. wait: 500
12. click: "#base-btn-satellite"
13. wait: 5000
14. screenshot: "09-route-on-satellite"
15. check-visual: "Маршрут (синяя/оранжевая линия) виден поверх спутниковой подложки, конечные точки маршрута отмечены маркерами"
---
### TC-UI-10 — Переключатель на мобильном
- тип: ui
- viewport: mobile
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. screenshot: "10-popup-mobile"
6. check-visual: "На мобильном viewport попап #terrain-popup помещается на экране целиком. Переключатель «Подложка» виден, обе кнопки нажимаемы, не перекрывают другие элементы попапа"
---
### TC-UI-11 — Активация «Спутник» на мобильном
- тип: ui
- viewport: mobile
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. screenshot: "11-satellite-mobile"
8. check-visual: "Спутниковая подложка отображается на мобильном устройстве. Тулбар внизу и попап работают корректно, переключатель «Спутник» подсвечен"
---
### TC-UI-12 — Persistence: спутник после перезагрузки
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. navigate: https://openclaw.mva154.duckdns.org/enduro/
8. wait: 5000
9. screenshot: "12-satellite-after-reload"
10. check-visual: "После перезагрузки карта сразу открывается со спутниковой подложкой (не со схемой). Активный режим — «Спутник»"
---
### TC-UI-13 — GPX-панель + Спутник
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#tb-gpx"
8. wait: 1000
9. screenshot: "13-gpx-sheet-on-satellite"
10. check-visual: "Открылась панель #sheet-gpx с пустым состоянием поверх спутниковой карты. Панель и подложка визуально не конфликтуют"
---
### TC-UI-14 — Совместимость с переключателем единиц
- тип: ui
- viewport: desktop
шаги:
1. navigate: https://openclaw.mva154.duckdns.org/enduro/
2. wait: 5000
3. click: "#terrain-toggle"
4. wait: 500
5. click: "#base-btn-satellite"
6. wait: 5000
7. click: "#unit-btn-mi"
8. wait: 1000
9. screenshot: "14-satellite-with-miles"
10. check-visual: "Подложка остаётся «Спутник», единицы переключены на мили (кнопка «мили» подсвечена). Оба переключателя видны и работают независимо"