diff --git a/docs/work-items/ET-007/01-brd.md b/docs/work-items/ET-007/01-brd.md new file mode 100644 index 0000000..235da43 --- /dev/null +++ b/docs/work-items/ET-007/01-brd.md @@ -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 закладывает архитектурную основу + (источник тайлов, точка переключения). diff --git a/docs/work-items/ET-007/02-trz.md b/docs/work-items/ET-007/02-trz.md new file mode 100644 index 0000000..ea6eb40 --- /dev/null +++ b/docs/work-items/ET-007/02-trz.md @@ -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`. +- Под переключателем — горизонтальная линия-разделитель (`
`), + как уже сделано между секциями попапа. + +### 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` (сразу после +`
Эндуро
` ИЛИ выше него — по +выбору разработчика; рекомендуется в самом верху для большей +заметности): + +```html + +
+ Подложка +
+ + +
+
+
+``` + +### 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 как «оставить». diff --git a/docs/work-items/ET-007/03-acceptance-criteria.md b/docs/work-items/ET-007/03-acceptance-criteria.md new file mode 100644 index 0000000..cece277 --- /dev/null +++ b/docs/work-items/ET-007/03-acceptance-criteria.md @@ -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 переключение темы работает в обеих подложках +``` diff --git a/docs/work-items/ET-007/04-test-plan.yaml b/docs/work-items/ET-007/04-test-plan.yaml new file mode 100644 index 0000000..a5ef729 --- /dev/null +++ b/docs/work-items/ET-007/04-test-plan.yaml @@ -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]" diff --git a/docs/work-items/ET-007/04b-ui-test-cases.md b/docs/work-items/ET-007/04b-ui-test-cases.md new file mode 100644 index 0000000..497f857 --- /dev/null +++ b/docs/work-items/ET-007/04b-ui-test-cases.md @@ -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: "Подложка остаётся «Спутник», единицы переключены на мили (кнопка «мили» подсвечена). Оба переключателя видны и работают независимо"