18 KiB
18 KiB
type, work_item_id, title, version, status, created_at, updated_at, authors
| type | work_item_id | title | version | status | created_at | updated_at | authors | |
|---|---|---|---|---|---|---|---|---|
| trz | ET-007 | ТЗ: Спутниковая карта (Схема / Спутник) | 1 | draft | 2026-05-31 | 2026-05-31 |
|
ТЗ — ET-007: Спутниковая карта (Схема / Спутник)
1. Функциональные требования
REQ-F-01: Переключатель «Схема / Спутник»
- В попап-панели слоёв (
#terrain-popup, открывается кнопкой#terrain-toggle) добавляется новая секция в самом верху панели — «Подложка». - Реализация — segmented-control (
.seg-control/.seg-btn) с двумя кнопками:- «Схема» (
data-base="schematic", IDbase-btn-schematic) — активна по умолчанию. - «Спутник» (
data-base="satellite", IDbase-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».
- URL-шаблон:
- Источник добавляется на карту лениво: при первом включении режима «Спутник», а не на старте приложения.
- ID источника:
satellite-raster. - ID слоя:
satellite-base.
REQ-F-03: Поведение в режиме «Спутник»
- При включении «Спутник»:
- Если ещё не добавлен — добавить source
satellite-rasterи layersatellite-baseсразу после слояbackground(т.е. ниже всех остальных слоёв). - Слой
osm-base(существующий) скрывается (visibility: none). - Слой
backgroundостаётся (показывает «дыры» если тайлы ещё не загрузились) — цвет фона#2a2a2aдля тёмной темы и#1a1a1aдля светлой темы в режиме «Спутник» (чтобы белый фон не «бликовал» под тёмными снимками).
- Если ещё не добавлен — добавить source
- При возврате на «Схема»:
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> ИЛИ выше него — по
выбору разработчика; рекомендуется в самом верху для большей
заметности):
<!-- 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):
.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)
{
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)
{
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) добавить вызов
первым:
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 как «оставить».