All checks were successful
12-review.md (REQUEST_CHANGES, attempt 2/3) flagged 6 must-fix items
in the analysis/architecture artefacts plus matching bugs that had
already leaked into the committed implementation. This patch lands
both: documents corrected, code aligned with corrected specs, tests
updated.
P1-1: TRZ/ADR/Data/Risks referenced fictional layer ids
(`trails-grade1..5-halo-satellite`, `paths-bridleway-halo-satellite`).
Actual style*.json has only `trails-track-halo-satellite` and
`trails-path-bridleway-halo-satellite`; grade differentiation lives
inside one `match` expression on `tracktype` within `trails-track`.
Docs rewritten to operate on real ids.
P1-2: POI labels contrast was broken — spec changed only halo-color
to black, leaving `text-color: #333333` (light theme baseline)
unreadable over the new black halo. Code+docs now switch BOTH
`text-color` (-> `#ffffff` on satellite) AND halo together, with
per-theme baselines (`#333333` light / `#e0e0e0` dark) restored on
return to Schematic.
P1-3: BRD §5 hillshade risk said «hillshade auto-disabled on
satellite», contradicting TRZ/ADR/AC. BRD wording aligned: hillshade
keeps working over satellite; visual check is AC-04.
P1-4: background-color had four divergent sources (`#1a1a1a`,
`#2a2a2a`, `#1a1a2e`, `#f0ede6`), incl. an inverted-theme typo and a
baseline `#1a1a1a` that didn't match the actual `style-dark.json:28`
value `#1a1a2e`. Settled on ADR-004's single-constant model: `#2a2a2a`
on satellite for both themes; on Schematic restore per-theme baselines
`#f0ede6` (light) / `#1a1a2e` (dark). `_applyBackgroundForSatellite`
fixed accordingly.
P1-5: app.js already had `layerState.basemap` and `toggleLayer
('basemap')` (legacy «Базовая карта» switch). Neither TRZ nor ADR
specified the interaction. Added save&restore contract: on entering
Satellite save `layerState.basemap` to `_savedBasemapState` and
force-hide `osm-base`; on returning to Schematic restore osm-base
visibility from the saved value. CSS hook `body.satellite-active
#btn-basemap { display:none }` keeps the user from trying to enable
a hybrid mode (out of scope, BRD §3). TRZ §5.6, ADR-004 §8.
P1-6: `restoreTrailsState()` and `onTrailsCheckbox()` only managed
visibility of `trails-track` / `trails-path-bridleway`, leaving
their halo-underlay siblings as «phantom» halos when the user
unchecked grunты/тропы under Satellite. Introduced
`_applyTrailHaloVisibility(map, base)` reading checkbox state from
DOM; called from `onTrailsCheckbox`, `restoreTrailsState`, and both
branches of `applyBaseLayer`. Rule: halo visible ⇔ (base ===
satellite) AND (checkbox ON). TRZ §5.7, ADR-004 §9.
Docs bumped: BRD v2, TRZ v2, AC v2, Data v2, Risks v2; ADR-004
получает «Ревизии»-секцию (status remains accepted — only editorial
fixes, no decision change).
Tests:
- tests/unit/base_layer.test.js: rewritten 2 background-color
assertions (#1a1a1a expectation removed), added 6 new tests for
P1-2 / P1-4 (POI text-color per-theme baselines, single satellite
bg #2a2a2a, baseline restore on Schematic).
- All 33 JS unit tests + 22 pytest static checks green.
- Full pytest suite: 76 passed (excluding pre-existing
shapely-import skipped collection in tests/unit/test_health.py).
Refs: ET-007
Review: docs/work-items/ET-007/12-review.md (P1-1..P1-6)
ADR: docs/work-items/ET-007/06-adr/ADR-004-satellite-base-layer.md (rev. 2026-05-31)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
499 lines
27 KiB
Markdown
499 lines
27 KiB
Markdown
---
|
||
type: trz
|
||
work_item_id: ET-007
|
||
title: "ТЗ: Спутниковая карта (Схема / Спутник)"
|
||
version: 2
|
||
status: draft
|
||
created_at: 2026-05-31
|
||
updated_at: 2026-05-31
|
||
changelog:
|
||
- "v2 (2026-05-31): code-review fixes (12-review.md, attempt 2/3) — P1-1..P1-6: реальные id halo-слоёв, контраст POI labels, единый satellite-bg, контракт с layerState.basemap, синхронизация halo с чекбоксами."
|
||
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`
|
||
для обеих тем (тёмно-серый, чтобы не «бликовал» под медленно
|
||
подгружающимися спутниковыми плитками; решение зафиксировано в
|
||
ADR-004 §6). Baseline `background-color` для возврата на «Схему»:
|
||
`#f0ede6` (light), `#1a1a2e` (dark) — см. Data §5.
|
||
- При возврате на «Схема»:
|
||
- `osm-base` снова видим (`visibility: visible`).
|
||
- `satellite-base` скрывается (`visibility: none`), но не удаляется
|
||
из стиля (быстрое повторное переключение).
|
||
|
||
### REQ-F-04: Совместимость со слоями приложения
|
||
|
||
Все клиентские слои должны корректно отображаться поверх спутника:
|
||
|
||
| Слой | Z-order над спутником | Доп. правила в режиме «Спутник» |
|
||
| ----------------------------- | --------------------- | ------------------------------------------------------------------------------ |
|
||
| Hillshade (`terrain-hillshade`) | поверх спутника | Включается/выключается чекбоксом как раньше; по умолчанию НЕ авто-выключается |
|
||
| TRI (`terrain-tri`) | поверх спутника | Аналогично hillshade |
|
||
| Trails — грунтовки (`trails-track`) | поверх terrain | Halo через парный underlay-слой `trails-track-halo-satellite` (единый halo на весь слой, без разбиения по grade) |
|
||
| Paths / bridleway (`trails-path-bridleway`) | поверх trails | Halo через парный underlay-слой `trails-path-bridleway-halo-satellite` |
|
||
| Asphalt-дороги (`trails-asphalt`) | поверх trails | Halo не вводится — слой по умолчанию скрыт (`visibility: none`, `line-opacity: 0`); если будет включён в будущем, halo добавляется тем же паттерном |
|
||
| POI circles (`poi-circles`) | поверх trails | Обводка `circle-stroke-color: #ffffff`, толщина 2 px |
|
||
| POI labels (`poi-labels`) | поверх POI | `text-color: #ffffff`, `text-halo-color: #000000`, `text-halo-width: 2` для читаемости на спутнике (см. REQ-F-04-POI ниже) |
|
||
| Route / Scenic / Link / Ruler | поверх POI | Без изменений |
|
||
| GPX-треки и waypoints | поверх Route | Без изменений (ET-006 уже совместим) |
|
||
|
||
**REQ-F-04-POI (контраст подписей POI на спутнике).** На спутнике
|
||
менять обе пары свойств `text-color` и `text-halo-*`, иначе тёмный
|
||
текст `#333333` (light-theme) останется нечитаем поверх тёмного halo.
|
||
Конкретные значения и baseline-возврат — в Data §5.
|
||
|
||
**Halo-слои в `style*.json` (подтверждено фактическим кодом
|
||
`src/web/style.json` и `style-dark.json`):** реальные id — это
|
||
`trails-track-halo-satellite` и `trails-path-bridleway-halo-satellite`.
|
||
Слоёв `trails-grade1..5-halo-satellite` или
|
||
`paths-bridleway-halo-satellite` **нет** и заводить их не нужно:
|
||
`trails-track` хранит дифференциацию по grade внутри одного `match`-
|
||
выражения по `tracktype`. На спутнике halo единого цвета/ширины
|
||
накладывается на весь `trails-track` целиком; разделять halo по grade
|
||
не требуется (визуально не различимо под линией grade-цвета).
|
||
|
||
Реализация:
|
||
- Halo для грунтовок и троп — пара underlay-слоёв
|
||
(`trails-track-halo-satellite`, `trails-path-bridleway-halo-satellite`),
|
||
уже присутствующих в обоих `style*.json` с `visibility: none`.
|
||
Включаются через `setLayoutProperty(..., 'visibility', 'visible')`
|
||
только в режиме «Спутник».
|
||
- Стили POI (circles и labels) на спутнике задаются динамически через
|
||
`setPaintProperty` при переключении режима; baseline-значения
|
||
возврата на «Схему» зафиксированы в `08-data-requirements.md` §5
|
||
и в `applyBaseLayer()` (см. §5.2 ниже).
|
||
|
||
### 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. Корректный z-order гарантируется тем, что
|
||
restoreBaseLayerState вызывается ПЕРВЫМ в rebuildMapOverlays
|
||
(см. ADR-004 §«Вариант O», O-A; см. также R-7 в Tech-Risks).
|
||
2.3. setLayoutProperty('satellite-base', 'visibility', 'visible').
|
||
2.4. Запомнить layerState.basemap в _savedBasemapState (см. §5.6).
|
||
Принудительно скрыть osm-base:
|
||
setLayoutProperty('osm-base', 'visibility', 'none').
|
||
2.5. Включить halo-слои (см. §5.7 — синхронизация с чекбоксами):
|
||
для каждой пары (base, halo) ∈
|
||
[('trails-track', 'trails-track-halo-satellite'),
|
||
('trails-path-bridleway', 'trails-path-bridleway-halo-satellite')]
|
||
выставить halo.visibility = base.visibility текущего слоя.
|
||
2.6. Применить динамические правки POI:
|
||
- poi-circles: circle-stroke-color = '#ffffff',
|
||
circle-stroke-width = 2;
|
||
- poi-labels: text-color = '#ffffff',
|
||
text-halo-color = '#000000',
|
||
text-halo-width = 2.
|
||
2.7. Сменить background-color на единую satellite-константу
|
||
'#2a2a2a' (для обеих тем, см. ADR-004 §6).
|
||
3. Иначе (base === 'schematic'):
|
||
3.1. setLayoutProperty('osm-base', 'visibility',
|
||
_savedBasemapState === false ? 'none' : 'visible') —
|
||
восстановить выбор пользователя по «Базовая карта»
|
||
(см. §5.6); по умолчанию (если не сохранено) — 'visible'.
|
||
3.2. setLayoutProperty('satellite-base', 'visibility', 'none')
|
||
(если слой существует).
|
||
3.3. Скрыть halo-underlay-слои:
|
||
для обеих пар выставить halo.visibility = 'none'.
|
||
3.4. Вернуть POI к baseline текущей темы (см. Data §5):
|
||
- poi-circles: circle-stroke-color / circle-stroke-width
|
||
читаются из Data §5 baseline (поэтапно: light → dark);
|
||
- poi-labels: text-color, text-halo-color, text-halo-width — то же.
|
||
Источник истины baseline'ов — Data §5; код держит две константы
|
||
per-theme и выбирает по текущей теме.
|
||
3.5. Background-color — установить baseline текущей темы из Data §5
|
||
('#f0ede6' light / '#1a1a2e' dark). Прямая запись через
|
||
setPaintProperty (не полагаемся на setStyle, потому что
|
||
applyBaseLayer вызывается и без смены стиля).
|
||
```
|
||
|
||
### 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/ADR-004 O-A: восстановить подложку ПЕРВОЙ — terrain/trails/POI
|
||
// ложатся поверх неё (z-order через порядок вставки, без beforeId).
|
||
// Функция определена в этом же файле (ADR-004 §2), глобально доступна.
|
||
restoreBaseLayerState();
|
||
// ── далее без изменений ──
|
||
restoreTerrainState();
|
||
restoreTrailsState();
|
||
// ...
|
||
}
|
||
```
|
||
|
||
### 5.6 Взаимодействие с существующим `toggleLayer('basemap')`
|
||
|
||
В `app.js:384–391` уже определены:
|
||
|
||
```js
|
||
const layerState = { tracks: true, paths: true, poi: true, basemap: true };
|
||
const layerGroups = { …, basemap: ['osm-base'] };
|
||
function toggleLayer(group) { …setLayoutProperty('osm-base', 'visibility', …) }
|
||
```
|
||
|
||
— это существующий механизм «Базовая карта (схема)» как
|
||
самостоятельного выключателя. ET-007 уважает этот механизм по
|
||
следующему контракту:
|
||
|
||
1. **При входе в «Спутник»** (`applyBaseLayer('satellite')`, §5.2 шаг
|
||
2.4): запомнить `layerState.basemap` в локальной переменной
|
||
`_savedBasemapState` (init: `null`). Затем **принудительно** скрыть
|
||
`osm-base`. `layerState.basemap` **не меняется** — UI-кнопка
|
||
`#btn-basemap` остаётся в прежнем визуальном состоянии.
|
||
2. **Пока активен «Спутник»**, кнопка «Базовая карта» скрыта из UI
|
||
(CSS-класс `.satellite-active` на корне приложения скрывает
|
||
`#btn-basemap`) — пользователь не должен пытаться включить схему
|
||
поверх спутника (гибридный режим out of scope BRD §3). Альтернатива
|
||
реализации — disabled, на усмотрение разработчика; визуальный
|
||
эффект и AC-02/AC-03 идентичны.
|
||
3. **При возврате на «Схему»** (§5.2 шаг 3.1): `osm-base.visibility`
|
||
восстанавливается из `_savedBasemapState` (по умолчанию `true` →
|
||
`'visible'`, если ранее пользователь сам выключал — `false` →
|
||
`'none'`). После восстановления `_savedBasemapState = null`.
|
||
4. **На «Схеме» (default-режим)**: `toggleLayer('basemap')` работает
|
||
ровно как раньше — пишет в `layerState.basemap` и переключает
|
||
`osm-base.visibility`. ET-007 этот код не трогает.
|
||
|
||
### 5.7 Синхронизация halo с чекбоксами «Грунтовки» / «Тропы» / «POI»
|
||
|
||
В `app.js:2783–2826` существуют `onTrailsCheckbox()` и
|
||
`restoreTrailsState()`, которые управляют `visibility` только
|
||
`trails-track` и `trails-path-bridleway`. Halo-underlay-слои
|
||
(`*-halo-satellite`) сейчас они не трогают — в режиме «Спутник» это
|
||
дало бы «фантом» halo без основной линии.
|
||
|
||
Правило (источник истины): **halo-слой видим ⇔ (текущая база ===
|
||
'satellite') AND (соответствующий пользовательский чекбокс ON)**.
|
||
|
||
Реализация:
|
||
|
||
1. Ввести хелпер `applyTrailHaloVisibility(trackOn, pathOn)`:
|
||
- для пары `('trails-track-halo-satellite', trackOn)` и
|
||
`('trails-path-bridleway-halo-satellite', pathOn)`:
|
||
`visibility = (currentBaseLayer === 'satellite' && checked) ? 'visible' : 'none'`.
|
||
2. В `onTrailsCheckbox()` после установки `visibility` основным слоям —
|
||
вызвать `applyTrailHaloVisibility(trackChecked, pathChecked)`.
|
||
3. В `restoreTrailsState()` после установки `visibility` основным слоям —
|
||
вызвать `applyTrailHaloVisibility(trackOn, pathOn)`.
|
||
4. В `applyBaseLayer('satellite')` (§5.2 шаг 2.5) и
|
||
`applyBaseLayer('schematic')` (§5.2 шаг 3.3) — читать текущее
|
||
состояние чекбоксов из DOM (`#trails-track-cb`, `#trails-path-cb`)
|
||
и вызвать тот же хелпер.
|
||
|
||
**POI:** для группы `poi-circles` / `poi-labels` отдельных
|
||
halo-underlay-слоёв нет — динамические правки `setPaintProperty`
|
||
(см. §5.2) уже привязаны к видимости самих слоёв. При выключении
|
||
чекбокса «POI» оба слоя становятся `visibility: none` через
|
||
существующий механизм `layerState.poi`/`restorePoiState()` — текстовые
|
||
halo-свойства просто не видны, поэтому отдельная синхронизация не
|
||
требуется.
|
||
|
||
## 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 как «оставить».
|