Rendre robustes les tuiles WMTS IGN : retry, urlencode, User-Agent, latitude bornée
This commit is contained in:
@ -59,6 +59,7 @@ def _lat_lon_to_tile(lat, lon, zoom):
|
||||
"""Convert lat/lon to Web Mercator tile coordinates."""
|
||||
n = 2 ** zoom
|
||||
col = int((lon + 180) / 360 * n)
|
||||
lat = max(-85.0511, min(85.0511, lat)) # borne Mercator : évite log/cos invalides
|
||||
lat_rad = math.radians(lat)
|
||||
row = int((1 - math.log(math.tan(lat_rad) + 1 / math.cos(lat_rad)) / math.pi) / 2 * n)
|
||||
return col, row
|
||||
@ -68,6 +69,7 @@ def _lat_lon_to_px(lat, lon, zoom, tile_size=256):
|
||||
"""Convert lat/lon to Web Mercator pixel coordinates."""
|
||||
n = 2 ** zoom
|
||||
px_x = (lon + 180) / 360 * n * tile_size
|
||||
lat = max(-85.0511, min(85.0511, lat))
|
||||
lat_rad = math.radians(lat)
|
||||
px_y = (1 - math.log(math.tan(lat_rad) + 1 / math.cos(lat_rad)) / math.pi) / 2 * n * tile_size
|
||||
return px_x, px_y
|
||||
@ -87,6 +89,8 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15, min_zoo
|
||||
Returns:
|
||||
numpy array (H, W, 3) uint8, or None on failure.
|
||||
"""
|
||||
import urllib.error
|
||||
import urllib.parse
|
||||
import urllib.request
|
||||
import io
|
||||
from PIL import Image as PILImage
|
||||
@ -134,58 +138,75 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15, min_zoo
|
||||
|
||||
tiles_downloaded = 0
|
||||
tiles_404 = 0
|
||||
tiles_failed = 0
|
||||
fmt = "image/png" if 'PLAN' in layer else "image/jpeg"
|
||||
|
||||
for col in range(col_min, col_max + 1):
|
||||
for row in range(row_min, row_max + 1):
|
||||
url = (
|
||||
f"{wmts_url}?SERVICE=WMTS&VERSION=1.0.0&REQUEST=GetTile"
|
||||
f"&LAYER={layer}&STYLE=normal"
|
||||
f"&TILEMATRIXSET={tile_matrix_set}"
|
||||
f"&TILEMATRIX={zoom}&TILECOL={col}&TILEROW={row}"
|
||||
f"&FORMAT={fmt}"
|
||||
)
|
||||
params = urllib.parse.urlencode({
|
||||
"SERVICE": "WMTS", "VERSION": "1.0.0", "REQUEST": "GetTile",
|
||||
"LAYER": layer, "STYLE": "normal",
|
||||
"TILEMATRIXSET": tile_matrix_set,
|
||||
"TILEMATRIX": zoom, "TILECOL": col, "TILEROW": row,
|
||||
"FORMAT": fmt,
|
||||
})
|
||||
url = f"{wmts_url}?{params}"
|
||||
|
||||
try:
|
||||
req = urllib.request.Request(url, headers={'User-Agent': 'Mozilla/5.0'})
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
tile_data = response.read()
|
||||
# 2 tentatives : un accident réseau (reset, timeout) ne doit
|
||||
# pas laisser un trou blanc définitif dans la dalle.
|
||||
tile_arr = None
|
||||
first_tile_404 = False
|
||||
for attempt in range(2):
|
||||
try:
|
||||
req = urllib.request.Request(
|
||||
url, headers={'User-Agent':
|
||||
'Mozilla/5.0 (compatible; lidar-archeo-pipeline)'})
|
||||
with urllib.request.urlopen(req, timeout=10) as response:
|
||||
tile_data = response.read()
|
||||
tile_img = PILImage.open(io.BytesIO(tile_data)).convert('RGB')
|
||||
tile_arr = np.array(tile_img)
|
||||
|
||||
tile_origin_x = col * tile_size
|
||||
tile_origin_y = row * tile_size
|
||||
|
||||
px_x = int(tile_origin_x - nw_px_x)
|
||||
px_y = int(tile_origin_y - nw_px_y)
|
||||
|
||||
x_off = max(0, -px_x)
|
||||
y_off = max(0, -px_y)
|
||||
dst_x_start = max(0, px_x)
|
||||
dst_y_start = max(0, px_y)
|
||||
dst_x_end = min(out_width, px_x + tile_size)
|
||||
dst_y_end = min(out_height, px_y + tile_size)
|
||||
|
||||
src_x = x_off
|
||||
src_y = y_off
|
||||
src_w = dst_x_end - dst_x_start
|
||||
src_h = dst_y_end - dst_y_start
|
||||
|
||||
if src_w > 0 and src_h > 0 and tile_arr.shape[0] >= src_y + src_h and tile_arr.shape[1] >= src_x + src_w:
|
||||
composite[dst_y_start:dst_y_end, dst_x_start:dst_x_end] = \
|
||||
tile_arr[src_y:src_y+src_h, src_x:src_x+src_w]
|
||||
tiles_downloaded += 1
|
||||
|
||||
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:
|
||||
break
|
||||
except urllib.error.HTTPError as e:
|
||||
if e.code == 404:
|
||||
tiles_404 += 1
|
||||
# If the very first tile is 404, this zoom is unavailable
|
||||
if col == col_min and row == row_min:
|
||||
logger.info(f" Zoom {zoom} non disponible (404) — essai zoom inférieur")
|
||||
first_tile_404 = True
|
||||
else:
|
||||
tiles_failed += 1
|
||||
break # erreur HTTP : inutile de réessayer
|
||||
except Exception:
|
||||
tiles_failed += 1
|
||||
if attempt == 0:
|
||||
time.sleep(0.5)
|
||||
if first_tile_404:
|
||||
break
|
||||
if tile_arr is None:
|
||||
continue
|
||||
|
||||
tile_origin_x = col * tile_size
|
||||
tile_origin_y = row * tile_size
|
||||
|
||||
px_x = int(tile_origin_x - nw_px_x)
|
||||
px_y = int(tile_origin_y - nw_px_y)
|
||||
|
||||
x_off = max(0, -px_x)
|
||||
y_off = max(0, -px_y)
|
||||
dst_x_start = max(0, px_x)
|
||||
dst_y_start = max(0, px_y)
|
||||
dst_x_end = min(out_width, px_x + tile_size)
|
||||
dst_y_end = min(out_height, px_y + tile_size)
|
||||
|
||||
src_x = x_off
|
||||
src_y = y_off
|
||||
src_w = dst_x_end - dst_x_start
|
||||
src_h = dst_y_end - dst_y_start
|
||||
|
||||
if src_w > 0 and src_h > 0 and tile_arr.shape[0] >= src_y + src_h and tile_arr.shape[1] >= src_x + src_w:
|
||||
composite[dst_y_start:dst_y_end, dst_x_start:dst_x_end] = \
|
||||
tile_arr[src_y:src_y+src_h, src_x:src_x+src_w]
|
||||
tiles_downloaded += 1
|
||||
else:
|
||||
continue
|
||||
# Only reach here if inner loop broke (first tile 404)
|
||||
@ -195,6 +216,9 @@ def download_ign_tiles(min_x, max_x, min_y, max_y, layer, zoom_level=15, min_zoo
|
||||
# No tiles at this zoom, try lower
|
||||
continue
|
||||
|
||||
if tiles_failed > 0:
|
||||
logger.warning(f" {tiles_failed} tuile(s) IGN en échec réseau ({layer}) "
|
||||
f"— la dalle peut avoir des trous")
|
||||
logger.info(f" → {tiles_downloaded} tuiles IGN téléchargées ({layer})")
|
||||
if tiles_downloaded == 0:
|
||||
continue
|
||||
@ -262,7 +286,7 @@ def generate_ign_overlay(dem_file, basename, vis_dir, resolution, layer, title,
|
||||
|
||||
from PIL import Image as PILImage
|
||||
ign_pil = PILImage.fromarray(result)
|
||||
ign_resized = ign_pil.resize((width, height), PILImage.LANCZOS)
|
||||
ign_resized = ign_pil.resize((width, height), PILImage.Resampling.LANCZOS)
|
||||
ign_arr = np.array(ign_resized)
|
||||
|
||||
with rasterio.open(
|
||||
|
||||
Reference in New Issue
Block a user