customized_farm_planner / weather_service.py
pranit144's picture
Upload 56 files
429a26d verified
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'}