Spaces:
Sleeping
Sleeping
Update air_quality_map.py
Browse files- air_quality_map.py +213 -49
air_quality_map.py
CHANGED
@@ -14,6 +14,7 @@ API_KEY = os.environ.get("EPA_AQS_API_KEY", "") # Get from environment variable
|
|
14 |
|
15 |
class AirQualityApp:
|
16 |
def __init__(self):
|
|
|
17 |
self.states = {
|
18 |
"AL": "Alabama", "AK": "Alaska", "AZ": "Arizona", "AR": "Arkansas",
|
19 |
"CA": "California", "CO": "Colorado", "CT": "Connecticut", "DE": "Delaware",
|
@@ -98,9 +99,61 @@ class AirQualityApp:
|
|
98 |
{"code": "42602", "value_represented": "Nitrogen dioxide"},
|
99 |
{"code": "81102", "value_represented": "PM10 - Local Conditions"}
|
100 |
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
101 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
102 |
def get_monitors(self, state_code, county_code=None, parameter_code=None):
|
103 |
-
|
104 |
# If we don't have API credentials, use mock data
|
105 |
if not EMAIL or not API_KEY:
|
106 |
return self.mock_get_monitors(state_code, county_code, parameter_code)
|
@@ -154,8 +207,9 @@ class AirQualityApp:
|
|
154 |
except Exception as e:
|
155 |
print(f"Error fetching monitors: {e}")
|
156 |
return self.mock_get_monitors(state_code, county_code, parameter_code)
|
157 |
-
|
158 |
def get_counties(self, state_code):
|
|
|
159 |
"""Fetch counties for a given state"""
|
160 |
# If we don't have API credentials, use mock data
|
161 |
if not EMAIL or not API_KEY:
|
@@ -195,8 +249,9 @@ class AirQualityApp:
|
|
195 |
except Exception as e:
|
196 |
print(f"Error fetching counties: {e}")
|
197 |
return []
|
198 |
-
|
199 |
def get_parameters(self):
|
|
|
200 |
"""Fetch available parameter codes (pollutants)"""
|
201 |
# If we don't have API credentials, use mock data
|
202 |
if not EMAIL or not API_KEY:
|
@@ -241,7 +296,20 @@ class AirQualityApp:
|
|
241 |
"""Fetch the latest AQI data for monitors"""
|
242 |
# If we don't have API credentials, use mock data
|
243 |
if not EMAIL or not API_KEY:
|
244 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
245 |
|
246 |
# Convert state code to numeric format for API
|
247 |
api_state_code = state_code
|
@@ -293,7 +361,7 @@ class AirQualityApp:
|
|
293 |
monitors = self.get_monitors(state_code, parameter_code=parameter_code)
|
294 |
|
295 |
if not monitors:
|
296 |
-
return "No monitoring stations found for the selected criteria."
|
297 |
|
298 |
# Convert to DataFrame for easier manipulation
|
299 |
df = pd.DataFrame(monitors)
|
@@ -306,29 +374,32 @@ class AirQualityApp:
|
|
306 |
print(f"After filtering, {len(df)} monitors remain")
|
307 |
|
308 |
if len(df) == 0:
|
309 |
-
return "No monitoring stations found for the selected county."
|
310 |
|
311 |
# Create a map centered on the mean latitude and longitude
|
312 |
center_lat = df["latitude"].mean()
|
313 |
center_lon = df["longitude"].mean()
|
314 |
|
315 |
# Create a map with a specific width and height - make it bigger
|
316 |
-
m = folium.Map(location=[center_lat, center_lon], zoom_start=7, width='100%', height=
|
317 |
|
318 |
# Add a marker cluster
|
319 |
marker_cluster = MarkerCluster().add_to(m)
|
320 |
|
321 |
# Get latest AQI data if credentials are provided
|
322 |
aqi_data = {}
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
|
|
|
|
|
|
|
332 |
|
333 |
# Add markers for each monitoring station
|
334 |
for _, row in df.iterrows():
|
@@ -415,15 +486,18 @@ class AirQualityApp:
|
|
415 |
icon=folium.Icon(color=color, icon="cloud"),
|
416 |
).add_to(marker_cluster)
|
417 |
|
418 |
-
# Return map HTML
|
419 |
map_html = m._repr_html_()
|
420 |
-
|
421 |
-
# Create legend HTML outside the map
|
422 |
legend_html = self.create_legend_html()
|
423 |
|
424 |
-
return {
|
|
|
|
|
|
|
|
|
425 |
|
426 |
def create_legend_html(self):
|
|
|
427 |
"""Create the HTML for the AQI legend"""
|
428 |
legend_html = """
|
429 |
<div style="padding: 10px; border: 1px solid #ccc; border-radius: 5px; background-color: white; margin-top: 10px;">
|
@@ -442,21 +516,95 @@ class AirQualityApp:
|
|
442 |
return legend_html
|
443 |
|
444 |
def get_aqi_category(self, aqi_value):
|
|
|
445 |
"""Determine AQI category based on value"""
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
|
452 |
-
|
453 |
-
|
454 |
-
|
455 |
-
|
456 |
-
|
457 |
-
|
458 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
459 |
|
|
|
460 |
def mock_get_counties(self, state_code):
|
461 |
"""Return mock county data for the specified state"""
|
462 |
if state_code in self.mock_counties:
|
@@ -488,6 +636,7 @@ class AirQualityApp:
|
|
488 |
numeric_state_code = state_code_mapping.get(state_code, "01") # Default to "01" if not found
|
489 |
else:
|
490 |
numeric_state_code = state_code
|
|
|
491 |
# Sample data for California
|
492 |
if state_code == "CA" or numeric_state_code == "06":
|
493 |
monitors = [
|
@@ -675,8 +824,9 @@ class AirQualityApp:
|
|
675 |
|
676 |
return monitors
|
677 |
|
|
|
678 |
def create_air_quality_map_ui():
|
679 |
-
"""Create the Gradio interface for the Air Quality Map application"""
|
680 |
app = AirQualityApp()
|
681 |
|
682 |
def update_counties(state_code):
|
@@ -684,8 +834,8 @@ def create_air_quality_map_ui():
|
|
684 |
counties = app.get_counties(state_code)
|
685 |
return gr.Dropdown(choices=counties)
|
686 |
|
687 |
-
def
|
688 |
-
"""Callback to generate and display the map"""
|
689 |
# Extract code from county string if provided
|
690 |
county_code = None
|
691 |
if county and ":" in county:
|
@@ -696,27 +846,35 @@ def create_air_quality_map_ui():
|
|
696 |
if parameter and ":" in parameter:
|
697 |
parameter_code = parameter.split(":")[0].strip()
|
698 |
|
699 |
-
# Generate the map
|
700 |
result = app.create_map(state, county_code, parameter_code)
|
701 |
|
702 |
if isinstance(result, dict):
|
703 |
-
#
|
704 |
-
|
705 |
<div>
|
706 |
{result["map"]}
|
707 |
{result["legend"]}
|
708 |
</div>
|
709 |
"""
|
710 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
711 |
else:
|
712 |
# Return error message or whatever was returned
|
713 |
-
|
|
|
714 |
|
715 |
# Create the UI
|
716 |
with gr.Blocks(title="Air Quality Monitoring Stations") as interface:
|
717 |
gr.Markdown("# NOAA Air Quality Monitoring Stations Map")
|
718 |
gr.Markdown("""
|
719 |
-
This application displays air quality monitoring stations in the United States.
|
720 |
|
721 |
**Note:** To use the actual EPA AQS API, you need to register for an API key and set
|
722 |
`EPA_AQS_EMAIL` and `EPA_AQS_API_KEY` environment variables in your Hugging Face Space.
|
@@ -747,12 +905,18 @@ def create_air_quality_map_ui():
|
|
747 |
allow_custom_value=True
|
748 |
)
|
749 |
|
750 |
-
# Button to generate map
|
751 |
-
map_button = gr.Button("Show Map")
|
752 |
-
|
753 |
-
|
754 |
-
|
|
|
|
|
755 |
map_html = gr.HTML(label="Air Quality Monitoring Stations Map")
|
|
|
|
|
|
|
|
|
756 |
|
757 |
# Set up event handlers
|
758 |
state_dropdown.change(
|
@@ -762,9 +926,9 @@ def create_air_quality_map_ui():
|
|
762 |
)
|
763 |
|
764 |
map_button.click(
|
765 |
-
fn=
|
766 |
inputs=[state_dropdown, county_dropdown, parameter_dropdown],
|
767 |
-
outputs=map_html
|
768 |
)
|
769 |
|
770 |
return interface
|
|
|
14 |
|
15 |
class AirQualityApp:
|
16 |
def __init__(self):
|
17 |
+
# [Keep all your existing initialization code the same]
|
18 |
self.states = {
|
19 |
"AL": "Alabama", "AK": "Alaska", "AZ": "Arizona", "AR": "Arkansas",
|
20 |
"CA": "California", "CO": "Colorado", "CT": "Connecticut", "DE": "Delaware",
|
|
|
99 |
{"code": "42602", "value_represented": "Nitrogen dioxide"},
|
100 |
{"code": "81102", "value_represented": "PM10 - Local Conditions"}
|
101 |
]
|
102 |
+
|
103 |
+
# Mock air quality data for demo
|
104 |
+
self.mock_aqi_data = {
|
105 |
+
"CA": self._generate_mock_aqi_data("CA"),
|
106 |
+
"NY": self._generate_mock_aqi_data("NY"),
|
107 |
+
"TX": self._generate_mock_aqi_data("TX")
|
108 |
+
}
|
109 |
|
110 |
+
def _generate_mock_aqi_data(self, state_code):
|
111 |
+
"""Generate mock AQI data for a state"""
|
112 |
+
import random
|
113 |
+
from datetime import datetime, timedelta
|
114 |
+
|
115 |
+
aqi_data = []
|
116 |
+
|
117 |
+
# Get numeric state code
|
118 |
+
numeric_state_code = self.state_code_mapping.get(state_code, "01")
|
119 |
+
|
120 |
+
# Generate data for the most recent 7 days
|
121 |
+
for days_ago in range(7):
|
122 |
+
# Generate date
|
123 |
+
date = (datetime.now() - timedelta(days=days_ago)).strftime("%Y-%m-%d")
|
124 |
+
|
125 |
+
# Generate random data for each county in the state
|
126 |
+
counties = self.mock_counties.get(state_code, [{"code": "001", "value": "County 1"}])
|
127 |
+
for county in counties:
|
128 |
+
county_code = county["code"]
|
129 |
+
|
130 |
+
# Generate data for each parameter
|
131 |
+
for param in self.mock_parameters:
|
132 |
+
param_code = param["code"]
|
133 |
+
param_name = param["value_represented"]
|
134 |
+
|
135 |
+
# Generate a few site numbers per county
|
136 |
+
for site_num in range(1, 4):
|
137 |
+
site_number = f"{site_num:04d}"
|
138 |
+
|
139 |
+
# Generate random AQI value (between 0 and 300)
|
140 |
+
aqi_value = random.randint(0, 300)
|
141 |
+
|
142 |
+
aqi_data.append({
|
143 |
+
"state_code": numeric_state_code,
|
144 |
+
"county_code": county_code,
|
145 |
+
"site_number": site_number,
|
146 |
+
"parameter_code": param_code,
|
147 |
+
"parameter_name": param_name,
|
148 |
+
"date_local": date,
|
149 |
+
"aqi": aqi_value
|
150 |
+
})
|
151 |
+
|
152 |
+
return aqi_data
|
153 |
+
|
154 |
+
# [Keep all your existing methods the same]
|
155 |
def get_monitors(self, state_code, county_code=None, parameter_code=None):
|
156 |
+
# [Same as original]
|
157 |
# If we don't have API credentials, use mock data
|
158 |
if not EMAIL or not API_KEY:
|
159 |
return self.mock_get_monitors(state_code, county_code, parameter_code)
|
|
|
207 |
except Exception as e:
|
208 |
print(f"Error fetching monitors: {e}")
|
209 |
return self.mock_get_monitors(state_code, county_code, parameter_code)
|
210 |
+
|
211 |
def get_counties(self, state_code):
|
212 |
+
# [Same as original]
|
213 |
"""Fetch counties for a given state"""
|
214 |
# If we don't have API credentials, use mock data
|
215 |
if not EMAIL or not API_KEY:
|
|
|
249 |
except Exception as e:
|
250 |
print(f"Error fetching counties: {e}")
|
251 |
return []
|
252 |
+
|
253 |
def get_parameters(self):
|
254 |
+
# [Same as original]
|
255 |
"""Fetch available parameter codes (pollutants)"""
|
256 |
# If we don't have API credentials, use mock data
|
257 |
if not EMAIL or not API_KEY:
|
|
|
296 |
"""Fetch the latest AQI data for monitors"""
|
297 |
# If we don't have API credentials, use mock data
|
298 |
if not EMAIL or not API_KEY:
|
299 |
+
# Use mock data for demo
|
300 |
+
if state_code in self.mock_aqi_data:
|
301 |
+
aqi_data = self.mock_aqi_data[state_code]
|
302 |
+
|
303 |
+
# Filter by county if provided
|
304 |
+
if county_code:
|
305 |
+
aqi_data = [item for item in aqi_data if item.get('county_code') == county_code]
|
306 |
+
|
307 |
+
# Filter by parameter if provided
|
308 |
+
if parameter_code:
|
309 |
+
aqi_data = [item for item in aqi_data if item.get('parameter_code') == parameter_code]
|
310 |
+
|
311 |
+
return aqi_data
|
312 |
+
return []
|
313 |
|
314 |
# Convert state code to numeric format for API
|
315 |
api_state_code = state_code
|
|
|
361 |
monitors = self.get_monitors(state_code, parameter_code=parameter_code)
|
362 |
|
363 |
if not monitors:
|
364 |
+
return {"map": "No monitoring stations found for the selected criteria.", "legend": "", "data": None}
|
365 |
|
366 |
# Convert to DataFrame for easier manipulation
|
367 |
df = pd.DataFrame(monitors)
|
|
|
374 |
print(f"After filtering, {len(df)} monitors remain")
|
375 |
|
376 |
if len(df) == 0:
|
377 |
+
return {"map": "No monitoring stations found for the selected county.", "legend": "", "data": None}
|
378 |
|
379 |
# Create a map centered on the mean latitude and longitude
|
380 |
center_lat = df["latitude"].mean()
|
381 |
center_lon = df["longitude"].mean()
|
382 |
|
383 |
# Create a map with a specific width and height - make it bigger
|
384 |
+
m = folium.Map(location=[center_lat, center_lon], zoom_start=7, width='100%', height=500)
|
385 |
|
386 |
# Add a marker cluster
|
387 |
marker_cluster = MarkerCluster().add_to(m)
|
388 |
|
389 |
# Get latest AQI data if credentials are provided
|
390 |
aqi_data = {}
|
391 |
+
all_aqi_readings = []
|
392 |
+
|
393 |
+
# Get AQI data either from API or mock data
|
394 |
+
aqi_results = self.get_latest_aqi(state_code, county_code, parameter_code)
|
395 |
+
|
396 |
+
# Create a lookup dictionary by site ID
|
397 |
+
for item in aqi_results:
|
398 |
+
site_id = f"{item['state_code']}-{item['county_code']}-{item['site_number']}"
|
399 |
+
if site_id not in aqi_data:
|
400 |
+
aqi_data[site_id] = []
|
401 |
+
aqi_data[site_id].append(item)
|
402 |
+
all_aqi_readings.append(item)
|
403 |
|
404 |
# Add markers for each monitoring station
|
405 |
for _, row in df.iterrows():
|
|
|
486 |
icon=folium.Icon(color=color, icon="cloud"),
|
487 |
).add_to(marker_cluster)
|
488 |
|
489 |
+
# Return map HTML, legend HTML, and data for the separate panel
|
490 |
map_html = m._repr_html_()
|
|
|
|
|
491 |
legend_html = self.create_legend_html()
|
492 |
|
493 |
+
return {
|
494 |
+
"map": map_html,
|
495 |
+
"legend": legend_html,
|
496 |
+
"data": all_aqi_readings
|
497 |
+
}
|
498 |
|
499 |
def create_legend_html(self):
|
500 |
+
# [Same as original]
|
501 |
"""Create the HTML for the AQI legend"""
|
502 |
legend_html = """
|
503 |
<div style="padding: 10px; border: 1px solid #ccc; border-radius: 5px; background-color: white; margin-top: 10px;">
|
|
|
516 |
return legend_html
|
517 |
|
518 |
def get_aqi_category(self, aqi_value):
|
519 |
+
# [Same as original]
|
520 |
"""Determine AQI category based on value"""
|
521 |
+
try:
|
522 |
+
aqi = int(aqi_value)
|
523 |
+
if aqi <= 50:
|
524 |
+
return "Good"
|
525 |
+
elif aqi <= 100:
|
526 |
+
return "Moderate"
|
527 |
+
elif aqi <= 150:
|
528 |
+
return "Unhealthy for Sensitive Groups"
|
529 |
+
elif aqi <= 200:
|
530 |
+
return "Unhealthy"
|
531 |
+
elif aqi <= 300:
|
532 |
+
return "Very Unhealthy"
|
533 |
+
else:
|
534 |
+
return "Hazardous"
|
535 |
+
except (ValueError, TypeError):
|
536 |
+
return "Unknown"
|
537 |
+
|
538 |
+
def format_air_quality_data_table(self, aqi_data):
|
539 |
+
"""Format air quality data as an HTML table for display"""
|
540 |
+
if not aqi_data or len(aqi_data) == 0:
|
541 |
+
return "<p>No air quality data available for the selected criteria.</p>"
|
542 |
+
|
543 |
+
# Sort by date (most recent first) and then by AQI value (highest first)
|
544 |
+
sorted_data = sorted(aqi_data,
|
545 |
+
key=lambda x: (x.get('date_local', ''), -int(x.get('aqi', 0)) if x.get('aqi') and str(x.get('aqi')).isdigit() else 0),
|
546 |
+
reverse=True)
|
547 |
+
|
548 |
+
# Group by location to show the latest readings for each site
|
549 |
+
site_data = {}
|
550 |
+
for item in sorted_data:
|
551 |
+
site_id = f"{item.get('state_code', '')}-{item.get('county_code', '')}-{item.get('site_number', '')}"
|
552 |
+
param = item.get('parameter_code', '')
|
553 |
+
key = f"{site_id}-{param}"
|
554 |
+
|
555 |
+
if key not in site_data:
|
556 |
+
site_data[key] = item
|
557 |
+
|
558 |
+
# Create HTML table
|
559 |
+
html = """
|
560 |
+
<div style="max-height: 500px; overflow-y: auto;">
|
561 |
+
<h3>Latest Air Quality Readings</h3>
|
562 |
+
<table style="width:100%; border-collapse: collapse;">
|
563 |
+
<tr style="background-color: #f2f2f2; position: sticky; top: 0;">
|
564 |
+
<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Date</th>
|
565 |
+
<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Location</th>
|
566 |
+
<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Pollutant</th>
|
567 |
+
<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">AQI</th>
|
568 |
+
<th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Category</th>
|
569 |
+
</tr>
|
570 |
+
"""
|
571 |
+
|
572 |
+
# Add rows for each site's latest readings
|
573 |
+
for i, item in enumerate(site_data.values()):
|
574 |
+
date = item.get('date_local', 'N/A')
|
575 |
+
|
576 |
+
# Try to get a nice location name
|
577 |
+
state_code = item.get('state_code', 'N/A')
|
578 |
+
county_code = item.get('county_code', 'N/A')
|
579 |
+
site_number = item.get('site_number', 'N/A')
|
580 |
+
location = f"Site {state_code}-{county_code}-{site_number}"
|
581 |
+
|
582 |
+
pollutant = item.get('parameter_name', 'N/A')
|
583 |
+
aqi_value = item.get('aqi', 'N/A')
|
584 |
+
category = self.get_aqi_category(aqi_value)
|
585 |
+
|
586 |
+
# Get appropriate color for the AQI category
|
587 |
+
category_color = self.aqi_legend_colors.get(category, "#cccccc")
|
588 |
+
|
589 |
+
row_style = ' style="background-color: #f9f9f9;"' if i % 2 == 0 else ''
|
590 |
+
html += f"""
|
591 |
+
<tr{row_style}>
|
592 |
+
<td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{date}</td>
|
593 |
+
<td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{location}</td>
|
594 |
+
<td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{pollutant}</td>
|
595 |
+
<td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{aqi_value}</td>
|
596 |
+
<td style="padding: 8px; text-align: left; border: 1px solid #ddd; background-color: {category_color};">{category}</td>
|
597 |
+
</tr>
|
598 |
+
"""
|
599 |
+
|
600 |
+
html += """
|
601 |
+
</table>
|
602 |
+
</div>
|
603 |
+
"""
|
604 |
+
|
605 |
+
return html
|
606 |
|
607 |
+
# [Keep all your mock data methods the same]
|
608 |
def mock_get_counties(self, state_code):
|
609 |
"""Return mock county data for the specified state"""
|
610 |
if state_code in self.mock_counties:
|
|
|
636 |
numeric_state_code = state_code_mapping.get(state_code, "01") # Default to "01" if not found
|
637 |
else:
|
638 |
numeric_state_code = state_code
|
639 |
+
|
640 |
# Sample data for California
|
641 |
if state_code == "CA" or numeric_state_code == "06":
|
642 |
monitors = [
|
|
|
824 |
|
825 |
return monitors
|
826 |
|
827 |
+
# Create the UI with the new Air Quality Data panel
|
828 |
def create_air_quality_map_ui():
|
829 |
+
"""Create the Gradio interface for the Air Quality Map application with a separate data panel"""
|
830 |
app = AirQualityApp()
|
831 |
|
832 |
def update_counties(state_code):
|
|
|
834 |
counties = app.get_counties(state_code)
|
835 |
return gr.Dropdown(choices=counties)
|
836 |
|
837 |
+
def show_map_and_data(state, county=None, parameter=None):
|
838 |
+
"""Callback to generate and display both the map and the air quality data"""
|
839 |
# Extract code from county string if provided
|
840 |
county_code = None
|
841 |
if county and ":" in county:
|
|
|
846 |
if parameter and ":" in parameter:
|
847 |
parameter_code = parameter.split(":")[0].strip()
|
848 |
|
849 |
+
# Generate the map and get data
|
850 |
result = app.create_map(state, county_code, parameter_code)
|
851 |
|
852 |
if isinstance(result, dict):
|
853 |
+
# Process map HTML
|
854 |
+
map_html = f"""
|
855 |
<div>
|
856 |
{result["map"]}
|
857 |
{result["legend"]}
|
858 |
</div>
|
859 |
"""
|
860 |
+
|
861 |
+
# Process air quality data for the separate panel
|
862 |
+
if result["data"]:
|
863 |
+
data_html = app.format_air_quality_data_table(result["data"])
|
864 |
+
else:
|
865 |
+
data_html = "<p>No air quality data available for the selected criteria.</p>"
|
866 |
+
|
867 |
+
return map_html, data_html
|
868 |
else:
|
869 |
# Return error message or whatever was returned
|
870 |
+
error_message = result if isinstance(result, str) else "An error occurred"
|
871 |
+
return error_message, "<p>No data available</p>"
|
872 |
|
873 |
# Create the UI
|
874 |
with gr.Blocks(title="Air Quality Monitoring Stations") as interface:
|
875 |
gr.Markdown("# NOAA Air Quality Monitoring Stations Map")
|
876 |
gr.Markdown("""
|
877 |
+
This application displays air quality monitoring stations in the United States and shows current air quality readings.
|
878 |
|
879 |
**Note:** To use the actual EPA AQS API, you need to register for an API key and set
|
880 |
`EPA_AQS_EMAIL` and `EPA_AQS_API_KEY` environment variables in your Hugging Face Space.
|
|
|
905 |
allow_custom_value=True
|
906 |
)
|
907 |
|
908 |
+
# Button to generate map and data
|
909 |
+
map_button = gr.Button("Show Map and Air Quality Data")
|
910 |
+
|
911 |
+
# Create two tabs for the map and data
|
912 |
+
with gr.Tabs() as tabs:
|
913 |
+
with gr.TabItem("Map"):
|
914 |
+
# HTML component to display the map
|
915 |
map_html = gr.HTML(label="Air Quality Monitoring Stations Map")
|
916 |
+
|
917 |
+
with gr.TabItem("Air Quality Data"):
|
918 |
+
# HTML component to display the air quality data
|
919 |
+
data_html = gr.HTML(label="Air Quality Readings")
|
920 |
|
921 |
# Set up event handlers
|
922 |
state_dropdown.change(
|
|
|
926 |
)
|
927 |
|
928 |
map_button.click(
|
929 |
+
fn=show_map_and_data,
|
930 |
inputs=[state_dropdown, county_dropdown, parameter_dropdown],
|
931 |
+
outputs=[map_html, data_html]
|
932 |
)
|
933 |
|
934 |
return interface
|