aakashdg commited on
Commit
ca9ccdd
·
verified ·
1 Parent(s): ab3cfaa

feat (GEE soil MCP server implementation)

Browse files
Files changed (1) hide show
  1. src/servers/soil.py +88 -89
src/servers/soil.py CHANGED
@@ -1,101 +1,100 @@
1
- import aiohttp
2
- import asyncio
 
 
 
3
  import os
4
- import xarray as xr
5
- import requests
6
- from datetime import datetime
7
  from typing import Dict, Any
8
 
9
 
10
- # ============================================================================
11
- # SOIL PROPERTIES SERVER (SoilGrids)
12
- # ============================================================================
13
-
14
  class SoilPropertiesServer:
15
- """SoilGrids API Server - Enhanced for reliability"""
16
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  async def get_data(self, lat: float, lon: float) -> Dict[str, Any]:
18
- """Get soil properties with retry logic and rate limit handling"""
19
  try:
20
- properties = ["clay", "sand", "silt", "phh2o", "soc"]
21
  results = {}
22
-
23
- timeout = aiohttp.ClientTimeout(total=15, connect=5, sock_read=10)
24
- connector = aiohttp.TCPConnector(limit=1, limit_per_host=1)
25
-
26
- async with aiohttp.ClientSession(timeout=timeout, connector=connector) as session:
27
- for prop in properties:
28
- # Retry logic (max 2 attempts)
29
- for attempt in range(2):
30
- try:
31
- url = "https://rest.isric.org/soilgrids/v2.0/properties/query"
32
- params = {
33
- "lon": lon,
34
- "lat": lat,
35
- "property": prop,
36
- "depth": "0-5cm",
37
- "value": "mean"
38
- }
39
-
40
- async with session.get(url, params=params) as response:
41
- if response.status == 200:
42
- data = await response.json()
43
- value = data['properties']['layers'][0]['depths'][0]['values']['mean']
44
-
45
- if value is not None:
46
- if prop == 'phh2o':
47
- results[prop] = round(value / 10, 1)
48
- elif prop == 'soc':
49
- results[prop] = value
50
- else:
51
- results[prop] = round(value / 10, 1)
52
- else:
53
- results[prop] = None
54
- break
55
-
56
- elif response.status == 429:
57
- if attempt == 0:
58
- await asyncio.sleep(1)
59
- continue
60
- else:
61
- results[prop] = None
62
- break
63
- else:
64
- results[prop] = None
65
- break
66
-
67
- except asyncio.TimeoutError:
68
- if attempt == 0:
69
- await asyncio.sleep(0.5)
70
- continue
71
- else:
72
- results[prop] = None
73
- break
74
- except Exception:
75
- results[prop] = None
76
- break
77
-
78
- await asyncio.sleep(0.2) # Delay between properties
79
-
80
- if any(v is not None for v in results.values()):
81
- return {
82
- "status": "success",
83
- "data": {
84
- "clay_percent": results.get("clay"),
85
- "sand_percent": results.get("sand"),
86
- "silt_percent": results.get("silt"),
87
- "pH": results.get("phh2o"),
88
- "organic_carbon_dg_per_kg": results.get("soc"),
89
- "data_source": "SoilGrids API (ISRIC)",
90
- "location": {"latitude": lat, "longitude": lon},
91
- "depth": "0-5cm (topsoil)"
92
- }
93
- }
94
- else:
95
  return {
96
  "status": "error",
97
- "error": "No soil data available for this location"
98
  }
99
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
100
  except Exception as e:
101
- return {"status": "error", "error": f"SoilGrids error: {str(e)}"}
 
 
 
 
1
+ """
2
+ Soil Properties Server using Google Earth Engine
3
+ """
4
+
5
+ import ee
6
  import os
7
+ import json
 
 
8
  from typing import Dict, Any
9
 
10
 
 
 
 
 
11
  class SoilPropertiesServer:
12
+ """Google Earth Engine Soil Server"""
13
+
14
+ def __init__(self):
15
+ self._initialize_gee()
16
+
17
+ self.layers = {
18
+ "clay": "OpenLandMap/SOL/SOL_CLAY-WFRACTION_USDA-3A1A1A_M/v02",
19
+ "sand": "OpenLandMap/SOL/SOL_SAND-WFRACTION_USDA-3A1A1A_M/v02",
20
+ "phh2o": "OpenLandMap/SOL/SOL_PH-H2O_USDA-4C1A2A_M/v02",
21
+ "soc": "OpenLandMap/SOL/SOL_ORGANIC-CARBON_USDA-6A1C_M/v02"
22
+ }
23
+ self.depth_band = "b0"
24
+
25
+ def _initialize_gee(self):
26
+ """Initialize GEE with service account"""
27
+ try:
28
+ # Check if service account key is provided
29
+ service_account_key = os.environ.get('GEE_SERVICE_ACCOUNT_KEY')
30
+
31
+ if service_account_key:
32
+ # Parse JSON key
33
+ credentials_dict = json.loads(service_account_key)
34
+ credentials = ee.ServiceAccountCredentials(
35
+ credentials_dict['client_email'],
36
+ key_data=service_account_key
37
+ )
38
+ ee.Initialize(credentials)
39
+ print("✅ GEE initialized with service account")
40
+ else:
41
+ # Fallback to default credentials (for local development)
42
+ ee.Initialize(project='MCPprototypeGEE')
43
+ print("✅ GEE initialized with default credentials")
44
+
45
+ except Exception as e:
46
+ print(f"⚠️ GEE initialization failed: {str(e)}")
47
+ raise
48
+
49
  async def get_data(self, lat: float, lon: float) -> Dict[str, Any]:
50
+ """Get soil properties at coordinate"""
51
  try:
52
+ point = ee.Geometry.Point([lon, lat])
53
  results = {}
54
+
55
+ for prop_name, image_id in self.layers.items():
56
+ try:
57
+ image = ee.Image(image_id).select(self.depth_band)
58
+ value = image.sample(point, 250).first().get(self.depth_band).getInfo()
59
+
60
+ if value is not None:
61
+ if prop_name in ['clay', 'sand']:
62
+ results[prop_name] = round(value / 10, 1)
63
+ elif prop_name == 'phh2o':
64
+ results[prop_name] = round(value / 10, 1)
65
+ elif prop_name == 'soc':
66
+ results[prop_name] = round(value / 10, 1)
67
+ else:
68
+ results[prop_name] = None
69
+ except:
70
+ results[prop_name] = None
71
+
72
+ if not any(v is not None for v in results.values()):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
73
  return {
74
  "status": "error",
75
+ "error": "No soil data available"
76
  }
77
+
78
+ silt = None
79
+ if results.get("clay") and results.get("sand"):
80
+ silt = round(100 - results["clay"] - results["sand"], 1)
81
+
82
+ return {
83
+ "status": "success",
84
+ "data": {
85
+ "clay_percent": results.get("clay"),
86
+ "sand_percent": results.get("sand"),
87
+ "silt_percent": silt,
88
+ "pH": results.get("phh2o"),
89
+ "organic_carbon_g_kg": results.get("soc"),
90
+ "data_source": "Google Earth Engine (OpenLandMap)",
91
+ "location": {"latitude": lat, "longitude": lon},
92
+ "depth": "0-5cm"
93
+ }
94
+ }
95
+
96
  except Exception as e:
97
+ return {
98
+ "status": "error",
99
+ "error": str(e)
100
+ }