Spaces:
Sleeping
Sleeping
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(""" | |
<div style="text-align: center; padding: 20px;"> | |
<h1>πΈ Pollen Forecast Map</h1> | |
<p>Click on the map to select a location and get detailed pollen forecasts powered by Google Pollen API</p> | |
</div> | |
""") | |
with gr.Row(): | |
with gr.Column(scale=1): | |
gr.HTML("<h3>π Location Selection</h3>") | |
# 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("<h4>π Quick Locations</h4>") | |
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() |