Spaces:
Sleeping
Sleeping
Update app.py
Browse files
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,
|
| 255 |
-
gs = GridSpec(
|
| 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 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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 |
-
#
|
| 276 |
-
|
| 277 |
-
|
| 278 |
-
ax3.
|
|
|
|
|
|
|
| 279 |
else:
|
| 280 |
-
ax3.text(0.5, 0.5, 'No
|
| 281 |
-
|
| 282 |
-
|
| 283 |
-
|
| 284 |
-
ax3.set_title('Snow Depth')
|
| 285 |
-
ax3.set_ylabel('Snow Depth (inches)')
|
| 286 |
-
ax3.set_xlabel('')
|
| 287 |
ax3.grid(True)
|
| 288 |
|
| 289 |
-
#
|
| 290 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 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[
|
| 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
|
| 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 |
-
|
|
|
|
|
|
| 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()
|