feat(terrain): zoom-aware paint для hillshade/TRI на z9-z11 (ET-013)
All checks were successful
All checks were successful
Понижаем UI-минзум hillshade с 10 до 9 и переводим raster-paint обоих terrain-слоёв в zoom-aware форму через MapLibre interpolate. На z9-z11 — пик opacity/contrast, чтобы рельеф читался как на z8; на z12-z14 — возврат к исходным значениям (регрессия по AC-10). TRI на z8 остаётся 0.70 (регрессия по AC-06), пик 0.80-0.85 на z9-z11. Изменения: - src/web/app.js: добавлены HILLSHADE_PAINT и TRI_PAINT; applyTerrainLayer расширена для поддержки object-paint (обратно-совместимо); порог updateHillshadeAvailability понижен до 9; вызовы для hillshade переведены на minzoom=9. - src/web/index.html: hint обновлён с «Зум 10+» на «Зум 9+». - tests/unit/test_terrain_paint.py: 17 тестов покрытия zoom-stops, контракта applyTerrainLayer и регрессий (UT-PAINT-*, UT-REG-*). - tests/integration/test_terrain_z9_tiles.py: smoke /terrain endpoint на z9-z11 + кэш-заголовки (IT-TILE-*). Backend, тайлы на диске, конфиги, стили — без изменений. Refs: ET-013 ADR: ADR-017 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -2725,6 +2725,48 @@ function initMiniRouteInteraction() {
|
||||
|
||||
const TERRAIN_BASE_URL = window.location.pathname.replace(/\/[^/]*$/, '') + '/terrain';
|
||||
|
||||
// ET-013: zoom-aware paint для слоёв рельефа (ADR-017).
|
||||
// Цель — компенсировать «потерю выразительности» перепадов на z9-z11.
|
||||
// Pre-z9 — hillshade не показывается (UI-минзум). На z9-z11 — максимальный
|
||||
// контраст и opacity, чтобы тени читались как на z8. К z12-z14 — возврат
|
||||
// к исходным значениям (тогда у пользователя есть другие способы
|
||||
// читать рельеф: подложка, грунтовки, POI).
|
||||
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'
|
||||
};
|
||||
|
||||
// ET-013: TRI остаётся 0.70 на z8 (регрессия), пик 0.80-0.85 на z9-z11.
|
||||
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'
|
||||
};
|
||||
|
||||
function toggleTerrainPopup() {
|
||||
const popup = document.getElementById('terrain-popup');
|
||||
const btn = document.getElementById('terrain-toggle');
|
||||
@@ -2779,8 +2821,9 @@ function onTerrainCheckbox() {
|
||||
btn.classList.toggle('active', hillshadeChecked || triChecked);
|
||||
|
||||
// Apply layers
|
||||
applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, 0.40, 10, 15);
|
||||
applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png', triChecked, 0.70, 5, 15);
|
||||
// ET-013: hillshade теперь доступен с z9; paint zoom-aware (см. HILLSHADE_PAINT / TRI_PAINT).
|
||||
applyTerrainLayer('terrain-hillshade', TERRAIN_BASE_URL + '/hillshade/{z}/{x}/{y}.png', hillshadeChecked, HILLSHADE_PAINT, 9, 15);
|
||||
applyTerrainLayer('terrain-tri', TERRAIN_BASE_URL + '/tri/{z}/{x}/{y}.png', triChecked, TRI_PAINT, 5, 15);
|
||||
}
|
||||
|
||||
|
||||
@@ -3313,12 +3356,29 @@ function onUnitChange() {
|
||||
}
|
||||
// <<< ET-005 unit toggle block <<<
|
||||
|
||||
function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
|
||||
/**
|
||||
* ET-013: обратно-совместимое расширение для поддержки zoom-aware paint.
|
||||
*
|
||||
* @param {string} id - id слоя.
|
||||
* @param {string} tileUrl - URL-шаблон тайлов.
|
||||
* @param {boolean} enabled - показывать ли слой.
|
||||
* @param {number|object} opacityOrPaint - либо число (старый контракт,
|
||||
* станет 'raster-opacity' + linear-resampling), либо объект paint-properties
|
||||
* целиком (должен содержать как минимум 'raster-opacity').
|
||||
* @param {number} minzoom
|
||||
* @param {number} maxzoom
|
||||
*/
|
||||
function applyTerrainLayer(id, tileUrl, enabled, opacityOrPaint, minzoom, maxzoom) {
|
||||
const map = window._map;
|
||||
if (!map) return;
|
||||
|
||||
|
||||
const sourceId = id + '-source';
|
||||
|
||||
|
||||
// ET-013: нормализация paint — число (старый контракт) или объект.
|
||||
const paint = (typeof opacityOrPaint === 'number')
|
||||
? { 'raster-opacity': opacityOrPaint, 'raster-resampling': 'linear' }
|
||||
: opacityOrPaint;
|
||||
|
||||
if (enabled) {
|
||||
// Add source if not exists
|
||||
if (!map.getSource(sourceId)) {
|
||||
@@ -3334,17 +3394,14 @@ function applyTerrainLayer(id, tileUrl, enabled, opacity, minzoom, maxzoom) {
|
||||
// Add layer if not exists
|
||||
if (!map.getLayer(id)) {
|
||||
// Insert before first road/trail layer for correct z-order
|
||||
const firstTrailLayer = map.getStyle().layers.find(l =>
|
||||
const firstTrailLayer = map.getStyle().layers.find(l =>
|
||||
l.id.startsWith('trails-') || l.id.startsWith('poi-')
|
||||
);
|
||||
map.addLayer({
|
||||
id: id,
|
||||
type: 'raster',
|
||||
source: sourceId,
|
||||
paint: {
|
||||
'raster-opacity': opacity,
|
||||
'raster-resampling': 'linear'
|
||||
},
|
||||
paint: paint,
|
||||
minzoom: minzoom,
|
||||
maxzoom: maxzoom
|
||||
}, firstTrailLayer ? firstTrailLayer.id : undefined);
|
||||
@@ -3365,7 +3422,7 @@ function updateHillshadeAvailability() {
|
||||
const hint = document.getElementById('terrain-hillshade-hint');
|
||||
const label = cb ? cb.closest('.terrain-checkbox') : null;
|
||||
|
||||
if (zoom < 10) {
|
||||
if (zoom < 9) { // ET-013: на z9 hillshade уже доступен
|
||||
if (cb) cb.disabled = true;
|
||||
if (label) label.classList.add('disabled');
|
||||
if (hint) hint.style.display = 'inline';
|
||||
|
||||
@@ -57,7 +57,7 @@
|
||||
<input type="checkbox" id="terrain-hillshade-cb" onchange="onTerrainCheckbox()">
|
||||
<span>Тени рельефа</span>
|
||||
</label>
|
||||
<span class="terrain-hint" id="terrain-hillshade-hint" style="display:none">Зум 10+</span>
|
||||
<span class="terrain-hint" id="terrain-hillshade-hint" style="display:none">Зум 9+</span>
|
||||
<label class="terrain-checkbox">
|
||||
<input type="checkbox" id="terrain-tri-cb" onchange="onTerrainCheckbox()">
|
||||
<span>Перепады</span>
|
||||
|
||||
Reference in New Issue
Block a user