Add web map with zone generation API, side job queue, and restore historical DTM hole rendering
- webapp.py: FastAPI serving the continuous map (port 8973) with /api/preview, /api/generate and /api/status; tiles are downloaded from IGN and processed in a logged subprocess, tracked live in a side "File de génération" panel that survives page reloads - fetch_ign.py: download missing 1 km LiDAR HD tiles from the IGN geoplateforme before processing - index.py: tile thumbnails and 500 m subtiles are now invalidated by mtime so regenerating a tile refreshes its cached images; progress logging per tile - dtm.py: back to the historical gap handling (small gaps filled by fillnodata only, larger holes left as nodata rendered black); lowest-return floor only via --bare-earth, IGN class selection via --ign-classes - cli.py: positional input now optional (--rebuild-index works alone) - docker-compose.yml: serve (GPU, port 8973) and process services; launch via docker compose only (documented in AGENTS.md/AGENTS.md) - tests: 131 passing, incl. regressions for thumbnail staleness, --rebuild-index without input, and nodata rendering
This commit is contained in:
98
lidar_pipeline/tests/test_fetch_ign.py
Normal file
98
lidar_pipeline/tests/test_fetch_ign.py
Normal file
@ -0,0 +1,98 @@
|
||||
"""Tests du téléchargement des dalles LiDAR HD de l'IGN (fetch_ign)."""
|
||||
|
||||
|
||||
def test_parse_tile_specs():
|
||||
"""Accepte 'col,row', 'col:row' et ignore les espaces."""
|
||||
from lidar_pipeline.fetch_ign import parse_tile_specs
|
||||
assert parse_tile_specs(["1055,6882"]) == [(1055, 6882)]
|
||||
assert parse_tile_specs(["1055:6883", " 651 , 6630 "]) == [(1055, 6883), (651, 6630)]
|
||||
|
||||
|
||||
def test_parse_tile_specs_rejects_invalid():
|
||||
"""Les spécifications mal formées lèvent une erreur explicite."""
|
||||
import pytest
|
||||
from lidar_pipeline.fetch_ign import parse_tile_specs
|
||||
with pytest.raises(ValueError):
|
||||
parse_tile_specs(["1055"])
|
||||
with pytest.raises(ValueError):
|
||||
parse_tile_specs(["abc,def"])
|
||||
with pytest.raises(ValueError):
|
||||
parse_tile_specs(["1055,6882,9999"])
|
||||
|
||||
|
||||
def test_tile_filename_pads_coordinates():
|
||||
"""Le nom de fichier DALLE utilise des coordonnées à 4 chiffres."""
|
||||
from lidar_pipeline.fetch_ign import tile_filename
|
||||
assert tile_filename(1055, 6882) == "LHD_FXX_1055_6882_PTS_LAMB93_IGN69.copc.laz"
|
||||
assert tile_filename(651, 6630) == "LHD_FXX_0651_6630_PTS_LAMB93_IGN69.copc.laz"
|
||||
|
||||
|
||||
def test_match_feature_uses_coordonnees_nw():
|
||||
"""La correspondance se fait sur lidarhd:coordonnees_NW (format 0816-6847)."""
|
||||
from lidar_pipeline.fetch_ign import match_feature
|
||||
features = [
|
||||
{"id": "LHD_FXX_1054_6882_PTS_LAMB93_IGN69_NE",
|
||||
"properties": {"lidarhd:coordonnees_NW": "1054-6882"},
|
||||
"assets": {"data": {"href": "https://example.org/a.copc.laz"}}},
|
||||
{"id": "LHD_FXX_1055_6882_PTS_LAMB93_IGN69_NE",
|
||||
"properties": {"lidarhd:coordonnees_NW": "1055-6882"},
|
||||
"assets": {"data": {"href": "https://example.org/b.copc.laz"}}},
|
||||
]
|
||||
assert match_feature(features, 1055, 6882) is features[1]
|
||||
assert match_feature(features, 9999, 9999) is None
|
||||
|
||||
|
||||
def test_find_tile_url_matches_and_returns_href(monkeypatch):
|
||||
"""find_tile_url interroge le STAC et retourne l'href de l'asset data."""
|
||||
from lidar_pipeline import fetch_ign
|
||||
|
||||
class FakeResponse:
|
||||
def __init__(self, payload):
|
||||
self._payload = payload.encode("utf-8")
|
||||
|
||||
def read(self):
|
||||
return self._payload
|
||||
|
||||
def __enter__(self):
|
||||
return self
|
||||
|
||||
def __exit__(self, *args):
|
||||
return False
|
||||
|
||||
payload = ('{"features": [{"properties": {"lidarhd:coordonnees_NW": "1055-6882"},'
|
||||
'"assets": {"data": {"href": "https://data.geopf.fr/x.copc.laz"}}}]}')
|
||||
captured = {}
|
||||
|
||||
def fake_urlopen(req, timeout=None):
|
||||
captured["url"] = req.full_url
|
||||
return FakeResponse(payload)
|
||||
|
||||
monkeypatch.setattr(fetch_ign.urllib.request, "urlopen", fake_urlopen)
|
||||
url = fetch_ign.find_tile_url(1055, 6882)
|
||||
assert url == "https://data.geopf.fr/x.copc.laz"
|
||||
assert "api.stac.teledetection.fr" in captured["url"]
|
||||
assert "bbox=" in captured["url"]
|
||||
|
||||
|
||||
def test_fetch_tiles_skips_existing_and_generated(tmp_path, monkeypatch):
|
||||
"""Pas de téléchargement si le LAZ existe ou si les visualisations existent."""
|
||||
from lidar_pipeline import fetch_ign
|
||||
|
||||
input_dir = tmp_path / "input"
|
||||
input_dir.mkdir()
|
||||
output_dir = tmp_path / "output"
|
||||
vis_dir = output_dir / "visualisations" / "LHD_FXX_1054_6882_PTS_LAMB93_IGN69"
|
||||
vis_dir.mkdir(parents=True)
|
||||
|
||||
# 1054,6882 : visualisations déjà générées
|
||||
# 1055,6882 : LAZ déjà présent
|
||||
existing = input_dir / fetch_ign.tile_filename(1055, 6882)
|
||||
existing.write_bytes(b"naze")
|
||||
|
||||
def fail_download(*args, **kwargs):
|
||||
raise AssertionError("ne doit pas être appelé")
|
||||
|
||||
monkeypatch.setattr(fetch_ign, "find_tile_url", fail_download)
|
||||
result = fetch_ign.fetch_tiles(input_dir, [(1054, 6882), (1055, 6882)],
|
||||
output_dir=output_dir)
|
||||
assert result == []
|
||||
Reference in New Issue
Block a user