import gradio as gr import requests import folium import os import json from datetime import datetime, timedelta import pandas as pd # Get API key from environment GOOGLE_API_KEY = os.getenv("GOOGLE_API_KEY") def create_map(lat=40.7128, lon=-74.0060): """Create a Folium map centered at given coordinates""" m = folium.Map( location=[lat, lon], zoom_start=10, tiles="OpenStreetMap" ) # Add a marker for the selected location folium.Marker( [lat, lon], popup=f"Selected Location: {lat:.4f}, {lon:.4f}", tooltip="Click to select this location", icon=folium.Icon(color='red', icon='info-sign') ).add_to(m) return m._repr_html_() def get_pollen_data(lat, lon, days=5): """Fetch pollen data from Google Pollen API""" if not GOOGLE_API_KEY: return "Error: Google API key not found. Please set GOOGLE_API_KEY as a secret." # Google Pollen API endpoint url = "https://pollen.googleapis.com/v1/forecast:lookup" # Calculate date range start_date = datetime.now() end_date = start_date + timedelta(days=days-1) params = { "key": GOOGLE_API_KEY, "location.longitude": lon, "location.latitude": lat, "days": days, "plantsDescription": True } try: response = requests.get(url, params=params) response.raise_for_status() data = response.json() # Debug: Print the structure to understand the API response print(f"API Response keys: {data.keys()}") if "dailyInfo" in data and len(data["dailyInfo"]) > 0: print(f"First day info keys: {data['dailyInfo'][0].keys()}") print(f"Date structure: {data['dailyInfo'][0].get('date', 'No date field')}") return data except requests.exceptions.RequestException as e: error_msg = f"Error fetching pollen data: {str(e)}" if hasattr(e, 'response') and e.response is not None: error_msg += f" (Status: {e.response.status_code})" return error_msg except json.JSONDecodeError as e: return f"Error parsing response: {str(e)}" except Exception as e: return f"Unexpected error: {str(e)}" def format_pollen_data(data): """Format pollen data for display""" if isinstance(data, str): # Error message return data, None if "dailyInfo" not in data: return "No pollen data available for this location.", None # Create formatted output output = [] output.append("# 🌸 Pollen Forecast Report\n") # Location info if available if "regionInfo" in data: region = data["regionInfo"] output.append(f"**Location:** {region.get('displayName', 'Unknown')}\n") # Daily pollen data daily_data = [] for day_info in data["dailyInfo"]: # Handle different date formats from the API date_info = day_info.get("date", {}) if isinstance(date_info, dict): # If date is a dict, try to extract the date string if "year" in date_info and "month" in date_info and "day" in date_info: year = date_info["year"] month = date_info["month"] day = date_info["day"] date_obj = datetime(year, month, day) else: # Fallback to today's date if we can't parse date_obj = datetime.now() elif isinstance(date_info, str): # If it's already a string, parse it try: date_obj = datetime.strptime(date_info, "%Y-%m-%d") except ValueError: date_obj = datetime.now() else: # Fallback date_obj = datetime.now() formatted_date = date_obj.strftime("%B %d, %Y") output.append(f"## 📅 {formatted_date}\n") if "pollenTypeInfo" in day_info: pollen_types = day_info["pollenTypeInfo"] # Create a row for the dataframe row_data = {"Date": formatted_date} for pollen in pollen_types: pollen_type = pollen["code"].replace("_", " ").title() index_info = pollen.get("indexInfo", {}) index_value = index_info.get("value", "N/A") category = index_info.get("category", "Unknown") # Add color coding based on category color_map = { "VERY_LOW": "🟢", "LOW": "🟡", "MEDIUM": "🟠", "HIGH": "🔴", "VERY_HIGH": "🟣" } color = color_map.get(category, "⚪") output.append(f"**{pollen_type}:** {color} {category} (Index: {index_value})") row_data[pollen_type] = f"{category} ({index_value})" # Add plant descriptions if available if "plantDescription" in pollen: plants = pollen["plantDescription"] if "plants" in plants: plant_list = [plant["displayName"] for plant in plants["plants"][:3]] # Show top 3 output.append(f" - *Main sources: {', '.join(plant_list)}*") output.append("") daily_data.append(row_data) output.append("---\n") # Create DataFrame for tabular view df = None if daily_data: df = pd.DataFrame(daily_data) # Add legend output.append("\n## 📊 Pollen Index Legend") output.append("🟢 Very Low | 🟡 Low | 🟠 Medium | 🔴 High | 🟣 Very High\n") # Add tips output.append("## 💡 Tips") output.append("- Check pollen levels before outdoor activities") output.append("- Take allergy medications during high pollen days") output.append("- Keep windows closed during peak pollen times") output.append("- Shower and change clothes after being outdoors") return "\n".join(output), df def update_location(lat, lon): """Update the map and fetch pollen data for new location""" if lat is None or lon is None: return create_map(), "Please select a location on the map.", None # Create new map new_map = create_map(lat, lon) # Get pollen data pollen_data = get_pollen_data(lat, lon) formatted_data, df = format_pollen_data(pollen_data) return new_map, formatted_data, df # Create the Gradio interface with gr.Blocks(title="🌸 Pollen Forecast Map", theme=gr.themes.Soft()) as app: gr.HTML("""

🌸 Pollen Forecast Map

Click on the map to select a location and get detailed pollen forecasts powered by Google Pollen API

""") with gr.Row(): with gr.Column(scale=1): gr.HTML("

📍 Location Selection

") # Coordinate inputs lat_input = gr.Number( label="Latitude", value=40.7128, precision=6, info="Enter latitude or click on map" ) lon_input = gr.Number( label="Longitude", value=-74.0060, precision=6, info="Enter longitude or click on map" ) update_btn = gr.Button("🔄 Update Location", variant="primary") # Preset locations gr.HTML("

📍 Quick Locations

") with gr.Row(): nyc_btn = gr.Button("🏙️ NYC", size="sm") la_btn = gr.Button("🌴 LA", size="sm") chicago_btn = gr.Button("🌬️ Chicago", size="sm") miami_btn = gr.Button("🏖️ Miami", size="sm") with gr.Column(scale=2): # Map display map_html = gr.HTML( value=create_map(), label="Interactive Map" ) with gr.Row(): with gr.Column(): # Pollen data output pollen_output = gr.Markdown( value="Select a location to view pollen forecast.", label="Pollen Forecast" ) # Data table pollen_table = gr.Dataframe( label="Pollen Data Summary", visible=False ) # Event handlers def set_nyc(): return 40.7128, -74.0060 def set_la(): return 34.0522, -118.2437 def set_chicago(): return 41.8781, -87.6298 def set_miami(): return 25.7617, -80.1918 # Button events update_btn.click( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) nyc_btn.click( fn=set_nyc, outputs=[lat_input, lon_input] ).then( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) la_btn.click( fn=set_la, outputs=[lat_input, lon_input] ).then( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) chicago_btn.click( fn=set_chicago, outputs=[lat_input, lon_input] ).then( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) miami_btn.click( fn=set_miami, outputs=[lat_input, lon_input] ).then( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) # Auto-update when coordinates change lat_input.change( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) lon_input.change( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) # Load initial data app.load( fn=update_location, inputs=[lat_input, lon_input], outputs=[map_html, pollen_output, pollen_table] ) if __name__ == "__main__": app.launch()