113 lines
3.4 KiB
Python
113 lines
3.4 KiB
Python
#!/usr/bin/env python3
|
|
# -*- coding: utf-8 -*-
|
|
"""Basemap tiles for matplotlib maps (road weather, ice, snow radar).
|
|
|
|
Prefer tile sources that do **not** require an API key:
|
|
|
|
- OpenStreetMap Mapnik — OK with an identifying User-Agent
|
|
(contextily's default UA gets "403 Access blocked" tiles)
|
|
- Esri WorldStreetMap — free for reasonable use, good road detail
|
|
- OpenTopoMap — terrain fallback
|
|
|
|
CARTO basemaps are avoided: they now overlay "API KEY REQUIRED".
|
|
|
|
Compatible with contextily 1.6.x (loogle-casa) and 1.7.x (loogle-bot):
|
|
in 1.6, ``headers=`` is forwarded to ``imshow`` and breaks the call,
|
|
so we set ``contextily.tile.USER_AGENT`` instead.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import inspect
|
|
import logging
|
|
from typing import Any, Optional
|
|
|
|
LOGGER = logging.getLogger("basemap_tiles")
|
|
|
|
BASEMAP_USER_AGENT = "LoogleCasa/1.0 (https://casa.loogle.it; meteo maps)"
|
|
BASEMAP_HEADERS = {"User-Agent": BASEMAP_USER_AGENT}
|
|
|
|
|
|
def _configure_user_agent() -> None:
|
|
try:
|
|
import contextily.tile as tile_mod
|
|
|
|
tile_mod.USER_AGENT = BASEMAP_USER_AGENT
|
|
except Exception as exc: # noqa: BLE001
|
|
LOGGER.debug("Impossibile impostare USER_AGENT contextily: %s", exc)
|
|
|
|
|
|
def _providers(ctx) -> list:
|
|
"""Ordered list of free (no API key) basemap providers."""
|
|
out = []
|
|
candidates = [
|
|
("OpenStreetMap", "Mapnik"),
|
|
("Esri", "WorldStreetMap"),
|
|
("OpenTopoMap",),
|
|
("OpenStreetMap", "DE"),
|
|
("OpenStreetMap", "HOT"),
|
|
]
|
|
for path in candidates:
|
|
try:
|
|
node = ctx.providers
|
|
for part in path:
|
|
node = getattr(node, part)
|
|
# skip leaf namespaces that aren't a provider dict
|
|
if hasattr(node, "url") or (isinstance(node, dict) and "url" in node):
|
|
out.append(node)
|
|
elif path == ("OpenTopoMap",):
|
|
# xyzservices LeafMap style
|
|
out.append(node)
|
|
except AttributeError:
|
|
continue
|
|
return out
|
|
|
|
|
|
def add_basemap(
|
|
ax: Any,
|
|
*,
|
|
alpha: float = 0.85,
|
|
attribution_size: int = 6,
|
|
crs: str = "EPSG:4326",
|
|
) -> bool:
|
|
"""Add a web basemap under *ax*. Returns True on success."""
|
|
try:
|
|
import contextily as ctx
|
|
except ImportError:
|
|
LOGGER.warning("contextily non disponibile: mappa senza basemap")
|
|
return False
|
|
|
|
_configure_user_agent()
|
|
providers = _providers(ctx)
|
|
if not providers:
|
|
LOGGER.warning("Nessun provider basemap trovato in contextily")
|
|
return False
|
|
|
|
call_kwargs: dict = {
|
|
"crs": crs,
|
|
"alpha": alpha,
|
|
"attribution_size": attribution_size,
|
|
}
|
|
try:
|
|
if "headers" in inspect.signature(ctx.add_basemap).parameters:
|
|
call_kwargs["headers"] = BASEMAP_HEADERS
|
|
except (TypeError, ValueError):
|
|
pass
|
|
|
|
last_err: Optional[BaseException] = None
|
|
for source in providers:
|
|
name = getattr(source, "name", None) or (
|
|
source.get("name") if isinstance(source, dict) else str(source)
|
|
)
|
|
try:
|
|
ctx.add_basemap(ax, source=source, **call_kwargs)
|
|
LOGGER.info("Basemap OK: %s", name)
|
|
return True
|
|
except Exception as exc: # noqa: BLE001
|
|
last_err = exc
|
|
LOGGER.warning("Basemap %s fallita: %s", name, exc)
|
|
|
|
if last_err:
|
|
LOGGER.warning("Nessuna basemap disponibile: %s", last_err)
|
|
return False
|