Ajouter le curseur d'intensité du relief, mémorisé et partagé dans le lien
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -329,6 +329,17 @@ def test_ui_main_layer_and_precision_modes():
|
|||||||
assert "'&P=' + STATE.mode" in _MAP_JS
|
assert "'&P=' + STATE.mode" in _MAP_JS
|
||||||
|
|
||||||
|
|
||||||
|
def test_ui_relief_intensity():
|
||||||
|
"""Curseur d'intensité (contraste) du relief : mémorisé, partagé dans le
|
||||||
|
lien (&I=, écrit seulement si ≠ 1), masqué en mode précision."""
|
||||||
|
from lidar_pipeline.mapui import _MAP_HTML, _MAP_JS
|
||||||
|
assert 'id="reliefIntensity"' in _MAP_HTML
|
||||||
|
assert 'min="0.5"' in _MAP_HTML and 'max="2"' in _MAP_HTML and 'step="0.1"' in _MAP_HTML
|
||||||
|
assert "function clampIntensity(" in _MAP_JS
|
||||||
|
assert "'contrast(' +" in _MAP_JS
|
||||||
|
assert "'&I=' +" in _MAP_JS and "seg.I" in _MAP_JS
|
||||||
|
|
||||||
|
|
||||||
def test_map_ui_tile_load_indicator_and_selection_pane():
|
def test_map_ui_tile_load_indicator_and_selection_pane():
|
||||||
"""Chargement : compteur de tuiles restantes + contour de dalle visible.
|
"""Chargement : compteur de tuiles restantes + contour de dalle visible.
|
||||||
|
|
||||||
|
|||||||
@ -45,6 +45,12 @@
|
|||||||
<button type="button" data-mode="precision" title="Précision géométrique seule : densité de points sol">Précision</button>
|
<button type="button" data-mode="precision" title="Précision géométrique seule : densité de points sol">Précision</button>
|
||||||
<button type="button" data-mode="compare" title="Comparer deux couches de part et d'autre d'une barre glissante (touche P : mode suivant)">Comparer</button>
|
<button type="button" data-mode="compare" title="Comparer deux couches de part et d'autre d'une barre glissante (touche P : mode suivant)">Comparer</button>
|
||||||
</div>
|
</div>
|
||||||
|
<div class="row" id="intensityRow">
|
||||||
|
<label for="reliefIntensity" class="hint">Intensité du relief</label>
|
||||||
|
<input type="range" id="reliefIntensity" min="0.5" max="2" step="0.1" value="1"
|
||||||
|
title="Contraste du relief (1 = rendu standard, le PDF garde le rendu standard)">
|
||||||
|
<span class="panel-stat" id="reliefIntensityVal">1×</span>
|
||||||
|
</div>
|
||||||
<div class="row" id="compareCtl" hidden>
|
<div class="row" id="compareCtl" hidden>
|
||||||
<select id="compareLeft" class="field" title="Couche à gauche de la barre"></select>
|
<select id="compareLeft" class="field" title="Couche à gauche de la barre"></select>
|
||||||
<span class="hint">⇆</span>
|
<span class="hint">⇆</span>
|
||||||
|
|||||||
@ -217,12 +217,22 @@ function validMain(meta, k) {
|
|||||||
return mainKeys(meta).includes(k) ? k : null;
|
return mainKeys(meta).includes(k) ? k : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Intensité du relief : contraste appliqué au conteneur de la couche
|
||||||
|
// principale (réglage de confort personnel, jamais figé comme défaut serveur
|
||||||
|
// ni appliqué au PDF exporté).
|
||||||
|
function clampIntensity(v) {
|
||||||
|
const n = Number(v);
|
||||||
|
return Number.isFinite(n) ? Math.round(Math.min(2, Math.max(0.5, n)) * 10) / 10 : 1;
|
||||||
|
}
|
||||||
|
const fmtIntensity = (v) => String(v).replace('.', ',') + '×';
|
||||||
|
|
||||||
function defaultState(meta) {
|
function defaultState(meta) {
|
||||||
const b = meta.default_base || {};
|
const b = meta.default_base || {};
|
||||||
return {
|
return {
|
||||||
main: validMain(meta, meta.default_main) || mainKeys(meta)[0] || null,
|
main: validMain(meta, meta.default_main) || mainKeys(meta)[0] || null,
|
||||||
mode: validMode(meta.default_mode) || 'relief',
|
mode: validMode(meta.default_mode) || 'relief',
|
||||||
compare: validCompare(null),
|
compare: validCompare(null),
|
||||||
|
intensity: 1,
|
||||||
base: { on: b.on !== undefined ? !!b.on : true,
|
base: { on: b.on !== undefined ? !!b.on : true,
|
||||||
opacity: b.opacity !== undefined ? Number(b.opacity) : 0.85,
|
opacity: b.opacity !== undefined ? Number(b.opacity) : 0.85,
|
||||||
// null = suit le thème (choix explicite seulement si b.dark est déjà
|
// null = suit le thème (choix explicite seulement si b.dark est déjà
|
||||||
@ -245,6 +255,7 @@ function loadState(meta) {
|
|||||||
// Ancien mode « les deux » (produit) mémorisé : relief.
|
// Ancien mode « les deux » (produit) mémorisé : relief.
|
||||||
mode: validMode(s.mode === 'both' ? 'relief' : s.mode) || def.mode,
|
mode: validMode(s.mode === 'both' ? 'relief' : s.mode) || def.mode,
|
||||||
compare: validCompare(s.compare),
|
compare: validCompare(s.compare),
|
||||||
|
intensity: clampIntensity(s.intensity),
|
||||||
base: Object.assign({}, def.base, sBase) };
|
base: Object.assign({}, def.base, sBase) };
|
||||||
}
|
}
|
||||||
// Lien partagé : prime sur l'état local (les anciens liens &L=… de la
|
// Lien partagé : prime sur l'état local (les anciens liens &L=… de la
|
||||||
@ -255,6 +266,7 @@ function loadState(meta) {
|
|||||||
if (hash.view) st.mode = hash.view.mode;
|
if (hash.view) st.mode = hash.view.mode;
|
||||||
if (hash.compare) st.compare = hash.compare;
|
if (hash.compare) st.compare = hash.compare;
|
||||||
if (hash.base) st.base = hash.base;
|
if (hash.base) st.base = hash.base;
|
||||||
|
if (hash.intensity != null) st.intensity = hash.intensity;
|
||||||
}
|
}
|
||||||
return st;
|
return st;
|
||||||
}
|
}
|
||||||
@ -271,13 +283,14 @@ function parseHash() {
|
|||||||
const m = /^#([\d.]+)\/(-?[\d.]+)\/(-?[\d.]+)(.*)$/.exec(location.hash || '');
|
const m = /^#([\d.]+)\/(-?[\d.]+)\/(-?[\d.]+)(.*)$/.exec(location.hash || '');
|
||||||
if (!m) return null;
|
if (!m) return null;
|
||||||
const out = { zoom: parseFloat(m[1]), lat: parseFloat(m[2]), lng: parseFloat(m[3]),
|
const out = { zoom: parseFloat(m[1]), lat: parseFloat(m[2]), lng: parseFloat(m[3]),
|
||||||
main: null, view: null, compare: null, base: null };
|
main: null, view: null, compare: null, base: null, intensity: null };
|
||||||
const seg = {};
|
const seg = {};
|
||||||
for (const part of m[4].split('&')) {
|
for (const part of m[4].split('&')) {
|
||||||
const i = part.indexOf('=');
|
const i = part.indexOf('=');
|
||||||
if (i > 0) seg[part.slice(0, i)] = part.slice(i + 1);
|
if (i > 0) seg[part.slice(0, i)] = part.slice(i + 1);
|
||||||
}
|
}
|
||||||
if (seg.M) out.main = seg.M;
|
if (seg.M) out.main = seg.M;
|
||||||
|
if (seg.I) out.intensity = clampIntensity(seg.I);
|
||||||
if (seg.P) {
|
if (seg.P) {
|
||||||
const f = seg.P.split(':');
|
const f = seg.P.split(':');
|
||||||
// Ancien mode « les deux » (produit) : s'ouvre en relief. L'opacité qui
|
// Ancien mode « les deux » (produit) : s'ouvre en relief. L'opacité qui
|
||||||
@ -311,6 +324,7 @@ function viewHash(full) {
|
|||||||
const dark = STATE.base.dark === null || STATE.base.dark === undefined ? themeIsDark() : STATE.base.dark;
|
const dark = STATE.base.dark === null || STATE.base.dark === undefined ? themeIsDark() : STATE.base.dark;
|
||||||
h += '&B=' + (STATE.base.on ? '1' : '0') + ':' + Math.round(STATE.base.opacity * 100)
|
h += '&B=' + (STATE.base.on ? '1' : '0') + ':' + Math.round(STATE.base.opacity * 100)
|
||||||
+ ':' + (dark ? '1' : '0');
|
+ ':' + (dark ? '1' : '0');
|
||||||
|
if (STATE.intensity !== 1) h += '&I=' + STATE.intensity;
|
||||||
}
|
}
|
||||||
return h;
|
return h;
|
||||||
}
|
}
|
||||||
@ -416,6 +430,13 @@ function applyLayers() {
|
|||||||
const container = base.getContainer && base.getContainer();
|
const container = base.getContainer && base.getContainer();
|
||||||
const dark = STATE.base.dark === null || STATE.base.dark === undefined ? themeIsDark() : STATE.base.dark;
|
const dark = STATE.base.dark === null || STATE.base.dark === undefined ? themeIsDark() : STATE.base.dark;
|
||||||
if (container) container.classList.toggle('base-dark', dark);
|
if (container) container.classList.toggle('base-dark', dark);
|
||||||
|
// Intensité (contraste) : posée sur le conteneur de la couche relief
|
||||||
|
// seule — la découpe de comparaison (clipPath) reste indépendante, les
|
||||||
|
// deux propriétés CSS coexistent sur le même conteneur.
|
||||||
|
for (const [key, layer] of LAYERS) {
|
||||||
|
const c = layer.getContainer && layer.getContainer();
|
||||||
|
if (c) c.style.filter = key === STATE.main && STATE.intensity !== 1 ? 'contrast(' + STATE.intensity + ')' : '';
|
||||||
|
}
|
||||||
applyCompareClip();
|
applyCompareClip();
|
||||||
updateRose();
|
updateRose();
|
||||||
}
|
}
|
||||||
@ -528,6 +549,9 @@ function renderPanel() {
|
|||||||
}
|
}
|
||||||
const prec = precisionKey();
|
const prec = precisionKey();
|
||||||
el('precBlock').hidden = !prec;
|
el('precBlock').hidden = !prec;
|
||||||
|
el('reliefIntensity').value = STATE.intensity;
|
||||||
|
el('reliefIntensityVal').textContent = fmtIntensity(STATE.intensity);
|
||||||
|
el('intensityRow').hidden = effectiveMode() === 'precision';
|
||||||
if (!prec) return;
|
if (!prec) return;
|
||||||
renderPrecLegend();
|
renderPrecLegend();
|
||||||
const mode = effectiveMode();
|
const mode = effectiveMode();
|
||||||
@ -1456,7 +1480,7 @@ window.addEventListener('hashchange', () => {
|
|||||||
if (!h) return;
|
if (!h) return;
|
||||||
applyingHash = true;
|
applyingHash = true;
|
||||||
map.setView([h.lat, h.lng], h.zoom);
|
map.setView([h.lat, h.lng], h.zoom);
|
||||||
if (h.main || h.view || h.base || h.compare) {
|
if (h.main || h.view || h.base || h.compare || h.intensity != null) {
|
||||||
STATE = loadState(META);
|
STATE = loadState(META);
|
||||||
saveState(); renderPanel(); renderBaseRow(); applyLayers();
|
saveState(); renderPanel(); renderBaseRow(); applyLayers();
|
||||||
}
|
}
|
||||||
@ -1511,6 +1535,12 @@ el('btnDefault').addEventListener('click', () => {
|
|||||||
|
|
||||||
el('modeSeg').querySelectorAll('button').forEach(b =>
|
el('modeSeg').querySelectorAll('button').forEach(b =>
|
||||||
b.addEventListener('click', () => setMode(b.dataset.mode)));
|
b.addEventListener('click', () => setMode(b.dataset.mode)));
|
||||||
|
el('reliefIntensity').addEventListener('input', (e) => {
|
||||||
|
STATE.intensity = clampIntensity(e.target.value);
|
||||||
|
el('reliefIntensityVal').textContent = fmtIntensity(STATE.intensity);
|
||||||
|
applyLayers();
|
||||||
|
});
|
||||||
|
el('reliefIntensity').addEventListener('change', saveState);
|
||||||
el('mainSel').addEventListener('change', (e) => {
|
el('mainSel').addEventListener('change', (e) => {
|
||||||
STATE.main = e.target.value;
|
STATE.main = e.target.value;
|
||||||
saveState(); renderPanel(); applyLayers();
|
saveState(); renderPanel(); applyLayers();
|
||||||
|
|||||||
Reference in New Issue
Block a user