diff --git a/lidar_pipeline/index.py b/lidar_pipeline/index.py index f5f00e9..a1ec763 100644 --- a/lidar_pipeline/index.py +++ b/lidar_pipeline/index.py @@ -198,13 +198,21 @@ VIZ_LEGENDS = { }, } -# Couche activée par défaut à l'ouverture de la carte. -DEFAULT_VIZ = 'hillshade_multi' +# Couches activées par défaut à l'ouverture de la carte : seules celles-ci +# sont allumées, toutes les autres sont désactivées (mais restent accessibles +# via le panneau). Ordre = pile basse → haute. +DEFAULT_LAYERS = ('slope', 'aspect', 'positive_openness', 'roughness') -# Restriction optionnelle du panneau de couches : None = proposer toutes -# les visualisations présentes sur disque (aucune restriction). Le sélecteur -# de génération/régénération est piloté par le même registre (voir _render_html). -PANEL_VIZ = None +# Couche « principale » (mise en avant ; index_tiles.json + affichage en +# direct de la webapp). +DEFAULT_VIZ = DEFAULT_LAYERS[0] + +# Restriction du panneau de couches : seule la sélection DEFAULT_LAYERS est +# proposée (couches conservées ; les autres ne sont plus ni affichées ni +# référencées). None = toutes les visualisations présentes sur disque. +# Le sélecteur de génération/régénération est piloté par le même registre +# (voir _render_html). +PANEL_VIZ = DEFAULT_LAYERS # Correspondance mot-clé de fichier de sortie → nom d'étape --only du pipeline # (les trois visualisations dont le nom de sortie diffère du nom d'étape, @@ -224,6 +232,16 @@ def step_to_keyword(step): return STEP_TO_KEYWORD.get(step, step) +def default_layers_present(all_viz_keys): + """Couches activées par défaut (DEFAULT_LAYERS) présentes sur le disque. + + Ordre conservé (DEFAULT_LAYERS) ; sert de base à l'état initial du + panneau (toutes les autres couches sont désactivées). + """ + present = set(all_viz_keys) + return [v for v in DEFAULT_LAYERS if v in present] + + def cells_with_all_viz(vis_dir, viz_keys, resolutions=(0.5,)): """Cellules (col, row) disposant de TOUTES les visualisations demandées. @@ -1061,7 +1079,9 @@ def build_index(output_dir, output_format='avif'): (output_dir / 'index_tiles.json').write_text(json.dumps({ 'tiles': display_tiles, 'viz_meta': {k: {'label': VIZ_LABELS.get(k, k)} for k in ordered_viz}, - 'stats': {'default_viz': default_viz, 'n_tiles': len(display_tiles)}, + 'stats': {'default_viz': default_viz, + 'default_layers': default_layers_present(all_viz_keys), + 'n_tiles': len(display_tiles)}, }, ensure_ascii=False), encoding='utf-8') _write_assets(output_dir) @@ -1129,13 +1149,15 @@ def _render_html(tiles, global_bbox, all_viz_keys, output_format): tiles_json = json.dumps(tiles, ensure_ascii=False) - # Options du sélecteur de génération/régénération : TOUTES les couches du - # registre (mêmes libellés que le panneau ; valeurs = noms d'étapes --only - # du pipeline), y compris celles absentes du disque — pour pouvoir les - # générer. Toutes pré-sélectionnées : une régénération refait la dalle - # complète, l'utilisateur pouvant désélectionner pour cibler. - gen_keys = [v for v in _VIZ_FALLBACK_ORDER if v in VIZ_LABELS] - gen_keys += sorted(k for k in VIZ_LABELS if k not in gen_keys) + # Options du sélecteur de génération/régénération : les couches du panneau + # (PANEL_VIZ si restreint, sinon le registre complet — mêmes libellés, + # valeurs = noms d'étapes --only du pipeline), y compris celles absentes + # du disque — pour pouvoir les générer. Toutes pré-sélectionnées : une + # régénération refait la dalle complète, l'utilisateur pouvant + # désélectionner pour cibler. + gen_base = PANEL_VIZ if PANEL_VIZ is not None else VIZ_LABELS + gen_keys = [v for v in _VIZ_FALLBACK_ORDER if v in gen_base] + gen_keys += sorted(k for k in gen_base if k not in gen_keys) gen_viz_options = "\n".join( f' ' @@ -1146,8 +1168,11 @@ def _render_html(tiles, global_bbox, all_viz_keys, output_format): for k in ordered_viz }, ensure_ascii=False) - stats_json = json.dumps({'default_viz': default_viz, 'n_tiles': len(tiles)}, - ensure_ascii=False) + stats_json = json.dumps( + {'default_viz': default_viz, + 'default_layers': default_layers_present(all_viz_keys), + 'n_tiles': len(tiles)}, + ensure_ascii=False) n_tiles = len(tiles) grid_w = global_bbox['max_col'] - global_bbox['min_col'] + 1 @@ -1855,10 +1880,17 @@ const LS_KEY = 'lidarLayers_v1'; function defaultLayers() { const order = Object.keys(VIZ_META); - const base = STATS.default_viz && VIZ_META[STATS.default_viz] - ? STATS.default_viz : order[0]; + // Couches activées par défaut : seules celles-ci sont allumées, toutes les + // autres sont désactivées (mais restent accessibles via le panneau). Repli + // sur la couche « principale » (default_viz) si la liste n'est pas fournie + // (index ancien) ; sinon la première couche disponible. + const list = (Array.isArray(STATS.default_layers) && STATS.default_layers.length) + ? STATS.default_layers + : ((STATS.default_viz && VIZ_META[STATS.default_viz]) + ? [STATS.default_viz] : [order[0]]); + const onSet = new Set(list.filter(k => VIZ_META[k])); const on = {}, opacity = {}; - for (const k of order) { on[k] = (k === base); opacity[k] = 1; } + for (const k of order) { on[k] = onSet.has(k); opacity[k] = 1; } return { order, on, opacity, osm: { on: true, opacity: 0.85, dark: true } }; } diff --git a/lidar_pipeline/tests/test_index.py b/lidar_pipeline/tests/test_index.py index ee92b7c..208ccd6 100644 --- a/lidar_pipeline/tests/test_index.py +++ b/lidar_pipeline/tests/test_index.py @@ -237,17 +237,18 @@ def test_build_index_generates_html(tmp_path): assert (assets / "app.css").read_text(encoding='utf-8').startswith('/*') app_js = (assets / "app.js").read_text(encoding='utf-8') assert "Couches" in app_js or "layers" in app_js - # Barre de génération : sélecteur proposant TOUTES les couches du registre - # (valeurs = noms d'étapes --only), y compris celles absentes du panneau - # (svf, hillshade) pour pouvoir les générer. La sélection est envoyée à - # /api/preview (détection des tuiles incomplètes) comme à /api/generate. + # Barre de génération : sélecteur restreint au panneau (PANEL_VIZ = + # DEFAULT_LAYERS ; valeurs = noms d'étapes --only). Les couches hors + # panneau (svf, hillshade, wavelet) ne sont plus proposées. assert 'id="genViz"' in content - assert 'value="wavelet"' in content - assert 'value="aniso_open"' not in content - assert 'value="pos_open"' in content assert 'value="slope"' in content - assert 'value="svf"' in content - assert 'value="hillshade"' in content + assert 'value="pos_open"' in content + assert 'value="aspect"' in content + assert 'value="roughness"' in content + assert 'value="wavelet"' not in content + assert 'value="svf"' not in content + assert 'value="hillshade"' not in content + assert 'value="aniso_open"' not in content assert "genViz" in app_js and "viz: selectedGenViz()" in app_js assert app_js.count("selectedGenViz()") >= 3 # définition + preview + generate # Adoption du dernier run au chargement (redémarrage du serveur) : @@ -347,34 +348,34 @@ def test_subtiles_cover_all_layers(tmp_path): assert t['viz'][viz]['full'].startswith("index_subtiles/"), viz -def test_panel_offers_all_layers(): - """Le panneau n'est plus restreint : toutes les couches connues sont proposées.""" - from lidar_pipeline.index import PANEL_VIZ, VIZ_LABELS, KEYWORD_TO_STEP +def test_panel_restricted_to_kept_layers(): + """Le panneau est restreint aux couches conservées (DEFAULT_LAYERS).""" + from lidar_pipeline.index import PANEL_VIZ, DEFAULT_LAYERS, KEYWORD_TO_STEP from lidar_pipeline.pipeline import VIZ_STEPS - assert PANEL_VIZ is None - # Chaque couche connue du registre correspond à une étape --only valide + assert PANEL_VIZ == DEFAULT_LAYERS + # Chaque couche conservée correspond à une étape --only valide steps = {name for name, _ in VIZ_STEPS} - assert set(VIZ_LABELS) - for key in VIZ_LABELS: + for key in PANEL_VIZ: assert KEYWORD_TO_STEP.get(key, key) in steps -def test_panel_selection_lists_full_registry(): - """Le panneau propose le registre complet, même sans données sur disque. +def test_panel_selection_lists_kept_layers(): + """Le panneau propose uniquement les couches conservées (PANEL_VIZ). - Une installation neuve (ou une couche jamais générée) doit quand même - afficher toutes les couches connues ; les couches historiques présentes - sur disque restent proposées (libellé = clé brute). + Même sans données sur disque, le panneau affiche les couches conservées ; + une couche absente du panneau n'est plus proposée, même présente sur + disque. """ - from lidar_pipeline.index import _panel_selection, VIZ_LABELS + from lidar_pipeline.index import _panel_selection, PANEL_VIZ ordered, default = _panel_selection({'aspect'}) - assert set(VIZ_LABELS) <= set(ordered) + assert set(ordered) == set(PANEL_VIZ) assert default == 'aspect' ordered, default = _panel_selection(set()) - assert set(VIZ_LABELS) <= set(ordered) + assert set(ordered) == set(PANEL_VIZ) assert default # toujours une couche par défaut + # Une couche présente sur disque mais hors panneau est exclue ordered, default = _panel_selection({'aniso_open'}) - assert 'aniso_open' in ordered + assert 'aniso_open' not in ordered def test_build_index_merges_cross_resolution_viz(tmp_path):