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.