--- type: data-requirements work_item_id: ET-013 title: "Требования к данным — ET-013: Zoom-aware paint для terrain-слоёв на z9-z11" version: 1 status: approved created_at: 2026-06-04 authors: - "agent:architect" --- # Требования к данным — ET-013 ## 1. Резюме ET-013 — **pure client render change**. Никаких изменений схемы БД, никаких новых таблиц/индексов/миграций, никаких изменений тайлов на диске, никаких новых ключей `localStorage`, никаких изменений конфигов источников. Меняется **только то, как уже существующие PNG-тайлы рельефа отрисовываются MapLibre на клиенте**: - `raster-opacity` становится `interpolate`-выражением по `['zoom']` (вместо константы). - Для hillshade добавляется `raster-contrast` (тоже `interpolate`). - `raster-resampling` для обоих terrain-слоёв переключается с `'linear'` на `'nearest'`. **Меняется:** - набор `raster paint properties` у двух MapLibre-слоёв (`terrain-hillshade`, `terrain-tri`); - визуальная читаемость рельефа на z9-z11 (целевое улучшение). **Не меняется:** - содержимое и формат PNG-тайлов в `data/terrain/{hillshade,tri,hypso}/` (PH-6 наследие); - schema БД `centralfederal.sqlite` и `gps_tracks.sqlite`; - контракт API `/terrain/{layer}/{z}/{x}/{y}.png` (REQ-F-18); - содержимое тайлов hypso (в UI не подключён, OOS); - параметры генератора hillshade на сервере (azimuth, altitude, z-factor — PH-6, OOS); - параметры классификации TRI (5-уровневая палитра — PH-6, OOS); - ключи `localStorage` (`terrain-hillshade`, `terrain-tri` — REQ-F-17); - содержимое `config/*.yaml`; - стили `style.json`, `style-dark.json` (растровые terrain-слои в них не описаны — добавляются динамически из JS). ## 2. Архитектурные границы данных | Слой данных | Тип | Расположение | Изменения в ET-013 | |-----------------------------------|----------------|----------------------------------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| | OSM-vector (`trails`) | существующий | `/app/data/centralfederal.sqlite` | **нет** | | Личные GPX треки (ET-006) | существующий | браузер (memory) | **нет** | | Публичные GPS треки (ET-008) | существующий | `/app/data/gps_tracks.sqlite` | **нет** | | OSRM-граф | существующий | `/app/data/enduro.osrm.*` | **нет** | | Terrain hillshade PNG | существующий | `data/terrain/hillshade/{z}/{x}/{y}.png` (z=8..14) | **read-only**: добавляется новая комбинация `(z=9, x, y)`, которая клиент раньше не запрашивал. Тайлы на диске уже есть (PH-6 нарезка) | | Terrain TRI PNG | существующий | `data/terrain/tri/{z}/{x}/{y}.png` (z=8..14) | **read-only**: те же тайлы, что и раньше; меняется только paint | | Terrain hypso PNG | существующий | `data/terrain/hypso/{z}/{x}/{y}.png` | **не используется** в ET-013 (OOS) | | User UI state | существующий | `localStorage` | **нет** новых ключей, нет миграции | | MapLibre client tile cache | существующий | браузер (LRU MapLibre, ~100 MB) | **расширяется ключевым пространством**: теперь могут лежать тайлы hillshade с `z = 9` (раньше не запрашивались) | | Серверный кэш `/terrain/*` | не предусмотрен | n/a (FileResponse + Cache-Control immutable) | **нет** | ## 3. Серверные данные ### 3.1 Структура `data/terrain/` **Без изменений vs PH-6.** Структура каталога: ``` data/terrain/ ├── hillshade/ │ ├── 8/{x}/{y}.png # baseline │ ├── 9/{x}/{y}.png # используется ET-013 впервые на клиенте │ ├── 10/{x}/{y}.png # baseline (10+ уже использовался) │ ├── 11/{x}/{y}.png │ ├── 12/{x}/{y}.png │ ├── 13/{x}/{y}.png │ └── 14/{x}/{y}.png ├── tri/ # та же структура, z=8..14 └── hypso/ # та же структура, в UI не подключён ``` Никаких ALTER/CREATE/INSERT/UPDATE/DELETE на стороне данных. Никакой догенерации тайлов. Никакого преобразования формата (PNG остаётся PNG 256×256). ### 3.2 Объёмы данных | Метрика | Текущее (PH-6) | После ET-013 | Гейт | |------------------------------------------|---------------------|-------------------------------|------------------------------------------------------| | Объём PNG hillshade на диске | ~ X MB (PH-6 baseline) | без изменений | n/a | | Объём PNG TRI на диске | ~ Y MB | без изменений | n/a | | Запросы hillshade за сессию | N (только z≥10) | ~ 1.25-1.35 × N (добавился z=9) | BRD M-10: ≤ +35% | | Запросы TRI за сессию | M (z=5..14) | без изменений | n/a | ### 3.3 Pre-deploy validation тайлов z9-z11 **Обязательная проверка перед merge** (BRD R-11, AC-19): ```bash curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/9/308/158.png' | head -1 curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/10/617/317.png' | head -1 curl -sI 'https://openclaw.mva154.duckdns.org/enduro/terrain/hillshade/11/1234/635.png' | head -1 ``` Ожидается `HTTP/1.1 200 OK` на все три. Если 404 — задача останавливается, открывается PH-6 follow-up «hillshade-z9-z14 backfill». См. `07-infra-requirements.md` §6.2 шаг 1. ### 3.4 API endpoint `terrain_tile` **Без изменений** (`src/api/main.py:1240`): - URL: `GET /terrain/{layer}/{z}/{x}/{y}.png`, `layer ∈ {hillshade, tri, hypso}`. - Возвращает: PNG из файловой системы (sendfile через `FileResponse`). - Заголовки: `Cache-Control: public, max-age=31536000, immutable` — без изменений. Браузерный кэш и nginx-кэш агрессивно поглощают повторы. - Контракт OpenAPI — без изменений (REQ-F-18, NFR-04). ## 4. Клиентские данные ### 4.1 localStorage **Без изменений vs PH-6 / ET-007.** Используются ключи: | Ключ | Назначение | Изменения в ET-013 | |----------------------------|---------------------------------------------|--------------------| | `terrain-hillshade` | `'1' | '0'` — чекбокс «Тени рельефа» | **нет** | | `terrain-tri` | `'1' | '0'` — чекбокс «Перепады» | **нет** | REQ-F-17 в TRZ §3: «никакой миграции localStorage не нужно». Существующие сессии при следующей загрузке автоматически получают новый UI-порог 9 (вместо 10) и новые `HILLSHADE_PAINT` / `TRI_PAINT` константы. Если у пользователя `terrain-hillshade === '1'` и текущий zoom ≥ 9 — hillshade покажется автоматически (раньше показался бы только на z ≥ 10). ### 4.2 MapLibre LRU (browser-side) Браузерный MapLibre кэширует растровые тайлы в собственном LRU (~100 MB по умолчанию). После ET-013: - Ключевое пространство кэша: `(source_id, z, x, y)` — расширяется для `terrain-hillshade-source` на `z = 9` (раньше source имел `minzoom: 10` → запросов z=9 не было). - Объём — управляется MapLibre, ~100 MB. Дельта мизерная (тайл hillshade ≈ 8-30 KB). - Никакой синхронизации/инвалидации не нужно (тайлы на сервере не меняются; `Cache-Control: immutable` гарантирует консистентность). ### 4.3 In-memory paint constants Новые константы в `src/web/app.js` после `TERRAIN_BASE_URL`: ```js const HILLSHADE_PAINT = { 'raster-opacity': ['interpolate', ['linear'], ['zoom'], 9, 0.65, 10, 0.60, 11, 0.55, 12, 0.50, 14, 0.40], 'raster-contrast': ['interpolate', ['linear'], ['zoom'], 9, 0.40, 10, 0.35, 11, 0.30, 12, 0.15, 14, 0.00], 'raster-resampling': 'nearest' }; const TRI_PAINT = { 'raster-opacity': ['interpolate', ['linear'], ['zoom'], 5, 0.55, 7, 0.65, 8, 0.70, 9, 0.80, 10, 0.85, 11, 0.85, 12, 0.75, 15, 0.70], 'raster-resampling': 'nearest' }; ``` - Это **компилируемые MapLibre `interpolate`-выражения**, не «данные» в архитектурном смысле. Живут в коде, изменяются коммитом (BRD §6 q&a «Делать ли paint-таблицы переменными окружения / config'ом? Нет — преждевременная абстракция»). - Память: < 1 KB суммарно. Производительность: MapLibre кэширует скомпилированные выражения (NFR-01). ## 5. Контракты API ### 5.1 `GET /terrain/{layer}/{z}/{x}/{y}.png` | Аспект | До ET-013 | После ET-013 | |-----------------------|--------------------------------------------------------|-------------------------------------------------------------------------------------| | Поддерживаемые `layer`| `hillshade`, `tri`, `hypso` | без изменений | | Path-параметр `z` | принимается любой валидный z, тайлы на диске z=8..14 | без изменений | | Response 200 | для существующих `(z, x, y)` PNG | без изменений | | Response 404 | для несуществующих `(z, x, y)` | без изменений | | Response Content-Type | `image/png` | без изменений | | Cache-Control | `public, max-age=31536000, immutable` | без изменений | **Старые клиенты** (старый `app.js` со старым `minzoom = 10` для hillshade) — продолжают работать. Никакого breaking change в контракте нет (NFR-04). ### 5.2 Прочие endpoint'ы ET-013 не трогает: `/api/gps-tracks/*`, `/api/trails/*`, `/api/route/*`, `/api/health`. Их контракты — без изменений. ## 6. Миграции **Нет.** Никаких миграций БД, миграций localStorage, миграций конфигов, миграций тайлов. При деплое в test: - `data/terrain/*` — без изменений (read-only для `app`). - БД `centralfederal.sqlite`, `gps_tracks.sqlite` — без изменений. - Серверный кэш — отсутствует у `/terrain/*` (статическая раздача с `Cache-Control: immutable`). - Клиентский MapLibre LRU — самоочищается при reload браузера; явной миграции не нужно. - localStorage — старые ключи интерпретируются как раньше; включённый ранее hillshade автоматически появится на z9 (REQ-F-17, AC-14). ## 7. Тестовые данные ### 7.1 Для unit-тестов `tests/unit/test_terrain_paint.py` (новый, REQ-F-13 / REQ-F-14): - Python-парсер исходного `src/web/app.js` через `re`. - Никаких внешних зависимостей. - Никаких фикстур данных. - Проверяет наличие `HILLSHADE_PAINT` / `TRI_PAINT`, наличие ключевых stops (`9, 0.65`, `11, 0.55`, `14, 0.40`, `8, 0.70`, `10, 0.85`), наличие `'raster-resampling': 'nearest'`, порог `zoom < 9` в `updateHillshadeAvailability`. ### 7.2 Для integration-тестов `tests/integration/test_terrain_z9_tiles.py` (новый, REQ-F-15): - Использует FastAPI `TestClient` для `src/api/main.py:app`. - Опирается на наличие файла `data/terrain/hillshade/9//.png` — если каталога нет, тест `skipped` с reason (CI без данных). - На test-среде mva154 (где данные есть) — выполняется как smoke-проверка endpoint'а. - Дополнительно: `test_hillshade_invalid_zoom_404` — sanity на невалидном zoom. ### 7.3 Для UI-тестов (Playwright) `04b-ui-test-cases.md` — список тест-кейсов TC-UI-01..TC-UI-10: - Запускается на test-среде `https://openclaw.mva154.duckdns.org/enduro/`. - Данные — реальные PNG-тайлы рельефа на mva154 (PH-6 нарезка). - Скриншот-эталоны для AC-06..AC-12 (визуальная читаемость) — в `tests/e2e/screenshots/et013/`. - Скриншоты сравниваются оператором (качественная приёмка), не пиксельный diff (BRD M-9, R-1..R-3). ## 8. Резервные копии и DR Без изменений vs PH-6. - БД `centralfederal.sqlite`, `gps_tracks.sqlite` — бэкап тем же crontab-скриптом, что и раньше; ET-013 не трогает. - PNG-тайлы `data/terrain/*` — регенерируются из SRTM при необходимости (PH-6 pipeline). RPO для тайлов = время регенерации (часы), но они read-only и не теряются при деплое ET-013. RPO для ET-013: 0 (никаких данных не пишется/не теряется). ## 9. Privacy / Compliance | Аспект | Требование | |-------------------------|--------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| | PII | **Нет.** PNG-тайлы рельефа — derivative из SRTM 30 м (NASA, public domain). Никаких персональных данных нигде в data-flow ET-013 | | Licensing | **Без изменений** (PH-6 наследие: SRTM 30 m — public domain; derivative PNG распространяется свободно). ET-013 не меняет источник данных | | Attribution | MapLibre attribution control отображает атрибуцию активных источников (OSM, Esri). Атрибуция SRTM/NASA не выводится в UI (PH-6 решение); ET-013 это не меняет | | GDPR / 152-ФЗ | Не применимо (нет PII) | ## 10. Связанные документы - `01-brd.md` §2.1 (текущая реализация), §3 (F-01..F-14), §6 (Зависимости.Данные) - `02-trz.md` §3 REQ-F-04..REQ-F-09 (paint constants), REQ-F-13..REQ-F-15 (тесты), REQ-F-17 (localStorage), REQ-F-18 (API), REQ-F-19 (configs/styles) - `06-adr/ADR-017-zoom-aware-terrain-paint.md` §«Решение», §«Последствия» - `07-infra-requirements.md` §3 (network), §6 (deploy procedure), §3.1 (ingress estimate) - `10-tech-risks.md` (этот пакет) - `docs/work-items/ET-012/08-data-requirements.md` — образец «read-pattern change» документа (наследие) - `docs/phases/PH-6.terrain/` — наследие нарезки тайлов