diff --git a/lidar_pipeline/index.py b/lidar_pipeline/index.py index f5acf9e..b524dba 100644 --- a/lidar_pipeline/index.py +++ b/lidar_pipeline/index.py @@ -1236,10 +1236,10 @@ _HTML_TEMPLATE = """
- - +
@@ -2243,22 +2243,31 @@ document.getElementById('osmRow').querySelector('.layer-name').title = // ============================================================ // Préconfigurations de couches : un preset définit les couches -// visibles, leurs transparences et l'ordre de pile (bas → haut). -// Les presets intégrés couvrent les couches du panneau ; les presets -// personnels (couches + transparences + ordre) sont persistés en -// localStorage et se suppriment depuis la liste. +// visibles, leurs niveaux de transparence (opacités des +// visualisations + fond de carte OSM) et l'ordre de pile +// (bas → haut). Les presets intégrés couvrent les couches du +// panneau ; les presets personnels (persistés en localStorage) +// se suppriment depuis la liste. // ============================================================ const PRESETS_LS = 'lidarPresets_v1'; +// Les presets portent les niveaux de transparence : opacité de chaque +// visualisation + fond de carte OSM (visibilité, opacité, mode sombre). const BUILTIN_PRESETS = [ { label: 'Relief', on: ['aspect', 'positive_openness'], - opacity: { aspect: 1, positive_openness: 0.65 } }, + opacity: { aspect: 1, positive_openness: 0.65 }, + osm: { on: false } }, { label: 'Pentes', on: ['aspect', 'slope'], - opacity: { aspect: 0.5, slope: 1 } }, + opacity: { aspect: 0.5, slope: 1 }, + osm: { on: true, opacity: 0.3 } }, { label: 'Cavités', on: ['slope', 'positive_openness'], - opacity: { slope: 0.5, positive_openness: 1 } }, - { label: 'Rugosité', on: ['roughness'], opacity: {} }, + opacity: { slope: 0.5, positive_openness: 1 }, + osm: { on: false } }, + { label: 'Rugosité', on: ['roughness'], + opacity: {}, + osm: { on: true, opacity: 0.3 } }, { label: 'Tout', on: ['slope', 'aspect', 'positive_openness', 'roughness'], - opacity: { slope: 0.8, aspect: 0.8, positive_openness: 0.55, roughness: 0.8 } }, + opacity: { slope: 0.8, aspect: 0.8, positive_openness: 0.55, roughness: 0.8 }, + osm: { on: true, opacity: 0.85, dark: true } }, ]; function loadCustomPresets() { @@ -2271,6 +2280,18 @@ function saveCustomPresets(list) { try { localStorage.setItem(PRESETS_LS, JSON.stringify(list)); } catch (e) { /* privé */ } } +// Niveaux de transparence d'un preset, lisibles dans la liste +// (opacité de chaque visualisation + fond de carte OSM). +function presetDetail(p) { + const parts = (p.on || []).filter(k => VIZ_META[k]) + .map(k => (VIZ_META[k].label || k) + ' ' + + Math.round(((p.opacity && p.opacity[k] != null) ? Number(p.opacity[k]) : 1) * 100) + '%'); + if (p.osm) parts.push('Fond ' + (p.osm.on + ? Math.round((p.osm.opacity != null ? Number(p.osm.opacity) : 0.85) * 100) + '%' + : 'masqué')); + return parts.join(' · '); +} + function applyPreset(p) { const want = (p.on || []).filter(k => VIZ_META[k]); const L = state.layers; @@ -2282,8 +2303,15 @@ function applyPreset(p) { L.on[k] = on; if (on) L.opacity[k] = (p.opacity && k in p.opacity) ? Number(p.opacity[k]) : 1; } + // Niveaux de transparence du fond de carte (visibilité, opacité, sombre) + if (p.osm) { + L.osm.on = !!p.osm.on; + if (p.osm.opacity != null) L.osm.opacity = Number(p.osm.opacity); + if (p.osm.dark != null) L.osm.dark = !!p.osm.dark; + } saveLayers(); renderLayerPanel(); + renderOsmRow(); syncTileLayers(); } @@ -2300,7 +2328,7 @@ function renderPresetSelect(selectedValue) { for (const p of BUILTIN_PRESETS) { const o = document.createElement('option'); o.value = 'builtin:' + p.label; - o.textContent = p.label; + o.textContent = p.label + ' — ' + presetDetail(p); presetSelectEl.appendChild(o); } const customs = loadCustomPresets(); @@ -2310,7 +2338,7 @@ function renderPresetSelect(selectedValue) { for (const p of customs) { const o = document.createElement('option'); o.value = 'custom:' + p.id; - o.textContent = p.label; + o.textContent = p.label + ' — ' + presetDetail(p); g.appendChild(o); } presetSelectEl.appendChild(g); @@ -2332,11 +2360,14 @@ presetSaveEl.addEventListener('click', () => { const name = prompt('Nom du preset :'); if (!name || !name.trim()) return; const enabled = state.layers.order.filter(k => state.layers.on[k]); + const osm = state.layers.osm; const p = { id: Date.now().toString(36), label: name.trim(), on: enabled, opacity: Object.fromEntries(enabled.map(k => [k, state.layers.opacity[k]])), + // Niveaux de transparence du fond de carte + osm: { on: osm.on, opacity: osm.opacity, dark: osm.dark }, }; const customs = loadCustomPresets(); customs.push(p); diff --git a/lidar_pipeline/tests/test_index.py b/lidar_pipeline/tests/test_index.py index c4d4f48..ae04341 100644 --- a/lidar_pipeline/tests/test_index.py +++ b/lidar_pipeline/tests/test_index.py @@ -275,12 +275,15 @@ def test_build_index_generates_html(tmp_path): assert 'id="layerRefresh"' in content assert "/api/layers" in app_js and "/api/sync" in app_js # Préconfigurations de couches (onglet Couches) : le sélecteur applique - # des presets intégrés (couches visibles, transparences, ordre de pile) - # et 💾/✕ gèrent les presets personnels persistés en localStorage. + # des presets intégrés (couches visibles, niveaux de transparence — + # visualisations + fond de carte OSM —, ordre de pile) et 💾/✕ gèrent + # les presets personnels persistés en localStorage. assert 'id="presetSelect"' in content assert 'id="presetSave"' in content assert "BUILTIN_PRESETS" in app_js and "applyPreset" in app_js - assert "lidarPresets_v1" in app_js + assert "presetDetail" in app_js and "lidarPresets_v1" in app_js + assert "osm: { on: false }" in app_js # presets portent le fond OSM + assert "p.osm" in app_js # l'application y inclut le fond # Partage de la vue : bouton « Partager » + lien contenant vue, couches # et transparences (hash #zoom,lat,lng&L=...&O=...) — le destinataire # reçoit l'état exact, indépendamment de son localStorage local.