Spaces:
Sleeping
Sleeping
import requests | |
import json | |
from datetime import datetime, date | |
from typing import Dict, Any, Optional | |
import logging | |
# Configure logging | |
logging.basicConfig(level=logging.INFO) | |
logger = logging.getLogger(__name__) | |
class WeatherService: | |
"""Service class for weather data integration""" | |
def __init__(self, api_key: str, base_url: str = "http://api.openweathermap.org/data/2.5"): | |
"""Initialize weather service""" | |
self.api_key = api_key | |
self.base_url = base_url | |
def get_current_weather(self, latitude: float, longitude: float) -> Dict[str, Any]: | |
""" | |
Get current weather data for a location | |
Args: | |
latitude: Location latitude | |
longitude: Location longitude | |
Returns: | |
Dictionary with weather data | |
""" | |
url = f"{self.base_url}/weather" | |
params = { | |
'lat': latitude, | |
'lon': longitude, | |
'appid': self.api_key, | |
'units': 'metric' # Celsius | |
} | |
try: | |
response = requests.get(url, params=params, timeout=10) | |
response.raise_for_status() | |
data = response.json() | |
# Keep the original format for template compatibility | |
logger.info(f"Weather data fetched successfully for coordinates ({latitude}, {longitude})") | |
return data | |
except requests.exceptions.RequestException as e: | |
logger.error(f"Error fetching weather data: {str(e)}") | |
return self._get_fallback_weather() | |
except Exception as e: | |
logger.error(f"Unexpected error in weather service: {str(e)}") | |
return self._get_fallback_weather() | |
def get_weather_forecast(self, latitude: float, longitude: float, days: int = 5) -> list: | |
""" | |
Get weather forecast for multiple days | |
Args: | |
latitude: Location latitude | |
longitude: Location longitude | |
days: Number of days to forecast (max 5 for free tier) | |
Returns: | |
List of weather data for each day | |
""" | |
url = f"{self.base_url}/forecast" | |
params = { | |
'lat': latitude, | |
'lon': longitude, | |
'appid': self.api_key, | |
'units': 'metric' | |
} | |
try: | |
response = requests.get(url, params=params, timeout=10) | |
response.raise_for_status() | |
data = response.json() | |
forecast_data = [] | |
# Group forecasts by date | |
daily_forecasts = {} | |
for item in data['list']: | |
forecast_date = datetime.fromtimestamp(item['dt']).date() | |
if forecast_date not in daily_forecasts: | |
daily_forecasts[forecast_date] = [] | |
daily_forecasts[forecast_date].append(item) | |
# Process each day's data | |
for forecast_date, day_data in list(daily_forecasts.items())[:days]: | |
temps = [item['main']['temp'] for item in day_data] | |
humidity_vals = [item['main']['humidity'] for item in day_data] | |
rainfall = sum([item.get('rain', {}).get('3h', 0) for item in day_data]) | |
daily_weather = { | |
'date': forecast_date, | |
'temperature_min': min(temps), | |
'temperature_max': max(temps), | |
'humidity': sum(humidity_vals) / len(humidity_vals), | |
'rainfall': rainfall, | |
'wind_speed': day_data[0]['wind'].get('speed', 0) * 3.6, | |
'weather_condition': day_data[0]['weather'][0].get('description', '').title(), | |
'weather_main': day_data[0]['weather'][0].get('main', '') | |
} | |
forecast_data.append(daily_weather) | |
logger.info(f"Weather forecast fetched successfully for {days} days") | |
return forecast_data | |
except requests.exceptions.RequestException as e: | |
logger.error(f"Error fetching weather forecast: {str(e)}") | |
return [self._get_fallback_weather() for _ in range(days)] | |
except Exception as e: | |
logger.error(f"Unexpected error in weather forecast: {str(e)}") | |
return [self._get_fallback_weather() for _ in range(days)] | |
def get_weather_alerts(self, latitude: float, longitude: float) -> list: | |
""" | |
Get weather alerts for a location | |
Args: | |
latitude: Location latitude | |
longitude: Location longitude | |
Returns: | |
List of weather alerts | |
""" | |
# Using One Call API for alerts (requires separate endpoint) | |
url = f"{self.base_url.replace('/data/2.5', '/data/3.0')}/onecall" | |
params = { | |
'lat': latitude, | |
'lon': longitude, | |
'appid': self.api_key, | |
'exclude': 'minutely,hourly' | |
} | |
try: | |
response = requests.get(url, params=params, timeout=10) | |
response.raise_for_status() | |
data = response.json() | |
alerts = data.get('alerts', []) | |
processed_alerts = [] | |
for alert in alerts: | |
processed_alert = { | |
'sender_name': alert.get('sender_name', 'Weather Service'), | |
'event': alert.get('event', 'Weather Alert'), | |
'description': alert.get('description', 'No description available'), | |
'start_time': datetime.fromtimestamp(alert.get('start', 0)), | |
'end_time': datetime.fromtimestamp(alert.get('end', 0)), | |
'tags': alert.get('tags', []) | |
} | |
processed_alerts.append(processed_alert) | |
return processed_alerts | |
except Exception as e: | |
logger.warning(f"Weather alerts not available: {str(e)}") | |
return [] | |
def _get_fallback_weather(self) -> Dict[str, Any]: | |
"""Provide fallback weather data when API fails""" | |
# Match the OpenWeatherMap API structure for compatibility with the template | |
current_time = datetime.now() | |
return { | |
'name': 'Unknown Location', | |
'main': { | |
'temp': 25.0, | |
'temp_min': 20.0, | |
'temp_max': 30.0, | |
'humidity': 60.0, | |
'pressure': 1013.0 | |
}, | |
'weather': [ | |
{ | |
'main': 'Unknown', | |
'description': 'Weather data unavailable', | |
'icon': '03d' # Default cloud icon | |
} | |
], | |
'wind': { | |
'speed': 10.0 / 3.6, # km/h to m/s | |
'deg': 180 | |
}, | |
'clouds': { | |
'all': 50 | |
}, | |
'visibility': 10000, # m | |
'sys': { | |
'sunrise': int(current_time.replace(hour=6, minute=0).timestamp()), | |
'sunset': int(current_time.replace(hour=18, minute=0).timestamp()) | |
}, | |
'dt': int(current_time.timestamp()) | |
} | |
def is_weather_suitable_for_activity(self, weather_data: Dict[str, Any], activity_type: str) -> bool: | |
""" | |
Check if weather is suitable for a specific farming activity | |
Args: | |
weather_data: Current weather data | |
activity_type: Type of farming activity | |
Returns: | |
Boolean indicating if weather is suitable | |
""" | |
temperature = weather_data.get('main', {}).get('temp', 25) | |
humidity = weather_data.get('main', {}).get('humidity', 60) | |
rainfall = 0 # OpenWeatherMap doesn't provide rainfall in the base API | |
wind_speed = weather_data.get('wind', {}).get('speed', 0) * 3.6 # Convert m/s to km/h | |
activity_guidelines = { | |
'irrigation': { | |
'max_temperature': 35, | |
'max_rainfall': 5, | |
'max_wind_speed': 20 | |
}, | |
'fertilizer': { | |
'max_temperature': 30, | |
'max_rainfall': 2, | |
'max_wind_speed': 15 | |
}, | |
'pesticide': { | |
'max_temperature': 28, | |
'max_rainfall': 1, | |
'max_wind_speed': 10, | |
'min_humidity': 40 | |
}, | |
'harvesting': { | |
'max_rainfall': 1, | |
'max_wind_speed': 25 | |
}, | |
'planting': { | |
'max_rainfall': 10, | |
'max_wind_speed': 20, | |
'min_temperature': 15 | |
} | |
} | |
guidelines = activity_guidelines.get(activity_type, {}) | |
# Check each constraint | |
if guidelines.get('max_temperature') and temperature > guidelines['max_temperature']: | |
return False | |
if guidelines.get('min_temperature') and temperature < guidelines['min_temperature']: | |
return False | |
if guidelines.get('max_rainfall') and rainfall > guidelines['max_rainfall']: | |
return False | |
if guidelines.get('max_wind_speed') and wind_speed > guidelines['max_wind_speed']: | |
return False | |
if guidelines.get('min_humidity') and humidity < guidelines['min_humidity']: | |
return False | |
return True | |
class AgroWeatherService(WeatherService): | |
"""Extended weather service with agricultural focus""" | |
def get_crop_specific_weather_advice(self, weather_data: Dict[str, Any], crop_type: str) -> Dict[str, str]: | |
""" | |
Get crop-specific weather advice | |
Args: | |
weather_data: Current weather data | |
crop_type: Type of crop | |
Returns: | |
Dictionary with weather-based advice | |
""" | |
temperature = weather_data.get('main', {}).get('temp', 25) | |
rainfall = 0 # OpenWeatherMap doesn't provide rainfall in the base API | |
humidity = weather_data.get('main', {}).get('humidity', 60) | |
crop_advice = { | |
'rice': self._get_rice_weather_advice(temperature, rainfall, humidity), | |
'wheat': self._get_wheat_weather_advice(temperature, rainfall, humidity), | |
'cotton': self._get_cotton_weather_advice(temperature, rainfall, humidity), | |
'sugarcane': self._get_sugarcane_weather_advice(temperature, rainfall, humidity), | |
'maize': self._get_maize_weather_advice(temperature, rainfall, humidity) | |
} | |
return crop_advice.get(crop_type.lower(), { | |
'advice': 'Monitor weather conditions regularly', | |
'warning': 'Follow general farming practices' | |
}) | |
def _get_rice_weather_advice(self, temp: float, rainfall: float, humidity: float) -> Dict[str, str]: | |
"""Get rice-specific weather advice""" | |
if rainfall > 50: | |
return {'advice': 'Ensure proper drainage', 'warning': 'Risk of flooding'} | |
elif temp > 35: | |
return {'advice': 'Increase irrigation frequency', 'warning': 'High temperature stress'} | |
elif humidity < 70: | |
return {'advice': 'Monitor for pest activity', 'warning': 'Low humidity may affect growth'} | |
else: | |
return {'advice': 'Good conditions for rice growth', 'warning': 'Continue normal operations'} | |
def _get_wheat_weather_advice(self, temp: float, rainfall: float, humidity: float) -> Dict[str, str]: | |
"""Get wheat-specific weather advice""" | |
if temp < 10: | |
return {'advice': 'Protect from frost', 'warning': 'Cold weather may damage crop'} | |
elif rainfall > 25: | |
return {'advice': 'Avoid field operations', 'warning': 'Excess moisture may cause fungal diseases'} | |
elif temp > 30: | |
return {'advice': 'Harvest if ready', 'warning': 'High temperature may reduce grain quality'} | |
else: | |
return {'advice': 'Optimal conditions for wheat', 'warning': 'Continue scheduled activities'} | |
def _get_cotton_weather_advice(self, temp: float, rainfall: float, humidity: float) -> Dict[str, str]: | |
"""Get cotton-specific weather advice""" | |
if humidity > 85: | |
return {'advice': 'Monitor for bollworm', 'warning': 'High humidity favors pest development'} | |
elif temp < 15: | |
return {'advice': 'Delay planting', 'warning': 'Cotton needs warm weather for germination'} | |
elif rainfall > 30: | |
return {'advice': 'Ensure drainage', 'warning': 'Waterlogging can damage cotton plants'} | |
else: | |
return {'advice': 'Good conditions for cotton', 'warning': 'Monitor for pest activity'} | |
def _get_sugarcane_weather_advice(self, temp: float, rainfall: float, humidity: float) -> Dict[str, str]: | |
"""Get sugarcane-specific weather advice""" | |
if temp > 38: | |
return {'advice': 'Increase irrigation', 'warning': 'High temperature stress on sugarcane'} | |
elif rainfall < 5 and humidity < 60: | |
return {'advice': 'Irrigate immediately', 'warning': 'Drought conditions detected'} | |
elif rainfall > 100: | |
return {'advice': 'Avoid harvesting', 'warning': 'Excess water may reduce sugar content'} | |
else: | |
return {'advice': 'Favorable conditions', 'warning': 'Continue normal care'} | |
def _get_maize_weather_advice(self, temp: float, rainfall: float, humidity: float) -> Dict[str, str]: | |
"""Get maize-specific weather advice""" | |
if temp < 12: | |
return {'advice': 'Delay sowing', 'warning': 'Cold weather affects maize germination'} | |
elif rainfall > 40: | |
return {'advice': 'Check for waterlogging', 'warning': 'Excess water may cause root rot'} | |
elif temp > 35 and humidity < 50: | |
return {'advice': 'Irrigate and mulch', 'warning': 'Hot and dry conditions stress maize'} | |
else: | |
return {'advice': 'Good growing conditions', 'warning': 'Monitor for pest activity'} | |