Weather Dashboard¶
Difficulty: Intermediate-Advanced
Time: 60-90 minutes
Learning Focus: API integration, data visualisation, environmental data analysis
Module: chat
Overview¶
Create a weather dashboard that fetches real-time weather data and forecasts from an API, then visualizes it with charts and provides AI-powered weather advice based on conditions.
Instructions¶
import requests
import matplotlib.pyplot as plt
import os
from datetime import datetime, timedelta
from hands_on_ai.chat import get_response
class WeatherDashboard:
"""
A simple weather dashboard that retrieves and displays weather data.
Students will need to sign up for a free API key from OpenWeatherMap.
"""
def __init__(self):
self.api_key = None
self.base_url = "https://api.openweathermap.org/data/2.5/"
self.output_dir = "weather_dashboard"
os.makedirs(self.output_dir, exist_ok=True)
def setup(self):
"""Set up the dashboard with the API key."""
print("=== Weather Dashboard Setup ===")
# Check for existing API key
key_file = os.path.join(self.output_dir, "api_key.txt")
if os.path.exists(key_file):
with open(key_file, 'r') as f:
self.api_key = f.read().strip()
print("API key loaded from file.")
# If no API key, prompt for one
if not self.api_key:
print("\nYou need an OpenWeatherMap API key to use this dashboard.")
print("Get a free API key at: https://openweathermap.org/api")
self.api_key = input("Enter your API key: ").strip()
# Save API key for future use
save_key = input("Save this API key for future use? (y/n): ").lower() == 'y'
if save_key:
with open(key_file, 'w') as f:
f.write(self.api_key)
print("API key saved.")
def get_current_weather(self, location):
"""Get current weather for a location."""
url = f"{self.base_url}weather"
params = {
'q': location,
'appid': self.api_key,
'units': 'metric' # Use metric by default
}
try:
response = requests.get(url, params=params)
response.raise_for_status() # Raise exception for HTTP errors
return response.json()
except requests.exceptions.HTTPError as http_err:
if response.status_code == 404:
print(f"Location '{location}' not found. Please check the spelling.")
else:
print(f"HTTP error: {http_err}")
return None
except Exception as err:
print(f"Error: {err}")
return None
def get_forecast(self, location, days=5):
"""Get weather forecast for a location."""
url = f"{self.base_url}forecast"
params = {
'q': location,
'appid': self.api_key,
'units': 'metric',
'cnt': days * 8 # API returns data in 3-hour steps, 8 per day
}
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except requests.exceptions.HTTPError as http_err:
if response.status_code == 404:
print(f"Location '{location}' not found. Please check the spelling.")
else:
print(f"HTTP error: {http_err}")
return None
except Exception as err:
print(f"Error: {err}")
return None
def display_current_weather(self, data):
"""Display current weather conditions."""
if not data:
return
try:
city = data['name']
country = data['sys']['country']
temp = data['main']['temp']
feels_like = data['main']['feels_like']
humidity = data['main']['humidity']
pressure = data['main']['pressure']
weather_desc = data['weather'][0]['description']
wind_speed = data['wind']['speed']
clouds = data['clouds']['all']
# Convert Unix timestamp to readable format
sunrise = datetime.fromtimestamp(data['sys']['sunrise']).strftime('%H:%M')
sunset = datetime.fromtimestamp(data['sys']['sunset']).strftime('%H:%M')
print("\n=== Current Weather Conditions ===")
print(f"Location: {city}, {country}")
print(f"Weather: {weather_desc.title()}")
print(f"Temperature: {temp}°C (Feels like: {feels_like}°C)")
print(f"Humidity: {humidity}%")
print(f"Pressure: {pressure} hPa")
print(f"Wind Speed: {wind_speed} m/s")
print(f"Cloud Cover: {clouds}%")
print(f"Sunrise: {sunrise}")
print(f"Sunset: {sunset}")
except KeyError as e:
print(f"Error parsing weather data: {e}")
def plot_forecast(self, data, location):
"""Create forecast plots and save them."""
if not data:
return
try:
# Extract forecast data
timestamps = []
temps = []
humidity = []
descriptions = []
for item in data['list']:
dt = datetime.fromtimestamp(item['dt'])
timestamps.append(dt)
temps.append(item['main']['temp'])
humidity.append(item['main']['humidity'])
descriptions.append(item['weather'][0]['description'])
# Create temperature forecast plot
plt.figure(figsize=(12, 6))
plt.plot(timestamps, temps, marker='o', color='#FF5733', linewidth=2)
plt.xlabel('Date & Time')
plt.ylabel('Temperature (°C)')
plt.title(f'Temperature Forecast for {location}')
plt.grid(True, linestyle='--', alpha=0.7)
plt.xticks(rotation=45)
plt.tight_layout()
# Save the plot
temp_plot_file = os.path.join(self.output_dir, f"{location.replace(',', '_')}_temp_forecast.png")
plt.savefig(temp_plot_file)
plt.close()
# Create humidity forecast plot
plt.figure(figsize=(12, 6))
plt.plot(timestamps, humidity, marker='s', color='#3498DB', linewidth=2)
plt.xlabel('Date & Time')
plt.ylabel('Humidity (%)')
plt.title(f'Humidity Forecast for {location}')
plt.grid(True, linestyle='--', alpha=0.7)
plt.xticks(rotation=45)
plt.tight_layout()
# Save the plot
humidity_plot_file = os.path.join(self.output_dir, f"{location.replace(',', '_')}_humidity_forecast.png")
plt.savefig(humidity_plot_file)
plt.close()
print(f"\nForecast plots saved to:\n- {temp_plot_file}\n- {humidity_plot_file}")
return temp_plot_file, humidity_plot_file
except KeyError as e:
print(f"Error parsing forecast data: {e}")
return None, None
def get_weather_summary(self, current_data, forecast_data, location):
"""Generate a summary of the weather conditions and forecast."""
if not current_data or not forecast_data:
return "Unable to generate weather summary due to missing data."
try:
# Extract key information
current_temp = current_data['main']['temp']
current_desc = current_data['weather'][0]['description']
# Get min/max for the next few days
daily_temps = {}
for item in forecast_data['list']:
dt = datetime.fromtimestamp(item['dt'])
date_str = dt.strftime('%Y-%m-%d')
if date_str not in daily_temps:
daily_temps[date_str] = {'temps': [], 'descs': []}
daily_temps[date_str]['temps'].append(item['main']['temp'])
daily_temps[date_str]['descs'].append(item['weather'][0]['description'])
# Create summary with key info
summary = f"Weather Summary for {location}:\n\n"
summary += f"Current Conditions: {current_desc.title()} at {current_temp}°C\n\n"
summary += "Forecast:\n"
for date_str, data in daily_temps.items():
if data['temps']: # Make sure we have data
min_temp = min(data['temps'])
max_temp = max(data['temps'])
# Get most common description
from collections import Counter
desc_counter = Counter(data['descs'])
most_common_desc = desc_counter.most_common(1)[0][0]
# Format date nicely (e.g., "Monday, Jan 15")
date_obj = datetime.strptime(date_str, '%Y-%m-%d')
formatted_date = date_obj.strftime('%A, %b %d')
summary += f"- {formatted_date}: {most_common_desc.title()}, {min_temp}°C to {max_temp}°C\n"
return summary
except KeyError as e:
print(f"Error generating weather summary: {e}")
return "Unable to generate weather summary."
def get_ai_weather_advice(self, current_data, forecast_data, location):
"""Get AI-generated weather advice based on conditions."""
if not current_data or not forecast_data:
return "Unable to generate weather advice due to missing data."
try:
# Prepare weather information for the AI
current_temp = current_data['main']['temp']
current_desc = current_data['weather'][0]['description']
current_humidity = current_data['main']['humidity']
current_wind = current_data['wind']['speed']
# Extract forecast information
tomorrow_data = forecast_data['list'][:8] # First 8 entries (24 hours)
tomorrow_descs = [item['weather'][0]['description'] for item in tomorrow_data]
tomorrow_temps = [item['main']['temp'] for item in tomorrow_data]
avg_tomorrow_temp = sum(tomorrow_temps) / len(tomorrow_temps)
min_tomorrow_temp = min(tomorrow_temps)
max_tomorrow_temp = max(tomorrow_temps)
# Create prompt for AI
prompt = f"""
Based on the following weather data for {location}:
Current conditions:
- Temperature: {current_temp}°C
- Description: {current_desc}
- Humidity: {current_humidity}%
- Wind speed: {current_wind} m/s
Tomorrow's forecast:
- Average temperature: {avg_tomorrow_temp:.1f}°C
- Range: {min_tomorrow_temp:.1f}°C to {max_tomorrow_temp:.1f}°C
- Conditions: {', '.join(set(tomorrow_descs))}
Please provide:
1. Practical advice for what to wear or prepare for today
2. Any weather warnings or precautions to be aware of
3. Suggested activities that would be appropriate for this weather
Keep your response conversational and under 150 words.
"""
try:
advice = get_response(prompt)
return advice
except Exception as e:
print(f"Error getting AI weather advice: {e}")
return "Unable to generate AI weather advice at this time."
except KeyError as e:
print(f"Error preparing data for AI advice: {e}")
return "Unable to generate weather advice due to missing data."
def run(self):
"""Run the main dashboard interface."""
self.setup()
if not self.api_key:
print("No API key provided. Exiting.")
return
print("\n=== Weather Dashboard ===")
location = input("Enter a city name (e.g., 'London' or 'London,UK'): ")
print(f"\nFetching weather data for {location}...")
current_data = self.get_current_weather(location)
if current_data:
self.display_current_weather(current_data)
# Get forecast data
print("\nFetching forecast data...")
forecast_data = self.get_forecast(location)
if forecast_data:
# Plot forecast
temp_plot, humidity_plot = self.plot_forecast(forecast_data, location)
# Generate weather summary
summary = self.get_weather_summary(current_data, forecast_data, location)
print("\n=== Weather Summary ===")
print(summary)
# Get AI advice if requested
get_advice = input("\nWould you like personalized weather advice? (y/n): ").lower() == 'y'
if get_advice:
print("\nGenerating advice...")
advice = self.get_ai_weather_advice(current_data, forecast_data, location)
print("\n=== Weather Advice ===")
print(advice)
# Save all info to a report file
save_report = input("\nSave a weather report file? (y/n): ").lower() == 'y'
if save_report:
try:
report_file = os.path.join(self.output_dir, f"{location.replace(',', '_')}_weather_report.txt")
with open(report_file, 'w') as f:
f.write(f"Weather Report for {location}\n")
f.write(f"Generated on {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n")
f.write("=== Current Conditions ===\n")
# Extract current conditions
f.write(f"Temperature: {current_data['main']['temp']}°C\n")
f.write(f"Feels like: {current_data['main']['feels_like']}°C\n")
f.write(f"Weather: {current_data['weather'][0]['description'].title()}\n")
f.write(f"Humidity: {current_data['main']['humidity']}%\n")
f.write(f"Wind speed: {current_data['wind']['speed']} m/s\n")
f.write(f"Pressure: {current_data['main']['pressure']} hPa\n\n")
# Add summary
f.write("=== Forecast Summary ===\n")
f.write(summary + "\n\n")
# Add advice if it was generated
if get_advice:
f.write("=== Weather Advice ===\n")
f.write(advice + "\n")
# Add note about plot files
if temp_plot and humidity_plot:
f.write("\nForecast plots saved as:\n")
f.write(f"- {os.path.basename(temp_plot)}\n")
f.write(f"- {os.path.basename(humidity_plot)}\n")
print(f"\nWeather report saved to: {report_file}")
except Exception as e:
print(f"Error saving weather report: {e}")
print("\nThank you for using the Weather Dashboard!")
# Run the dashboard
if __name__ == "__main__":
dashboard = WeatherDashboard()
dashboard.run()
Extension Ideas¶
- Add support for multiple locations and comparison views
- Implement unit conversion between metric and imperial
- Create a historical weather data retrieval and analysis feature
- Add precipitation and wind forecasts with appropriate visualisations
- Implement a daily weather notification system
- Create a map-based visualisation of weather data