jeffliulab commited on
Commit
4692887
·
1 Parent(s): 2507ab5

Add precipitation/wind/humidity maps; fix titles to clarify input vs forecast

Browse files
__pycache__/model_utils.cpython-313.pyc CHANGED
Binary files a/__pycache__/model_utils.cpython-313.pyc and b/__pycache__/model_utils.cpython-313.pyc differ
 
__pycache__/visualization.cpython-313.pyc CHANGED
Binary files a/__pycache__/visualization.cpython-313.pyc and b/__pycache__/visualization.cpython-313.pyc differ
 
app.py CHANGED
@@ -15,6 +15,9 @@ from model_utils import run_forecast, load_model, AVAILABLE_MODELS
15
  from visualization import (
16
  get_static_maps,
17
  plot_temperature,
 
 
 
18
  )
19
 
20
  logging.basicConfig(level=logging.INFO)
@@ -267,9 +270,12 @@ def do_forecast(model_display: str, progress=gr.Progress()):
267
  model_label = model_display.split("(")[0].strip()
268
  hero = _hero_html(r, cycle_str, forecast_str, model_label)
269
  temp_fig = plot_temperature(input_array, r, cycle_str, forecast_str)
 
 
 
270
  status = f"Forecast complete — HRRR cycle {cycle_str}"
271
 
272
- return hero, sat_fig, street_fig, temp_fig, status
273
 
274
 
275
  # ── Build UI ──────────────────────────────────────────────────────────
@@ -324,6 +330,22 @@ with gr.Blocks(title="Tufts Jumbo Weather Forecast", css=CUSTOM_CSS) as demo:
324
  elem_classes=["map-cell"],
325
  )
326
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
327
  # ── About ─────────────────────────────────────────────────────
328
  with gr.Accordion("About this demo", open=False):
329
  gr.Markdown(
@@ -340,7 +362,8 @@ with gr.Blocks(title="Tufts Jumbo Weather Forecast", css=CUSTOM_CSS) as demo:
340
  run_btn.click(
341
  fn=do_forecast,
342
  inputs=[model_dd],
343
- outputs=[hero_html, sat_plot, street_plot, temp_plot, status_bar],
 
344
  )
345
 
346
 
 
15
  from visualization import (
16
  get_static_maps,
17
  plot_temperature,
18
+ plot_precipitation,
19
+ plot_wind_speed,
20
+ plot_humidity,
21
  )
22
 
23
  logging.basicConfig(level=logging.INFO)
 
270
  model_label = model_display.split("(")[0].strip()
271
  hero = _hero_html(r, cycle_str, forecast_str, model_label)
272
  temp_fig = plot_temperature(input_array, r, cycle_str, forecast_str)
273
+ precip_fig = plot_precipitation(input_array, r, cycle_str, forecast_str)
274
+ wind_fig = plot_wind_speed(input_array, r, cycle_str, forecast_str)
275
+ humid_fig = plot_humidity(input_array, r, cycle_str, forecast_str)
276
  status = f"Forecast complete — HRRR cycle {cycle_str}"
277
 
278
+ return hero, sat_fig, street_fig, temp_fig, precip_fig, wind_fig, humid_fig, status
279
 
280
 
281
  # ── Build UI ──────────────────────────────────────────────────────────
 
330
  elem_classes=["map-cell"],
331
  )
332
 
333
+ gr.HTML('<div class="maps-heading">Current Input Fields &ensp; with 24 h Forecast at Jumbo</div>')
334
+
335
+ with gr.Row(equal_height=True):
336
+ precip_plot = gr.Plot(
337
+ label="Precipitation",
338
+ elem_classes=["map-cell"],
339
+ )
340
+ wind_plot = gr.Plot(
341
+ label="Wind Speed",
342
+ elem_classes=["map-cell"],
343
+ )
344
+ humid_plot = gr.Plot(
345
+ label="Humidity",
346
+ elem_classes=["map-cell"],
347
+ )
348
+
349
  # ── About ─────────────────────────────────────────────────────
350
  with gr.Accordion("About this demo", open=False):
351
  gr.Markdown(
 
362
  run_btn.click(
363
  fn=do_forecast,
364
  inputs=[model_dd],
365
+ outputs=[hero_html, sat_plot, street_plot, temp_plot,
366
+ precip_plot, wind_plot, humid_plot, status_bar],
367
  )
368
 
369
 
visualization.py CHANGED
@@ -176,7 +176,7 @@ def plot_temperature(
176
 
177
  temp_c = forecast["temperature_c"]
178
  temp_f = forecast["temperature_f"]
179
- label = f"Forecast {forecast_str}\n{temp_c:+.1f} °C / {temp_f:.0f} °F"
180
  ax.text(
181
  JUMBO_LON + 0.45, JUMBO_LAT + 0.35, label,
182
  fontsize=8.5, fontweight="bold", fontfamily="sans-serif",
@@ -185,7 +185,142 @@ def plot_temperature(
185
  )
186
 
187
  ax.set_title(
188
- f"2 m Temperature — {cycle_str}",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
189
  fontsize=12, fontweight="600",
190
  fontfamily="sans-serif", pad=8, color="#1D1D1F",
191
  )
 
176
 
177
  temp_c = forecast["temperature_c"]
178
  temp_f = forecast["temperature_f"]
179
+ label = f"24h Forecast: {forecast_str}\n{temp_c:+.1f} °C / {temp_f:.0f} °F"
180
  ax.text(
181
  JUMBO_LON + 0.45, JUMBO_LAT + 0.35, label,
182
  fontsize=8.5, fontweight="bold", fontfamily="sans-serif",
 
185
  )
186
 
187
  ax.set_title(
188
+ f"Current 2 m Temperature (Input) — {cycle_str}",
189
+ fontsize=12, fontweight="600",
190
+ fontfamily="sans-serif", pad=8, color="#1D1D1F",
191
+ )
192
+ fig.subplots_adjust(left=0.02, right=0.95, bottom=0.02, top=0.93)
193
+ return fig
194
+
195
+
196
+ def plot_precipitation(
197
+ input_array: np.ndarray,
198
+ forecast: dict,
199
+ cycle_str: str,
200
+ forecast_str: str,
201
+ ) -> Figure:
202
+ """Render 1-hour accumulated precipitation map with forecast annotation."""
203
+ fig, ax = _make_ax()
204
+
205
+ precip_field = input_array[:, :, 6] # APCP_1hr_acc_fcst@surface (mm)
206
+ masked = np.ma.masked_invalid(precip_field)
207
+
208
+ im = ax.pcolormesh(
209
+ X_GRID, Y_GRID, masked,
210
+ cmap="YlGnBu", shading="auto", transform=PROJ, zorder=5,
211
+ vmin=0, vmax=10,
212
+ )
213
+ ax.add_feature(cfeature.COASTLINE, linewidth=0.5, color="#444", zorder=10)
214
+ ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor="#666", zorder=10)
215
+
216
+ cbar = fig.colorbar(im, ax=ax, shrink=0.72, pad=0.03, aspect=28)
217
+ cbar.set_label("mm", fontsize=10, fontfamily="sans-serif")
218
+ cbar.ax.tick_params(labelsize=8)
219
+
220
+ ax.plot(JUMBO_LON, JUMBO_LAT, **_MARKER)
221
+
222
+ precip = forecast["precipitation_mm"]
223
+ label = f"24h Forecast: {forecast_str}\n{precip:.2f} mm — {forecast['rain_status']}"
224
+ ax.text(
225
+ JUMBO_LON + 0.45, JUMBO_LAT + 0.35, label,
226
+ fontsize=8.5, fontweight="bold", fontfamily="sans-serif",
227
+ color="white", transform=ccrs.PlateCarree(), zorder=25,
228
+ bbox=dict(boxstyle="round,pad=0.35", fc="#1C1C1E", ec="white", alpha=0.88, lw=0.8),
229
+ )
230
+
231
+ ax.set_title(
232
+ f"Current Precipitation (Input) — {cycle_str}",
233
+ fontsize=12, fontweight="600",
234
+ fontfamily="sans-serif", pad=8, color="#1D1D1F",
235
+ )
236
+ fig.subplots_adjust(left=0.02, right=0.95, bottom=0.02, top=0.93)
237
+ return fig
238
+
239
+
240
+ def plot_wind_speed(
241
+ input_array: np.ndarray,
242
+ forecast: dict,
243
+ cycle_str: str,
244
+ forecast_str: str,
245
+ ) -> Figure:
246
+ """Render 10 m wind speed map with forecast annotation."""
247
+ fig, ax = _make_ax()
248
+
249
+ u = input_array[:, :, 2] # UGRD@10m (m/s)
250
+ v = input_array[:, :, 3] # VGRD@10m (m/s)
251
+ speed_field = np.sqrt(u**2 + v**2)
252
+ masked = np.ma.masked_invalid(speed_field)
253
+
254
+ im = ax.pcolormesh(
255
+ X_GRID, Y_GRID, masked,
256
+ cmap="viridis", shading="auto", transform=PROJ, zorder=5,
257
+ vmin=0, vmax=20,
258
+ )
259
+ ax.add_feature(cfeature.COASTLINE, linewidth=0.5, color="#444", zorder=10)
260
+ ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor="#666", zorder=10)
261
+
262
+ cbar = fig.colorbar(im, ax=ax, shrink=0.72, pad=0.03, aspect=28)
263
+ cbar.set_label("m/s", fontsize=10, fontfamily="sans-serif")
264
+ cbar.ax.tick_params(labelsize=8)
265
+
266
+ ax.plot(JUMBO_LON, JUMBO_LAT, **_MARKER)
267
+
268
+ ws = forecast["wind_speed_ms"]
269
+ wd = forecast["wind_dir_str"]
270
+ label = f"24h Forecast: {forecast_str}\n{ws:.1f} m/s from {wd}"
271
+ ax.text(
272
+ JUMBO_LON + 0.45, JUMBO_LAT + 0.35, label,
273
+ fontsize=8.5, fontweight="bold", fontfamily="sans-serif",
274
+ color="white", transform=ccrs.PlateCarree(), zorder=25,
275
+ bbox=dict(boxstyle="round,pad=0.35", fc="#1C1C1E", ec="white", alpha=0.88, lw=0.8),
276
+ )
277
+
278
+ ax.set_title(
279
+ f"Current 10 m Wind Speed (Input) — {cycle_str}",
280
+ fontsize=12, fontweight="600",
281
+ fontfamily="sans-serif", pad=8, color="#1D1D1F",
282
+ )
283
+ fig.subplots_adjust(left=0.02, right=0.95, bottom=0.02, top=0.93)
284
+ return fig
285
+
286
+
287
+ def plot_humidity(
288
+ input_array: np.ndarray,
289
+ forecast: dict,
290
+ cycle_str: str,
291
+ forecast_str: str,
292
+ ) -> Figure:
293
+ """Render 2 m relative humidity map with forecast annotation."""
294
+ fig, ax = _make_ax()
295
+
296
+ rh_field = input_array[:, :, 1] # RH@2m_above_ground (%)
297
+ masked = np.ma.masked_invalid(rh_field)
298
+
299
+ im = ax.pcolormesh(
300
+ X_GRID, Y_GRID, masked,
301
+ cmap="BrBG", shading="auto", transform=PROJ, zorder=5,
302
+ vmin=0, vmax=100,
303
+ )
304
+ ax.add_feature(cfeature.COASTLINE, linewidth=0.5, color="#444", zorder=10)
305
+ ax.add_feature(cfeature.STATES, linewidth=0.3, edgecolor="#666", zorder=10)
306
+
307
+ cbar = fig.colorbar(im, ax=ax, shrink=0.72, pad=0.03, aspect=28)
308
+ cbar.set_label("%", fontsize=10, fontfamily="sans-serif")
309
+ cbar.ax.tick_params(labelsize=8)
310
+
311
+ ax.plot(JUMBO_LON, JUMBO_LAT, **_MARKER)
312
+
313
+ rh = forecast["humidity_pct"]
314
+ label = f"24h Forecast: {forecast_str}\n{rh:.0f}%"
315
+ ax.text(
316
+ JUMBO_LON + 0.45, JUMBO_LAT + 0.35, label,
317
+ fontsize=8.5, fontweight="bold", fontfamily="sans-serif",
318
+ color="white", transform=ccrs.PlateCarree(), zorder=25,
319
+ bbox=dict(boxstyle="round,pad=0.35", fc="#1C1C1E", ec="white", alpha=0.88, lw=0.8),
320
+ )
321
+
322
+ ax.set_title(
323
+ f"Current 2 m Humidity (Input) — {cycle_str}",
324
  fontsize=12, fontweight="600",
325
  fontfamily="sans-serif", pad=8, color="#1D1D1F",
326
  )