nakas commited on
Commit
93f3aed
Β·
verified Β·
1 Parent(s): c12d9d7

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +338 -0
app.py ADDED
@@ -0,0 +1,338 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import requests
3
+ import pandas as pd
4
+ import folium
5
+ from folium import plugins
6
+ import json
7
+ from datetime import datetime, timedelta
8
+ import plotly.express as px
9
+ import plotly.graph_objects as go
10
+ from plotly.subplots import make_subplots
11
+
12
+ class AirQualityMonitor:
13
+ def __init__(self):
14
+ self.base_url = "https://www.airnowapi.org/aq"
15
+ # You'll need to get an API key from https://docs.airnowapi.org/
16
+ self.api_key = "YOUR_API_KEY_HERE" # Replace with actual API key
17
+
18
+ def get_current_observations(self, state_code=None, bbox=None):
19
+ """Get current air quality observations"""
20
+ if not self.api_key or self.api_key == "YOUR_API_KEY_HERE":
21
+ # Return sample data for demo purposes
22
+ return self._get_sample_data()
23
+
24
+ url = f"{self.base_url}/observation/zipCode/current/"
25
+ params = {
26
+ 'format': 'application/json',
27
+ 'API_KEY': self.api_key,
28
+ 'distance': 100
29
+ }
30
+
31
+ if state_code:
32
+ # Get observations for a specific state
33
+ params['zipCode'] = self._get_state_zip(state_code)
34
+
35
+ try:
36
+ response = requests.get(url, params=params)
37
+ response.raise_for_status()
38
+ return response.json()
39
+ except Exception as e:
40
+ print(f"API Error: {e}")
41
+ return self._get_sample_data()
42
+
43
+ def _get_state_zip(self, state_code):
44
+ """Get a representative zip code for a state"""
45
+ state_zips = {
46
+ 'CA': '90210', 'NY': '10001', 'TX': '73301', 'FL': '33101',
47
+ 'IL': '60601', 'PA': '19101', 'OH': '43215', 'GA': '30301',
48
+ 'NC': '27601', 'MI': '48201', 'NJ': '07001', 'VA': '23219',
49
+ 'WA': '98101', 'AZ': '85001', 'MA': '02101', 'TN': '37201',
50
+ 'IN': '46201', 'MO': '63101', 'MD': '21201', 'WI': '53201'
51
+ }
52
+ return state_zips.get(state_code, '90210')
53
+
54
+ def _get_sample_data(self):
55
+ """Return sample data for demonstration"""
56
+ return [
57
+ {
58
+ "DateObserved": "2025-06-29",
59
+ "HourObserved": 14,
60
+ "LocalTimeZone": "PST",
61
+ "ReportingArea": "Los Angeles-South Coast Air Basin",
62
+ "StateCode": "CA",
63
+ "Latitude": 34.0522,
64
+ "Longitude": -118.2437,
65
+ "ParameterName": "PM2.5",
66
+ "AQI": 65,
67
+ "Category": {"Number": 2, "Name": "Moderate"}
68
+ },
69
+ {
70
+ "DateObserved": "2025-06-29",
71
+ "HourObserved": 14,
72
+ "LocalTimeZone": "EST",
73
+ "ReportingArea": "New York",
74
+ "StateCode": "NY",
75
+ "Latitude": 40.7128,
76
+ "Longitude": -74.0060,
77
+ "ParameterName": "Ozone",
78
+ "AQI": 45,
79
+ "Category": {"Number": 1, "Name": "Good"}
80
+ },
81
+ {
82
+ "DateObserved": "2025-06-29",
83
+ "HourObserved": 14,
84
+ "LocalTimeZone": "CST",
85
+ "ReportingArea": "Chicago",
86
+ "StateCode": "IL",
87
+ "Latitude": 41.8781,
88
+ "Longitude": -87.6298,
89
+ "ParameterName": "PM2.5",
90
+ "AQI": 85,
91
+ "Category": {"Number": 3, "Name": "Unhealthy for Sensitive Groups"}
92
+ },
93
+ {
94
+ "DateObserved": "2025-06-29",
95
+ "HourObserved": 14,
96
+ "LocalTimeZone": "MST",
97
+ "ReportingArea": "Phoenix",
98
+ "StateCode": "AZ",
99
+ "Latitude": 33.4484,
100
+ "Longitude": -112.0740,
101
+ "ParameterName": "Ozone",
102
+ "AQI": 95,
103
+ "Category": {"Number": 3, "Name": "Unhealthy for Sensitive Groups"}
104
+ },
105
+ {
106
+ "DateObserved": "2025-06-29",
107
+ "HourObserved": 14,
108
+ "LocalTimeZone": "PST",
109
+ "ReportingArea": "Seattle",
110
+ "StateCode": "WA",
111
+ "Latitude": 47.6062,
112
+ "Longitude": -122.3321,
113
+ "ParameterName": "PM2.5",
114
+ "AQI": 25,
115
+ "Category": {"Number": 1, "Name": "Good"}
116
+ },
117
+ {
118
+ "DateObserved": "2025-06-29",
119
+ "HourObserved": 14,
120
+ "LocalTimeZone": "EST",
121
+ "ReportingArea": "Miami",
122
+ "StateCode": "FL",
123
+ "Latitude": 25.7617,
124
+ "Longitude": -80.1918,
125
+ "ParameterName": "Ozone",
126
+ "AQI": 55,
127
+ "Category": {"Number": 2, "Name": "Moderate"}
128
+ }
129
+ ]
130
+
131
+ def get_aqi_color(aqi):
132
+ """Return color based on AQI value"""
133
+ if aqi <= 50:
134
+ return "#00E400" # Green
135
+ elif aqi <= 100:
136
+ return "#FFFF00" # Yellow
137
+ elif aqi <= 150:
138
+ return "#FF7E00" # Orange
139
+ elif aqi <= 200:
140
+ return "#FF0000" # Red
141
+ elif aqi <= 300:
142
+ return "#8F3F97" # Purple
143
+ else:
144
+ return "#7E0023" # Maroon
145
+
146
+ def create_air_quality_map(data):
147
+ """Create an interactive map with air quality data"""
148
+ if not data:
149
+ # Create empty map centered on US
150
+ m = folium.Map(location=[39.8283, -98.5795], zoom_start=4)
151
+ folium.Marker(
152
+ [39.8283, -98.5795],
153
+ popup="No data available. Please add your AirNow API key.",
154
+ icon=folium.Icon(color='red')
155
+ ).add_to(m)
156
+ return m._repr_html_()
157
+
158
+ # Calculate map center
159
+ lats = [float(station['Latitude']) for station in data]
160
+ lons = [float(station['Longitude']) for station in data]
161
+ center_lat = sum(lats) / len(lats)
162
+ center_lon = sum(lons) / len(lons)
163
+
164
+ # Create map
165
+ m = folium.Map(location=[center_lat, center_lon], zoom_start=4)
166
+
167
+ # Add markers for each monitoring station
168
+ for station in data:
169
+ lat = float(station['Latitude'])
170
+ lon = float(station['Longitude'])
171
+ aqi = station.get('AQI', 0)
172
+ parameter = station.get('ParameterName', 'Unknown')
173
+ area = station.get('ReportingArea', 'Unknown Area')
174
+ category = station.get('Category', {}).get('Name', 'Unknown')
175
+
176
+ color = get_aqi_color(aqi)
177
+
178
+ popup_html = f"""
179
+ <div style="width: 200px;">
180
+ <h4>{area}</h4>
181
+ <p><strong>AQI:</strong> {aqi}</p>
182
+ <p><strong>Parameter:</strong> {parameter}</p>
183
+ <p><strong>Category:</strong> {category}</p>
184
+ <p><strong>Date:</strong> {station.get('DateObserved', 'N/A')}</p>
185
+ <p><strong>Time:</strong> {station.get('HourObserved', 'N/A')}:00 {station.get('LocalTimeZone', '')}</p>
186
+ </div>
187
+ """
188
+
189
+ folium.CircleMarker(
190
+ location=[lat, lon],
191
+ radius=8,
192
+ popup=folium.Popup(popup_html, max_width=250),
193
+ color='black',
194
+ weight=1,
195
+ fill=True,
196
+ fillColor=color,
197
+ fillOpacity=0.8,
198
+ tooltip=f"{area}: AQI {aqi}"
199
+ ).add_to(m)
200
+
201
+ # Add legend
202
+ legend_html = '''
203
+ <div style="position: fixed;
204
+ top: 10px; right: 10px; width: 160px; height: 140px;
205
+ background-color: white; border:2px solid grey; z-index:9999;
206
+ font-size:14px; padding: 10px">
207
+ <h4>AQI Legend</h4>
208
+ <p><i class="fa fa-circle" style="color:#00E400"></i> Good (0-50)</p>
209
+ <p><i class="fa fa-circle" style="color:#FFFF00"></i> Moderate (51-100)</p>
210
+ <p><i class="fa fa-circle" style="color:#FF7E00"></i> Unhealthy for Sensitive (101-150)</p>
211
+ <p><i class="fa fa-circle" style="color:#FF0000"></i> Unhealthy (151-200)</p>
212
+ <p><i class="fa fa-circle" style="color:#8F3F97"></i> Very Unhealthy (201-300)</p>
213
+ </div>
214
+ '''
215
+ m.get_root().html.add_child(folium.Element(legend_html))
216
+
217
+ return m._repr_html_()
218
+
219
+ def create_aqi_chart(data):
220
+ """Create a chart showing AQI levels by location"""
221
+ if not data:
222
+ return None
223
+
224
+ df = pd.DataFrame(data)
225
+ df['AQI'] = pd.to_numeric(df['AQI'], errors='coerce')
226
+
227
+ # Create bar chart
228
+ fig = px.bar(df,
229
+ x='ReportingArea',
230
+ y='AQI',
231
+ color='AQI',
232
+ color_continuous_scale='RdYlGn_r',
233
+ title='Air Quality Index by Location',
234
+ labels={'AQI': 'Air Quality Index', 'ReportingArea': 'Location'})
235
+
236
+ fig.update_layout(xaxis_tickangle=-45, height=500)
237
+ fig.update_traces(hovertemplate='<b>%{x}</b><br>AQI: %{y}<extra></extra>')
238
+
239
+ return fig
240
+
241
+ def get_air_quality_summary(data):
242
+ """Generate a summary of air quality data"""
243
+ if not data:
244
+ return "No air quality data available."
245
+
246
+ df = pd.DataFrame(data)
247
+ df['AQI'] = pd.to_numeric(df['AQI'], errors='coerce')
248
+
249
+ total_stations = len(df)
250
+ avg_aqi = df['AQI'].mean()
251
+ max_aqi = df['AQI'].max()
252
+ min_aqi = df['AQI'].min()
253
+
254
+ # Count by category
255
+ categories = df['Category'].apply(lambda x: x.get('Name', 'Unknown') if isinstance(x, dict) else 'Unknown')
256
+ category_counts = categories.value_counts()
257
+
258
+ summary = f"""
259
+ ## Air Quality Summary
260
+
261
+ **Total Monitoring Stations:** {total_stations}
262
+ **Average AQI:** {avg_aqi:.1f}
263
+ **Highest AQI:** {max_aqi}
264
+ **Lowest AQI:** {min_aqi}
265
+
266
+ ### Air Quality Categories:
267
+ """
268
+
269
+ for category, count in category_counts.items():
270
+ percentage = (count / total_stations) * 100
271
+ summary += f"- **{category}:** {count} stations ({percentage:.1f}%)\n"
272
+
273
+ return summary
274
+
275
+ def update_data():
276
+ """Main function to fetch and display air quality data"""
277
+ monitor = AirQualityMonitor()
278
+ data = monitor.get_current_observations()
279
+
280
+ map_html = create_air_quality_map(data)
281
+ chart = create_aqi_chart(data)
282
+ summary = get_air_quality_summary(data)
283
+
284
+ return map_html, chart, summary
285
+
286
+ # Create Gradio interface
287
+ with gr.Blocks(title="Air Quality Monitor", theme=gr.themes.Soft()) as demo:
288
+ gr.Markdown("# 🌬️ Real-Time Air Quality Monitor")
289
+ gr.Markdown("Monitor air quality data from NOAA/EPA AirNow stations across the United States")
290
+
291
+ with gr.Row():
292
+ gr.Markdown("""
293
+ This application displays real-time air quality data from over 2,000 monitoring stations
294
+ across the United States. The data includes Air Quality Index (AQI) values for various
295
+ pollutants like PM2.5, PM10, Ozone, and more.
296
+
297
+ **Note:** To access live data, you need to:
298
+ 1. Get a free API key from [AirNow API](https://docs.airnowapi.org/)
299
+ 2. Replace `YOUR_API_KEY_HERE` in the code with your actual API key
300
+
301
+ Currently showing sample data for demonstration.
302
+ """)
303
+
304
+ with gr.Row():
305
+ update_btn = gr.Button("πŸ”„ Update Air Quality Data", variant="primary", size="lg")
306
+
307
+ with gr.Row():
308
+ with gr.Column(scale=2):
309
+ map_output = gr.HTML(label="Air Quality Map")
310
+ with gr.Column(scale=1):
311
+ summary_output = gr.Markdown(label="Summary")
312
+
313
+ with gr.Row():
314
+ chart_output = gr.Plot(label="AQI Chart")
315
+
316
+ # Add information section
317
+ with gr.Row():
318
+ gr.Markdown("""
319
+ ## About Air Quality Index (AQI)
320
+
321
+ The AQI is an index for reporting daily air quality. It tells you how clean or polluted your air is:
322
+
323
+ - **Good (0-50)**: 🟒 Air quality is satisfactory
324
+ - **Moderate (51-100)**: 🟑 Acceptable for most people
325
+ - **Unhealthy for Sensitive Groups (101-150)**: 🟠 May cause problems for sensitive individuals
326
+ - **Unhealthy (151-200)**: πŸ”΄ May cause health problems for everyone
327
+ - **Very Unhealthy (201-300)**: 🟣 Health alert for everyone
328
+ - **Hazardous (301+)**: 🟀 Health emergency
329
+
330
+ **Data Source:** EPA AirNow - Real-time air quality observations from government monitoring stations
331
+ """)
332
+
333
+ # Load initial data
334
+ demo.load(update_data, outputs=[map_output, chart_output, summary_output])
335
+ update_btn.click(update_data, outputs=[map_output, chart_output, summary_output])
336
+
337
+ if __name__ == "__main__":
338
+ demo.launch()