Spaces:
Sleeping
Sleeping
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 +0 -0
- __pycache__/visualization.cpython-313.pyc +0 -0
- app.py +25 -2
- visualization.py +137 -2
__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,
|
|
|
|
| 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   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 |
)
|