nakas's picture
Create app.py
93f3aed verified
raw
history blame
12.4 kB
import gradio as gr
import requests
import pandas as pd
import folium
from folium import plugins
import json
from datetime import datetime, timedelta
import plotly.express as px
import plotly.graph_objects as go
from plotly.subplots import make_subplots
class AirQualityMonitor:
def __init__(self):
self.base_url = "https://www.airnowapi.org/aq"
# You'll need to get an API key from https://docs.airnowapi.org/
self.api_key = "YOUR_API_KEY_HERE" # Replace with actual API key
def get_current_observations(self, state_code=None, bbox=None):
"""Get current air quality observations"""
if not self.api_key or self.api_key == "YOUR_API_KEY_HERE":
# Return sample data for demo purposes
return self._get_sample_data()
url = f"{self.base_url}/observation/zipCode/current/"
params = {
'format': 'application/json',
'API_KEY': self.api_key,
'distance': 100
}
if state_code:
# Get observations for a specific state
params['zipCode'] = self._get_state_zip(state_code)
try:
response = requests.get(url, params=params)
response.raise_for_status()
return response.json()
except Exception as e:
print(f"API Error: {e}")
return self._get_sample_data()
def _get_state_zip(self, state_code):
"""Get a representative zip code for a state"""
state_zips = {
'CA': '90210', 'NY': '10001', 'TX': '73301', 'FL': '33101',
'IL': '60601', 'PA': '19101', 'OH': '43215', 'GA': '30301',
'NC': '27601', 'MI': '48201', 'NJ': '07001', 'VA': '23219',
'WA': '98101', 'AZ': '85001', 'MA': '02101', 'TN': '37201',
'IN': '46201', 'MO': '63101', 'MD': '21201', 'WI': '53201'
}
return state_zips.get(state_code, '90210')
def _get_sample_data(self):
"""Return sample data for demonstration"""
return [
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "PST",
"ReportingArea": "Los Angeles-South Coast Air Basin",
"StateCode": "CA",
"Latitude": 34.0522,
"Longitude": -118.2437,
"ParameterName": "PM2.5",
"AQI": 65,
"Category": {"Number": 2, "Name": "Moderate"}
},
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "EST",
"ReportingArea": "New York",
"StateCode": "NY",
"Latitude": 40.7128,
"Longitude": -74.0060,
"ParameterName": "Ozone",
"AQI": 45,
"Category": {"Number": 1, "Name": "Good"}
},
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "CST",
"ReportingArea": "Chicago",
"StateCode": "IL",
"Latitude": 41.8781,
"Longitude": -87.6298,
"ParameterName": "PM2.5",
"AQI": 85,
"Category": {"Number": 3, "Name": "Unhealthy for Sensitive Groups"}
},
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "MST",
"ReportingArea": "Phoenix",
"StateCode": "AZ",
"Latitude": 33.4484,
"Longitude": -112.0740,
"ParameterName": "Ozone",
"AQI": 95,
"Category": {"Number": 3, "Name": "Unhealthy for Sensitive Groups"}
},
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "PST",
"ReportingArea": "Seattle",
"StateCode": "WA",
"Latitude": 47.6062,
"Longitude": -122.3321,
"ParameterName": "PM2.5",
"AQI": 25,
"Category": {"Number": 1, "Name": "Good"}
},
{
"DateObserved": "2025-06-29",
"HourObserved": 14,
"LocalTimeZone": "EST",
"ReportingArea": "Miami",
"StateCode": "FL",
"Latitude": 25.7617,
"Longitude": -80.1918,
"ParameterName": "Ozone",
"AQI": 55,
"Category": {"Number": 2, "Name": "Moderate"}
}
]
def get_aqi_color(aqi):
"""Return color based on AQI value"""
if aqi <= 50:
return "#00E400" # Green
elif aqi <= 100:
return "#FFFF00" # Yellow
elif aqi <= 150:
return "#FF7E00" # Orange
elif aqi <= 200:
return "#FF0000" # Red
elif aqi <= 300:
return "#8F3F97" # Purple
else:
return "#7E0023" # Maroon
def create_air_quality_map(data):
"""Create an interactive map with air quality data"""
if not data:
# Create empty map centered on US
m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
folium.Marker(
[39.8283, -98.5795],
popup="No data available. Please add your AirNow API key.",
icon=folium.Icon(color='red')
).add_to(m)
return m._repr_html_()
# Calculate map center
lats = [float(station['Latitude']) for station in data]
lons = [float(station['Longitude']) for station in data]
center_lat = sum(lats) / len(lats)
center_lon = sum(lons) / len(lons)
# Create map
m = folium.Map(location=[center_lat, center_lon], zoom_start=4)
# Add markers for each monitoring station
for station in data:
lat = float(station['Latitude'])
lon = float(station['Longitude'])
aqi = station.get('AQI', 0)
parameter = station.get('ParameterName', 'Unknown')
area = station.get('ReportingArea', 'Unknown Area')
category = station.get('Category', {}).get('Name', 'Unknown')
color = get_aqi_color(aqi)
popup_html = f"""
<div style="width: 200px;">
<h4>{area}</h4>
<p><strong>AQI:</strong> {aqi}</p>
<p><strong>Parameter:</strong> {parameter}</p>
<p><strong>Category:</strong> {category}</p>
<p><strong>Date:</strong> {station.get('DateObserved', 'N/A')}</p>
<p><strong>Time:</strong> {station.get('HourObserved', 'N/A')}:00 {station.get('LocalTimeZone', '')}</p>
</div>
"""
folium.CircleMarker(
location=[lat, lon],
radius=8,
popup=folium.Popup(popup_html, max_width=250),
color='black',
weight=1,
fill=True,
fillColor=color,
fillOpacity=0.8,
tooltip=f"{area}: AQI {aqi}"
).add_to(m)
# Add legend
legend_html = '''
<div style="position: fixed;
top: 10px; right: 10px; width: 160px; height: 140px;
background-color: white; border:2px solid grey; z-index:9999;
font-size:14px; padding: 10px">
<h4>AQI Legend</h4>
<p><i class="fa fa-circle" style="color:#00E400"></i> Good (0-50)</p>
<p><i class="fa fa-circle" style="color:#FFFF00"></i> Moderate (51-100)</p>
<p><i class="fa fa-circle" style="color:#FF7E00"></i> Unhealthy for Sensitive (101-150)</p>
<p><i class="fa fa-circle" style="color:#FF0000"></i> Unhealthy (151-200)</p>
<p><i class="fa fa-circle" style="color:#8F3F97"></i> Very Unhealthy (201-300)</p>
</div>
'''
m.get_root().html.add_child(folium.Element(legend_html))
return m._repr_html_()
def create_aqi_chart(data):
"""Create a chart showing AQI levels by location"""
if not data:
return None
df = pd.DataFrame(data)
df['AQI'] = pd.to_numeric(df['AQI'], errors='coerce')
# Create bar chart
fig = px.bar(df,
x='ReportingArea',
y='AQI',
color='AQI',
color_continuous_scale='RdYlGn_r',
title='Air Quality Index by Location',
labels={'AQI': 'Air Quality Index', 'ReportingArea': 'Location'})
fig.update_layout(xaxis_tickangle=-45, height=500)
fig.update_traces(hovertemplate='<b>%{x}</b><br>AQI: %{y}<extra></extra>')
return fig
def get_air_quality_summary(data):
"""Generate a summary of air quality data"""
if not data:
return "No air quality data available."
df = pd.DataFrame(data)
df['AQI'] = pd.to_numeric(df['AQI'], errors='coerce')
total_stations = len(df)
avg_aqi = df['AQI'].mean()
max_aqi = df['AQI'].max()
min_aqi = df['AQI'].min()
# Count by category
categories = df['Category'].apply(lambda x: x.get('Name', 'Unknown') if isinstance(x, dict) else 'Unknown')
category_counts = categories.value_counts()
summary = f"""
## Air Quality Summary
**Total Monitoring Stations:** {total_stations}
**Average AQI:** {avg_aqi:.1f}
**Highest AQI:** {max_aqi}
**Lowest AQI:** {min_aqi}
### Air Quality Categories:
"""
for category, count in category_counts.items():
percentage = (count / total_stations) * 100
summary += f"- **{category}:** {count} stations ({percentage:.1f}%)\n"
return summary
def update_data():
"""Main function to fetch and display air quality data"""
monitor = AirQualityMonitor()
data = monitor.get_current_observations()
map_html = create_air_quality_map(data)
chart = create_aqi_chart(data)
summary = get_air_quality_summary(data)
return map_html, chart, summary
# Create Gradio interface
with gr.Blocks(title="Air Quality Monitor", theme=gr.themes.Soft()) as demo:
gr.Markdown("# 🌬️ Real-Time Air Quality Monitor")
gr.Markdown("Monitor air quality data from NOAA/EPA AirNow stations across the United States")
with gr.Row():
gr.Markdown("""
This application displays real-time air quality data from over 2,000 monitoring stations
across the United States. The data includes Air Quality Index (AQI) values for various
pollutants like PM2.5, PM10, Ozone, and more.
**Note:** To access live data, you need to:
1. Get a free API key from [AirNow API](https://docs.airnowapi.org/)
2. Replace `YOUR_API_KEY_HERE` in the code with your actual API key
Currently showing sample data for demonstration.
""")
with gr.Row():
update_btn = gr.Button("πŸ”„ Update Air Quality Data", variant="primary", size="lg")
with gr.Row():
with gr.Column(scale=2):
map_output = gr.HTML(label="Air Quality Map")
with gr.Column(scale=1):
summary_output = gr.Markdown(label="Summary")
with gr.Row():
chart_output = gr.Plot(label="AQI Chart")
# Add information section
with gr.Row():
gr.Markdown("""
## About Air Quality Index (AQI)
The AQI is an index for reporting daily air quality. It tells you how clean or polluted your air is:
- **Good (0-50)**: 🟒 Air quality is satisfactory
- **Moderate (51-100)**: 🟑 Acceptable for most people
- **Unhealthy for Sensitive Groups (101-150)**: 🟠 May cause problems for sensitive individuals
- **Unhealthy (151-200)**: πŸ”΄ May cause health problems for everyone
- **Very Unhealthy (201-300)**: 🟣 Health alert for everyone
- **Hazardous (301+)**: 🟀 Health emergency
**Data Source:** EPA AirNow - Real-time air quality observations from government monitoring stations
""")
# Load initial data
demo.load(update_data, outputs=[map_output, chart_output, summary_output])
update_btn.click(update_data, outputs=[map_output, chart_output, summary_output])
if __name__ == "__main__":
demo.launch()