Auto-retry IGN tiles at lower zoom on 404

When zoom 20 tiles are unavailable (rural areas), fall back to zoom 19, 18,
etc. down to 15. Breaks out immediately on first-tile 404 to avoid wasting
requests at unsupported zoom levels.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Jacquin Antoine
2026-05-14 02:21:47 +02:00
parent e2bd6b2536
commit f988ddb76d

View File

@ -76,6 +76,8 @@ def _lat_lon_to_px(lat, lon, zoom, tile_size=256):
def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15):
"""Download IGN WMTS tiles for the given bounds using Web Mercator (PM).
If the first tile returns 404, automatically retries at lower zoom levels.
Args:
min_x, max_x, min_y, max_y: Bounds in Lambert 93.
layer: IGN WMTS layer name.
@ -106,25 +108,32 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15):
tile_matrix_set = "PM"
tile_size = 256
col_min, row_min = _lat_lon_to_tile(nw_lat, nw_lon, zoom_level)
col_max, row_max = _lat_lon_to_tile(se_lat, se_lon, zoom_level)
# Try downloading at the requested zoom level; fall back to lower zooms on 404
min_zoom = 15
for zoom in range(zoom_level, min_zoom - 1, -1):
col_min, row_min = _lat_lon_to_tile(nw_lat, nw_lon, zoom)
col_max, row_max = _lat_lon_to_tile(se_lat, se_lon, zoom)
nw_px_x, nw_px_y = _lat_lon_to_px(nw_lat, nw_lon, zoom_level)
se_px_x, se_px_y = _lat_lon_to_px(se_lat, se_lon, zoom_level)
nw_px_x, nw_px_y = _lat_lon_to_px(nw_lat, nw_lon, zoom)
se_px_x, se_px_y = _lat_lon_to_px(se_lat, se_lon, zoom)
out_width = int(se_px_x - nw_px_x)
out_height = int(se_px_y - nw_px_y)
if out_width <= 0 or out_height <= 0 or out_width > 10000 or out_height > 10000:
logger.warning(f" Image IGN trop grande: {out_width}x{out_height}px — abandon")
return None
logger.warning(f" Image IGN trop grande: {out_width}x{out_height}px — zoom {zoom} abandon")
continue
total_tiles = (col_max - col_min + 1) * (row_max - row_min + 1)
logger.info(f" Zoom {zoom_level}: {total_tiles} tuiles à télécharger ({out_width}x{out_height}px)")
if zoom < zoom_level:
logger.info(f" ↓ Retry zoom {zoom_level}{zoom}: {total_tiles} tuiles ({out_width}x{out_height}px)")
else:
logger.info(f" Zoom {zoom}: {total_tiles} tuiles à télécharger ({out_width}x{out_height}px)")
composite = np.full((out_height, out_width, 3), 255, dtype=np.uint8)
tiles_downloaded = 0
tiles_404 = 0
fmt = "image/png" if 'PLAN' in layer else "image/jpeg"
for col in range(col_min, col_max + 1):
@ -133,7 +142,7 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15):
f"{wmts_url}?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile"
f"&LAYER={layer}&STYLE=normal"
f"&TILEMATRIXSET={tile_matrix_set}"
f"&TILEMATRIX={zoom_level}&TILECOL={col}&TILEROW={row}"
f"&TILEMATRIX={zoom}&TILECOL={col}&TILEROW={row}"
f"&FORMAT={fmt}"
)
@ -167,16 +176,33 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15):
tile_arr[src_y:src_y+src_h, src_x:src_x+src_w]
tiles_downloaded += 1
except Exception as e:
if tiles_downloaded == 0 and col == col_min and row == row_min:
logger.error(f" ✗ Erreur tuile IGN: {e}")
except urllib.error.HTTPError as e:
if e.code == 404:
tiles_404 += 1
# If the very first tile is 404, this zoom level is unavailable
if col == col_min and row == row_min:
logger.info(f" Zoom {zoom} non disponible (404) — essai zoom inférieur")
break
continue
except Exception:
continue
else:
continue
# Only reach here if inner loop broke (first tile 404)
break
if tiles_404 > 0 and tiles_downloaded == 0:
# No tiles at this zoom, try lower
continue
logger.info(f"{tiles_downloaded} tuiles IGN téléchargées ({layer})")
if tiles_downloaded == 0:
return None
continue
return composite
logger.error(" ✗ Aucun zoom disponible pour cette zone")
return None
def generate_ign_overlay(dem_file, basename, vis_dir, resolution, layer, title, legend_label, description, out_suffix):
"""Generate an IGN basemap overlay visualization.