nakas commited on
Commit
707ea7b
·
verified ·
1 Parent(s): 4e60581

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +184 -98
app.py CHANGED
@@ -60,11 +60,133 @@ def get_raw_data(station_id):
60
  traceback.print_exc()
61
  return None
62
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
63
  def scrape_snow_depth():
64
  """
65
  Scrapes snow depth data from the weather.gov timeseries page.
66
- Searches for a table that has headers with keywords 'time' and 'snow'.
67
- Returns a DataFrame with columns "timestamp" and "snowDepth".
68
  """
69
  url = "https://www.weather.gov/wrh/timeseries?site=YCTIM&hours=720&units=english&chart=on&headers=on&obs=tabular&hourly=false&pview=standard&font=12&plot="
70
  try:
@@ -74,7 +196,6 @@ def scrape_snow_depth():
74
  return pd.DataFrame()
75
  soup = BeautifulSoup(response.text, 'html.parser')
76
 
77
- # Look through all tables for one with headers that include keywords "time" and "snow"
78
  tables = soup.find_all("table")
79
  target_table = None
80
  for table in tables:
@@ -91,7 +212,6 @@ def scrape_snow_depth():
91
  print("No table with required headers found.")
92
  return pd.DataFrame()
93
 
94
- # Determine the column indices using a case-insensitive search
95
  header_row = target_table.find("tr")
96
  headers = [th.get_text(strip=True) for th in header_row.find_all("th")]
97
  time_index = None
@@ -101,11 +221,11 @@ def scrape_snow_depth():
101
  time_index = i
102
  if "snow" in header.lower():
103
  snow_index = i
 
104
  if time_index is None or snow_index is None:
105
  print("Required columns not found in the table headers.")
106
  return pd.DataFrame()
107
 
108
- # Extract rows from the table (skip the header row)
109
  data = []
110
  rows = target_table.find_all("tr")[1:]
111
  for row in rows:
@@ -117,82 +237,15 @@ def scrape_snow_depth():
117
  data.append((time_text, snow_text))
118
 
119
  df = pd.DataFrame(data, columns=["Time", "Snow Depth"])
120
- # Convert "Time" to datetime objects
121
  df["Time"] = pd.to_datetime(df["Time"], errors="coerce")
122
- # Convert "Snow Depth" to numeric values
123
  df["Snow Depth"] = pd.to_numeric(df["Snow Depth"], errors="coerce")
124
  print("Scraped snow depth data:")
125
  print(df.head())
126
- # Rename columns to match the API data
127
  return df.rename(columns={"Time": "timestamp", "Snow Depth": "snowDepth"})
128
  except Exception as e:
129
  print(f"Error scraping snow depth: {e}")
130
  return pd.DataFrame()
131
 
132
- def parse_raw_data(data):
133
- """
134
- Parse the raw JSON data into a DataFrame.
135
- """
136
- if not data or 'features' not in data:
137
- return None
138
-
139
- records = []
140
- for feature in data['features']:
141
- props = feature['properties']
142
-
143
- # Extract any properties with "snow" in their key (if present)
144
- snow_fields = {k: v for k, v in props.items() if 'snow' in k.lower()}
145
- if snow_fields:
146
- print("\nFound snow-related fields:")
147
- for k, v in snow_fields.items():
148
- print(f"{k}: {v}")
149
-
150
- record = {
151
- 'timestamp': props['timestamp'],
152
- 'temperature': props.get('temperature', {}).get('value'),
153
- 'wind_speed': props.get('windSpeed', {}).get('value'),
154
- 'wind_direction': props.get('windDirection', {}).get('value')
155
- }
156
-
157
- # Add any snow-related fields to the record
158
- for k, v in snow_fields.items():
159
- if isinstance(v, dict) and 'value' in v:
160
- record[k] = v['value']
161
- else:
162
- record[k] = v
163
-
164
- records.append(record)
165
-
166
- df = pd.DataFrame(records)
167
-
168
- print("\nDataFrame columns from API:")
169
- print(df.columns.tolist())
170
- print("\nSample of raw API data:")
171
- print(df.head())
172
-
173
- return df
174
-
175
- def process_weather_data(df):
176
- """
177
- Process the weather DataFrame.
178
- """
179
- if df is None or df.empty:
180
- return None
181
-
182
- # Convert timestamp
183
- df['timestamp'] = pd.to_datetime(df['timestamp'])
184
- df['date'] = df['timestamp'].dt.date
185
-
186
- # Convert temperature from Celsius to Fahrenheit if not null
187
- if df['temperature'].notna().all():
188
- df['temperature'] = (df['temperature'] * 9/5) + 32
189
-
190
- # Convert wind speed from km/h to mph if not null (original unit is km/h)
191
- if df['wind_speed'].notna().all():
192
- df['wind_speed'] = df['wind_speed'] * 0.621371
193
-
194
- return df
195
-
196
  def create_wind_rose(ax, data, title):
197
  """
198
  Create a wind rose subplot.
@@ -249,22 +302,26 @@ def create_wind_rose(ax, data, title):
249
  def create_visualizations(df):
250
  """
251
  Create static visualizations using matplotlib.
252
- Plots temperature, wind speed, and snow depth.
253
  """
254
- fig = plt.figure(figsize=(20, 24))
255
- gs = GridSpec(5, 2, figure=fig)
256
 
 
257
  ax1 = fig.add_subplot(gs[0, :])
258
- ax2 = fig.add_subplot(gs[1, :])
259
- ax3 = fig.add_subplot(gs[2, :])
260
-
261
  if not df['temperature'].isna().all():
262
- ax1.plot(df['timestamp'], df['temperature'], linewidth=2)
263
- ax1.set_title('Temperature Over Time')
 
 
 
 
 
264
  ax1.set_ylabel('Temperature (°F)')
265
  ax1.set_xlabel('')
266
  ax1.grid(True)
267
 
 
 
268
  if not df['wind_speed'].isna().all():
269
  ax2.plot(df['timestamp'], df['wind_speed'], linewidth=2)
270
  ax2.set_title('Wind Speed Over Time')
@@ -272,34 +329,62 @@ def create_visualizations(df):
272
  ax2.set_xlabel('')
273
  ax2.grid(True)
274
 
275
- # Plot snow depth if available
276
- if 'snowDepth' in df.columns and not df['snowDepth'].isna().all():
277
- ax3.plot(df['timestamp'], df['snowDepth'], linewidth=2)
278
- ax3.set_ylim(0, 80) # Fixed y-axis limit to 80 inches
 
 
279
  else:
280
- ax3.text(0.5, 0.5, 'No snow depth data available',
281
- horizontalalignment='center',
282
- verticalalignment='center',
283
- transform=ax3.transAxes)
284
- ax3.set_title('Snow Depth')
285
- ax3.set_ylabel('Snow Depth (inches)')
286
- ax3.set_xlabel('')
287
  ax3.grid(True)
288
 
289
- # Format x-axis labels
290
- for ax in [ax1, ax2, ax3]:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
291
  ax.tick_params(axis='x', rotation=45)
292
  ax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y-%m-%d %H:%M'))
293
 
 
294
  dates = sorted(df['date'].unique())
295
  for i, date in enumerate(dates):
296
- if i < 2:
297
- ax = fig.add_subplot(gs[4, i], projection='polar')
298
  day_data = df[df['date'] == date].copy()
299
  create_wind_rose(ax, day_data, pd.to_datetime(date).strftime('%Y-%m-%d'))
300
 
301
  plt.tight_layout()
302
-
303
  return fig
304
 
305
  def get_weather_data(station_id, hours):
@@ -357,7 +442,7 @@ def fetch_and_display(station_id, hours):
357
  # Create Gradio interface
358
  with gr.Blocks() as demo:
359
  gr.Markdown("# Weather Data Viewer")
360
- gr.Markdown("Displays temperature, wind speed, and snow depth from NWS stations.")
361
 
362
  with gr.Row():
363
  station_id = gr.Textbox(label="Station ID", value="YCTIM")
@@ -375,4 +460,5 @@ with gr.Blocks() as demo:
375
  )
376
 
377
  # Launch the app
378
- demo.launch()
 
 
60
  traceback.print_exc()
61
  return None
62
 
63
+ def parse_raw_data(data):
64
+ """
65
+ Parse the raw JSON data into a DataFrame with additional weather information.
66
+ """
67
+ if not data or 'features' not in data:
68
+ return None
69
+
70
+ records = []
71
+ print("\n=== Detailed Weather Information ===")
72
+
73
+ for feature in data['features']:
74
+ props = feature['properties']
75
+
76
+ # Print detailed information for each observation
77
+ print(f"\nTimestamp: {props['timestamp']}")
78
+
79
+ # Present Weather
80
+ if props['presentWeather']:
81
+ print("Present Weather:")
82
+ for weather in props['presentWeather']:
83
+ print(f" - {weather}")
84
+ else:
85
+ print("Present Weather: None reported")
86
+
87
+ # Precipitation
88
+ precip = props.get('precipitationLast3Hours', {}).get('value')
89
+ print(f"Precipitation (last 3 hours): {precip if precip is not None else 'Not reported'} mm")
90
+
91
+ # Raw Message
92
+ raw_msg = props.get('rawMessage', '')
93
+ if raw_msg:
94
+ print(f"Raw Message: {raw_msg}")
95
+ else:
96
+ print("Raw Message: None")
97
+
98
+ # Extract text description if available
99
+ text_desc = props.get('textDescription', '')
100
+ if text_desc:
101
+ print(f"Text Description: {text_desc}")
102
+
103
+ print("-" * 50)
104
+
105
+ # Create record for DataFrame
106
+ record = {
107
+ 'timestamp': props['timestamp'],
108
+ 'temperature': props.get('temperature', {}).get('value'),
109
+ 'wind_speed': props.get('windSpeed', {}).get('value'),
110
+ 'wind_direction': props.get('windDirection', {}).get('value'),
111
+ 'precipitation_3h': props.get('precipitationLast3Hours', {}).get('value'),
112
+ 'present_weather': '; '.join(str(w) for w in props.get('presentWeather', [])) if props.get('presentWeather') else None,
113
+ 'raw_message': props.get('rawMessage', ''),
114
+ 'text_description': props.get('textDescription', ''),
115
+ 'dewpoint': props.get('dewpoint', {}).get('value'),
116
+ 'relative_humidity': props.get('relativeHumidity', {}).get('value'),
117
+ 'wind_chill': props.get('windChill', {}).get('value')
118
+ }
119
+
120
+ records.append(record)
121
+
122
+ df = pd.DataFrame(records)
123
+
124
+ print("\nDataFrame columns from API:")
125
+ print(df.columns.tolist())
126
+ print("\nDetailed sample of raw API data:")
127
+ pd.set_option('display.max_columns', None)
128
+ print(df.head().to_string())
129
+
130
+ return df
131
+
132
+ def process_weather_data(df):
133
+ """
134
+ Process the weather DataFrame including precipitation and additional fields.
135
+ """
136
+ if df is None or df.empty:
137
+ return None
138
+
139
+ # Convert timestamp
140
+ df['timestamp'] = pd.to_datetime(df['timestamp'])
141
+ df['date'] = df['timestamp'].dt.date
142
+
143
+ # Convert temperature from Celsius to Fahrenheit if not null
144
+ if df['temperature'].notna().any():
145
+ df['temperature'] = (df['temperature'] * 9/5) + 32
146
+
147
+ # Convert wind speed from km/h to mph if not null
148
+ if df['wind_speed'].notna().any():
149
+ df['wind_speed'] = df['wind_speed'] * 0.621371
150
+
151
+ # Convert precipitation from mm to inches if not null
152
+ if 'precipitation_3h' in df.columns and df['precipitation_3h'].notna().any():
153
+ df['precipitation_3h'] = df['precipitation_3h'] * 0.0393701 # mm to inches
154
+
155
+ # Convert dewpoint from Celsius to Fahrenheit if not null
156
+ if 'dewpoint' in df.columns and df['dewpoint'].notna().any():
157
+ df['dewpoint'] = (df['dewpoint'] * 9/5) + 32
158
+
159
+ # Convert wind chill from Celsius to Fahrenheit if not null
160
+ if 'wind_chill' in df.columns and df['wind_chill'].notna().any():
161
+ df['wind_chill'] = (df['wind_chill'] * 9/5) + 32
162
+
163
+ # Print summary of weather conditions
164
+ print("\n=== Weather Summary ===")
165
+ print(f"Time range: {df['timestamp'].min()} to {df['timestamp'].max()}")
166
+
167
+ print("\nPrecipitation Summary:")
168
+ precip_data = df[df['precipitation_3h'].notna()]
169
+ if not precip_data.empty:
170
+ print(f"Total precipitation events: {len(precip_data)}")
171
+ print(f"Maximum 3-hour precipitation: {precip_data['precipitation_3h'].max():.2f} inches")
172
+ else:
173
+ print("No precipitation data available")
174
+
175
+ print("\nPresent Weather Conditions Summary:")
176
+ weather_data = df[df['present_weather'].notna()]
177
+ if not weather_data.empty:
178
+ unique_conditions = weather_data['present_weather'].unique()
179
+ print("Observed weather conditions:")
180
+ for condition in unique_conditions:
181
+ print(f" - {condition}")
182
+ else:
183
+ print("No present weather conditions reported")
184
+
185
+ return df
186
+
187
  def scrape_snow_depth():
188
  """
189
  Scrapes snow depth data from the weather.gov timeseries page.
 
 
190
  """
191
  url = "https://www.weather.gov/wrh/timeseries?site=YCTIM&hours=720&units=english&chart=on&headers=on&obs=tabular&hourly=false&pview=standard&font=12&plot="
192
  try:
 
196
  return pd.DataFrame()
197
  soup = BeautifulSoup(response.text, 'html.parser')
198
 
 
199
  tables = soup.find_all("table")
200
  target_table = None
201
  for table in tables:
 
212
  print("No table with required headers found.")
213
  return pd.DataFrame()
214
 
 
215
  header_row = target_table.find("tr")
216
  headers = [th.get_text(strip=True) for th in header_row.find_all("th")]
217
  time_index = None
 
221
  time_index = i
222
  if "snow" in header.lower():
223
  snow_index = i
224
+
225
  if time_index is None or snow_index is None:
226
  print("Required columns not found in the table headers.")
227
  return pd.DataFrame()
228
 
 
229
  data = []
230
  rows = target_table.find_all("tr")[1:]
231
  for row in rows:
 
237
  data.append((time_text, snow_text))
238
 
239
  df = pd.DataFrame(data, columns=["Time", "Snow Depth"])
 
240
  df["Time"] = pd.to_datetime(df["Time"], errors="coerce")
 
241
  df["Snow Depth"] = pd.to_numeric(df["Snow Depth"], errors="coerce")
242
  print("Scraped snow depth data:")
243
  print(df.head())
 
244
  return df.rename(columns={"Time": "timestamp", "Snow Depth": "snowDepth"})
245
  except Exception as e:
246
  print(f"Error scraping snow depth: {e}")
247
  return pd.DataFrame()
248
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
249
  def create_wind_rose(ax, data, title):
250
  """
251
  Create a wind rose subplot.
 
302
  def create_visualizations(df):
303
  """
304
  Create static visualizations using matplotlib.
 
305
  """
306
+ fig = plt.figure(figsize=(20, 30)) # Increased height to accommodate new plots
307
+ gs = GridSpec(7, 2, figure=fig) # Increased number of rows
308
 
309
+ # Temperature plot
310
  ax1 = fig.add_subplot(gs[0, :])
 
 
 
311
  if not df['temperature'].isna().all():
312
+ ax1.plot(df['timestamp'], df['temperature'], linewidth=2, label='Temperature')
313
+ if 'wind_chill' in df.columns and not df['wind_chill'].isna().all():
314
+ ax1.plot(df['timestamp'], df['wind_chill'], linewidth=2, label='Wind Chill', linestyle='--')
315
+ if 'dewpoint' in df.columns and not df['dewpoint'].isna().all():
316
+ ax1.plot(df['timestamp'], df['dewpoint'], linewidth=2, label='Dewpoint', linestyle=':')
317
+ ax1.legend()
318
+ ax1.set_title('Temperature Measurements Over Time')
319
  ax1.set_ylabel('Temperature (°F)')
320
  ax1.set_xlabel('')
321
  ax1.grid(True)
322
 
323
+ # Wind speed plot
324
+ ax2 = fig.add_subplot(gs[1, :])
325
  if not df['wind_speed'].isna().all():
326
  ax2.plot(df['timestamp'], df['wind_speed'], linewidth=2)
327
  ax2.set_title('Wind Speed Over Time')
 
329
  ax2.set_xlabel('')
330
  ax2.grid(True)
331
 
332
+ # Precipitation plot (new)
333
+ ax3 = fig.add_subplot(gs[2, :])
334
+ if 'precipitation_3h' in df.columns and not df['precipitation_3h'].isna().all():
335
+ ax3.bar(df['timestamp'], df['precipitation_3h'], width=0.02)
336
+ ax3.set_title('Precipitation (Last 3 Hours)')
337
+ ax3.set_ylabel('Precipitation (inches)')
338
  else:
339
+ ax3.text(0.5, 0.5, 'No precipitation data available',
340
+ horizontalalignment='center',
341
+ verticalalignment='center',
342
+ transform=ax3.transAxes)
 
 
 
343
  ax3.grid(True)
344
 
345
+ # Relative Humidity plot (new)
346
+ ax4 = fig.add_subplot(gs[3, :])
347
+ if 'relative_humidity' in df.columns and not df['relative_humidity'].isna().all():
348
+ ax4.plot(df['timestamp'], df['relative_humidity'], linewidth=2)
349
+ ax4.set_title('Relative Humidity Over Time')
350
+ ax4.set_ylabel('Relative Humidity (%)')
351
+ ax4.set_ylim(0, 100)
352
+ else:
353
+ ax4.text(0.5, 0.5, 'No humidity data available',
354
+ horizontalalignment='center',
355
+ verticalalignment='center',
356
+ transform=ax4.transAxes)
357
+ ax4.grid(True)
358
+
359
+ # Snow depth plot
360
+ ax5 = fig.add_subplot(gs[4, :])
361
+ if 'snowDepth' in df.columns and not df['snowDepth'].isna().all():
362
+ ax5.plot(df['timestamp'], df['snowDepth'], linewidth=2)
363
+ ax5.set_ylim(0, 80) # Fixed y-axis limit to 80 inches
364
+ else:
365
+ ax5.text(0.5, 0.5, 'No snow depth data available',
366
+ horizontalalignment='center',
367
+ verticalalignment='center',
368
+ transform=ax5.transAxes)
369
+ ax5.set_title('Snow Depth')
370
+ ax5.set_ylabel('Snow Depth (inches)')
371
+ ax5.set_xlabel('')
372
+ ax5.grid(True)
373
+
374
+ # Format x-axis labels for all plots
375
+ for ax in [ax1, ax2, ax3, ax4, ax5]:
376
  ax.tick_params(axis='x', rotation=45)
377
  ax.xaxis.set_major_formatter(plt.matplotlib.dates.DateFormatter('%Y-%m-%d %H:%M'))
378
 
379
+ # Wind rose plots
380
  dates = sorted(df['date'].unique())
381
  for i, date in enumerate(dates):
382
+ if i < 2: # Only show last two days
383
+ ax = fig.add_subplot(gs[6, i], projection='polar')
384
  day_data = df[df['date'] == date].copy()
385
  create_wind_rose(ax, day_data, pd.to_datetime(date).strftime('%Y-%m-%d'))
386
 
387
  plt.tight_layout()
 
388
  return fig
389
 
390
  def get_weather_data(station_id, hours):
 
442
  # Create Gradio interface
443
  with gr.Blocks() as demo:
444
  gr.Markdown("# Weather Data Viewer")
445
+ gr.Markdown("Displays comprehensive weather data from NWS stations including temperature, wind, precipitation, and snow depth.")
446
 
447
  with gr.Row():
448
  station_id = gr.Textbox(label="Station ID", value="YCTIM")
 
460
  )
461
 
462
  # Launch the app
463
+ if __name__ == "__main__":
464
+ demo.launch()