Projeter les coins du bbox de zone (PROJ) au lieu d exiger un rectangle plein

This commit is contained in:
Antoine Jacquin
2026-09-20 00:45:45 +02:00
parent eec8421f23
commit b5f3faa14d

View File

@ -917,19 +917,44 @@ def _build_subtiles(tile, offered_viz_keys, output_dir, sub_dir_name):
return usable return usable
def _l93_points_to_wgs84_corners(xs, ys):
"""Transforme des points L93 en [[lat, lon], …] (ordre conservé).
rasterio.warp (PROJ exact) si disponible, sinon pyproj, sinon
l'approximation affine (webapp légère sans GDAL — précision ~km).
Même transformation que attach_gps_bounds : les coins produits
coïncident exactement avec ceux des dalles.
"""
try:
from rasterio.warp import transform as warp_transform
lons, lats = warp_transform('EPSG:2154', 'EPSG:4326', list(xs), list(ys))
except ImportError:
try:
from pyproj import Transformer
tr = Transformer.from_crs('EPSG:2154', 'EPSG:4326', always_xy=True)
lons, lats = tr.transform(list(xs), list(ys))
except ImportError:
pairs = [_approx_l93_to_wgs84(x, y) for x, y in zip(xs, ys)]
lons = [p[0] for p in pairs]
lats = [p[1] for p in pairs]
return [[la, lo] for la, lo in zip(lats, lons)]
def _build_overview(zone_records, output_dir): def _build_overview(zone_records, output_dir):
"""Assemble les mosaïques d'overview par couche pour la vue dézoomée. """Assemble les mosaïques d'overview par couche pour la vue dézoomée.
Une image WebP par (zone géographique × couche), couvrant la grille L93 Une image WebP par (zone géographique × couche), couvrant le bbox L93
des dalles de la zone (cases sans dalle transparentes), assemblée depuis des dalles de la zone (cases sans dalle transparentes), assemblée depuis
les vignettes index_thumbs (256 px/km) : au dézoom l'interface l'affiche les vignettes index_thumbs (256 px/km) : au dézoom l'interface l'affiche
à la place des vignettes individuelles — 1 requête et ~30 Mo décodés par à la place des vignettes individuelles — 1 requête et ~30 Mo décodés par
couche et par zone au lieu de centaines. Une mosaïque par zone : les couche et par zone au lieu de centaines. Une mosaïque par zone : les
grilles distantes (plusieurs secteurs, parfois à des centaines de km) grilles distantes (plusieurs secteurs, parfois à des centaines de km)
n'ont pas de coins partagés et surtout pas la même échelle utile. n'ont pas de coins partagés et surtout pas la même échelle utile. Les
Résolution plafonnée à 256 px/km et 3072 px de côté : l'overview n'est coins du bbox sont projetés par la même transformation que les dalles —
affiché que tant qu'il reste au moins 1:1 avec l'écran, jamais étiré la mosaïque s'emboîte exactement sur les vignettes, même si la zone
donc jamais flou. n'est pas un rectangle plein. Résolution plafonnée à 256 px/km et
3072 px de côté : l'overview n'est affiché que tant qu'il reste au
moins 1:1 avec l'écran, jamais étiré donc jamais flou.
Args: Args:
zone_records: enregistrements par zone ({'tiles': [dalle...]}) avec zone_records: enregistrements par zone ({'tiles': [dalle...]}) avec
@ -960,28 +985,18 @@ def _build_overview(zone_records, output_dir):
max_col = max(t['col'] for t in have) max_col = max(t['col'] for t in have)
min_row = min(t['row'] for t in have) min_row = min(t['row'] for t in have)
max_row = max(t['row'] for t in have) max_row = max(t['row'] for t in have)
# Lattice exact des coins (km L93 → WGS84) : les coins partagés de
# dalles voisines proviennent de la même transformation pyproj →
# cohérents, la mosaïque s'emboîte exactement comme les vignettes.
lattice = {}
for t in have:
c = t['corners']
lattice[(t['col'], t['row'])] = c[0] # SW
lattice[(t['col'] + 1, t['row'])] = c[1] # SE
lattice[(t['col'] + 1, t['row'] + 1)] = c[2] # NE
lattice[(t['col'], t['row'] + 1)] = c[3] # NW
quad = [(min_col, min_row), (max_col + 1, min_row),
(max_col + 1, max_row + 1), (min_col, max_row + 1)]
if any(k not in lattice for k in quad):
logger.debug(f"Overview : coins de grille absents pour "
f"{zone.get('label', '?')} — zone ignorée")
continue
km_w = max_col + 1 - min_col km_w = max_col + 1 - min_col
km_h = max_row + 1 - min_row km_h = max_row + 1 - min_row
px_per_km = max(4, min(_OVERVIEW_PX_PER_KM, px_per_km = max(4, min(_OVERVIEW_PX_PER_KM,
_OVERVIEW_MAX_PX // max(km_w, km_h))) _OVERVIEW_MAX_PX // max(km_w, km_h)))
w, h = km_w * px_per_km, km_h * px_per_km w, h = km_w * px_per_km, km_h * px_per_km
corners = [lattice[k] for k in quad] # Coins SW, SE, NE, NW du bbox L93 (dalle (col,row) : Y ∈ [(row−1),
# row] km — cf. attach_gps_bounds), projetés comme les coins de dalles.
xs = [min_col * 1000, (max_col + 1) * 1000,
(max_col + 1) * 1000, min_col * 1000]
ys = [(min_row - 1) * 1000, (min_row - 1) * 1000,
max_row * 1000, max_row * 1000]
corners = _l93_points_to_wgs84_corners(xs, ys)
viz_keys = [] viz_keys = []
for t in have: for t in have:
for k in t['viz']: for k in t['viz']: