diff --git a/README.md b/README.md index e92f8d0..6429e62 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,73 @@ # oaf-psd-bootcamp -Initial repository for the Open Avenues: Professional Software Development Bootcamp +# Weather App + +## Overview + +The Weather App is designed to provide users with real-time weather and air quality information for a given location. This README.md file outlines the data utilization strategy, including the sources of data, API integration, and how the data is processed and presented in the application. + +## Data Sources + +The Weather App utilizes two primary data sources: + +1. **Weather Data:** + - Open-Meteo API: Used to fetch hourly weather forecasts based on geographical coordinates. + - Endpoint: `https://api.open-meteo.com/v1/forecast` + +2. **Air Quality Data:** + - OpenWeatherMap API: Used to retrieve air quality information for a specific location. + - Endpoint: `https://api.openweathermap.org/data/2.5/air_pollution` + +## API Key Requirements + +To use the Weather App, you need to obtain API keys for both data sources: + +- Open-Meteo API: No API key is required for the Open-Meteo API. +- OpenWeatherMap API: Sign up for a free account and obtain an API key from [OpenWeatherMap](https://openweathermap.org/api). + +## Project Structure + +The project is structured into several components: + +1. **APICallingService:** + - Manages the interaction with the Open-Meteo and OpenWeatherMap APIs. + - Fetches weather and air quality data. + +2. **DataSourceHandler:** + - Takes care of handling and combining data from multiple sources. + - Responsible for further data processing. + +3. **MockedDataService:** + - Provides mocked data for testing purposes. + +4. **WeatherDatabase:** + - Handles interactions with the weather database. + +5. **Main Script:** + - Combines data from different sources and visualizes it using Matplotlib. + +## Data Utilization Strategy + +1. **Weather Data:** + - The Weather App retrieves hourly weather forecasts using the Open-Meteo API. + - The data includes time, temperature at 2 meters, and the city. + +2. **Air Quality Data:** + - The OpenWeatherMap API is used to obtain air quality information. + - The air quality index (AQI) is extracted and combined with weather data. + +3. **Combining Data:** + - The `APICallingService` fetches data separately from weather and air quality APIs. + - The `DataSourceHandler` combines these datasets into a unified format. + +4. **Visualization:** + - Matplotlib is used for visualizing the combined data. + - The script generates a plot displaying the temperature trend and air quality index over time. + +## Usage + +1. **Obtain API Keys:** + - Sign up for OpenWeatherMap API and obtain the API key. + - Replace 'YOUR_OPENWEATHERMAP_API_KEY' in the code with your actual API key. + +2. **Run the App:** + - Execute the main script to fetch and visualize weather and air quality data. diff --git a/week5/main.py b/week5/main.py index 628b6fb..cad22fb 100644 --- a/week5/main.py +++ b/week5/main.py @@ -1,6 +1,5 @@ -#I don't really have progress on main.py yet I don't know how to tie this structure of the program all together yet it's getting to be a lot of pieces to put together for a beginner like me. -#I will try though. - +#I haven't made much progress on main.py and I don't totally understand how to tie the overall design of my weather application together +# I have a lot of components and pieces but I am not sure how to make everything work while remaining loosely coupled etc import argparse diff --git a/week5/repository.py b/week5/repository.py index bd9383e..8530953 100644 --- a/week5/repository.py +++ b/week5/repository.py @@ -1,3 +1,4 @@ +#my idea for this file is to make repostiroy a interface layer that works between the database and other classes but I don't know if this is necessary to decouple the design / classes. class WeatherDataRepository: def __init__(self, database): self.database = database @@ -7,5 +8,3 @@ def get_weather_data(self, city): def insert_weather_data(self, city, temperature, conditions): self.database.insert_data(city, temperature, conditions) - - #this file could be like a interface to talk between the database and other classes diff --git a/week5/weather.py b/week5/weather.py index 6d01f3a..fafe9ee 100644 --- a/week5/weather.py +++ b/week5/weather.py @@ -2,6 +2,39 @@ import matplotlib.pyplot as plt import random from datetime import datetime, timedelta +from weather_database import WeatherDatabase +from geopy.geocoders import Photon +Latitude = "25.594095" +Longitude = "85.137566" + + +geolocator = Photon(user_agent="geoapiExercises") +Latitude = "25.594095" +Longitude = "85.137566" + +location = geolocator.reverse(Latitude+","+Longitude) + +# Display +#print(location) +#print(type(location)) +print(type(location)) +print(location.raw.keys()) +print(location.raw.values()) +address = location.raw['properties'] + +#print(address) + + +city = address.get('city', '') +state = address.get('state', '') +country = address.get('country', '') +code = address.get('country_code') +zipcode = address.get('postcode') +print('City : ',city) +print('State : ',state) +print('Country : ',country) +print('Zip Code : ', zipcode) + class AbstractDataService: @@ -13,7 +46,8 @@ def get_data(self, startDatetime: datetime, endDatetime: datetime): # Mocked data retrieval logic mocked_data = { 'time': [], - 'temperature_2m': [] + 'temperature_2m': [], + 'city': [] } start_temperature = 5.0 @@ -25,6 +59,7 @@ def get_data(self, startDatetime: datetime, endDatetime: datetime): mocked_data['time'].append(time) mocked_data['temperature_2m'].append(temperature) + mocked_data['city'].append("test city") return mocked_data @@ -38,6 +73,33 @@ def call_api(self, payload: dict, url: str): r = requests.get(url, params=payload) json_data = r.json() hourly_data = json_data.get("hourly", {}) + # Append latitude and longitude to hourly_data + hourly_data['latitude'] = payload.get('latitude') + hourly_data['longitude'] = payload.get('longitude') + Longitude = str(payload.get('longitude')) + Latitude = str(payload.get('latitude')) + #Is this now tightly coupled because we're using the geolocator insdie the call_api, inside the serivce API? + #Should we put the geolocator inside a class and access it via an interface or something? + #Figure out the city from the payload longitude + latitude + geolocator = Photon(user_agent="geoapiExercises") + location = geolocator.reverse(Latitude+","+Longitude) + city = location.raw['properties']['city'] + hourly_data['city'] = city + + print("Fetching air quality data...") + air_quality_payload = { + 'lat': payload.get('latitude'), + 'lon': payload.get('longitude'), + 'appid': '########################' # Replace with your API key + } + r = requests.get("https://api.openweathermap.org/data/2.5/air_pollution", params=air_quality_payload) + json_air_data = r.json() + air_data = json_air_data.get("list", {}) + #print(json_air_data) + print(air_data) + + + #hourly_data['city'].append("test city") # Implement logic to call the external API with the provided payload # Return the API response #return {"api_key": "api_value"} @@ -78,32 +140,35 @@ def create_data_service(self, service_type): factory = DataServiceFactory() # Create Mocked Data Service -mocked_service = factory.create_data_service("mocked") -print(f"Type of Mocked Service: {type(mocked_service)}") +#mocked_service = factory.create_data_service("mocked") +#print(f"Type of Mocked Service: {type(mocked_service)}") # Create API Data Service api_service = factory.create_data_service("api") print(f"Type of API Service: {type(api_service)}") -print(type(datetime), "---------------") -beginDate = datetime.now() - timedelta(days=7) -endDate = datetime.now() +#print(type(datetime), "---------------") +#beginDate = datetime.now() - timedelta(days=7) +#endDate = datetime.now() # Use DataSourceHandler to handle data from Mocked Service -mocked_data_handler = DataSourceHandler(mocked_service) -mocked_data_handler.handle_data(beginDate, endDate) +#mocked_data_handler = DataSourceHandler(mocked_service) +#mocked_data_handler.handle_data(beginDate, endDate) # Use DataSourceHandler to handle data from API Service -api_data_handler = DataSourceHandler(api_service) -api_data_handler.handle_data(beginDate, endDate) +#api_data_handler = DataSourceHandler(api_service) +#api_data_handler.handle_data(beginDate, endDate) + +#mocked_service = MockedDataService() +#print(type(mocked_service)) +#mocked_data = mocked_service.get_data(beginDate, endDate) -mocked_service = MockedDataService() -print(type(mocked_service)) -mocked_data = mocked_service.get_data(beginDate, endDate) +#print(mocked_data) -print(mocked_data) +#api_service = APICallingService() +hourly_data = api_service.call_api(payload, url) # print(type(payload)) @@ -111,7 +176,22 @@ def create_data_service(self, service_type): #json_data = r.json() #hourly_data = json_data.get("hourly", {}) -hourly_data = mocked_data +#hourly_data = mocked_data +print(type(hourly_data["time"])) +print(type(hourly_data["temperature_2m"])) +print(type(hourly_data["city"])) +# View keys +print("Keys:", hourly_data.keys()) + +# View values +print("Values:", hourly_data.values()) +DB_File = "weather_db" +weather_db = WeatherDatabase(DB_File) +weather_db.close_connect() +#insert_data(self, city, temperature, time): +#weather_db.insert_data() +# View items +#print("Items:", hourly_data.items()) # print(hourly_data) @@ -127,3 +207,4 @@ def create_data_service(self, service_type): plt.show() print("Hurray") + diff --git a/week5/weather_database.py b/week5/weather_database.py new file mode 100644 index 0000000..87ac8c8 --- /dev/null +++ b/week5/weather_database.py @@ -0,0 +1,67 @@ +import sqlite3 +from datetime import datetime, timedelta + +class WeatherDatabase: + + def __init__(self, db_file): + + self.conn = sqlite3.connect(":memory:") + + self.cursor = self.conn.cursor() + + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS weather( + id INTEGER PRIMARY KEY AUTOINCREMENT, + city TEXT, + temperature REAL, + time TEXT + ) + ''') + + def insert_data(self, city, temperature, time): + + timestamp = datetime.now() + + self.cursor.execute(''' + SELECT * FROM weather + WHERE city = ? + ORDER BY time DESC + LIMIT 1 + ''', (city, )) + + data = self.cursor.fetchone() + + # Check if data exists and if it is older than 24 hours before inserting + if data and datetime.strptime(data[4], "%Y-%m-%d %H:%M:%S") > timestamp - timedelta(hours=24): + return data + else: + self.cursor.execute(''' + INSERT INTO weather (city, temperature, time, timestamp) VALUES (?, ?, ?, ?) + ''', (city, temperature, time, timestamp.strftime("%Y-%m-%d %H:%M:%S"))) + self.conn.commit() + + def get_weather_data(self, weather_service, city): + + self.cursor.excute(''' + SELECT * FROM weather + WHERE city = ? + ''', (city,)) + data = self.cursor.fetchall() + + if data: + return data + else: + weather_data = weather_service.get_weather_data(city) + + self.cursor.excute(''' + INSERT INTO weather (city, temperature) + VALIES (?, ?) + ''', (city, str(weather_data))) + self.conn.commit() + + return weather_data + + def close_connect(self): + self.conn.close() + print("Database connection closed") diff --git a/week7/weather.py b/week7/weather.py new file mode 100644 index 0000000..9080063 --- /dev/null +++ b/week7/weather.py @@ -0,0 +1,371 @@ +import sys +import requests +import matplotlib.pyplot as plt +import random +from datetime import datetime, timedelta +from weather_database import WeatherDatabase +from geopy.geocoders import Photon +Latitude = "25.594095" +Longitude = "85.137566" + + +geolocator = Photon(user_agent="geoapiExercises") +Latitude = "25.594095" +Longitude = "85.137566" + +location = geolocator.reverse(Latitude+","+Longitude) + +# Display +#print(location) +#print(type(location)) +print(type(location)) +print(location.raw.keys()) +print(location.raw.values()) +address = location.raw['properties'] + +#print(address) + + +city = address.get('city', '') +state = address.get('state', '') +country = address.get('country', '') +code = address.get('country_code') +zipcode = address.get('postcode') +print('City : ',city) +print('State : ',state) +print('Country : ',country) +print('Zip Code : ', zipcode) + +class VisualizationHandler(): + def visualize_data(self, hourly_data: dict): + + fig, ax = plt.subplots() + + + ax.set_xlabel("Time (hourly)") + + ax.set_ylabel("temperature_2m") + print(hourly_data) + print(type(hourly_data)) + ax.plot(hourly_data["time"], hourly_data["temperature_2m"]) + + plt.show() + + def utilize_data(self, hourly_data: dict, weather_db: WeatherDatabase): + city = hourly_data.get('city', '') + temperature = hourly_data.get('temperature_2m', []) + time = hourly_data.get('time', []) + + # Store data in the database + for i in range(len(time)): + weather_db.insert_data(city, temperature[i], time[i]) + + # Additional data utilization logic can be added here + # For example, you can perform analysis on the data, generate reports, etc. + + print("Data utilization completed.") + + def analyze_data(self, weather_db: WeatherDatabase): + # Calculate average temperature for each city + cities = weather_db.get_all_cities() + average_temperatures = [] + + for city_info in location_lookups: + city = city_info["name"] + if city in cities: + data = weather_db.get_weather_data_for_city(city) + temperatures = [entry[2] for entry in data] # Assuming the temperature is at index 2 + average_temperature = sum(temperatures) / len(temperatures) + average_temperatures.append(average_temperature) + print(f"Average temperature for {city}: {average_temperature:.2f}°C") + else: + print(f"No data available for {city}") + + # Plotting the bar chart + self.plot_average_temperatures(location_lookups, average_temperatures) + + def plot_average_temperatures(self, cities, average_temperatures): + plt.figure(figsize=(10, 6)) + plt.bar(cities, average_temperatures, color='blue') + plt.xlabel('Cities') + plt.ylabel('Average Temperature (°C)') + plt.title('Average Temperature for Five Cities') + plt.show() + +class AbstractDataService: + def get_data(self, startDate: datetime, endDate: datetime): + raise NotImplementedError("Subclasses must implement this method") + + def utilize_data(self, hourly_data: dict, weather_db: WeatherDatabase): + raise NotImplementedError("Subclasses must implement this method") + +class MockedDataService(AbstractDataService): + def get_data(self, startDatetime: datetime, endDatetime: datetime): + # Mocked data retrieval logic + mocked_data = { + 'time': [], + 'temperature_2m': [], + 'city': [] + } + + start_temperature = 5.0 + end_temperature = 20.0 + # Generate mocked time and temperature data + for i in range(72): # Assuming 72 data points based on the provided results + time = f'2023-11-26T{str(i).zfill(2)}:00' + temperature = round(random.uniform(start_temperature, end_temperature), 1) + + mocked_data['time'].append(time) + mocked_data['temperature_2m'].append(temperature) + mocked_data['city'].append("test city") + + return mocked_data + + def utilize_data(self, hourly_data: dict, weather_db: WeatherDatabase): + print("we should do stuff here") + +class APICallingService(AbstractDataService): + def get_data(self, startDate: datetime, endDate: datetime, weather_db: WeatherDatabase): + # Check the database for existing data first + cached_data = weather_db.get_weather_data(self, city) + if cached_data: + print("Using cached data from the database.") + return cached_data + + # If data is not found in the database, make an API call + print("Making API call to fetch new data...") + api_data = self.call_api(payload, url) + + if api_data: + # Store the newly fetched data in the database + self.utilize_data(api_data, weather_db) + return api_data + else: + print("Error fetching data from API.") + return None + + def utilize_data(self, hourly_data: dict, weather_db: WeatherDatabase): + print("we should do stuff here") + + def call_api(self, payload: dict, url: str): + try: + r = requests.get(url, params=payload) + r.raise_for_status() # Raise an HTTPError for bad responses (4xx and 5xx status codes) + json_data = r.json() + except requests.exceptions.RequestException as e: + print(f"Error during API call: {e}") + return None + + hourly_data = json_data.get("hourly", {}) + + if hourly_data is None: + print("Error: No 'hourly' data in API response.") + return None + # Append latitude and longitude to hourly_data + hourly_data['latitude'] = payload.get('latitude') + hourly_data['longitude'] = payload.get('longitude') + Longitude = str(payload.get('longitude')) + Latitude = str(payload.get('latitude')) + #Is this now tightly coupled because we're using the geolocator insdie the call_api, inside the serivce API? + #Should we put the geolocator inside a class and access it via an interface or something? + #Figure out the city from the payload longitude + latitude + geolocator = Photon(user_agent="geoapiExercises") + location = geolocator.reverse(Latitude+","+Longitude) + city = location.raw['properties']['city'] + hourly_data['city'] = city + + print("Fetching air quality data...") + air_quality_payload = { + 'lat': payload.get('latitude'), + 'lon': payload.get('longitude'), + 'appid': 'ff8ef6c023a22aa043afef8f7e308d67' # Replace with your API key + } + r = requests.get("https://api.openweathermap.org/data/2.5/air_pollution", params=air_quality_payload) + json_air_data = r.json() + air_data = json_air_data.get("list", {}) + #print(json_air_data) + print(air_data) + + + #hourly_data['city'].append("test city") + # Implement logic to call the external API with the provided payload + # Return the API response + #return {"api_key": "api_value"} + return hourly_data + + + +class DataSourceHandler: + def __init__(self, data_service: AbstractDataService, visualization_handler: VisualizationHandler): + self.data_service = data_service + self.visualization_handler = visualization_handler + + def handle_data(self, startDate: datetime, endDate: datetime, weather_db: WeatherDatabase): + if(weather_db is None): + print("No weather database provided") + data = self.data_service.get_data(startDate, endDate) + else: + print("Weather database provided") + data = self.data_service.get_data(startDate, endDate, weather_db) + # Implement handling logic + print("Handling data:") + print(data) + print(startDate) + print(endDate) + # Use the provided VisualizationHandler + + self.visualization_handler.visualize_data(data) + + # Utilize data in the database + self.data_service.utilize_data(data, weather_db) + + +class DataServiceFactory: + def create_data_service(self, service_type: str): + if service_type == "mocked": + return MockedDataService() + elif service_type == "api": + return APICallingService() + else: + raise ValueError("Invalid service type") + + +url = "https://api.open-meteo.com/v1/forecast" + +location_lookups = [ + { + "name": "Detroit", + "longitude": 83.0458, + "latitude": 42.3314 + }, + { + "name": "Marquette", + "longitude": 87.3956, + "latitude": 46.5436 + }, + { + "name": "San Jose", + "longitude": -121.8863, + "latitude": 37.3382 + }, + { + "name": "Miami", + "longitude": -80.1918, + "latitude": 25.7617 + } + ] + +payload = {'latitude': 37.7723281, + 'longitude': -122.4530167, + 'hourly': 'temperature_2m'} + + + +# Example usage +factory = DataServiceFactory() + +# Create Mocked Data Service +#mocked_service = factory.create_data_service("mocked") +#print(f"Type of Mocked Service: {type(mocked_service)}")- +/*-- + +# Create API Data Service +api_service = factory.create_data_service("api") +print(f"Type of API Service: {type(api_service)}") +#print(type(datetime), "---------------") +#beginDate = datetime.now() - timedelta(days=7) +#endDate = datetime.now() + +# Use DataSourceHandler to handle data from Mocked Servic- +/*-- +#mocked_data_handler = DataSourceHandler(mocked_service) +#mocked_data_handler.handle_data(beginDate, endDate) + +# Use DataSourceHandler to handle data from API Service +#api_data_handler = DataSourceHandler(api_service) +#api_data_handler.handle_data(beginDate, endDate) + + + + +#mocked_service = MockedDataService() +#print(type(mocked_service)) +#mocked_data = mocked_service.get_data(beginDate, endDate- +/*-- + +#print(mocked_data) + +#api_service = APICallingService() +hourly_data = api_service.call_api(payload, url) +# print(type(payload)) + + +# r = requests.get(url, params=payload) + +#json_data = r.json() +#hourly_data = json_data.get("hourly", {}) +#hourly_data = mocked_data +print(type(hourly_data["time"])) +print(type(hourly_data["temperature_2m"])) +print(type(hourly_data["city"])) +# View keys +print("Keys:", hourly_data.keys()) + +# View values +print("Values:", hourly_data.values()) +DB_File = "weather_db" +weather_db = WeatherDatabase(DB_File) +#weather_db.close_connect() +#insert_data(self, city, temperature, time): +#weather_db.insert_data() +# View items +#print("Items:", hourly_data.items()) + + +# print(hourly_data) +print(type(hourly_data)) +vizHandler = VisualizationHandler() +vizHandler.visualize_data(hourly_data) + + +print("Hurray") + + + + + +# Modify the main part of the code to utilize the data +#if __name__ == "__main__": +usable_service = factory.create_data_service("api") +data_handler = DataSourceHandler(usable_service, Visualiz- +/*-- +dler()) + +# Define the database file +#DB_File = "weather_db" + +# Create an instance of the WeatherDatabase +#weather_db = WeatherDatabase(DB_File) + +# Open the database connection +#weather_db.open_connect() + +# Handle data and utilize it +beginDate = datetime.now() - timedelta(days=7) +endDate = datetime.now() +data_handler.handle_data(beginDate, endDate, weather_db) + + +# Use DataSourceHandler to handle data from API Service +api_data_handler = DataSourceHandler(api_service, Visuali- +/*-- +ndler()) +api_data_handler.handle_data(beginDate, endDate, weather_- +/*-- + +# Analyze and plot average temperatures for cities +vizHandler.analyze_data(weather_db, location_lookups) + +# Close the database connection +weather_db.close_connect() + diff --git a/week7/weather_database.py b/week7/weather_database.py new file mode 100644 index 0000000..35faee2 --- /dev/null +++ b/week7/weather_database.py @@ -0,0 +1,80 @@ +import sqlite3 +from datetime import datetime, timedelta + +class WeatherDatabase: + + def __init__(self, db_file): + + self.conn = sqlite3.connect(":memory:") + + self.cursor = self.conn.cursor() + + + self.cursor.execute(''' + CREATE TABLE IF NOT EXISTS weather( + id INTEGER PRIMARY KEY AUTOINCREMENT, + city TEXT, + temperature REAL, + time TEXT + ) + ''') + + def insert_data(self, city, temperature, time): + + timestamp = datetime.now() + + self.cursor.execute(''' + SELECT * FROM weather + WHERE city = ? + ORDER BY time DESC + LIMIT 1 + ''', (city, )) + + data = self.cursor.fetchone() + + # Check if data exists and if it is older than 24 hours before inserting + if data and datetime.strptime(data[4], "%Y-%m-%d %H:%M:%S") > timestamp - timedelta(hours=24): + return data + else: + self.cursor.execute(''' + INSERT INTO weather (city, temperature, time, timestamp) VALUES (?, ?, ?, ?) + ''', (city, temperature, time, timestamp.strftime("%Y-%m-%d %H:%M:%S"))) + self.conn.commit() + + def get_weather_data(self, city): + self.cursor.execute(''' + SELECT * FROM weather + WHERE city = ? + ORDER BY time DESC + LIMIT 1 + ''', (city,)) + + data = self.cursor.fetchone() + + if data: + return data[2] # Return the temperature from the cached data + else: + return None + + + def get_all_cities(self): + self.cursor.execute(''' + SELECT DISTINCT city FROM weather + ''') + cities = [entry[0] for entry in self.cursor.fetchall()] + return cities + + def get_weather_data_for_city(self, city): + self.cursor.execute(''' + SELECT * FROM weather + WHERE city = ? + ''', (city,)) + data = self.cursor.fetchall() + return data + + def open_connect(self): + print("Database connection opened") + + def close_connect(self): + self.conn.close() + print("Database connection closed")