Inclure les niveaux de transparence du fond de carte dans les presets
Les presets portent désormais les niveaux de transparence complets : opacité de chaque visualisation + fond de carte OSM (visibilité, opacité, mode sombre). Les libellés du sélecteur affichent les niveaux (ex. « Relief — Aspect 100% · Openness positive 65% · Fond masqué ») et l'application d'un preset resynchronise la ligne du fond de carte.
This commit is contained in:
@ -1236,10 +1236,10 @@ _HTML_TEMPLATE = """<!DOCTYPE html>
|
|||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="preset-row">
|
<div class="preset-row">
|
||||||
<select id="presetSelect" class="gen-select" title="Préconfigurations : couches visibles, transparences et ordre de pile">
|
<select id="presetSelect" class="gen-select" title="Préconfigurations : couches visibles, niveaux de transparence (visualisations + fond de carte) et ordre de pile">
|
||||||
<option value="">Préconfigurations…</option>
|
<option value="">Préconfigurations…</option>
|
||||||
</select>
|
</select>
|
||||||
<button id="presetSave" class="icon-btn" title="Enregistrer l'état actuel (couches, transparences, ordre) comme preset">💾</button>
|
<button id="presetSave" class="icon-btn" title="Enregistrer l'état actuel (couches, niveaux de transparence, ordre) comme preset">💾</button>
|
||||||
<button id="presetDelete" class="icon-btn" hidden title="Supprimer le preset sélectionné">✕</button>
|
<button id="presetDelete" class="icon-btn" hidden title="Supprimer le preset sélectionné">✕</button>
|
||||||
</div>
|
</div>
|
||||||
<div id="vizLayers"></div>
|
<div id="vizLayers"></div>
|
||||||
@ -2243,22 +2243,31 @@ document.getElementById('osmRow').querySelector('.layer-name').title =
|
|||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// Préconfigurations de couches : un preset définit les couches
|
// Préconfigurations de couches : un preset définit les couches
|
||||||
// visibles, leurs transparences et l'ordre de pile (bas → haut).
|
// visibles, leurs niveaux de transparence (opacités des
|
||||||
// Les presets intégrés couvrent les couches du panneau ; les presets
|
// visualisations + fond de carte OSM) et l'ordre de pile
|
||||||
// personnels (couches + transparences + ordre) sont persistés en
|
// (bas → haut). Les presets intégrés couvrent les couches du
|
||||||
// localStorage et se suppriment depuis la liste.
|
// panneau ; les presets personnels (persistés en localStorage)
|
||||||
|
// se suppriment depuis la liste.
|
||||||
// ============================================================
|
// ============================================================
|
||||||
const PRESETS_LS = 'lidarPresets_v1';
|
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 = [
|
const BUILTIN_PRESETS = [
|
||||||
{ label: 'Relief', on: ['aspect', 'positive_openness'],
|
{ 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'],
|
{ 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'],
|
{ label: 'Cavités', on: ['slope', 'positive_openness'],
|
||||||
opacity: { slope: 0.5, positive_openness: 1 } },
|
opacity: { slope: 0.5, positive_openness: 1 },
|
||||||
{ label: 'Rugosité', on: ['roughness'], opacity: {} },
|
osm: { on: false } },
|
||||||
|
{ label: 'Rugosité', on: ['roughness'],
|
||||||
|
opacity: {},
|
||||||
|
osm: { on: true, opacity: 0.3 } },
|
||||||
{ label: 'Tout', on: ['slope', 'aspect', 'positive_openness', 'roughness'],
|
{ 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() {
|
function loadCustomPresets() {
|
||||||
@ -2271,6 +2280,18 @@ function saveCustomPresets(list) {
|
|||||||
try { localStorage.setItem(PRESETS_LS, JSON.stringify(list)); } catch (e) { /* privé */ }
|
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) {
|
function applyPreset(p) {
|
||||||
const want = (p.on || []).filter(k => VIZ_META[k]);
|
const want = (p.on || []).filter(k => VIZ_META[k]);
|
||||||
const L = state.layers;
|
const L = state.layers;
|
||||||
@ -2282,8 +2303,15 @@ function applyPreset(p) {
|
|||||||
L.on[k] = on;
|
L.on[k] = on;
|
||||||
if (on) L.opacity[k] = (p.opacity && k in p.opacity) ? Number(p.opacity[k]) : 1;
|
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();
|
saveLayers();
|
||||||
renderLayerPanel();
|
renderLayerPanel();
|
||||||
|
renderOsmRow();
|
||||||
syncTileLayers();
|
syncTileLayers();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2300,7 +2328,7 @@ function renderPresetSelect(selectedValue) {
|
|||||||
for (const p of BUILTIN_PRESETS) {
|
for (const p of BUILTIN_PRESETS) {
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = 'builtin:' + p.label;
|
o.value = 'builtin:' + p.label;
|
||||||
o.textContent = p.label;
|
o.textContent = p.label + ' — ' + presetDetail(p);
|
||||||
presetSelectEl.appendChild(o);
|
presetSelectEl.appendChild(o);
|
||||||
}
|
}
|
||||||
const customs = loadCustomPresets();
|
const customs = loadCustomPresets();
|
||||||
@ -2310,7 +2338,7 @@ function renderPresetSelect(selectedValue) {
|
|||||||
for (const p of customs) {
|
for (const p of customs) {
|
||||||
const o = document.createElement('option');
|
const o = document.createElement('option');
|
||||||
o.value = 'custom:' + p.id;
|
o.value = 'custom:' + p.id;
|
||||||
o.textContent = p.label;
|
o.textContent = p.label + ' — ' + presetDetail(p);
|
||||||
g.appendChild(o);
|
g.appendChild(o);
|
||||||
}
|
}
|
||||||
presetSelectEl.appendChild(g);
|
presetSelectEl.appendChild(g);
|
||||||
@ -2332,11 +2360,14 @@ presetSaveEl.addEventListener('click', () => {
|
|||||||
const name = prompt('Nom du preset :');
|
const name = prompt('Nom du preset :');
|
||||||
if (!name || !name.trim()) return;
|
if (!name || !name.trim()) return;
|
||||||
const enabled = state.layers.order.filter(k => state.layers.on[k]);
|
const enabled = state.layers.order.filter(k => state.layers.on[k]);
|
||||||
|
const osm = state.layers.osm;
|
||||||
const p = {
|
const p = {
|
||||||
id: Date.now().toString(36),
|
id: Date.now().toString(36),
|
||||||
label: name.trim(),
|
label: name.trim(),
|
||||||
on: enabled,
|
on: enabled,
|
||||||
opacity: Object.fromEntries(enabled.map(k => [k, state.layers.opacity[k]])),
|
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();
|
const customs = loadCustomPresets();
|
||||||
customs.push(p);
|
customs.push(p);
|
||||||
|
|||||||
@ -275,12 +275,15 @@ def test_build_index_generates_html(tmp_path):
|
|||||||
assert 'id="layerRefresh"' in content
|
assert 'id="layerRefresh"' in content
|
||||||
assert "/api/layers" in app_js and "/api/sync" in app_js
|
assert "/api/layers" in app_js and "/api/sync" in app_js
|
||||||
# Préconfigurations de couches (onglet Couches) : le sélecteur applique
|
# Préconfigurations de couches (onglet Couches) : le sélecteur applique
|
||||||
# des presets intégrés (couches visibles, transparences, ordre de pile)
|
# des presets intégrés (couches visibles, niveaux de transparence —
|
||||||
# et 💾/✕ gèrent les presets personnels persistés en localStorage.
|
# 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="presetSelect"' in content
|
||||||
assert 'id="presetSave"' in content
|
assert 'id="presetSave"' in content
|
||||||
assert "BUILTIN_PRESETS" in app_js and "applyPreset" in app_js
|
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
|
# Partage de la vue : bouton « Partager » + lien contenant vue, couches
|
||||||
# et transparences (hash #zoom,lat,lng&L=...&O=...) — le destinataire
|
# et transparences (hash #zoom,lat,lng&L=...&O=...) — le destinataire
|
||||||
# reçoit l'état exact, indépendamment de son localStorage local.
|
# reçoit l'état exact, indépendamment de son localStorage local.
|
||||||
|
|||||||
Reference in New Issue
Block a user