import aiohttp import asyncio import os import xarray as xr import requests from datetime import datetime from typing import Dict, Any # ============================================================================ # SOIL PROPERTIES SERVER (SoilGrids) # ============================================================================ class SoilPropertiesServer: """SoilGrids API Server - Enhanced for reliability""" async def get_data(self, lat: float, lon: float) -> Dict[str, Any]: """Get soil properties with retry logic and rate limit handling""" try: properties = ["clay", "sand", "silt", "phh2o", "soc"] results = {} timeout = aiohttp.ClientTimeout(total=15, connect=5, sock_read=10) connector = aiohttp.TCPConnector(limit=1, limit_per_host=1) async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session: for prop in properties: # Retry logic (max 2 attempts) for attempt in range(2): try: url = "https://rest.isric.org/soilgrids/v2.0/properties/query" params = { "lon": lon, "lat": lat, "property": prop, "depth": "0-5cm", "value": "mean" } async with session.get(url, params=params) as response: if response.status == 200: data = await response.json() value = data['properties']['layers'][0]['depths'][0]['values']['mean'] if value is not None: if prop == 'phh2o': results[prop] = round(value / 10, 1) elif prop == 'soc': results[prop] = value else: results[prop] = round(value / 10, 1) else: results[prop] = None break elif response.status == 429: if attempt == 0: await asyncio.sleep(1) continue else: results[prop] = None break else: results[prop] = None break except asyncio.TimeoutError: if attempt == 0: await asyncio.sleep(0.5) continue else: results[prop] = None break except Exception: results[prop] = None break await asyncio.sleep(0.2) # Delay between properties if any(v is not None for v in results.values()): return { "status": "success", "data": { "clay_percent": results.get("clay"), "sand_percent": results.get("sand"), "silt_percent": results.get("silt"), "pH": results.get("phh2o"), "organic_carbon_dg_per_kg": results.get("soc"), "data_source": "SoilGrids API (ISRIC)", "location": {"latitude": lat, "longitude": lon}, "depth": "0-5cm (topsoil)" } } else: return { "status": "error", "error": "No soil data available for this location" } except Exception as e: return {"status": "error", "error": f"SoilGrids error: {str(e)}"}