---
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/<x>/<y>.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/` — наследие нарезки тайлов
