nakas commited on
Commit
27e66a4
·
verified ·
1 Parent(s): e82923b

Delete air_quality_map.py

Browse files
Files changed (1) hide show
  1. air_quality_map.py +0 -781
air_quality_map.py DELETED
@@ -1,781 +0,0 @@
1
- import gradio as gr
2
- import requests
3
- import pandas as pd
4
- import folium
5
- from folium.plugins import MarkerCluster
6
- import tempfile
7
- import os
8
- import json
9
-
10
- # Get API credentials from environment variables
11
- EPA_AQS_API_BASE_URL = "https://aqs.epa.gov/data/api"
12
- EMAIL = os.environ.get("EPA_AQS_EMAIL", "") # Get from environment variable
13
- 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",
20
- "FL": "Florida", "GA": "Georgia", "HI": "Hawaii", "ID": "Idaho",
21
- "IL": "Illinois", "IN": "Indiana", "IA": "Iowa", "KS": "Kansas",
22
- "KY": "Kentucky", "LA": "Louisiana", "ME": "Maine", "MD": "Maryland",
23
- "MA": "Massachusetts", "MI": "Michigan", "MN": "Minnesota", "MS": "Mississippi",
24
- "MO": "Missouri", "MT": "Montana", "NE": "Nebraska", "NV": "Nevada",
25
- "NH": "New Hampshire", "NJ": "New Jersey", "NM": "New Mexico", "NY": "New York",
26
- "NC": "North Carolina", "ND": "North Dakota", "OH": "Ohio", "OK": "Oklahoma",
27
- "OR": "Oregon", "PA": "Pennsylvania", "RI": "Rhode Island", "SC": "South Carolina",
28
- "SD": "South Dakota", "TN": "Tennessee", "TX": "Texas", "UT": "Utah",
29
- "VT": "Vermont", "VA": "Virginia", "WA": "Washington", "WV": "West Virginia",
30
- "WI": "Wisconsin", "WY": "Wyoming", "DC": "District of Columbia"
31
- }
32
-
33
- # Mapping from two-letter state codes to numeric state codes for API
34
- self.state_code_mapping = {
35
- "AL": "01", "AK": "02", "AZ": "04", "AR": "05",
36
- "CA": "06", "CO": "08", "CT": "09", "DE": "10",
37
- "FL": "12", "GA": "13", "HI": "15", "ID": "16",
38
- "IL": "17", "IN": "18", "IA": "19", "KS": "20",
39
- "KY": "21", "LA": "22", "ME": "23", "MD": "24",
40
- "MA": "25", "MI": "26", "MN": "27", "MS": "28",
41
- "MO": "29", "MT": "30", "NE": "31", "NV": "32",
42
- "NH": "33", "NJ": "34", "NM": "35", "NY": "36",
43
- "NC": "37", "ND": "38", "OH": "39", "OK": "40",
44
- "OR": "41", "PA": "42", "RI": "44", "SC": "45",
45
- "SD": "46", "TN": "47", "TX": "48", "UT": "49",
46
- "VT": "50", "VA": "51", "WA": "53", "WV": "54",
47
- "WI": "55", "WY": "56", "DC": "11"
48
- }
49
-
50
- # AQI categories with their corresponding colors - using only valid Folium icon colors
51
- self.aqi_categories = {
52
- "Good": "green",
53
- "Moderate": "orange",
54
- "Unhealthy for Sensitive Groups": "orange",
55
- "Unhealthy": "red",
56
- "Very Unhealthy": "purple",
57
- "Hazardous": "darkred"
58
- }
59
-
60
- # Color mapping for the legend (using original colors for display)
61
- self.aqi_legend_colors = {
62
- "Good": "#00e400", # Green
63
- "Moderate": "#ffff00", # Yellow
64
- "Unhealthy for Sensitive Groups": "#ff7e00", # Orange
65
- "Unhealthy": "#ff0000", # Red
66
- "Very Unhealthy": "#99004c", # Purple
67
- "Hazardous": "#7e0023" # Maroon
68
- }
69
-
70
- # Sample county data for demo
71
- self.mock_counties = {
72
- "CA": [
73
- {"code": "037", "value": "Los Angeles"},
74
- {"code": "067", "value": "Sacramento"},
75
- {"code": "073", "value": "San Diego"},
76
- {"code": "075", "value": "San Francisco"}
77
- ],
78
- "NY": [
79
- {"code": "061", "value": "New York"},
80
- {"code": "047", "value": "Kings (Brooklyn)"},
81
- {"code": "081", "value": "Queens"},
82
- {"code": "005", "value": "Bronx"}
83
- ],
84
- "TX": [
85
- {"code": "201", "value": "Harris (Houston)"},
86
- {"code": "113", "value": "Dallas"},
87
- {"code": "029", "value": "Bexar (San Antonio)"},
88
- {"code": "453", "value": "Travis (Austin)"}
89
- ]
90
- }
91
-
92
- # Sample parameters for demo
93
- self.mock_parameters = [
94
- {"code": "88101", "value_represented": "PM2.5 - Local Conditions"},
95
- {"code": "44201", "value_represented": "Ozone"},
96
- {"code": "42401", "value_represented": "Sulfur dioxide"},
97
- {"code": "42101", "value_represented": "Carbon monoxide"},
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
- """Fetch monitoring stations for a given state and optional county"""
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)
107
-
108
- # Convert state code to numeric format for API
109
- api_state_code = state_code
110
- if len(state_code) == 2 and state_code in self.state_code_mapping:
111
- api_state_code = self.state_code_mapping[state_code]
112
-
113
- # API endpoint for monitoring sites
114
- endpoint = f"{EPA_AQS_API_BASE_URL}/monitors/byState"
115
-
116
- params = {
117
- "email": EMAIL,
118
- "key": API_KEY,
119
- "state": api_state_code,
120
- "bdate": "20240101", # Beginning date (YYYYMMDD)
121
- "edate": "20240414", # End date (YYYYMMDD)
122
- }
123
-
124
- if county_code:
125
- params["county"] = county_code
126
-
127
- if parameter_code:
128
- params["param"] = parameter_code
129
-
130
- try:
131
- response = requests.get(endpoint, params=params)
132
- data = response.json()
133
-
134
- # Add detailed debugging
135
- print(f"API Response Keys: {list(data.keys()) if isinstance(data, dict) else 'Not a dictionary'}")
136
- if isinstance(data, dict) and "Header" in data:
137
- print(f"Header type: {type(data['Header'])}, content: {data['Header'][:100]}...")
138
-
139
- # Handle the specific response structure we observed
140
- if isinstance(data, dict):
141
- if "Data" in data and isinstance(data["Data"], list):
142
- return data["Data"]
143
- elif "Header" in data and isinstance(data["Header"], list):
144
- if len(data["Header"]) > 0 and data["Header"][0].get("status") == "Success":
145
- return data.get("Data", [])
146
- else:
147
- print(f"Header does not contain success status: {data['Header']}")
148
- # Special case - return mock data if we can't parse the API response
149
- print(f"Using mock data instead of API response for state {state_code}")
150
- return self.mock_get_monitors(state_code, county_code, parameter_code)
151
- else:
152
- print(f"Unexpected response format for monitors: {type(data)}")
153
- return self.mock_get_monitors(state_code, county_code, parameter_code)
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:
162
- return self.mock_get_counties(state_code)
163
-
164
- # Convert state code to numeric format for API
165
- api_state_code = state_code
166
- if len(state_code) == 2 and state_code in self.state_code_mapping:
167
- api_state_code = self.state_code_mapping[state_code]
168
-
169
- endpoint = f"{EPA_AQS_API_BASE_URL}/list/countiesByState"
170
-
171
- params = {
172
- "email": EMAIL,
173
- "key": API_KEY,
174
- "state": api_state_code
175
- }
176
-
177
- try:
178
- response = requests.get(endpoint, params=params)
179
- data = response.json()
180
-
181
- # Handle the specific response structure we observed
182
- counties = []
183
- if isinstance(data, dict) and "Data" in data and isinstance(data["Data"], list):
184
- counties = data["Data"]
185
-
186
- # Format as "code: name" for dropdown
187
- result = []
188
- for c in counties:
189
- code = c.get("code")
190
- value = c.get("value_represented")
191
- if code and value:
192
- result.append(f"{code}: {value}")
193
-
194
- return result
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:
203
- return self.mock_get_parameters()
204
-
205
- endpoint = f"{EPA_AQS_API_BASE_URL}/list/parametersByClass"
206
-
207
- params = {
208
- "email": EMAIL,
209
- "key": API_KEY,
210
- "pc": "CRITERIA" # Filter to criteria pollutants
211
- }
212
-
213
- try:
214
- response = requests.get(endpoint, params=params)
215
- data = response.json()
216
-
217
- # Handle the specific response structure we observed
218
- parameters = []
219
- if isinstance(data, dict) and "Data" in data and isinstance(data["Data"], list):
220
- parameters = data["Data"]
221
-
222
- # Format as "code: name" for dropdown
223
- result = []
224
- for p in parameters:
225
- code = p.get("code")
226
- value = p.get("value_represented")
227
- if not code:
228
- code = p.get("parameter_code")
229
- if not value:
230
- value = p.get("parameter_name")
231
-
232
- if code and value:
233
- result.append(f"{code}: {value}")
234
-
235
- return result
236
- except Exception as e:
237
- print(f"Error fetching parameters: {e}")
238
- return []
239
-
240
- def get_latest_aqi(self, state_code, county_code=None, parameter_code=None):
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
- return self.mock_get_aqi_data(state_code, county_code, parameter_code)
245
-
246
- # Convert state code to numeric format for API
247
- api_state_code = state_code
248
- if len(state_code) == 2 and state_code in self.state_code_mapping:
249
- api_state_code = self.state_code_mapping[state_code]
250
-
251
- endpoint = f"{EPA_AQS_API_BASE_URL}/dailyData/byState"
252
-
253
- params = {
254
- "email": EMAIL,
255
- "key": API_KEY,
256
- "state": api_state_code,
257
- "bdate": "20240314", # Beginning date (YYYYMMDD) - last 30 days
258
- "edate": "20240414", # End date (YYYYMMDD) - current date
259
- }
260
-
261
- # The county parameter might not be supported here either
262
- # We'll filter results by county after getting them
263
-
264
- if parameter_code:
265
- params["param"] = parameter_code
266
-
267
- try:
268
- response = requests.get(endpoint, params=params)
269
- data = response.json()
270
-
271
- # Add detailed debugging
272
- print(f"AQI API Response Keys: {list(data.keys()) if isinstance(data, dict) else 'Not a dictionary'}")
273
- if isinstance(data, dict) and "Header" in data:
274
- print(f"AQI Header type: {type(data['Header'])}, content: {data['Header'][:100]}...")
275
-
276
- # Handle the specific response structure we observed
277
- aqi_data = []
278
- if isinstance(data, dict) and "Data" in data and isinstance(data["Data"], list):
279
- aqi_data = data["Data"]
280
-
281
- # Filter by county if provided
282
- if county_code and aqi_data:
283
- aqi_data = [item for item in aqi_data if item.get('county_code') == county_code]
284
-
285
- return aqi_data
286
- except Exception as e:
287
- print(f"Error fetching AQI data: {e}")
288
- return self.mock_get_aqi_data(state_code, county_code, parameter_code)
289
-
290
- def mock_get_aqi_data(self, state_code, county_code=None, parameter_code=None):
291
- """Generate mock AQI data for demonstration"""
292
- # Get monitors first
293
- monitors = self.mock_get_monitors(state_code, county_code, parameter_code)
294
-
295
- # Create mock AQI data for each monitor
296
- mock_aqi_data = []
297
-
298
- for monitor in monitors:
299
- # Create 5 days of mock readings for each monitor
300
- for i in range(5):
301
- # Base AQI value depends on the parameter
302
- base_aqi = 0
303
- if monitor.get("parameter_code") == "88101": # PM2.5
304
- base_aqi = 35
305
- elif monitor.get("parameter_code") == "44201": # Ozone
306
- base_aqi = 45
307
- elif monitor.get("parameter_code") == "42401": # SO2
308
- base_aqi = 25
309
- else:
310
- base_aqi = 30
311
-
312
- # Vary the AQI by +/- 20 points randomly
313
- import random
314
- aqi_value = max(0, min(300, base_aqi + random.randint(-20, 20)))
315
-
316
- # Date is "2024-04-XX" where XX starts from 10 and goes back
317
- date = f"2024-04-{14-i:02d}"
318
-
319
- mock_aqi_data.append({
320
- "state_code": monitor.get("state_code"),
321
- "county_code": monitor.get("county_code"),
322
- "site_number": monitor.get("site_number"),
323
- "parameter_code": monitor.get("parameter_code"),
324
- "parameter_name": monitor.get("parameter_name"),
325
- "date_local": date,
326
- "aqi": aqi_value,
327
- "category": self.get_aqi_category(aqi_value),
328
- "city_name": monitor.get("city_name", "Unknown"),
329
- "local_site_name": monitor.get("local_site_name", "Unknown")
330
- })
331
-
332
- return mock_aqi_data
333
-
334
- def create_map_and_data(self, state_code, county_code=None, parameter_code=None):
335
- """Create a map with air quality monitoring stations and separate data for display"""
336
- # Get monitors (don't pass county_code to API)
337
- monitors = self.get_monitors(state_code, parameter_code=parameter_code)
338
-
339
- if not monitors:
340
- return {
341
- "map": "No monitoring stations found for the selected criteria.",
342
- "data_html": "",
343
- "legend": ""
344
- }
345
-
346
- # Convert to DataFrame for easier manipulation
347
- df = pd.DataFrame(monitors)
348
-
349
- # Filter by county if provided - after getting the monitors
350
- if county_code:
351
- print(f"Filtering by county_code: {county_code}")
352
- county_code_str = str(county_code)
353
- df = df[df['county_code'].astype(str) == county_code_str]
354
- print(f"After filtering, {len(df)} monitors remain")
355
-
356
- if len(df) == 0:
357
- return {
358
- "map": "No monitoring stations found for the selected county.",
359
- "data_html": "",
360
- "legend": ""
361
- }
362
-
363
- # Create a map centered on the mean latitude and longitude
364
- center_lat = df["latitude"].mean()
365
- center_lon = df["longitude"].mean()
366
-
367
- # Create a map with a specific width and height
368
- m = folium.Map(location=[center_lat, center_lon], zoom_start=7, width='100%', height=600)
369
-
370
- # Add a marker cluster
371
- marker_cluster = MarkerCluster().add_to(m)
372
-
373
- # Get latest AQI data
374
- if EMAIL and API_KEY:
375
- aqi_results = self.get_latest_aqi(state_code, parameter_code=parameter_code)
376
- else:
377
- aqi_results = self.mock_get_aqi_data(state_code, county_code, parameter_code)
378
-
379
- # Create a lookup dictionary by site ID
380
- aqi_data = {}
381
- for item in aqi_results:
382
- site_id = f"{item['state_code']}-{item['county_code']}-{item['site_number']}"
383
- if site_id not in aqi_data:
384
- aqi_data[site_id] = []
385
- aqi_data[site_id].append(item)
386
-
387
- # Initialize a list to collect all air quality readings for display in UI
388
- all_readings = []
389
-
390
- # Add markers for each monitoring station with minimal popup content
391
- for _, row in df.iterrows():
392
- site_id = f"{row['state_code']}-{row['county_code']}-{row['site_number']}"
393
-
394
- # Default marker color is blue
395
- color = "blue"
396
-
397
- # Get AQI data for this station if available
398
- station_aqi_data = aqi_data.get(site_id, [])
399
-
400
- # Add all readings from this station to the collected data
401
- for reading in station_aqi_data:
402
- all_readings.append({
403
- "site_id": site_id,
404
- "site_name": row['local_site_name'],
405
- "city": row.get('city_name', 'N/A'),
406
- "county": row.get('county_name', 'N/A'),
407
- "state": row.get('state_name', 'N/A'),
408
- "date": reading.get('date_local', 'N/A'),
409
- "pollutant": reading.get('parameter_name', 'N/A'),
410
- "aqi": reading.get('aqi', 'N/A'),
411
- "category": self.get_aqi_category(reading.get('aqi', 0)) if reading.get('aqi') else 'N/A'
412
- })
413
-
414
- # Set marker color based on latest AQI if available
415
- if station_aqi_data:
416
- # Sort by date (most recent first)
417
- station_aqi_data.sort(key=lambda x: x.get('date_local', ''), reverse=True)
418
-
419
- # Get latest AQI for marker color
420
- if station_aqi_data[0].get('aqi'):
421
- latest_aqi = station_aqi_data[0].get('aqi')
422
- aqi_category = self.get_aqi_category(latest_aqi)
423
- color = self.aqi_categories.get(aqi_category, "blue")
424
-
425
- # Create simple popup content with just station name and link to data
426
- popup_content = f"""
427
- <div>
428
- <h4>{row['local_site_name']}</h4>
429
- <p><strong>Site ID:</strong> {site_id}</p>
430
- <p>See detailed data in the panel below.</p>
431
- </div>
432
- """
433
-
434
- # Add marker to cluster with simple popup
435
- popup = folium.Popup(popup_content, max_width=300)
436
- folium.Marker(
437
- location=[row["latitude"], row["longitude"]],
438
- popup=popup,
439
- icon=folium.Icon(color=color, icon="cloud"),
440
- ).add_to(marker_cluster)
441
-
442
- # Sort all readings by date (most recent first)
443
- all_readings.sort(key=lambda x: x.get('date', ''), reverse=True)
444
-
445
- # Convert all readings to HTML table for display in UI
446
- data_html = self.create_readings_table_html(all_readings)
447
-
448
- # Return map HTML, data HTML, and legend HTML separately
449
- return {
450
- "map": m._repr_html_(),
451
- "data_html": data_html,
452
- "legend": self.create_legend_html()
453
- }
454
-
455
- def create_readings_table_html(self, readings):
456
- """Create an HTML table of air quality readings"""
457
- if not readings:
458
- return "<p>No air quality data available for the selected criteria.</p>"
459
-
460
- html = """
461
- <div style="max-height: 500px; overflow-y: auto;">
462
- <h3>Air Quality Readings</h3>
463
- <table style="width:100%; border-collapse: collapse; margin-top: 10px;">
464
- <tr style="background-color: #f2f2f2; position: sticky; top: 0;">
465
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Station</th>
466
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Location</th>
467
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Date</th>
468
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Pollutant</th>
469
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">AQI</th>
470
- <th style="padding: 8px; text-align: left; border: 1px solid #ddd;">Category</th>
471
- </tr>
472
- """
473
-
474
- for i, reading in enumerate(readings):
475
- # Get background color for AQI category
476
- category = reading.get('category', 'N/A')
477
- bg_color = self.aqi_legend_colors.get(category, "#ffffff")
478
-
479
- # For better readability, use a lighter version of the color
480
- if category != 'Good' and category != 'N/A':
481
- # Add alpha transparency to the color
482
- bg_color = bg_color + "40" # 40 is 25% opacity in hex
483
-
484
- # Alternate row colors for better readability
485
- row_style = ' style="background-color: #f9f9f9;"' if i % 2 == 0 else ''
486
-
487
- location = f"{reading.get('city', 'N/A')}, {reading.get('state', 'N/A')}"
488
-
489
- html += f"""
490
- <tr{row_style}>
491
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{reading.get('site_name', 'N/A')}</td>
492
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{location}</td>
493
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{reading.get('date', 'N/A')}</td>
494
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{reading.get('pollutant', 'N/A')}</td>
495
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd;">{reading.get('aqi', 'N/A')}</td>
496
- <td style="padding: 8px; text-align: left; border: 1px solid #ddd; background-color: {bg_color};">{category}</td>
497
- </tr>
498
- """
499
-
500
- html += """
501
- </table>
502
- </div>
503
- """
504
-
505
- return html
506
-
507
- def create_legend_html(self):
508
- """Create the HTML for the AQI legend"""
509
- legend_html = """
510
- <div style="padding: 10px; border: 1px solid #ccc; border-radius: 5px; background-color: white; margin-top: 10px;">
511
- <h4 style="margin-top: 0;">AQI Categories</h4>
512
- <div style="display: grid; grid-template-columns: auto 1fr; grid-gap: 5px; align-items: center;">
513
- """
514
-
515
- for category, color in self.aqi_legend_colors.items():
516
- legend_html += f'<span style="background-color: {color}; width: 20px; height: 20px; display: inline-block;"></span>'
517
- legend_html += f'<span>{category}</span>'
518
-
519
- legend_html += """
520
- </div>
521
- </div>
522
- """
523
- return legend_html
524
-
525
- def get_aqi_category(self, aqi_value):
526
- """Determine AQI category based on value"""
527
- try:
528
- aqi = int(aqi_value)
529
- if aqi <= 50:
530
- return "Good"
531
- elif aqi <= 100:
532
- return "Moderate"
533
- elif aqi <= 150:
534
- return "Unhealthy for Sensitive Groups"
535
- elif aqi <= 200:
536
- return "Unhealthy"
537
- elif aqi <= 300:
538
- return "Very Unhealthy"
539
- else:
540
- return "Hazardous"
541
- except (ValueError, TypeError):
542
- return "N/A"
543
-
544
- def mock_get_counties(self, state_code):
545
- """Return mock county data for the specified state"""
546
- if state_code in self.mock_counties:
547
- counties = self.mock_counties[state_code]
548
- return [f"{c['code']}: {c['value']}" for c in counties]
549
- else:
550
- # Return generic counties for other states
551
- return [
552
- "001: County 1",
553
- "002: County 2",
554
- "003: County 3",
555
- "004: County 4"
556
- ]
557
-
558
- def mock_get_parameters(self):
559
- """Return mock parameter data"""
560
- return [f"{p['code']}: {p['value_represented']}" for p in self.mock_parameters]
561
-
562
- def mock_get_monitors(self, state_code, county_code=None, parameter_code=None):
563
- """Mock function to return sample data for development"""
564
- # Get state code in proper format
565
- if len(state_code) == 2:
566
- # Convert 2-letter state code to numeric format for mock data
567
- state_code_mapping = {
568
- "CA": "06",
569
- "NY": "36",
570
- "TX": "48"
571
- }
572
- numeric_state_code = state_code_mapping.get(state_code, "01") # Default to "01" if not found
573
- else:
574
- numeric_state_code = state_code
575
-
576
- # Sample data for California
577
- if state_code == "CA" or numeric_state_code == "06":
578
- monitors = [
579
- {
580
- "state_code": "06",
581
- "county_code": "037",
582
- "site_number": "0001",
583
- "parameter_code": "88101",
584
- "parameter_name": "PM2.5 - Local Conditions",
585
- "poc": 1,
586
- "latitude": 34.0667,
587
- "longitude": -118.2275,
588
- "local_site_name": "Los Angeles - North Main Street",
589
- "address": "1630 North Main Street",
590
- "city_name": "Los Angeles",
591
- "county_name": "Los Angeles",
592
- "state_name": "California",
593
- "cbsa_name": "Los Angeles-Long Beach-Anaheim",
594
- "date_established": "1998-01-01",
595
- "last_sample_date": "2024-04-10"
596
- },
597
- {
598
- "state_code": "06",
599
- "county_code": "037",
600
- "site_number": "0002",
601
- "parameter_code": "44201",
602
- "parameter_name": "Ozone",
603
- "poc": 1,
604
- "latitude": 34.0667,
605
- "longitude": -118.2275,
606
- "local_site_name": "Los Angeles - North Main Street",
607
- "address": "1630 North Main Street",
608
- "city_name": "Los Angeles",
609
- "county_name": "Los Angeles",
610
- "state_name": "California",
611
- "cbsa_name": "Los Angeles-Long Beach-Anaheim",
612
- "date_established": "1998-01-01",
613
- "last_sample_date": "2024-04-10"
614
- },
615
- {
616
- "state_code": "06",
617
- "county_code": "067",
618
- "site_number": "0010",
619
- "parameter_code": "88101",
620
- "parameter_name": "PM2.5 - Local Conditions",
621
- "poc": 1,
622
- "latitude": 38.5661,
623
- "longitude": -121.4926,
624
- "local_site_name": "Sacramento - T Street",
625
- "address": "1309 T Street",
626
- "city_name": "Sacramento",
627
- "county_name": "Sacramento",
628
- "state_name": "California",
629
- "cbsa_name": "Sacramento-Roseville",
630
- "date_established": "1999-03-01",
631
- "last_sample_date": "2024-04-10"
632
- },
633
- {
634
- "state_code": "06",
635
- "county_code": "073",
636
- "site_number": "0005",
637
- "parameter_code": "88101",
638
- "parameter_name": "PM2.5 - Local Conditions",
639
- "poc": 1,
640
- "latitude": 32.7333,
641
- "longitude": -117.1500,
642
- "local_site_name": "San Diego - Beardsley Street",
643
- "address": "1110 Beardsley Street",
644
- "city_name": "San Diego",
645
- "county_name": "San Diego",
646
- "state_name": "California",
647
- "cbsa_name": "San Diego-Carlsbad",
648
- "date_established": "1999-04-15",
649
- "last_sample_date": "2024-04-10"
650
- }
651
- ]
652
- # Sample data for New York
653
- elif state_code == "NY" or numeric_state_code == "36":
654
- monitors = [
655
- {
656
- "state_code": "36",
657
- "county_code": "061",
658
- "site_number": "0010",
659
- "parameter_code": "88101",
660
- "parameter_name": "PM2.5 - Local Conditions",
661
- "poc": 1,
662
- "latitude": 40.7159,
663
- "longitude": -73.9876,
664
- "local_site_name": "New York - PS 59",
665
- "address": "228 East 57th Street",
666
- "city_name": "New York",
667
- "county_name": "New York",
668
- "state_name": "New York",
669
- "cbsa_name": "New York-Newark-Jersey City",
670
- "date_established": "1999-07-15",
671
- "last_sample_date": "2024-04-10"
672
- },
673
- {
674
- "state_code": "36",
675
- "county_code": "061",
676
- "site_number": "0079",
677
- "parameter_code": "44201",
678
- "parameter_name": "Ozone",
679
- "poc": 1,
680
- "latitude": 40.8160,
681
- "longitude": -73.9510,
682
- "local_site_name": "New York - IS 52",
683
- "address": "681 Kelly Street",
684
- "city_name": "Bronx",
685
- "county_name": "Bronx",
686
- "state_name": "New York",
687
- "cbsa_name": "New York-Newark-Jersey City",
688
- "date_established": "1998-01-01",
689
- "last_sample_date": "2024-04-10"
690
- }
691
- ]
692
- # Sample data for Texas
693
- elif state_code == "TX" or numeric_state_code == "48":
694
- monitors = [
695
- {
696
- "state_code": "48",
697
- "county_code": "201",
698
- "site_number": "0024",
699
- "parameter_code": "88101",
700
- "parameter_name": "PM2.5 - Local Conditions",
701
- "poc": 1,
702
- "latitude": 29.7349,
703
- "longitude": -95.3063,
704
- "local_site_name": "Houston - Clinton Drive",
705
- "address": "9525 Clinton Drive",
706
- "city_name": "Houston",
707
- "county_name": "Harris",
708
- "state_name": "Texas",
709
- "cbsa_name": "Houston-The Woodlands-Sugar Land",
710
- "date_established": "1997-09-01",
711
- "last_sample_date": "2024-04-10"
712
- },
713
- {
714
- "state_code": "48",
715
- "county_code": "113",
716
- "site_number": "0050",
717
- "parameter_code": "44201",
718
- "parameter_name": "Ozone",
719
- "poc": 1,
720
- "latitude": 32.8198,
721
- "longitude": -96.8602,
722
- "local_site_name": "Dallas - Hinton Street",
723
- "address": "1415 Hinton Street",
724
- "city_name": "Dallas",
725
- "county_name": "Dallas",
726
- "state_name": "Texas",
727
- "cbsa_name": "Dallas-Fort Worth-Arlington",
728
- "date_established": "1998-01-01",
729
- "last_sample_date": "2024-04-10"
730
- }
731
- ]
732
- else:
733
- # Default data for other states - generate some random monitors
734
- monitors = [
735
- {
736
- "state_code": state_code,
737
- "county_code": "001",
738
- "site_number": "0001",
739
- "parameter_code": "88101",
740
- "parameter_name": "PM2.5 - Local Conditions",
741
- "poc": 1,
742
- "latitude": 40.0 + float(ord(state_code[0])) / 10,
743
- "longitude": -90.0 - float(ord(state_code[1])) / 10,
744
- "local_site_name": f"{self.states.get(state_code, 'Unknown')} - Station 1",
745
- "address": "123 Main Street",
746
- "city_name": "City 1",
747
- "county_name": "County 1",
748
- "state_name": self.states.get(state_code, "Unknown"),
749
- "cbsa_name": f"{self.states.get(state_code, 'Unknown')} Metro Area",
750
- "date_established": "2000-01-01",
751
- "last_sample_date": "2024-04-10"
752
- },
753
- {
754
- "state_code": state_code,
755
- "county_code": "002",
756
- "site_number": "0002",
757
- "parameter_code": "44201",
758
- "parameter_name": "Ozone",
759
- "poc": 1,
760
- "latitude": 40.5 + float(ord(state_code[0])) / 10,
761
- "longitude": -90.5 - float(ord(state_code[1])) / 10,
762
- "local_site_name": f"{self.states.get(state_code, 'Unknown')} - Station 2",
763
- "address": "456 Oak Street",
764
- "city_name": "City 2",
765
- "county_name": "County 2",
766
- "state_name": self.states.get(state_code, "Unknown"),
767
- "cbsa_name": f"{self.states.get(state_code, 'Unknown')} Metro Area",
768
- "date_established": "2000-01-01",
769
- "last_sample_date": "2024-04-10"
770
- }
771
- ]
772
-
773
- # Filter by county if provided
774
- if county_code:
775
- monitors = [m for m in monitors if m["county_code"] == county_code]
776
-
777
- # Filter by parameter if provided
778
- if parameter_code:
779
- monitors = [m for m in monitors if m["parameter_code"] == parameter_code]
780
-
781
- return monitors