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:
@ -95,4 +95,58 @@ class TestApplyColormap:
|
||||
tif_file = _make_test_tif(tmp_path, data)
|
||||
result = tif_to_png(tif_file, tmp_path, 5.0)
|
||||
assert result is not None
|
||||
assert result.exists()
|
||||
assert result.exists()
|
||||
|
||||
|
||||
class TestTifToCrop:
|
||||
"""Conversion TIF → dalle cartographique (tif_to_crop)."""
|
||||
|
||||
@staticmethod
|
||||
def _write_named_tif(tmp_path, name, arr):
|
||||
transform = from_bounds(660000, 6700000, 661000, 6701000, arr.shape[1], arr.shape[0])
|
||||
tif_file = tmp_path / name
|
||||
with rasterio.open(
|
||||
tif_file, 'w', driver='GTiff', height=arr.shape[0], width=arr.shape[1],
|
||||
count=1, dtype='float32', crs='EPSG:2154', transform=transform,
|
||||
nodata=float('nan'), compress='lzw'
|
||||
) as dst:
|
||||
dst.write(arr.astype('float32'), 1)
|
||||
return tif_file
|
||||
|
||||
def test_nodata_renders_black(self, tmp_path):
|
||||
"""Le nodata restant est rendu en noir (comportement historique).
|
||||
|
||||
Les trous du MNT sont comblés en amont (interpolation dans
|
||||
create_dtm_fast, tous modes) ; ce qui reste en nodata doit rester
|
||||
visible en noir sur la dalle plutôt qu'inventé au rendu.
|
||||
"""
|
||||
from PIL import Image as PILImage
|
||||
from lidar_pipeline.rendering import tif_to_crop
|
||||
|
||||
data = np.random.default_rng(7).normal(50, 10, (40, 40)).astype(np.float32)
|
||||
data[15:25, 15:25] = np.nan
|
||||
tif_file = self._write_named_tif(tmp_path, "LHD_test_slope.tif", data)
|
||||
|
||||
# WebP lossless : l'encodeur AVIF de l'image « saigne » légèrement les
|
||||
# bords du noir même en lossless — on teste la logique nodata→noir,
|
||||
# pas les artefacts du codec.
|
||||
out = tif_to_crop(tif_file, tmp_path, 5.0, keep_tif=True,
|
||||
quality=100, output_format='webp')
|
||||
assert out is not None and out.exists()
|
||||
|
||||
rgb = np.asarray(PILImage.open(str(out)).convert('RGB'))
|
||||
hole = rgb[15:25, 15:25, :]
|
||||
assert np.all(hole == 0), "le nodata doit être rendu en noir"
|
||||
|
||||
def test_without_nodata(self, tmp_path):
|
||||
"""Un TIF sans nodata est converti sans crash, taille préservée."""
|
||||
from PIL import Image as PILImage
|
||||
from lidar_pipeline.rendering import tif_to_crop
|
||||
|
||||
data = np.random.default_rng(7).normal(50, 10, (40, 40)).astype(np.float32)
|
||||
tif_file = self._write_named_tif(tmp_path, "LHD_test_slope.tif", data)
|
||||
|
||||
out = tif_to_crop(tif_file, tmp_path, 5.0)
|
||||
assert out is not None and out.exists()
|
||||
img = PILImage.open(str(out))
|
||||
assert img.size == (40, 40)
|
||||
Reference in New Issue
Block a user