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:
@ -157,3 +157,61 @@ class TestRayTrace:
|
||||
)
|
||||
assert pos.shape == (4, 2, rows, cols)
|
||||
assert neg.shape == (4, 2, rows, cols)
|
||||
|
||||
|
||||
class TestNodataPreserved:
|
||||
"""Nodata préservé dans les rendus (comportement historique).
|
||||
|
||||
Les trous du MNT sont comblés en amont (create_dtm_fast, tous modes) ;
|
||||
si un nodata subsiste malgré tout, hillshade/slope/aspect le restituent
|
||||
(rendu noir en carte) au lieu d'inventer des valeurs interpolées.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _dem_with_hole(synthetic_dem, tmp_path):
|
||||
import rasterio
|
||||
with rasterio.open(synthetic_dem) as src:
|
||||
arr = src.read(1).copy()
|
||||
profile = src.profile.copy()
|
||||
arr[80:120, 80:120] = np.nan
|
||||
dem_hole = tmp_path / "dem_hole.tif"
|
||||
profile.update(dtype='float32', nodata=float('nan'))
|
||||
with rasterio.open(dem_hole, 'w', **profile) as dst:
|
||||
dst.write(arr.astype('float32'), 1)
|
||||
return dem_hole
|
||||
|
||||
def test_aspect_solo_preserves_nodata(self, synthetic_dem, tmp_path):
|
||||
from lidar_pipeline.visualizations import generate_aspect
|
||||
dem_hole = self._dem_with_hole(synthetic_dem, tmp_path)
|
||||
out = generate_aspect(dem_hole, "solo", tmp_path, 5.0)
|
||||
assert out is not None and out.exists()
|
||||
import rasterio
|
||||
with rasterio.open(out) as src:
|
||||
data = src.read(1)
|
||||
assert np.isnan(data[80:120, 80:120]).all(), "le trou doit rester en nodata"
|
||||
# Le gradient au bord du trou propage NaN sur un anneau de 1 px :
|
||||
# on vérifie une zone éloignée du trou
|
||||
assert not np.isnan(data[0:40, 0:40]).any(), "NaN loin du trou"
|
||||
|
||||
def test_aspect_shared_preserves_nodata(self, synthetic_dem, tmp_path):
|
||||
from lidar_pipeline.visualizations import SharedDEM, generate_aspect
|
||||
dem_hole = self._dem_with_hole(synthetic_dem, tmp_path)
|
||||
shared = SharedDEM(dem_hole, 5.0)
|
||||
out = generate_aspect(dem_hole, "partage", tmp_path, 5.0, shared=shared)
|
||||
assert out is not None and out.exists()
|
||||
import rasterio
|
||||
with rasterio.open(out) as src:
|
||||
data = src.read(1)
|
||||
assert np.isnan(data[80:120, 80:120]).all(), "le trou doit rester en nodata"
|
||||
assert not np.isnan(data[0:40, 0:40]).any(), "NaN loin du trou"
|
||||
|
||||
def test_slope_and_hillshade_preserve_nodata(self, synthetic_dem, tmp_path):
|
||||
from lidar_pipeline.visualizations import generate_slope, generate_hillshade
|
||||
dem_hole = self._dem_with_hole(synthetic_dem, tmp_path)
|
||||
import rasterio
|
||||
for gen, name in ((generate_slope, "p"), (generate_hillshade, "h")):
|
||||
out = gen(dem_hole, name, tmp_path, 5.0)
|
||||
assert out is not None and out.exists()
|
||||
with rasterio.open(out) as src:
|
||||
data = src.read(1)
|
||||
assert np.isnan(data[80:120, 80:120]).any(), f"{out.name} : trou disparu"
|
||||
|
||||
Reference in New Issue
Block a user