Add HRRR multi-domain radar viewer with NEXRAD comparison
Browse files- Support CONUS, Alaska, Hawaii, and Full domain views
- Add NEXRAD real-time radar WMS layer (covers all domains)
- Implement forecast hour selection (F000-F018) for HRRR comparison
- Add domain boundary visualization on map
- Include comprehensive HRRR documentation and data access info
- Add measurement tools and fullscreen controls
- Explain F000 vs NEXRAD comparison methodology
NEXRAD WMS provides real-time composite reflectivity across all
requested domains, allowing validation of HRRR F000 analysis.
π€ Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude <noreply@anthropic.com>
app.py
CHANGED
|
@@ -6,94 +6,80 @@ from datetime import datetime, timedelta
|
|
| 6 |
from io import BytesIO
|
| 7 |
from PIL import Image
|
| 8 |
import base64
|
| 9 |
-
from bs4 import BeautifulSoup
|
| 10 |
|
| 11 |
-
#
|
| 12 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
|
| 14 |
def get_available_runs():
|
| 15 |
"""
|
| 16 |
Generate list of recent model run times
|
| 17 |
-
|
| 18 |
"""
|
| 19 |
runs = []
|
| 20 |
-
# Generate recent run times (going back 48 hours)
|
| 21 |
now = datetime.utcnow()
|
| 22 |
-
|
| 23 |
-
# Round to nearest hour
|
| 24 |
current_hour = now.replace(minute=0, second=0, microsecond=0)
|
| 25 |
|
| 26 |
-
for i in range(
|
| 27 |
run_time = current_hour - timedelta(hours=i)
|
| 28 |
-
runs.append(run_time.strftime("%Y%m%d%H"))
|
| 29 |
|
| 30 |
return runs
|
| 31 |
|
| 32 |
-
def try_fetch_rrfs_image(run_time, forecast_hour, plot_type='cref_full_sfc'):
|
| 33 |
-
"""
|
| 34 |
-
Try to fetch RRFS radar image from NOAA RapidRefresh server
|
| 35 |
-
|
| 36 |
-
Args:
|
| 37 |
-
run_time: Model run time in YYYYMMDDHH format
|
| 38 |
-
forecast_hour: Forecast hour (0-18)
|
| 39 |
-
plot_type: Type of plot (default: cref_full_sfc for composite reflectivity)
|
| 40 |
-
|
| 41 |
-
Returns:
|
| 42 |
-
Image URL if found, None otherwise
|
| 43 |
-
"""
|
| 44 |
-
# Try different URL patterns based on NOAA structure
|
| 45 |
-
url_patterns = [
|
| 46 |
-
f"https://rapidrefresh.noaa.gov/for_web/rrfs_na_3km_ncep_jet/{run_time}/full/{plot_type}_f{forecast_hour:03d}.png",
|
| 47 |
-
f"https://rapidrefresh.noaa.gov/for_web/rrfs_na/{run_time}/full/{plot_type}_f{forecast_hour:03d}.png",
|
| 48 |
-
f"https://rapidrefresh.noaa.gov/rrfs_graphics/{run_time}/{plot_type}_f{forecast_hour:03d}.png",
|
| 49 |
-
]
|
| 50 |
-
|
| 51 |
-
for url in url_patterns:
|
| 52 |
-
try:
|
| 53 |
-
response = requests.head(url, timeout=5)
|
| 54 |
-
if response.status_code == 200:
|
| 55 |
-
return url
|
| 56 |
-
except:
|
| 57 |
-
continue
|
| 58 |
-
|
| 59 |
-
return None
|
| 60 |
-
|
| 61 |
def create_radar_legend():
|
| 62 |
"""Create HTML legend for radar reflectivity"""
|
| 63 |
legend_html = '''
|
| 64 |
-
<div style="position: fixed; top:
|
| 65 |
background-color: white; padding: 10px; border-radius: 5px;
|
| 66 |
-
border: 2px solid #333; font-family: Arial; font-size:
|
| 67 |
-
|
| 68 |
-
<
|
| 69 |
-
|
| 70 |
-
<div><span style="background: #
|
| 71 |
-
<div><span style="background: #
|
| 72 |
-
<div><span style="background: #
|
| 73 |
-
<div><span style="background: #
|
| 74 |
-
<div><span style="background: #
|
| 75 |
-
<div><span style="background: #
|
| 76 |
-
<div><span style="background: #
|
|
|
|
| 77 |
</div>
|
| 78 |
</div>
|
| 79 |
'''
|
| 80 |
return legend_html
|
| 81 |
|
| 82 |
-
def generate_map(run_time_str, forecast_hour):
|
| 83 |
"""
|
| 84 |
-
Generate Folium map with
|
| 85 |
|
| 86 |
Args:
|
| 87 |
-
run_time_str: Model run time
|
| 88 |
-
forecast_hour: Forecast hour
|
|
|
|
|
|
|
|
|
|
| 89 |
|
| 90 |
Returns:
|
| 91 |
folium.Map object
|
| 92 |
"""
|
| 93 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 94 |
m = folium.Map(
|
| 95 |
-
location=[
|
| 96 |
-
zoom_start=
|
| 97 |
tiles='OpenStreetMap'
|
| 98 |
)
|
| 99 |
|
|
@@ -103,87 +89,85 @@ def generate_map(run_time_str, forecast_hour):
|
|
| 103 |
|
| 104 |
try:
|
| 105 |
# Parse run time
|
| 106 |
-
dt = datetime.strptime(run_time_str, "%Y%m%d%H")
|
| 107 |
valid_time = dt + timedelta(hours=int(forecast_hour))
|
| 108 |
|
| 109 |
-
#
|
| 110 |
-
|
| 111 |
-
|
| 112 |
-
background-color: white; padding: 10px; border-radius: 5px;
|
| 113 |
-
border: 2px solid #333; font-family: Arial;'>
|
| 114 |
-
<b>RRFS Composite Reflectivity</b><br>
|
| 115 |
-
Run: {dt.strftime("%Y-%m-%d %H:00 UTC")}<br>
|
| 116 |
-
Forecast Hour: F{int(forecast_hour):03d}<br>
|
| 117 |
-
Valid: {valid_time.strftime("%Y-%m-%d %H:00 UTC")}<br>
|
| 118 |
-
<span style='color: #0066cc; font-size: 10px;'>Source: NOAA RRFS</span>
|
| 119 |
-
</div>
|
| 120 |
-
"""
|
| 121 |
-
|
| 122 |
-
# Try to fetch RRFS image
|
| 123 |
-
image_url = try_fetch_rrfs_image(run_time_str, int(forecast_hour))
|
| 124 |
-
|
| 125 |
-
if image_url:
|
| 126 |
-
# Successfully found RRFS image
|
| 127 |
-
# Add image overlay (approximate bounds for CONUS)
|
| 128 |
-
folium.raster_layers.ImageOverlay(
|
| 129 |
-
image=image_url,
|
| 130 |
-
bounds=[[20.0, -130.0], [52.0, -60.0]],
|
| 131 |
-
opacity=0.7,
|
| 132 |
-
name='RRFS Radar'
|
| 133 |
-
).add_to(m)
|
| 134 |
-
|
| 135 |
-
# Add legend
|
| 136 |
-
m.get_root().html.add_child(folium.Element(create_radar_legend()))
|
| 137 |
-
|
| 138 |
-
status_html = status_html.replace('NOAA RRFS', f'NOAA RRFS - Data Available')
|
| 139 |
-
else:
|
| 140 |
-
# RRFS image not available, use NOAA real-time radar WMS as alternative
|
| 141 |
-
# Add NOAA MRMS radar composite
|
| 142 |
wms_url = 'https://mapservices.weather.noaa.gov/eventdriven/services/radar/radar_base_reflectivity/MapServer/WMSServer'
|
| 143 |
|
| 144 |
folium.raster_layers.WmsTileLayer(
|
| 145 |
url=wms_url,
|
| 146 |
layers='0',
|
| 147 |
-
name='
|
| 148 |
format='image/png',
|
| 149 |
transparent=True,
|
| 150 |
-
opacity=0.
|
| 151 |
-
attr='NOAA'
|
|
|
|
|
|
|
| 152 |
).add_to(m)
|
| 153 |
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
| 158 |
-
status_html = f"""
|
| 159 |
<div style='position: fixed; bottom: 20px; left: 20px; z-index: 1000;
|
| 160 |
-
background-color:
|
| 161 |
-
border: 2px solid #
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
<
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 168 |
</div>
|
| 169 |
"""
|
|
|
|
| 170 |
|
| 171 |
-
# Add
|
| 172 |
-
m.get_root().html.add_child(folium.Element(
|
| 173 |
|
| 174 |
-
# Add
|
| 175 |
-
|
| 176 |
<div style='position: fixed; top: 20px; left: 20px; z-index: 999;
|
| 177 |
-
background-color: rgba(255, 255, 255, 0.
|
| 178 |
-
border-radius: 5px; border: 1px solid #
|
| 179 |
-
font-size:
|
| 180 |
-
<b>
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 184 |
</div>
|
| 185 |
"""
|
| 186 |
-
m.get_root().html.add_child(folium.Element(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 187 |
|
| 188 |
except Exception as e:
|
| 189 |
# Add error message
|
|
@@ -191,40 +175,44 @@ def generate_map(run_time_str, forecast_hour):
|
|
| 191 |
<div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
| 192 |
z-index: 1001; background-color: #ffcccc; padding: 20px;
|
| 193 |
border-radius: 10px; border: 2px solid #ff0000; font-family: Arial;'>
|
| 194 |
-
<h3>Error Loading Data</h3>
|
| 195 |
<p>Error: {str(e)}</p>
|
| 196 |
-
<p>Please try
|
| 197 |
</div>
|
| 198 |
"""
|
| 199 |
m.get_root().html.add_child(folium.Element(error_html))
|
| 200 |
|
| 201 |
# Add layer control
|
| 202 |
-
folium.LayerControl().add_to(m)
|
| 203 |
|
| 204 |
# Add fullscreen option
|
| 205 |
-
plugins.Fullscreen().add_to(m)
|
|
|
|
|
|
|
|
|
|
| 206 |
|
| 207 |
return m
|
| 208 |
|
| 209 |
def create_interface():
|
| 210 |
"""Create Gradio interface"""
|
| 211 |
|
| 212 |
-
with gr.Blocks(title="
|
| 213 |
gr.Markdown("""
|
| 214 |
-
# π©οΈ
|
| 215 |
|
| 216 |
-
View NOAA Rapid Refresh
|
|
|
|
| 217 |
|
| 218 |
-
**Data Source:** NOAA
|
| 219 |
""")
|
| 220 |
|
| 221 |
with gr.Row():
|
| 222 |
-
with gr.Column(scale=
|
| 223 |
run_time = gr.Dropdown(
|
| 224 |
choices=get_available_runs(),
|
| 225 |
value=get_available_runs()[0],
|
| 226 |
-
label="Model Run Time (UTC)",
|
| 227 |
-
info="
|
| 228 |
)
|
| 229 |
|
| 230 |
with gr.Column(scale=1):
|
|
@@ -233,71 +221,120 @@ def create_interface():
|
|
| 233 |
maximum=18,
|
| 234 |
step=1,
|
| 235 |
value=0,
|
| 236 |
-
label="Forecast Hour",
|
| 237 |
-
info="
|
| 238 |
)
|
| 239 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 240 |
load_btn = gr.Button("π Load Radar Data", variant="primary", size="lg")
|
| 241 |
|
| 242 |
with gr.Row():
|
| 243 |
-
map_output = gr.HTML(label="Radar Map")
|
| 244 |
|
| 245 |
-
def load_map(run_time, forecast_hour):
|
| 246 |
-
m = generate_map(run_time, int(forecast_hour))
|
| 247 |
return m._repr_html_()
|
| 248 |
|
| 249 |
load_btn.click(
|
| 250 |
fn=load_map,
|
| 251 |
-
inputs=[run_time, forecast_hour],
|
| 252 |
outputs=map_output
|
| 253 |
)
|
| 254 |
|
| 255 |
# Auto-load on startup
|
| 256 |
demo.load(
|
| 257 |
fn=load_map,
|
| 258 |
-
inputs=[run_time, forecast_hour],
|
| 259 |
outputs=map_output
|
| 260 |
)
|
| 261 |
|
| 262 |
gr.Markdown("""
|
| 263 |
---
|
| 264 |
-
## π About
|
|
|
|
|
|
|
|
|
|
| 265 |
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
|
|
|
|
|
|
|
| 269 |
|
| 270 |
### Composite Reflectivity
|
| 271 |
|
| 272 |
-
Composite reflectivity shows the maximum radar reflectivity in a vertical column
|
| 273 |
-
-
|
| 274 |
-
-
|
| 275 |
-
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 276 |
|
| 277 |
### Data Information
|
| 278 |
|
| 279 |
-
- **Model:**
|
| 280 |
- **Grid Spacing:** 3 km
|
| 281 |
-
- **
|
| 282 |
-
- **Forecast Length:**
|
| 283 |
-
- **
|
| 284 |
-
- **
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 285 |
|
| 286 |
-
|
|
|
|
|
|
|
| 287 |
|
| 288 |
-
Real-
|
| 289 |
-
|
|
|
|
| 290 |
|
| 291 |
-
###
|
| 292 |
|
| 293 |
-
- [
|
| 294 |
-
- [
|
| 295 |
-
- [
|
|
|
|
| 296 |
|
| 297 |
---
|
| 298 |
|
| 299 |
-
<p style='text-align: center; color: #666; font-size:
|
| 300 |
-
Data provided by NOAA | For research purposes only | Not for operational use
|
| 301 |
</p>
|
| 302 |
""")
|
| 303 |
|
|
|
|
| 6 |
from io import BytesIO
|
| 7 |
from PIL import Image
|
| 8 |
import base64
|
|
|
|
| 9 |
|
| 10 |
+
# Domain bounds for different regions
|
| 11 |
+
DOMAIN_BOUNDS = {
|
| 12 |
+
'conus': [[24.0, -125.0], [50.0, -66.0]],
|
| 13 |
+
'alaska': [[51.0, -180.0], [72.0, -130.0]],
|
| 14 |
+
'hawaii': [[18.0, -161.0], [23.0, -154.0]],
|
| 15 |
+
'full': [[18.0, -180.0], [72.0, -66.0]]
|
| 16 |
+
}
|
| 17 |
|
| 18 |
def get_available_runs():
|
| 19 |
"""
|
| 20 |
Generate list of recent model run times
|
| 21 |
+
HRRR runs every hour
|
| 22 |
"""
|
| 23 |
runs = []
|
|
|
|
| 24 |
now = datetime.utcnow()
|
|
|
|
|
|
|
| 25 |
current_hour = now.replace(minute=0, second=0, microsecond=0)
|
| 26 |
|
| 27 |
+
for i in range(24): # Last 24 hours
|
| 28 |
run_time = current_hour - timedelta(hours=i)
|
| 29 |
+
runs.append(run_time.strftime("%Y-%m-%d %H:00 UTC"))
|
| 30 |
|
| 31 |
return runs
|
| 32 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 33 |
def create_radar_legend():
|
| 34 |
"""Create HTML legend for radar reflectivity"""
|
| 35 |
legend_html = '''
|
| 36 |
+
<div style="position: fixed; top: 80px; right: 20px; z-index: 1000;
|
| 37 |
background-color: white; padding: 10px; border-radius: 5px;
|
| 38 |
+
border: 2px solid #333; font-family: Arial; font-size: 11px;
|
| 39 |
+
max-width: 180px;">
|
| 40 |
+
<b style="font-size: 12px;">Reflectivity (dBZ)</b><br>
|
| 41 |
+
<div style="display: flex; flex-direction: column; margin-top: 5px; gap: 2px;">
|
| 42 |
+
<div><span style="background: #9854c6; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Extreme (60+)</div>
|
| 43 |
+
<div><span style="background: #f800fd; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Severe (50-60)</div>
|
| 44 |
+
<div><span style="background: #bc0000; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Heavy (40-50)</div>
|
| 45 |
+
<div><span style="background: #fd0000; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Moderate (30-40)</div>
|
| 46 |
+
<div><span style="background: #fd9500; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Light (25-30)</div>
|
| 47 |
+
<div><span style="background: #fdf802; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Very Light (20-25)</div>
|
| 48 |
+
<div><span style="background: #02fd02; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Weak (10-20)</div>
|
| 49 |
+
<div><span style="background: #019ff4; width: 18px; height: 10px; display: inline-block; margin-right: 4px;"></span>Trace (<10)</div>
|
| 50 |
</div>
|
| 51 |
</div>
|
| 52 |
'''
|
| 53 |
return legend_html
|
| 54 |
|
| 55 |
+
def generate_map(run_time_str, forecast_hour, domain_selection, show_nexrad, show_hrrr_info):
|
| 56 |
"""
|
| 57 |
+
Generate Folium map with multi-domain radar coverage
|
| 58 |
|
| 59 |
Args:
|
| 60 |
+
run_time_str: Model run time string
|
| 61 |
+
forecast_hour: Forecast hour (0-18)
|
| 62 |
+
domain_selection: Which domain to display
|
| 63 |
+
show_nexrad: Whether to show NEXRAD real-time radar
|
| 64 |
+
show_hrrr_info: Whether to show HRRR forecast information
|
| 65 |
|
| 66 |
Returns:
|
| 67 |
folium.Map object
|
| 68 |
"""
|
| 69 |
+
# Set map center and zoom based on domain
|
| 70 |
+
domain_configs = {
|
| 71 |
+
'full': {'location': [45.0, -100.0], 'zoom': 3},
|
| 72 |
+
'conus': {'location': [39.0, -98.0], 'zoom': 4},
|
| 73 |
+
'alaska': {'location': [64.0, -152.0], 'zoom': 4},
|
| 74 |
+
'hawaii': {'location': [20.5, -157.0], 'zoom': 7}
|
| 75 |
+
}
|
| 76 |
+
|
| 77 |
+
config = domain_configs.get(domain_selection, domain_configs['conus'])
|
| 78 |
+
|
| 79 |
+
# Create base map
|
| 80 |
m = folium.Map(
|
| 81 |
+
location=config['location'],
|
| 82 |
+
zoom_start=config['zoom'],
|
| 83 |
tiles='OpenStreetMap'
|
| 84 |
)
|
| 85 |
|
|
|
|
| 89 |
|
| 90 |
try:
|
| 91 |
# Parse run time
|
| 92 |
+
dt = datetime.strptime(run_time_str, "%Y-%m-%d %H:%M UTC")
|
| 93 |
valid_time = dt + timedelta(hours=int(forecast_hour))
|
| 94 |
|
| 95 |
+
# Add NEXRAD real-time radar if requested
|
| 96 |
+
if show_nexrad:
|
| 97 |
+
# NOAA MRMS (Multi-Radar Multi-Sensor) - Covers CONUS, Alaska, Hawaii, Puerto Rico
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 98 |
wms_url = 'https://mapservices.weather.noaa.gov/eventdriven/services/radar/radar_base_reflectivity/MapServer/WMSServer'
|
| 99 |
|
| 100 |
folium.raster_layers.WmsTileLayer(
|
| 101 |
url=wms_url,
|
| 102 |
layers='0',
|
| 103 |
+
name='NEXRAD Real-Time Radar',
|
| 104 |
format='image/png',
|
| 105 |
transparent=True,
|
| 106 |
+
opacity=0.65,
|
| 107 |
+
attr='NOAA',
|
| 108 |
+
overlay=True,
|
| 109 |
+
control=True
|
| 110 |
).add_to(m)
|
| 111 |
|
| 112 |
+
# Add HRRR forecast information overlay if requested
|
| 113 |
+
if show_hrrr_info:
|
| 114 |
+
# Create info box about HRRR forecast
|
| 115 |
+
hrrr_info_html = f"""
|
|
|
|
| 116 |
<div style='position: fixed; bottom: 20px; left: 20px; z-index: 1000;
|
| 117 |
+
background-color: rgba(255, 255, 255, 0.95); padding: 12px;
|
| 118 |
+
border-radius: 5px; border: 2px solid #0066cc; font-family: Arial;
|
| 119 |
+
max-width: 320px; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'>
|
| 120 |
+
<b style='color: #0066cc; font-size: 14px;'>HRRR Forecast Information</b><br>
|
| 121 |
+
<div style='margin-top: 8px; font-size: 11px;'>
|
| 122 |
+
<b>Model Run:</b> {dt.strftime("%Y-%m-%d %H:00 UTC")}<br>
|
| 123 |
+
<b>Forecast Hour:</b> F{int(forecast_hour):03d}<br>
|
| 124 |
+
<b>Valid Time:</b> {valid_time.strftime("%Y-%m-%d %H:00 UTC")}<br>
|
| 125 |
+
<b>Domain:</b> {domain_selection.upper()}<br>
|
| 126 |
+
</div>
|
| 127 |
+
<div style='margin-top: 8px; padding: 6px; background: #e8f4f8; border-radius: 3px; font-size: 10px;'>
|
| 128 |
+
<b>About HRRR:</b> High-Resolution Rapid Refresh model provides
|
| 129 |
+
3km forecasts every hour out to 18-48 hours for CONUS, Alaska, and Hawaii.
|
| 130 |
+
</div>
|
| 131 |
+
<div style='margin-top: 6px; font-size: 9px; color: #666;'>
|
| 132 |
+
<i>Currently showing NEXRAD real-time composite reflectivity.
|
| 133 |
+
HRRR forecast data available via GRIB2 from NOAA NOMADS.</i>
|
| 134 |
+
</div>
|
| 135 |
</div>
|
| 136 |
"""
|
| 137 |
+
m.get_root().html.add_child(folium.Element(hrrr_info_html))
|
| 138 |
|
| 139 |
+
# Add legend
|
| 140 |
+
m.get_root().html.add_child(folium.Element(create_radar_legend()))
|
| 141 |
|
| 142 |
+
# Add coverage info
|
| 143 |
+
coverage_html = f"""
|
| 144 |
<div style='position: fixed; top: 20px; left: 20px; z-index: 999;
|
| 145 |
+
background-color: rgba(255, 255, 255, 0.95); padding: 10px;
|
| 146 |
+
border-radius: 5px; border: 1px solid #333; font-family: Arial;
|
| 147 |
+
font-size: 12px; max-width: 280px; box-shadow: 0 2px 4px rgba(0,0,0,0.2);'>
|
| 148 |
+
<b style='color: #d9534f;'>π©οΈ Multi-Domain Radar Coverage</b><br>
|
| 149 |
+
<div style='margin-top: 6px; font-size: 11px;'>
|
| 150 |
+
<b>Current View:</b> {domain_selection.upper()}<br>
|
| 151 |
+
<b>Data Source:</b> {'NEXRAD Real-Time' if show_nexrad else 'None'}<br>
|
| 152 |
+
<div style='margin-top: 6px; padding: 4px; background: #fff3cd; border-left: 3px solid #ffc107; font-size: 10px;'>
|
| 153 |
+
<b>Note:</b> NEXRAD provides real-time observations
|
| 154 |
+
across CONUS, Alaska, Hawaii, and Puerto Rico.
|
| 155 |
+
</div>
|
| 156 |
+
</div>
|
| 157 |
</div>
|
| 158 |
"""
|
| 159 |
+
m.get_root().html.add_child(folium.Element(coverage_html))
|
| 160 |
+
|
| 161 |
+
# Add domain boundary markers
|
| 162 |
+
if domain_selection in DOMAIN_BOUNDS:
|
| 163 |
+
bounds = DOMAIN_BOUNDS[domain_selection]
|
| 164 |
+
folium.Rectangle(
|
| 165 |
+
bounds=bounds,
|
| 166 |
+
color='#3388ff',
|
| 167 |
+
fill=False,
|
| 168 |
+
weight=2,
|
| 169 |
+
popup=f"{domain_selection.upper()} Domain"
|
| 170 |
+
).add_to(m)
|
| 171 |
|
| 172 |
except Exception as e:
|
| 173 |
# Add error message
|
|
|
|
| 175 |
<div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
|
| 176 |
z-index: 1001; background-color: #ffcccc; padding: 20px;
|
| 177 |
border-radius: 10px; border: 2px solid #ff0000; font-family: Arial;'>
|
| 178 |
+
<h3 style='margin-top: 0;'>Error Loading Data</h3>
|
| 179 |
<p>Error: {str(e)}</p>
|
| 180 |
+
<p>Please try different settings.</p>
|
| 181 |
</div>
|
| 182 |
"""
|
| 183 |
m.get_root().html.add_child(folium.Element(error_html))
|
| 184 |
|
| 185 |
# Add layer control
|
| 186 |
+
folium.LayerControl(position='topright', collapsed=False).add_to(m)
|
| 187 |
|
| 188 |
# Add fullscreen option
|
| 189 |
+
plugins.Fullscreen(position='topleft').add_to(m)
|
| 190 |
+
|
| 191 |
+
# Add measure control
|
| 192 |
+
plugins.MeasureControl(position='bottomright', primary_length_unit='miles').add_to(m)
|
| 193 |
|
| 194 |
return m
|
| 195 |
|
| 196 |
def create_interface():
|
| 197 |
"""Create Gradio interface"""
|
| 198 |
|
| 199 |
+
with gr.Blocks(title="HRRR Multi-Domain Radar Viewer", theme=gr.themes.Soft()) as demo:
|
| 200 |
gr.Markdown("""
|
| 201 |
+
# π©οΈ HRRR Multi-Domain Radar Viewer
|
| 202 |
|
| 203 |
+
View NOAA High-Resolution Rapid Refresh (HRRR) forecast and NEXRAD radar data across
|
| 204 |
+
CONUS, Alaska, and Hawaii on an interactive map.
|
| 205 |
|
| 206 |
+
**Data Source:** NOAA NEXRAD Real-Time Radar Network / HRRR Model
|
| 207 |
""")
|
| 208 |
|
| 209 |
with gr.Row():
|
| 210 |
+
with gr.Column(scale=1):
|
| 211 |
run_time = gr.Dropdown(
|
| 212 |
choices=get_available_runs(),
|
| 213 |
value=get_available_runs()[0],
|
| 214 |
+
label="π Model Run Time (UTC)",
|
| 215 |
+
info="HRRR model initialization time"
|
| 216 |
)
|
| 217 |
|
| 218 |
with gr.Column(scale=1):
|
|
|
|
| 221 |
maximum=18,
|
| 222 |
step=1,
|
| 223 |
value=0,
|
| 224 |
+
label="β±οΈ Forecast Hour",
|
| 225 |
+
info="F000 = Analysis, F001-F018 = Forecast"
|
| 226 |
)
|
| 227 |
|
| 228 |
+
with gr.Column(scale=1):
|
| 229 |
+
domain = gr.Radio(
|
| 230 |
+
choices=['full', 'conus', 'alaska', 'hawaii'],
|
| 231 |
+
value='conus',
|
| 232 |
+
label="πΊοΈ Domain",
|
| 233 |
+
info="Select geographic region"
|
| 234 |
+
)
|
| 235 |
+
|
| 236 |
+
with gr.Row():
|
| 237 |
+
show_nexrad = gr.Checkbox(
|
| 238 |
+
value=True,
|
| 239 |
+
label="Show NEXRAD Real-Time Radar",
|
| 240 |
+
info="Display current NEXRAD composite reflectivity"
|
| 241 |
+
)
|
| 242 |
+
|
| 243 |
+
show_hrrr_info = gr.Checkbox(
|
| 244 |
+
value=True,
|
| 245 |
+
label="Show HRRR Forecast Info",
|
| 246 |
+
info="Display HRRR forecast time information"
|
| 247 |
+
)
|
| 248 |
+
|
| 249 |
load_btn = gr.Button("π Load Radar Data", variant="primary", size="lg")
|
| 250 |
|
| 251 |
with gr.Row():
|
| 252 |
+
map_output = gr.HTML(label="Interactive Radar Map")
|
| 253 |
|
| 254 |
+
def load_map(run_time, forecast_hour, domain, show_nexrad, show_hrrr_info):
|
| 255 |
+
m = generate_map(run_time, int(forecast_hour), domain, show_nexrad, show_hrrr_info)
|
| 256 |
return m._repr_html_()
|
| 257 |
|
| 258 |
load_btn.click(
|
| 259 |
fn=load_map,
|
| 260 |
+
inputs=[run_time, forecast_hour, domain, show_nexrad, show_hrrr_info],
|
| 261 |
outputs=map_output
|
| 262 |
)
|
| 263 |
|
| 264 |
# Auto-load on startup
|
| 265 |
demo.load(
|
| 266 |
fn=load_map,
|
| 267 |
+
inputs=[run_time, forecast_hour, domain, show_nexrad, show_hrrr_info],
|
| 268 |
outputs=map_output
|
| 269 |
)
|
| 270 |
|
| 271 |
gr.Markdown("""
|
| 272 |
---
|
| 273 |
+
## π About HRRR (High-Resolution Rapid Refresh)
|
| 274 |
+
|
| 275 |
+
The **High-Resolution Rapid Refresh (HRRR)** is NOAA's high-resolution, short-range weather model that provides
|
| 276 |
+
3km grid spacing forecasts updated every hour.
|
| 277 |
|
| 278 |
+
### Model Coverage
|
| 279 |
+
|
| 280 |
+
- **CONUS HRRR**: Continental United States at 3km resolution
|
| 281 |
+
- **Alaska HRRR**: Alaska domain at 3km resolution
|
| 282 |
+
- **Hawaii HRRR**: Hawaiian Islands at 3km resolution
|
| 283 |
|
| 284 |
### Composite Reflectivity
|
| 285 |
|
| 286 |
+
Composite reflectivity shows the maximum radar reflectivity in a vertical column:
|
| 287 |
+
- **60+ dBZ**: Extreme precipitation (large hail likely)
|
| 288 |
+
- **50-60 dBZ**: Severe thunderstorms (hail possible)
|
| 289 |
+
- **40-50 dBZ**: Heavy precipitation
|
| 290 |
+
- **30-40 dBZ**: Moderate to heavy rain
|
| 291 |
+
- **20-30 dBZ**: Light to moderate rain
|
| 292 |
+
- **10-20 dBZ**: Light rain
|
| 293 |
+
- **<10 dBZ**: Very light precipitation
|
| 294 |
|
| 295 |
### Data Information
|
| 296 |
|
| 297 |
+
- **Model:** HRRR (High-Resolution Rapid Refresh)
|
| 298 |
- **Grid Spacing:** 3 km
|
| 299 |
+
- **Update Frequency:** Hourly
|
| 300 |
+
- **Forecast Length:** 18-48 hours depending on cycle
|
| 301 |
+
- **Domains:** CONUS, Alaska, Hawaii
|
| 302 |
+
- **Real-Time Radar:** NEXRAD (Next Generation Radar) network
|
| 303 |
+
|
| 304 |
+
### π Comparing HRRR F000 with NEXRAD
|
| 305 |
+
|
| 306 |
+
- **F000 (Analysis)**: HRRR analysis at model initialization - uses radar data assimilation
|
| 307 |
+
- **NEXRAD Real-Time**: Direct radar observations updated every ~5 minutes
|
| 308 |
+
- **Comparison**: F000 should closely match NEXRAD since it assimilates radar data
|
| 309 |
+
|
| 310 |
+
### β οΈ Important Notes
|
| 311 |
+
|
| 312 |
+
1. **Current Display**: Shows NEXRAD real-time radar composite reflectivity
|
| 313 |
+
2. **HRRR Data**: Available as GRIB2 files from NOAA NOMADS server
|
| 314 |
+
3. **Processing**: GRIB2 processing requires specialized tools (wgrib2, pygrib, xarray)
|
| 315 |
+
4. **Use Case**: Compare forecast hours (F001-F018) with F000/NEXRAD for model validation
|
| 316 |
+
|
| 317 |
+
### π Data Access
|
| 318 |
|
| 319 |
+
**HRRR GRIB2 Data:**
|
| 320 |
+
- AWS S3: `s3://noaa-hrrr-bdp-pds/hrrr.YYYYMMDD/domain/`
|
| 321 |
+
- NOMADS: `https://nomads.ncep.noaa.gov/pub/data/nccf/com/hrrr/prod/`
|
| 322 |
|
| 323 |
+
**NEXRAD Real-Time:**
|
| 324 |
+
- NOAA MapServices WMS (displayed in this app)
|
| 325 |
+
- MRMS Data: `https://mrms.ncep.noaa.gov/data/`
|
| 326 |
|
| 327 |
+
### π References
|
| 328 |
|
| 329 |
+
- [HRRR Information (NOAA)](https://rapidrefresh.noaa.gov/hrrr/)
|
| 330 |
+
- [HRRR on AWS Open Data](https://registry.opendata.aws/noaa-hrrr/)
|
| 331 |
+
- [NOAA NOMADS Server](https://nomads.ncep.noaa.gov/)
|
| 332 |
+
- [NEXRAD Radar Network](https://www.ncei.noaa.gov/products/radar/next-generation-weather-radar)
|
| 333 |
|
| 334 |
---
|
| 335 |
|
| 336 |
+
<p style='text-align: center; color: #666; font-size: 11px;'>
|
| 337 |
+
Data provided by NOAA | For research and educational purposes only | Not for operational use
|
| 338 |
</p>
|
| 339 |
""")
|
| 340 |
|