"""Isócronas de múltiples puntos en paralelo via Valhalla (async).""" from __future__ import annotations import asyncio import httpx async def valhalla_isochrones_async( requests: list[dict], base_url: str = "http://localhost:8002", costing: str = "auto", concurrency: int = 6, timeout_s: float = 120.0, denoise: float = 0.6, generalize_m: int = 50, ) -> list[dict | None]: """Calcula isócronas para múltiples puntos en paralelo usando Valhalla. Args: requests: Lista de dicts con 'lat' (float), 'lon' (float), 'minutes' (int) y opcionalmente 'id' (str). Cada elemento genera una isócrona. base_url: URL base del servidor Valhalla. costing: Modelo de coste ('auto', 'bicycle', 'pedestrian', etc.). concurrency: Número máximo de requests simultáneas. timeout_s: Timeout en segundos por request. denoise: Factor de suavizado del contorno (0-1). generalize_m: Tolerancia de generalización en metros. Returns: Lista paralela a 'requests' con GeoJSON dict o None por cada punto. Preserva el orden de entrada. """ url = f"{base_url.rstrip('/')}/isochrone" sem = asyncio.Semaphore(concurrency) timeout = httpx.Timeout(timeout_s) async def _fetch_one( client: httpx.AsyncClient, req: dict, ) -> dict | None: payload = { "locations": [{"lat": float(req["lat"]), "lon": float(req["lon"])}], "costing": costing, "contours": [{"time": int(req["minutes"])}], "polygons": True, "denoise": denoise, "generalize": generalize_m, "format": "geojson", } try: async with sem: r = await client.post(url, json=payload) r.raise_for_status() return r.json() except Exception: return None async with httpx.AsyncClient(timeout=timeout) as client: tasks = [_fetch_one(client, req) for req in requests] return list(await asyncio.gather(*tasks))