From 6b88bcee2851c55ff06cfe944fa239ec68a0e408 Mon Sep 17 00:00:00 2001 From: claude-bot Date: Thu, 4 Jun 2026 09:40:50 +0000 Subject: [PATCH] architect(ET): auto-commit from architect run_id=79 --- docs/architecture/adr/README.md | 1 + .../ADR-017-zoom-aware-terrain-paint.md | 367 ++++++++++++++++++ .../ET-013/07-infra-requirements.md | 249 ++++++++++++ .../work-items/ET-013/08-data-requirements.md | 289 ++++++++++++++ docs/work-items/ET-013/10-tech-risks.md | 357 +++++++++++++++++ 5 files changed, 1263 insertions(+) create mode 100644 docs/work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md create mode 100644 docs/work-items/ET-013/07-infra-requirements.md create mode 100644 docs/work-items/ET-013/08-data-requirements.md create mode 100644 docs/work-items/ET-013/10-tech-risks.md diff --git a/docs/architecture/adr/README.md b/docs/architecture/adr/README.md index 42bddfd..2600a61 100644 --- a/docs/architecture/adr/README.md +++ b/docs/architecture/adr/README.md @@ -20,3 +20,4 @@ | ADR-014 | GPX-download эндпоинт публичного трека: `xml.etree.ElementTree`-builder + fetch+Blob на клиенте | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-014-gpx-download-endpoint.md) | | ADR-015 | Политика реэкспорта публичных треков: per-source `download_allowed` в `gps_sources.yaml`, default-deny (whitelist `osm` для MVP) | accepted | 2026-06-03 | [ET-011](../../work-items/ET-011/06-adr/ADR-015-source-redistribution-policy.md) | | ADR-016 | Снижение minzoom публичных GPS-треков до z5: калибровка существующих tier-таблиц `build_gps_mvt`/`_simplify_coords`, on-demand MVT остаётся, без heat-map/clustering | accepted | 2026-06-04 | [ET-012](../../work-items/ET-012/06-adr/ADR-016-z5-tiling-policy.md) | +| ADR-017 | Zoom-aware paint для hillshade/TRI на z9-z11: `interpolate`-выражения по `raster-opacity` и `raster-contrast`, `raster-resampling: 'nearest'`, понижение UI-минзума hillshade с 10 до 9; без перегенерации растровых тайлов | accepted | 2026-06-04 | [ET-013](../../work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md) | diff --git a/docs/work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md b/docs/work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md new file mode 100644 index 0000000..bd257e5 --- /dev/null +++ b/docs/work-items/ET-013/06-adr/ADR-017-zoom-aware-terrain-paint.md @@ -0,0 +1,367 @@ +--- +type: adr +work_item_id: ET-013 +adr_id: ADR-017 +title: "ADR-017: Zoom-aware paint для hillshade/TRI — калибровка клиентских raster-слоёв вместо перегенерации тайлов" +status: accepted +created_at: 2026-06-04 +updated_at: 2026-06-04 +authors: + - "agent:architect" +supersedes: [] +superseded_by: [] +labels: + - "ET-013:terrain-paint" + - "minor-change" +--- + +# ADR-017 — Zoom-aware paint для hillshade/TRI на z9-z11 + +## Статус + +**Accepted.** Архитектурное решение для ET-013. + +Это **калибровка клиентского рендера** растровых terrain-слоёв +(а не пересмотр архитектуры рельефа из PH-6). BRD §3 F-14 допускает +отсутствие отдельного ADR. ADR оформляется по прецеденту ADR-016 +(ET-012) — ради единого индекса архитектурных решений и чтобы +зафиксировать **причины отклонения** более «жирных» альтернатив +(перегенерация hillshade с z-factor 2.5, переход на raster-dem, +multidirectional hillshade, theme-specific paint-таблицы), иначе +они вернутся в обсуждение в следующем work-item. + +## Контекст + +### Текущее состояние (после PH-6 / ET-007) + +- Растровые тайлы рельефа нарезаны **z8-z14** (PNG 256×256) из + SRTM 30 м: hillshade (azimuth 315°, altitude 45°, z-factor 1.5), + TRI (5-уровневая классификация), hypso (в UI не подключён). +- Раздача — `GET /terrain/{layer}/{z}/{x}/{y}.png` через FastAPI + (`src/api/main.py:1240`), `Cache-Control: immutable`. +- Клиент (`src/web/app.js`) создаёт MapLibre raster source/layer + динамически в `applyTerrainLayer(id, tileUrl, enabled, opacity, + minzoom, maxzoom)`. **Сигнатура хардкодит paint:** + `{ 'raster-opacity': opacity_number, 'raster-resampling': 'linear' }`. +- Вызовы (`src/web/app.js:2782-2783`): + - hillshade: `opacity=0.40, minzoom=10, maxzoom=15`. + - TRI: `opacity=0.70, minzoom=5, maxzoom=15`. +- UI-минзум hillshade в `updateHillshadeAvailability` (строка 3368): + `if (zoom < 10) cb.disabled = true`. +- В стилях `style.json` / `style-dark.json` растровых terrain-слоёв + **нет** — они добавляются динамически из JS. + +### Проблема + +При зумах z9-z11 (ключевой масштаб для выбора эндуро-маршрута между +двумя точками) рельеф визуально «теряется»: + +- z9: hillshade выключен UI-гейтом, TRI с opacity 0.70 виден, но + пятна мельче чем на z8. +- z10-z11: hillshade включается, но opacity 0.40 + отсутствие + усиления контраста + linear-resampling делают тени «бледной + плёнкой»; TRI на тех же opacity не компенсирует. + +Архитектурный вопрос: **как восстановить выразительность z9-z11 +без перегенерации растровых тайлов, без новых endpoint'ов, без +новых данных и без смены paint-pipeline'а у MapLibre.** + +## Рассмотренные варианты + +### Вариант P (Pipeline) — где править + +- **P-A — Frontend paint-калибровка** (выбран): + - paint hillshade/TRI становится zoom-aware через MapLibre + `interpolate`-выражение по `['zoom']`. + - Меняются параметры существующих paint-properties: + `raster-opacity`, `raster-contrast`, `raster-resampling`. + - 0 изменений в backend, 0 в тайлах на диске. + +- **P-B — Перегенерация hillshade с z-factor 2.5-3.0 для z9-z14.** + Отклонён в этой задаче: + - Требует доступа к infra-pipeline SRTM, пересборки и редеплоя + растровых тайлов (без CI-автоматизации сейчас). + - Долгий feedback-loop (часы регенерации на регион); калибровка + paint даёт результат за минуты. + - Затрагивает все zoom-уровни сразу, в т.ч. z8 (регрессия BRD F-11). + - **Открыт как follow-up** «hillshade-rerender-z9-z14», если P-A + окажется недостаточным. + +- **P-C — Переход на MapLibre `hillshade` (raster-dem) layer.** + Отклонён: + - Требует поднять DEM в формате Terrarium/Mapbox-RGB (новый + pipeline, новые тайлы, новый source-type, новые URL). + - Это смена архитектуры рельефа, не калибровка. Большой скачок + рисков и времени реализации. + - Не решает поставленную проблему быстрее, чем P-A. + +- **P-D — Векторные горизонтали (contours).** + Отклонён: + - Контуров в стэке нет. Это новая фича уровня PH-6.5, требует + pipeline на отдельных vector tiles (планировщик стилей, + атрибуты высот, симплификация). + - Не заменяет hillshade/TRI, а дополняет — другая фича. + +- **P-E — Multidirectional hillshade (4 азимута, blend).** + Отклонён: + - Требует пересборки тайлов и комбинирующего layer. + - Дороже P-A на порядок при том же визуальном эффекте на z9-z11. + +### Вариант O (Opacity scaling) — как именно скалировать opacity + +- **O-A — Step-функция через `case [zoom_in [9,10,11]]`.** Отклонён — + ступенчатые скачки видны как «вспышки» при плавном зуме. + +- **O-B — Linear `interpolate` со stops для z9-z14** (выбран): + - Hillshade `raster-opacity`: `9→0.65, 10→0.60, 11→0.55, 12→0.50, 14→0.40`. + - Поведение на z<9 не определено (но не нужно — UI-гейт отключает слой). + - На z14-z15 значение «закреплено» на исходных 0.40 (clamping + у MapLibre на верхнем стопе) → регрессия z14 (BRD F-12, AC-10) + выполняется автоматически. + - TRI `raster-opacity`: `5→0.55, 7→0.65, 8→0.70, 9→0.80, 10→0.85, + 11→0.85, 12→0.75, 15→0.70`. + - Точка `8→0.70` явная → регрессия z8 (BRD F-11, AC-06) выполняется + автоматически. + +- **O-C — Exponential `interpolate ['exponential', 2]`.** Отклонён: + - Перерасход контраста на z11-z12 → темно/«пересвет» (R-1). + - Linear проще и достаточен для 5 stops в узком диапазоне. + +### Вариант C (Contrast) — добавлять ли raster-contrast + +- **C-A — Добавить `raster-contrast` zoom-aware для hillshade** + (выбран): + - Stops: `9→0.40, 10→0.35, 11→0.30, 12→0.15, 14→0.00`. + - На z14 значение 0 → регрессия (AC-10) выполняется автоматически. + - Только для hillshade. На TRI контраст не имеет смысла + (категориальная палитра), его не трогаем. + +- **C-B — Не трогать контраст, поднять только opacity.** Отклонён: + - Opacity 0.65 без контраста на z9 — это просто «более тёмная + плёнка», а не «более выразительный рельеф». Качественный тест + (TC-UI-04-Z10-Q) на этом варианте не пройдёт. + +- **C-C — Уменьшать `raster-brightness-min/max` вместо contrast.** + Отклонён: + - Более сложная двухпараметрическая настройка для того же эффекта. + - `raster-contrast` — стандартный для подобных случаев property. + +### Вариант R (Resampling) — nearest vs linear + +- **R-A — `'nearest'` на hillshade и TRI** (выбран): + - hillshade на nearest сохраняет «жёсткие края» теней SRTM — рельеф + читается резче. + - TRI — категориальная палитра; linear-resampling размывает границы + между уровнями шероховатости → пятна «текут». `'nearest'` + сохраняет границы. + - MapLibre **не поддерживает** `interpolate` для `raster-resampling` + → выбираем глобально `'nearest'` для обоих слоёв. На z12-z14 + компромисс приемлем (текстура остаётся читаемой при overzoom; + см. R-T-3). + +- **R-B — Глобально `'linear'`.** Отклонён: + - Сохраняет текущую «размытую» картинку, проблема не решается. + +- **R-C — Динамическое переключение `nearest`↔`linear` через + отдельный layer.** Отклонён: + - Удваивает количество raster-layers (2 hillshade + 2 TRI), плюс + логика «когда какой layer показывать» по `getZoom()` → + сложность не оправдана. + +### Вариант U (UI gate) — минзум hillshade + +- **U-A — Понизить UI-порог с 10 до 9** (выбран): + - Тайлы z9 на диске **есть** (нарезка z8-z14 по PH-6 BRD; pre-deploy + smoke в `07-infra-requirements.md` §6.2 шаг 1 это подтверждает). + - Аналогично понижается `source.minzoom` с 10 до 9 (BRD F-02, + REQ-F-02). + - HTML hint обновляется с «Зум 10+» на «Зум 9+» (REQ-F-10). + +- **U-B — Понизить дальше до z8.** Отклонён: + - На z8 hillshade-тайлы 256 px покрывают ~150 км по широте — крупные + тени становятся неразборчивым «шумом». TRI работает лучше. + - Если будущий BRD захочет — отдельная задача. + +- **U-C — Не менять UI-порог, оставить 10.** Отклонён: + - Тогда на z9 пользователь не видит hillshade вообще — основная + жалоба BRD не решается. + +### Вариант T (Theme-specific paint) — отдельные таблицы для dark/satellite + +- **T-A — Один paint для всех тем** (выбран в MVP): + - Простой код, одна правда о stops. + - AC-11 (dark) и AC-12 (satellite) — качественные проверки. Если + оператор подтвердит читаемость на dark и satellite — конец истории. + - Соглашение: если AC-11/AC-12 проваливаются — открывается **ADR-018 + "theme-specific terrain paint"** как follow-up; в нём вводится + подписка на `theme-change` и переключение paint через + `setPaintProperty` (BRD R-2, R-3). + +- **T-B — Сразу theme-specific paint в ET-013.** Отклонён: + - Преждевременная сложность; неизвестно, действительно ли нужны + разные stops (вероятность по риск-таблице: средне-низкая). + - Расширяет scope: понадобится подписка на смену темы, отдельные + константы, новые тесты на каждый theme×layer×zoom. + +### Вариант A (API-расширение `applyTerrainLayer`) — как передавать paint + +- **A-A — Обратно-совместимое расширение: `opacityOrPaint: number | + object`** (выбран): + - Внутри функции — нормализация: если число → старый paint-объект + с `linear` resampling; если объект → используется как есть. + - Сохраняет старый контракт для возможных будущих вызовов + (сейчас вызовов только два, оба в `onTerrainCheckbox`). + - Unit-тестируется через AC-22, UT-COMPAT-01. + +- **A-B — Сменить сигнатуру на `applyTerrainLayer(id, tileUrl, + enabled, paint, minzoom, maxzoom)` без обратной совместимости.** + Отклонён: + - Если в будущем кто-то скопирует функцию для других raster-слоёв + (POI tiles, scenic) с числом — придётся переписывать вызовы. + - Стоимость обратной совместимости — 3 строки кода. + +- **A-C — Завести новые функции `applyHillshadeLayer` / + `applyTRILayer`.** Отклонён: + - Дубликация. `applyTerrainLayer` уже обобщённая, она и есть точка + расширения. + +### Вариант M (Module split) — выносить ли константы в отдельный файл + +- **M-A — `HILLSHADE_PAINT` / `TRI_PAINT` живут в `app.js` рядом с + `TERRAIN_BASE_URL`** (выбран): + - В стэке нет JS-bundler'а, нет ES-import-graph'а (vanilla JS, + скрипты грузятся `