Raccorder les bords de tuiles voisines et encadrer les rendus en cours

Le MNT de chaque dalle s'étend d'une bande de 100 m remplie avec les
points sol des 8 tuiles voisines (option « Raccord des bords ») : les
rendus à grand noyau (openness, SVF, LRM) deviennent continus d'une
dalle à l'autre, les images restent recadrées sur le kilomètre exact.

Pendant une génération, la carte encadre les dalles du run : orange
pulsant en cours de rendu, rouge en échec — les coins WGS84 sont portés
par /api/status pour toutes les dalles non terminées.

La file de génération trie les dalles du nord au sud et passe à 10
workers GPU.
This commit is contained in:
Antoine Jacquin
2026-09-19 20:44:44 +02:00
parent 204b2ad111
commit 5f018fe2b6
15 changed files with 1075 additions and 247 deletions

View File

@ -170,7 +170,7 @@ RGB_LEGENDS = {
# Fusion des textes de légende (titre / lecture du rendu / méthode de calcul)
# depuis la source unique VIZ_LEGENDS (index.py, sans dépendance lourde).
from .index import VIZ_LEGENDS
from .index import VIZ_LEGENDS, parse_basename_coords
for _key, _info in COLORMAPS.items():
_info.update(VIZ_LEGENDS[_key])
@ -178,6 +178,37 @@ for _key, _info in RGB_LEGENDS.items():
_info.update(VIZ_LEGENDS[_key])
def _core_tile_window(tif_file, src):
"""Fenêtre raster de la dalle nominale 1 km (raccord des bords).
Un TIF produit sur une emprise étendue (bande de raccord remplie avec les
tuiles voisines, cf. --edge-buffer dans dtm.py) est recadré sur la dalle
LHD exacte : les images finales restent des carrés de 1 km alignés sur la
grille multi-tuiles, sans artefact au passage d'une tuile à l'autre.
Retourne None si le TIF ne déborde pas (rien à recadrer) ou si le nom ne
porte pas de coordonnées LHD.
"""
coords = parse_basename_coords(Path(tif_file).stem)
if coords is None:
return None
col_km, row_km = coords
# Grille LHD : (col, row) = coin nord-ouest en km → X ∈ [col, col+1] km,
# Y ∈ [row-1, row] km (bord nord = row).
west = float(col_km) * 1000.0
north = float(row_km) * 1000.0
south = north - 1000.0
try:
from rasterio.windows import Window, from_bounds
win = from_bounds(west, south, west + 1000.0, north, src.transform)
win = win.round_offsets().round_lengths().intersection(
Window(0, 0, src.width, src.height))
except Exception:
return None
if win.width < src.width or win.height < src.height:
return win
return None
def _apply_colormap(data, tif_file, resolution=None):
"""Apply the registered colormap normalization to data based on filename.
@ -387,6 +418,15 @@ def tif_to_png(tif_file, vis_dir, resolution, keep_tif=False, source_info=None,
transform = src.transform
crs = src.crs
# Raccord des bords : recadrage sur la dalle nominale 1 km (les
# coordonnées GPS, l'échelle et la légende suivent le recadrage).
core_win = _core_tile_window(tif_file, src)
if core_win is not None:
rows = slice(core_win.row_off, core_win.row_off + core_win.height)
cols = slice(core_win.col_off, core_win.col_off + core_win.width)
data = data[rows, cols, :] if is_rgb else data[rows, cols]
transform = src.window_transform(core_win)
if is_rgb:
height, width, _ = data.shape
else:
@ -813,6 +853,14 @@ def tif_to_crop(tif_file, vis_dir, resolution, keep_tif=False, quality=98, outpu
data = data.astype(np.float32, copy=False)
data[data == src.nodata] = np.nan
# Raccord des bords : recadrage sur la dalle nominale 1 km
core_win = _core_tile_window(tif_file, src)
if core_win is not None:
rows = slice(core_win.row_off, core_win.row_off + core_win.height)
cols = slice(core_win.col_off, core_win.col_off + core_win.width)
data = data[rows, cols, :] if data.ndim == 3 else data[rows, cols]
# Apply colormap normalization
data, cmap_name, title, legend_label, description, is_rgb_result, _cvmin, _cvmax = _apply_colormap(data, tif_file, resolution=resolution)