import gradio as gr import requests import folium import json import time import os from typing import Dict, List, Optional, Tuple import pandas as pd class AirQualityMapper: """Class to handle AirNow API interactions and map generation""" def __init__(self): self.base_url = "https://www.airnowapi.org" self.aqi_colors = { "Good": "#00E400", "Moderate": "#FFFF00", "Unhealthy for Sensitive Groups": "#FF7E00", "Unhealthy": "#FF0000", "Very Unhealthy": "#8F3F97", "Hazardous": "#7E0023" } self.aqi_ranges = { (0, 50): "Good", (51, 100): "Moderate", (101, 150): "Unhealthy for Sensitive Groups", (151, 200): "Unhealthy", (201, 300): "Very Unhealthy", (301, 500): "Hazardous" } def get_aqi_category(self, aqi_value: int) -> str: """Get AQI category based on value""" for (min_val, max_val), category in self.aqi_ranges.items(): if min_val <= aqi_value <= max_val: return category return "Unknown" def get_aqi_color(self, category: str) -> str: """Get color for AQI category""" return self.aqi_colors.get(category, "#808080") def fetch_airnow_data(self, api_key: str) -> Tuple[List[Dict], str]: """ Fetch air quality data from AirNow API Returns: (data_list, status_message) """ if not api_key or api_key.strip() == "": return [], "❌ Please enter a valid AirNow API key" try: # Get data for major US cities and regions # We'll use a comprehensive list of state capitals and major cities locations = [ ("90210", "California"), ("10001", "New York"), ("60601", "Illinois"), ("75201", "Texas"), ("33101", "Florida"), ("30301", "Georgia"), ("98101", "Washington"), ("97201", "Oregon"), ("80201", "Colorado"), ("85001", "Arizona"), ("89101", "Nevada"), ("84101", "Utah"), ("59601", "Montana"), ("58501", "North Dakota"), ("57501", "South Dakota"), ("68501", "Nebraska"), ("66601", "Kansas"), ("73101", "Oklahoma"), ("55101", "Minnesota"), ("50301", "Iowa"), ("65101", "Missouri"), ("72201", "Arkansas"), ("70801", "Louisiana"), ("39201", "Mississippi"), ("35201", "Alabama"), ("37201", "Tennessee"), ("40601", "Kentucky"), ("25301", "West Virginia"), ("23219", "Virginia"), ("27601", "North Carolina"), ("29201", "South Carolina"), ("32301", "Florida"), ("01501", "Massachusetts"), ("06101", "Connecticut"), ("02901", "Rhode Island"), ("03301", "New Hampshire"), ("05601", "Vermont"), ("04330", "Maine"), ("19901", "Delaware"), ("21201", "Maryland"), ("17101", "Pennsylvania"), ("07001", "New Jersey"), ("12201", "New York"), ("43215", "Ohio"), ("46201", "Indiana"), ("48601", "Michigan"), ("53201", "Wisconsin"), ("99501", "Alaska"), ("96801", "Hawaii") ] all_data = [] for zipcode, state in locations: try: # Current observations endpoint url = f"{self.base_url}/aq/observation/zipCode/current/" params = { "format": "application/json", "zipCode": zipcode, "distance": 50, # 50 mile radius "API_KEY": api_key } response = requests.get(url, params=params, timeout=10) if response.status_code == 200: data = response.json() if data: # If data is not empty for observation in data: observation['source_state'] = state observation['source_zipcode'] = zipcode all_data.extend(data) # Add delay to respect rate limits time.sleep(0.5) except requests.exceptions.RequestException as e: continue # Skip this location and continue with others if not all_data: return [], "⚠️ No air quality data found. Please check your API key or try again later." # Remove duplicates based on reporting area seen_areas = set() unique_data = [] for item in all_data: area_key = (item.get('ReportingArea', ''), item.get('StateCode', '')) if area_key not in seen_areas: seen_areas.add(area_key) unique_data.append(item) return unique_data, f"✅ Successfully loaded {len(unique_data)} monitoring locations" except Exception as e: return [], f"❌ Error fetching data: {str(e)}" def create_map(self, data: List[Dict]) -> str: """Create an interactive map with air quality data""" if not data: # Create a basic US map if no data m = folium.Map(location=[39.8283, -98.5795], zoom_start=4) folium.Marker( [39.8283, -98.5795], popup="No data available. Please check your API key.", icon=folium.Icon(color='red', icon='info-sign') ).add_to(m) return m._repr_html_() # Calculate center point of all data lats = [item['Latitude'] for item in data if 'Latitude' in item] lons = [item['Longitude'] for item in data if 'Longitude' in item] if lats and lons: center_lat = sum(lats) / len(lats) center_lon = sum(lons) / len(lons) else: center_lat, center_lon = 39.8283, -98.5795 # Center of US # Create map m = folium.Map(location=[center_lat, center_lon], zoom_start=4) # Add markers for each monitoring location for item in data: try: lat = item.get('Latitude') lon = item.get('Longitude') aqi = item.get('AQI', 0) parameter = item.get('ParameterName', 'Unknown') area = item.get('ReportingArea', 'Unknown Area') state = item.get('StateCode', 'Unknown') category = item.get('Category', {}).get('Name', self.get_aqi_category(aqi)) if lat is None or lon is None: continue # Get color based on AQI category color = self.get_aqi_color(category) # Create popup content popup_content = f"""
AQI: {aqi} ({category})
Parameter: {parameter}
Location: {lat:.3f}, {lon:.3f}
Last Updated: {item.get('DateObserved', 'Unknown')} {item.get('HourObserved', '')}:00
Good (0-50)
Moderate (51-100)
Unhealthy for Sensitive (101-150)
Unhealthy (151-200)
Very Unhealthy (201-300)
Hazardous (301+)