diff --git a/lidar_pipeline/tests/test_mapserve.py b/lidar_pipeline/tests/test_mapserve.py index 482d950..5553fa7 100644 --- a/lidar_pipeline/tests/test_mapserve.py +++ b/lidar_pipeline/tests/test_mapserve.py @@ -306,19 +306,17 @@ def test_healthz_and_assets(tmp_path, monkeypatch): def test_ui_main_layer_and_precision_modes(): """Une couche d'affichage principal et la précision : relief seul, - précision seule, ou précision en « produit » sur le relief. + précision seule, ou comparaison de part et d'autre d'une barre glissante. - Plus de pile réordonnable ni de fusion par couche. Le produit est - cantonné à la pile LiDAR par un conteneur isolé (sinon il assombrirait - aussi le fond de carte). Bascule rapide au clavier (touche P). + Plus de pile réordonnable ni de fusion par couche. Bascule rapide au + clavier (touche P). """ from lidar_pipeline.mapui import _MAP_CSS, _MAP_HTML, _MAP_JS - for mode in ('data-mode="relief"', 'data-mode="precision"', 'data-mode="both"'): + for mode in ('data-mode="relief"', 'data-mode="precision"', 'data-mode="compare"'): assert mode in _MAP_HTML - assert 'id="precOpacity"' in _MAP_HTML and 'id="precLegend"' in _MAP_HTML + assert 'id="precLegend"' in _MAP_HTML assert 'id="mainSel"' in _MAP_HTML - assert "blend = 'multiply'" in _MAP_JS - assert "createPane('lidarStack')" in _MAP_JS and "isolation = 'isolate'" in _MAP_JS + assert "createPane('lidarStack')" in _MAP_JS assert "e.key === 'p'" in _MAP_JS # Légende en 16 paliers exacts (échelle log de la densité) assert "const PREC_LEVELS = 16;" in _MAP_JS and "repeat(16, 1fr)" in _MAP_CSS @@ -509,6 +507,25 @@ def test_meta_serves_reading_text(tmp_path, monkeypatch): assert layers["densite_sol"]["reading"] +def test_ui_compare_mode_replaces_both(): + """Mode « Comparer » : une barre glissante sépare deux couches (relief, + précision ou OSM seul) ; l'ancien mode « les deux » (produit) disparaît + et un ancien lien P=both s'ouvre en relief.""" + from lidar_pipeline.mapui import _MAP_CSS, _MAP_HTML, _MAP_JS + assert 'data-mode="compare"' in _MAP_HTML and 'data-mode="both"' not in _MAP_HTML + assert 'id="precOpacity"' not in _MAP_HTML and "precOpacity" not in _MAP_JS + assert "mixBlendMode" not in _MAP_JS and "multiply" not in _MAP_JS + for id_ in ("compareBar", "compareLeft", "compareRight", "compareCtl"): + assert f'id="{id_}"' in _MAP_HTML, id_ + assert "function applyCompareClip(" in _MAP_JS and "clipPath" in _MAP_JS + assert "map.on('move', applyCompareClip)" in _MAP_JS + assert "'&C=' +" in _MAP_JS + # Ancien lien P=both : mode relief + parse = _MAP_JS[_MAP_JS.index("function parseHash("):_MAP_JS.index("function viewHash(")] + assert "'both'" in parse and "'relief'" in parse + assert "#compareBar" in _MAP_CSS and "touch-action: none" in _MAP_CSS + + def test_ui_applies_server_defaults(): """L'interface part des défauts du serveur et sait les (re)définir.""" from lidar_pipeline.mapui import _MAP_HTML, _MAP_JS @@ -516,7 +533,8 @@ def test_ui_applies_server_defaults(): assert "api/map/defaults" in _MAP_JS assert "meta.default_main" in _MAP_JS and "meta.default_mode" in _MAP_JS assert "meta.default_base" in _MAP_JS - assert "meta.default_precision_opacity" in _MAP_JS + assert "default_precision_opacity" not in _MAP_JS + assert "precision_opacity" not in _MAP_JS # Réinitialiser oublie l'état local avant de reprendre celui du serveur assert "lsDel(LS_KEY)" in _MAP_JS diff --git a/lidar_pipeline/web/map.css b/lidar_pipeline/web/map.css index a259a7b..ec7efbb 100644 --- a/lidar_pipeline/web/map.css +++ b/lidar_pipeline/web/map.css @@ -57,6 +57,13 @@ html, body { height: 100%; overflow: hidden; background: var(--bg); color: var(--text); font: var(--fs-md)/1.45 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; } #map { position: absolute; inset: 0; background: var(--bg); } +/* Mode Comparer : barre glissante qui sépare les deux couches. */ +#compareBar { position: fixed; top: 0; bottom: 0; width: 4px; margin-left: -2px; z-index: var(--z-map-ui); + background: var(--surface); box-shadow: var(--shadow-sm); cursor: ew-resize; touch-action: none; } +#compareBar span { position: absolute; top: 50%; left: 50%; transform: translate(-50%, -50%); + width: 36px; height: 36px; border-radius: var(--r-full); display: flex; align-items: center; + justify-content: center; background: var(--surface); color: var(--text); border: 1px solid var(--border); + box-shadow: var(--shadow-sm); font-size: var(--fs-lg); } .leaflet-container { background: var(--bg); font: inherit; } /* Fond OSM assombri pour laisser ressortir les rendus LiDAR. Filtre posé sur le conteneur de la couche, pas sur chaque tuile : même rendu (opérations @@ -129,13 +136,11 @@ input[type="range"]::-moz-range-thumb { width: 10px; height: 10px; border-radius gap: var(--sp-2); padding: var(--sp-1) 0; } .base-row .base-ctl { grid-column: 2 / -1; display: grid; grid-template-columns: minmax(0, 1fr) 40px; gap: var(--sp-2); align-items: center; } -/* Affichage : une couche principale, la précision seule ou superposée. */ +/* Affichage : une couche principale, la précision seule ou en comparaison. */ .view-row { display: grid; grid-template-columns: auto minmax(0, 1fr); align-items: center; gap: 8px; padding: 8px 4px 4px; border-top: 1px solid var(--border); margin-top: 4px; } .view-label { font-size: var(--fs-xs); font-weight: 700; letter-spacing: 0.06em; text-transform: uppercase; color: var(--muted); } .view-row .layer-name { color: var(--text); } -.prec-ctl { display: grid; grid-template-columns: auto minmax(0, 1fr) 34px; gap: 8px; - align-items: center; padding: 6px 4px 0; font-size: var(--fs-xs); color: var(--muted); } .prec-legend { padding: 8px 4px 2px; } .prec-bar { display: grid; grid-template-columns: repeat(16, 1fr); height: 10px; border: 1px solid var(--border); border-radius: 3px; overflow: hidden; } diff --git a/lidar_pipeline/web/map.html b/lidar_pipeline/web/map.html index 7425449..882c096 100644 --- a/lidar_pipeline/web/map.html +++ b/lidar_pipeline/web/map.html @@ -10,6 +10,7 @@
+