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:
@ -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):
|
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).
|
"""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:
|
Args:
|
||||||
min_x, max_x, min_y, max_y: Bounds in Lambert 93.
|
min_x, max_x, min_y, max_y: Bounds in Lambert 93.
|
||||||
layer: IGN WMTS layer name.
|
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_matrix_set = "PM"
|
||||||
tile_size = 256
|
tile_size = 256
|
||||||
|
|
||||||
col_min, row_min = _lat_lon_to_tile(nw_lat, nw_lon, zoom_level)
|
# Try downloading at the requested zoom level; fall back to lower zooms on 404
|
||||||
col_max, row_max = _lat_lon_to_tile(se_lat, se_lon, zoom_level)
|
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)
|
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_level)
|
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_width = int(se_px_x - nw_px_x)
|
||||||
out_height = int(se_px_y - nw_px_y)
|
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:
|
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")
|
logger.warning(f" Image IGN trop grande: {out_width}x{out_height}px — zoom {zoom} abandon")
|
||||||
return None
|
continue
|
||||||
|
|
||||||
total_tiles = (col_max - col_min + 1) * (row_max - row_min + 1)
|
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)
|
composite = np.full((out_height, out_width, 3), 255, dtype=np.uint8)
|
||||||
|
|
||||||
tiles_downloaded = 0
|
tiles_downloaded = 0
|
||||||
|
tiles_404 = 0
|
||||||
fmt = "image/png" if 'PLAN' in layer else "image/jpeg"
|
fmt = "image/png" if 'PLAN' in layer else "image/jpeg"
|
||||||
|
|
||||||
for col in range(col_min, col_max + 1):
|
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"{wmts_url}?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile"
|
||||||
f"&LAYER={layer}&STYLE=normal"
|
f"&LAYER={layer}&STYLE=normal"
|
||||||
f"&TILEMATRIXSET={tile_matrix_set}"
|
f"&TILEMATRIXSET={tile_matrix_set}"
|
||||||
f"&TILEMATRIX={zoom_level}&TILECOL={col}&TILEROW={row}"
|
f"&TILEMATRIX={zoom}&TILECOL={col}&TILEROW={row}"
|
||||||
f"&FORMAT={fmt}"
|
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]
|
tile_arr[src_y:src_y+src_h, src_x:src_x+src_w]
|
||||||
tiles_downloaded += 1
|
tiles_downloaded += 1
|
||||||
|
|
||||||
except Exception as e:
|
except urllib.error.HTTPError as e:
|
||||||
if tiles_downloaded == 0 and col == col_min and row == row_min:
|
if e.code == 404:
|
||||||
logger.error(f" ✗ Erreur tuile IGN: {e}")
|
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
|
continue
|
||||||
|
|
||||||
logger.info(f" → {tiles_downloaded} tuiles IGN téléchargées ({layer})")
|
logger.info(f" → {tiles_downloaded} tuiles IGN téléchargées ({layer})")
|
||||||
if tiles_downloaded == 0:
|
if tiles_downloaded == 0:
|
||||||
return None
|
continue
|
||||||
return composite
|
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):
|
def generate_ign_overlay(dem_file, basename, vis_dir, resolution, layer, title, legend_label, description, out_suffix):
|
||||||
"""Generate an IGN basemap overlay visualization.
|
"""Generate an IGN basemap overlay visualization.
|
||||||
|
|||||||
Reference in New Issue
Block a user