Borner le comblement du MNT à l'enveloppe des points, ajouter la couche précision et un affichage relief/précision, encoder les sous-tuiles une seule fois en q75
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
@ -210,7 +210,9 @@ def test_map_meta(tmp_path, monkeypatch):
|
||||
assert meta["tile_size"] == 512 and meta["zoom_offset"] == -1
|
||||
assert meta["max_native_zoom"] == 19
|
||||
assert meta["bounds"] and meta["stamp"] > 0
|
||||
assert meta["default_layers"] == ["relief_oriente"]
|
||||
assert meta["default_main"] == "relief_oriente"
|
||||
assert meta["precision_layer"] is None # densité absente de la fixture
|
||||
assert meta["default_mode"] == "relief"
|
||||
|
||||
|
||||
def test_only_panel_layers_are_served(tmp_path, monkeypatch):
|
||||
@ -256,31 +258,31 @@ def test_healthz_and_assets(tmp_path, monkeypatch):
|
||||
mapserve.assets("introuvable.js")
|
||||
|
||||
|
||||
def test_ui_offers_reorder_and_blend_modes():
|
||||
"""Pile de couches : réordonnancement au doigt ET modes de fusion.
|
||||
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.
|
||||
|
||||
La superposition ne se réduit pas à de la transparence : chaque couche
|
||||
porte un mix-blend-mode, cantonné à la pile LiDAR par un conteneur isolé
|
||||
(sinon « Produit » assombrirait aussi le fond de carte).
|
||||
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).
|
||||
"""
|
||||
from lidar_pipeline.mapui import _MAP_CSS, _MAP_HTML, _MAP_JS
|
||||
# Modes proposés
|
||||
for mode in ("multiply", "screen", "overlay", "soft-light", "difference",
|
||||
"luminosity"):
|
||||
assert f"'{mode}'" in _MAP_JS, mode
|
||||
# Réordonnancement : glisser-déposer ET boutons (seuls utilisables au doigt)
|
||||
assert "function moveLayer(" in _MAP_JS
|
||||
assert "moveLayer(key, 1)" in _MAP_JS and "moveLayer(key, -1)" in _MAP_JS
|
||||
assert "dragstart" in _MAP_JS and "layer-move" in _MAP_CSS
|
||||
# Pile isolée + fusion par couche et fusion de la pile sur le fond
|
||||
assert "createPane('lidarStack')" in _MAP_JS
|
||||
assert "isolation = 'isolate'" in _MAP_JS
|
||||
assert "map.createPane(paneName, stack)" in _MAP_JS
|
||||
assert "pane.style.mixBlendMode" in _MAP_JS
|
||||
assert "STATE.stackBlend" in _MAP_JS and 'id="stackBlend"' in _MAP_HTML
|
||||
# Le lien partagé transporte la fusion (4e champ, rétrocompatible)
|
||||
assert "(STATE.blend[k] || 'normal')" in _MAP_JS
|
||||
assert "const b = validBlend(f[3]);" in _MAP_JS
|
||||
for mode in ('data-mode="relief"', 'data-mode="precision"', 'data-mode="both"'):
|
||||
assert mode in _MAP_HTML
|
||||
assert 'id="precOpacity"' in _MAP_HTML and '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 "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
|
||||
# Pile et fusions supprimées
|
||||
for gone in ("function moveLayer(", "reorderRelative", "stackBlend", "dragstart", "blendOptions"):
|
||||
assert gone not in _MAP_JS, gone
|
||||
assert "Haut de pile" not in _MAP_HTML
|
||||
# Lien partagé : principale, mode:opacité, fond
|
||||
assert "'&M=' + STATE.main" in _MAP_JS
|
||||
assert "'&P=' + STATE.mode" in _MAP_JS
|
||||
|
||||
|
||||
def test_map_ui_tile_load_indicator_and_selection_pane():
|
||||
@ -301,85 +303,73 @@ def test_map_ui_tile_load_indicator_and_selection_pane():
|
||||
|
||||
|
||||
def test_defaults_roundtrip(tmp_path, monkeypatch):
|
||||
"""L'état réglé depuis la carte devient la configuration servie à tous."""
|
||||
"""L'affichage réglé depuis la carte devient la configuration servie à tous."""
|
||||
import lidar_pipeline.mapserve as mapserve
|
||||
_setup(tmp_path, monkeypatch)
|
||||
_setup(tmp_path, monkeypatch, layers=("relief_oriente", "aspect", "densite_sol"))
|
||||
monkeypatch.setattr(mapserve, "DEFAULTS_FILE", tmp_path / ".map-defaults.json")
|
||||
|
||||
# Sans enregistrement : réglages du registre (index.py)
|
||||
meta = mapserve.map_meta()
|
||||
assert meta["defaults_saved"] is False
|
||||
assert meta["default_order"] is None
|
||||
assert meta["default_stack_blend"] == "normal"
|
||||
assert meta["default_main"] == "relief_oriente"
|
||||
assert meta["precision_layer"] == "densite_sol"
|
||||
assert meta["default_mode"] == "relief"
|
||||
assert meta["default_precision_opacity"] == 0.6
|
||||
|
||||
req = mapserve.DefaultsRequest(
|
||||
order=["slope", "aspect"], on=["aspect"],
|
||||
opacity={"aspect": 0.6, "slope": 1.0},
|
||||
blend={"aspect": "multiply", "slope": "normal"},
|
||||
base={"on": True, "opacity": 0.4, "dark": False},
|
||||
stack_blend="soft-light")
|
||||
req = mapserve.DefaultsRequest(main="aspect", mode="both", precision_opacity=0.4,
|
||||
base={"on": True, "opacity": 0.4, "dark": False})
|
||||
assert mapserve.set_defaults(req)["enregistré"] is True
|
||||
|
||||
meta = mapserve.map_meta()
|
||||
assert meta["defaults_saved"] is True
|
||||
assert meta["default_order"] == ["slope", "aspect"]
|
||||
assert meta["default_layers"] == ["aspect"]
|
||||
assert meta["default_opacity"]["aspect"] == 0.6
|
||||
assert meta["default_blend"]["aspect"] == "multiply"
|
||||
assert meta["default_main"] == "aspect"
|
||||
assert meta["default_mode"] == "both"
|
||||
assert meta["default_precision_opacity"] == 0.4
|
||||
assert meta["default_base"] == {"on": True, "opacity": 0.4, "dark": False}
|
||||
assert meta["default_stack_blend"] == "soft-light"
|
||||
# Persisté sur disque : survit au redémarrage du conteneur
|
||||
assert (tmp_path / ".map-defaults.json").is_file()
|
||||
assert mapserve.get_defaults()["defaults"]["on"] == ["aspect"]
|
||||
assert mapserve.get_defaults()["defaults"]["mode"] == "both"
|
||||
|
||||
# Retrait : retour aux réglages du registre
|
||||
assert mapserve.clear_defaults()["supprimé"] is True
|
||||
assert mapserve.map_meta()["defaults_saved"] is False
|
||||
|
||||
|
||||
def test_defaults_obsolete_layers_fall_back(tmp_path, monkeypatch):
|
||||
"""Défauts figés sur des couches qui ne sont plus servies : registre.
|
||||
|
||||
Sinon un nouveau navigateur arrive sur une carte sans aucune couche
|
||||
LiDAR allumée (cas de la prod après le passage au seul relief orienté).
|
||||
"""
|
||||
def test_defaults_legacy_stack_file_falls_back(tmp_path, monkeypatch):
|
||||
"""Fichier de l'ancienne pile (order/on/blend) ou couche disparue :
|
||||
couche principale du registre, mode relief ; le fond est conservé."""
|
||||
import json
|
||||
import lidar_pipeline.mapserve as mapserve
|
||||
_setup(tmp_path, monkeypatch, layers=("relief_oriente",))
|
||||
defaults = tmp_path / ".map-defaults.json"
|
||||
monkeypatch.setattr(mapserve, "DEFAULTS_FILE", defaults)
|
||||
defaults.write_text(json.dumps({
|
||||
"order": ["aspect", "positive_openness"],
|
||||
"on": ["aspect", "positive_openness"], "opacity": {}, "blend": {},
|
||||
"base": {"on": True, "opacity": 0.85, "dark": True},
|
||||
"stack_blend": "normal"}), encoding="utf-8")
|
||||
"order": ["aspect"], "on": ["aspect"], "opacity": {}, "blend": {},
|
||||
"base": {"on": True, "opacity": 0.5, "dark": True}, "stack_blend": "normal"}),
|
||||
encoding="utf-8")
|
||||
meta = mapserve.map_meta()
|
||||
assert meta["default_layers"] == ["relief_oriente"]
|
||||
# Tout éteint volontairement (couches connues) : respecté
|
||||
defaults.write_text(json.dumps({
|
||||
"order": ["relief_oriente"], "on": [], "opacity": {}, "blend": {},
|
||||
"base": {}, "stack_blend": "normal"}), encoding="utf-8")
|
||||
assert mapserve.map_meta()["default_layers"] == []
|
||||
assert meta["default_main"] == "relief_oriente"
|
||||
assert meta["default_mode"] == "relief"
|
||||
assert meta["default_base"]["opacity"] == 0.5
|
||||
defaults.write_text(json.dumps({"main": "aspect", "mode": "vaudou"}), encoding="utf-8")
|
||||
meta = mapserve.map_meta()
|
||||
assert meta["default_main"] == "relief_oriente" and meta["default_mode"] == "relief"
|
||||
|
||||
|
||||
def test_defaults_sanitised(tmp_path, monkeypatch):
|
||||
"""Couches inconnues écartées, opacités bornées, fusions validées."""
|
||||
"""Couche inconnue ou précision refusée comme principale, mode validé, opacités bornées."""
|
||||
import lidar_pipeline.mapserve as mapserve
|
||||
_setup(tmp_path, monkeypatch)
|
||||
_setup(tmp_path, monkeypatch, layers=("relief_oriente", "densite_sol"))
|
||||
monkeypatch.setattr(mapserve, "DEFAULTS_FILE", tmp_path / ".map-defaults.json")
|
||||
data = mapserve.set_defaults(mapserve.DefaultsRequest(
|
||||
order=["aspect", "inexistante"], on=["aspect", "inexistante"],
|
||||
opacity={"aspect": 5, "slope": "x", "inexistante": 0.5},
|
||||
blend={"aspect": "vaudou", "slope": "screen"},
|
||||
base={"opacity": -3}, stack_blend="vaudou"))["defaults"]
|
||||
assert "inexistante" not in data["order"] and "inexistante" not in data["on"]
|
||||
# Les couches présentes mais non citées complètent l'ordre
|
||||
assert set(data["order"]) == {"aspect", "slope"}
|
||||
assert data["opacity"]["aspect"] == 1.0 # borné à 1
|
||||
assert data["opacity"]["slope"] == 1.0 # valeur illisible → opaque
|
||||
assert data["blend"] == {"slope": "screen"} # mode inconnu écarté
|
||||
main="inexistante", mode="vaudou", precision_opacity=5,
|
||||
base={"opacity": -3}))["defaults"]
|
||||
assert data["main"] is None
|
||||
assert data["mode"] == "relief"
|
||||
assert data["precision_opacity"] == 1.0 # borné à 1
|
||||
assert data["base"]["opacity"] == 0.0 # borné à 0
|
||||
assert data["stack_blend"] == "normal" # repli
|
||||
data = mapserve.set_defaults(mapserve.DefaultsRequest(main="densite_sol"))["defaults"]
|
||||
assert data["main"] is None # la précision n'est pas une principale
|
||||
|
||||
|
||||
def test_ui_applies_server_defaults():
|
||||
@ -387,38 +377,13 @@ def test_ui_applies_server_defaults():
|
||||
from lidar_pipeline.mapui import _MAP_HTML, _MAP_JS
|
||||
assert 'id="btnDefault"' in _MAP_HTML and 'id="btnReset"' in _MAP_HTML
|
||||
assert "api/map/defaults" in _MAP_JS
|
||||
assert "meta.default_order" 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 "validBlend(meta.default_stack_blend)" in _MAP_JS
|
||||
assert "meta.default_precision_opacity" in _MAP_JS
|
||||
# Réinitialiser oublie l'état local avant de reprendre celui du serveur
|
||||
assert "localStorage.removeItem(LS_KEY)" in _MAP_JS
|
||||
|
||||
|
||||
def test_ui_reorder_shows_destination():
|
||||
"""Réorganiser doit être lisible : repère de chute, fantôme, arrivée signalée.
|
||||
|
||||
Sans repère, on lâche une couche sans savoir où elle atterrit — la pile
|
||||
n'est pas un détail, elle décide de ce qu'on voit.
|
||||
"""
|
||||
from lidar_pipeline.mapui import _MAP_CSS, _MAP_HTML, _MAP_JS
|
||||
# Barre d'insertion selon la moitié survolée, effacée entre deux survols
|
||||
assert ".layer-row.drop-before" in _MAP_CSS and ".layer-row.drop-after" in _MAP_CSS
|
||||
assert "clearDropMarks" in _MAP_JS
|
||||
assert "box.height / 2" in _MAP_JS
|
||||
assert "row.classList.add(before ? 'drop-before' : 'drop-after')" in _MAP_JS
|
||||
# Ligne déplacée estompée pendant le glisser
|
||||
assert ".layer-row.dragging" in _MAP_CSS and "classList.add('dragging')" in _MAP_JS
|
||||
# Insertion relative (avant/après la ligne visée), pas un simple échange
|
||||
assert "function reorderRelative(" in _MAP_JS
|
||||
# Arrivée mise en évidence puis ramenée dans le champ de vision
|
||||
assert "just-moved" in _MAP_CSS and "@keyframes moved" in _MAP_CSS
|
||||
assert "scrollIntoView" in _MAP_JS
|
||||
assert "renderPanel(key)" in _MAP_JS and "renderPanel(src)" in _MAP_JS
|
||||
# Extrémités de la pile nommées
|
||||
assert 'id="edgeTop"' in _MAP_HTML and 'id="edgeBottom"' in _MAP_HTML
|
||||
assert "Haut de pile" in _MAP_HTML and "Bas de pile" in _MAP_HTML
|
||||
|
||||
|
||||
def test_ui_uses_standard_tilelayer():
|
||||
"""L'interface s'appuie sur le LOD natif de Leaflet, pas sur un palier maison."""
|
||||
from lidar_pipeline.mapui import _MAP_JS, render_html, ui_version
|
||||
|
||||
Reference in New Issue
Block a user