nakas Claude commited on
Commit
96047e1
Β·
1 Parent(s): f0928d3

Add side-by-side NEXRAD vs HRRR comparison with hue differentiation

Browse files

- Fetch HRRR composite reflectivity images from multiple URL patterns
- Apply green-blue hue shift to HRRR data for visual differentiation
- Display NEXRAD (red/purple) and HRRR (cyan/green) simultaneously
- Add separate opacity controls for each layer (0.0-1.0)
- Support CONUS, Alaska, and Hawaii domains
- Include comparison tips and alignment checking guide
- Show data availability status for each layer
- Add numpy for image processing and color transformation

Users can now overlay both datasets with transparency to visually
validate HRRR F000 analysis alignment with real-time NEXRAD radar.
Mixed color areas indicate overlapping precipitation features.

πŸ€– Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude <noreply@anthropic.com>

Files changed (2) hide show
  1. app.py +281 -156
  2. requirements.txt +1 -0
app.py CHANGED
@@ -6,6 +6,7 @@ from datetime import datetime, timedelta
6
  from io import BytesIO
7
  from PIL import Image
8
  import base64
 
9
 
10
  # Domain bounds for different regions
11
  DOMAIN_BOUNDS = {
@@ -15,56 +16,156 @@ DOMAIN_BOUNDS = {
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 (&lt;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 = {
@@ -80,85 +181,95 @@ def generate_map(run_time_str, forecast_hour, domain_selection, show_nexrad, sho
80
  m = folium.Map(
81
  location=config['location'],
82
  zoom_start=config['zoom'],
83
- tiles='OpenStreetMap'
84
  )
85
 
86
  # Add alternative tile layers
87
- folium.TileLayer('CartoDB positron', name='Light Map').add_to(m)
88
  folium.TileLayer('CartoDB dark_matter', name='Dark Map').add_to(m)
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(
@@ -170,14 +281,12 @@ def generate_map(run_time_str, forecast_hour, domain_selection, show_nexrad, sho
170
  ).add_to(m)
171
 
172
  except Exception as e:
173
- # Add error message
174
  error_html = f"""
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))
@@ -185,10 +294,8 @@ def generate_map(run_time_str, forecast_hour, domain_selection, show_nexrad, sho
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
@@ -196,14 +303,14 @@ def generate_map(run_time_str, forecast_hour, domain_selection, show_nexrad, sho
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():
@@ -212,7 +319,7 @@ def create_interface():
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):
@@ -222,119 +329,137 @@ def create_interface():
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
 
 
6
  from io import BytesIO
7
  from PIL import Image
8
  import base64
9
+ import numpy as np
10
 
11
  # Domain bounds for different regions
12
  DOMAIN_BOUNDS = {
 
16
  'full': [[18.0, -180.0], [72.0, -66.0]]
17
  }
18
 
19
+ # HRRR image bounds (approximate for composite reflectivity images)
20
+ HRRR_IMAGE_BOUNDS = {
21
+ 'conus': [[20.0, -130.0], [52.0, -60.0]],
22
+ 'alaska': [[48.0, -180.0], [75.0, -125.0]],
23
+ 'hawaii': [[17.0, -162.0], [24.0, -153.0]]
24
+ }
25
+
26
  def get_available_runs():
27
+ """Generate list of recent model run times"""
 
 
 
28
  runs = []
29
  now = datetime.utcnow()
30
  current_hour = now.replace(minute=0, second=0, microsecond=0)
31
 
32
+ for i in range(48): # Last 48 hours
33
  run_time = current_hour - timedelta(hours=i)
34
  runs.append(run_time.strftime("%Y-%m-%d %H:00 UTC"))
35
 
36
  return runs
37
 
38
+ def try_fetch_hrrr_image(run_time_str, forecast_hour, domain='conus'):
39
+ """
40
+ Try to fetch HRRR composite reflectivity image from various NOAA sources
41
+
42
+ Returns:
43
+ PIL Image object if found, None otherwise
44
+ """
45
+ dt = datetime.strptime(run_time_str, "%Y-%m-%d %H:%M UTC")
46
+ run_str = dt.strftime("%Y%m%d%H")
47
+
48
+ # Map domain to HRRR naming
49
+ domain_map = {
50
+ 'conus': 'conus',
51
+ 'alaska': 'alaska',
52
+ 'hawaii': 'hawaii',
53
+ 'full': 'conus' # Start with conus for full view
54
+ }
55
+
56
+ hrrr_domain = domain_map.get(domain, 'conus')
57
+
58
+ # Try multiple URL patterns for HRRR composite reflectivity
59
+ url_patterns = [
60
+ # Pattern 1: Standard for_web structure
61
+ f"https://rapidrefresh.noaa.gov/hrrr/HRRR/for_web/hrrr_ncep_jet/{run_str}/{hrrr_domain}/refc_sfc_f{forecast_hour:02d}.png",
62
+ # Pattern 2: Alternative structure
63
+ f"https://rapidrefresh.noaa.gov/hrrr/HRRR/for_web/hrrr_{hrrr_domain}/{run_str}/refc_sfc_f{forecast_hour:02d}.png",
64
+ # Pattern 3: Simplified path
65
+ f"https://rapidrefresh.noaa.gov/hrrr/for_web/{run_str}/{hrrr_domain}/refc_sfc_f{forecast_hour:02d}.png",
66
+ # Pattern 4: Direct HRRR graphics
67
+ f"https://rapidrefresh.noaa.gov/hrrr/HRRR/displayMapLocalDiskDateDomainZipTZModel.cgi?keys=hrrr_ncep_jet:&runtime={run_str}&plot_type=refc&fcst={forecast_hour:02d}",
68
+ ]
69
+
70
+ for url in url_patterns:
71
+ try:
72
+ response = requests.get(url, timeout=10)
73
+ if response.status_code == 200 and len(response.content) > 1000: # Valid image
74
+ img = Image.open(BytesIO(response.content))
75
+ return img, url
76
+ except:
77
+ continue
78
+
79
+ return None, None
80
+
81
+ def apply_hue_shift(image, hue_shift=0.3):
82
+ """
83
+ Apply hue shift to image to differentiate from NEXRAD
84
+
85
+ Args:
86
+ image: PIL Image
87
+ hue_shift: Hue shift amount (0-1), 0.3 = greenish-blue tint
88
+
89
+ Returns:
90
+ PIL Image with hue shifted
91
+ """
92
+ # Convert to RGBA if not already
93
+ if image.mode != 'RGBA':
94
+ image = image.convert('RGBA')
95
+
96
+ # Convert to numpy array
97
+ img_array = np.array(image)
98
+
99
+ # Separate RGB and alpha channels
100
+ rgb = img_array[:, :, :3].astype(float)
101
+ alpha = img_array[:, :, 3]
102
+
103
+ # Convert RGB to HSV
104
+ rgb_normalized = rgb / 255.0
105
+
106
+ # Simple hue shift by rotating RGB values
107
+ # This gives a greenish-blue tint to HRRR data
108
+ r, g, b = rgb_normalized[:,:,0], rgb_normalized[:,:,1], rgb_normalized[:,:,2]
109
+
110
+ # Apply color tint - shift toward cyan/green for HRRR
111
+ r_new = r * 0.6 + g * 0.2 + b * 0.2 # Reduce red
112
+ g_new = r * 0.1 + g * 0.8 + b * 0.1 # Enhance green
113
+ b_new = r * 0.1 + g * 0.3 + b * 0.6 # Moderate blue
114
+
115
+ # Stack and convert back
116
+ rgb_shifted = np.stack([r_new, g_new, b_new], axis=2)
117
+ rgb_shifted = np.clip(rgb_shifted * 255, 0, 255).astype(np.uint8)
118
+
119
+ # Recombine with alpha
120
+ img_shifted = np.dstack([rgb_shifted, alpha])
121
+
122
+ return Image.fromarray(img_shifted, 'RGBA')
123
+
124
+ def image_to_data_url(image):
125
+ """Convert PIL Image to data URL for folium"""
126
+ buffered = BytesIO()
127
+ image.save(buffered, format="PNG")
128
+ img_str = base64.b64encode(buffered.getvalue()).decode()
129
+ return f"data:image/png;base64,{img_str}"
130
+
131
+ def create_legends():
132
+ """Create HTML legends for both radar types"""
133
  legend_html = '''
134
  <div style="position: fixed; top: 80px; right: 20px; z-index: 1000;
135
  background-color: white; padding: 10px; border-radius: 5px;
136
  border: 2px solid #333; font-family: Arial; font-size: 11px;
137
+ max-width: 200px;">
138
  <b style="font-size: 12px;">Reflectivity (dBZ)</b><br>
139
+ <div style="margin-top: 8px;">
140
+ <div style="display: flex; gap: 8px; margin-bottom: 6px;">
141
+ <div style="flex: 1;">
142
+ <b style="font-size: 10px; color: #d9534f;">NEXRAD</b><br>
143
+ <div style="margin-top: 3px; font-size: 9px;">Real-time<br>(Standard colors)</div>
144
+ </div>
145
+ <div style="flex: 1;">
146
+ <b style="font-size: 10px; color: #5cb85c;">HRRR</b><br>
147
+ <div style="margin-top: 3px; font-size: 9px;">Forecast<br>(Green-blue tint)</div>
148
+ </div>
149
+ </div>
150
+ </div>
151
+ <div style="display: flex; flex-direction: column; margin-top: 8px; gap: 2px; border-top: 1px solid #ccc; padding-top: 6px;">
152
+ <div style="font-size: 10px;"><span style="background: #9854c6; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>60+ (Extreme)</div>
153
+ <div style="font-size: 10px;"><span style="background: #f800fd; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>50-60 (Severe)</div>
154
+ <div style="font-size: 10px;"><span style="background: #bc0000; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>40-50 (Heavy)</div>
155
+ <div style="font-size: 10px;"><span style="background: #fd0000; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>30-40 (Moderate)</div>
156
+ <div style="font-size: 10px;"><span style="background: #fd9500; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>25-30 (Light)</div>
157
+ <div style="font-size: 10px;"><span style="background: #fdf802; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>20-25</div>
158
+ <div style="font-size: 10px;"><span style="background: #02fd02; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>10-20 (Weak)</div>
159
+ <div style="font-size: 10px;"><span style="background: #019ff4; width: 16px; height: 8px; display: inline-block; margin-right: 4px;"></span>&lt;10 (Trace)</div>
160
  </div>
161
  </div>
162
  '''
163
  return legend_html
164
 
165
+ def generate_map(run_time_str, forecast_hour, domain_selection,
166
+ show_nexrad, nexrad_opacity, show_hrrr, hrrr_opacity):
167
  """
168
+ Generate Folium map with both NEXRAD and HRRR overlays for comparison
 
 
 
 
 
 
 
 
 
 
169
  """
170
  # Set map center and zoom based on domain
171
  domain_configs = {
 
181
  m = folium.Map(
182
  location=config['location'],
183
  zoom_start=config['zoom'],
184
+ tiles='CartoDB positron'
185
  )
186
 
187
  # Add alternative tile layers
188
+ folium.TileLayer('OpenStreetMap', name='Street Map').add_to(m)
189
  folium.TileLayer('CartoDB dark_matter', name='Dark Map').add_to(m)
190
 
191
  try:
 
192
  dt = datetime.strptime(run_time_str, "%Y-%m-%d %H:%M UTC")
193
  valid_time = dt + timedelta(hours=int(forecast_hour))
194
 
195
+ data_status = []
196
+
197
+ # Add HRRR forecast overlay if requested
198
+ if show_hrrr:
199
+ hrrr_img, hrrr_url = try_fetch_hrrr_image(run_time_str, int(forecast_hour), domain_selection)
200
+
201
+ if hrrr_img:
202
+ # Apply hue shift to HRRR data (greenish-blue tint)
203
+ hrrr_shifted = apply_hue_shift(hrrr_img)
204
+ hrrr_data_url = image_to_data_url(hrrr_shifted)
205
+
206
+ # Get bounds for this domain
207
+ bounds = HRRR_IMAGE_BOUNDS.get(domain_selection, HRRR_IMAGE_BOUNDS['conus'])
208
+
209
+ folium.raster_layers.ImageOverlay(
210
+ image=hrrr_data_url,
211
+ bounds=bounds,
212
+ opacity=hrrr_opacity,
213
+ name='HRRR Forecast (Green-Blue Tint)',
214
+ overlay=True,
215
+ control=True
216
+ ).add_to(m)
217
+
218
+ data_status.append(f"βœ“ HRRR F{int(forecast_hour):03d} loaded")
219
+ else:
220
+ data_status.append(f"βœ— HRRR F{int(forecast_hour):03d} not available")
221
+
222
  # Add NEXRAD real-time radar if requested
223
  if show_nexrad:
 
224
  wms_url = 'https://mapservices.weather.noaa.gov/eventdriven/services/radar/radar_base_reflectivity/MapServer/WMSServer'
225
 
226
  folium.raster_layers.WmsTileLayer(
227
  url=wms_url,
228
  layers='0',
229
+ name='NEXRAD Real-Time (Standard Colors)',
230
  format='image/png',
231
  transparent=True,
232
+ opacity=nexrad_opacity,
233
  attr='NOAA',
234
  overlay=True,
235
  control=True
236
  ).add_to(m)
237
 
238
+ data_status.append("βœ“ NEXRAD Real-Time loaded")
239
+
240
+ # Add comparison info box
241
+ comparison_html = f"""
242
+ <div style='position: fixed; bottom: 20px; left: 20px; z-index: 1000;
243
+ background-color: rgba(255, 255, 255, 0.95); padding: 12px;
244
+ border-radius: 5px; border: 2px solid #0066cc; font-family: Arial;
245
+ max-width: 340px; box-shadow: 0 2px 5px rgba(0,0,0,0.3);'>
246
+ <b style='color: #0066cc; font-size: 14px;'>πŸ“Š Data Comparison View</b><br>
247
+ <div style='margin-top: 8px; font-size: 11px;'>
248
+ <b>Model Run:</b> {dt.strftime("%Y-%m-%d %H:00 UTC")}<br>
249
+ <b>Forecast Hour:</b> F{int(forecast_hour):03d}<br>
250
+ <b>Valid Time:</b> {valid_time.strftime("%Y-%m-%d %H:00 UTC")}<br>
251
+ <b>Domain:</b> {domain_selection.upper()}<br>
 
 
 
 
 
 
 
 
 
252
  </div>
253
+ <div style='margin-top: 8px; padding: 6px; background: #e8f4f8; border-radius: 3px; font-size: 10px;'>
254
+ <b>Data Status:</b><br>
255
+ {'<br>'.join(data_status) if data_status else 'No data layers selected'}
256
+ </div>
257
+ <div style='margin-top: 6px; padding: 6px; background: #fff3cd; border-radius: 3px; font-size: 9px;'>
258
+ <b>πŸ’‘ Comparison Tips:</b><br>
259
+ β€’ <span style="color: #d9534f;">Red/Purple</span> = NEXRAD standard colors<br>
260
+ β€’ <span style="color: #5cb85c;">Green/Cyan</span> = HRRR with color shift<br>
261
+ β€’ Overlapping areas appear <b>mixed</b><br>
262
+ β€’ Perfect alignment = good model performance<br>
263
+ β€’ Use opacity sliders to adjust visibility
 
 
 
 
 
 
 
 
 
264
  </div>
265
  </div>
266
  """
267
+ m.get_root().html.add_child(folium.Element(comparison_html))
268
+
269
+ # Add legend
270
+ m.get_root().html.add_child(folium.Element(create_legends()))
271
 
272
+ # Add domain boundary
273
  if domain_selection in DOMAIN_BOUNDS:
274
  bounds = DOMAIN_BOUNDS[domain_selection]
275
  folium.Rectangle(
 
281
  ).add_to(m)
282
 
283
  except Exception as e:
 
284
  error_html = f"""
285
  <div style='position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
286
  z-index: 1001; background-color: #ffcccc; padding: 20px;
287
  border-radius: 10px; border: 2px solid #ff0000; font-family: Arial;'>
288
  <h3 style='margin-top: 0;'>Error Loading Data</h3>
289
  <p>Error: {str(e)}</p>
 
290
  </div>
291
  """
292
  m.get_root().html.add_child(folium.Element(error_html))
 
294
  # Add layer control
295
  folium.LayerControl(position='topright', collapsed=False).add_to(m)
296
 
297
+ # Add fullscreen and measure controls
298
  plugins.Fullscreen(position='topleft').add_to(m)
 
 
299
  plugins.MeasureControl(position='bottomright', primary_length_unit='miles').add_to(m)
300
 
301
  return m
 
303
  def create_interface():
304
  """Create Gradio interface"""
305
 
306
+ with gr.Blocks(title="HRRR vs NEXRAD Radar Comparison", theme=gr.themes.Soft()) as demo:
307
  gr.Markdown("""
308
+ # 🌩️ HRRR vs NEXRAD Radar Comparison Viewer
309
 
310
+ Compare NOAA HRRR forecast composite reflectivity with real-time NEXRAD radar data.
311
+ **Both layers shown simultaneously with different colors for visual alignment checking.**
312
 
313
+ **Data Sources:** NOAA NEXRAD Real-Time Radar (standard colors) + HRRR Model Forecast (green-blue tint)
314
  """)
315
 
316
  with gr.Row():
 
319
  choices=get_available_runs(),
320
  value=get_available_runs()[0],
321
  label="πŸ• Model Run Time (UTC)",
322
+ info="HRRR initialization time"
323
  )
324
 
325
  with gr.Column(scale=1):
 
329
  step=1,
330
  value=0,
331
  label="⏱️ Forecast Hour",
332
+ info="F000 = Analysis (best for comparison)"
333
  )
334
 
335
  with gr.Column(scale=1):
336
  domain = gr.Radio(
337
+ choices=['conus', 'alaska', 'hawaii'],
338
  value='conus',
339
  label="πŸ—ΊοΈ Domain",
340
+ info="Geographic region"
341
  )
342
 
343
+ gr.Markdown("### 🎨 Layer Controls - Adjust to Compare Alignment")
344
+
345
  with gr.Row():
346
+ with gr.Column(scale=1):
347
+ show_nexrad = gr.Checkbox(
348
+ value=True,
349
+ label="πŸ“‘ Show NEXRAD Real-Time",
350
+ info="Standard red/purple colors"
351
+ )
352
+ nexrad_opacity = gr.Slider(
353
+ minimum=0.0,
354
+ maximum=1.0,
355
+ value=0.6,
356
+ step=0.1,
357
+ label="NEXRAD Opacity",
358
+ info="Lower to see HRRR underneath"
359
+ )
360
 
361
+ with gr.Column(scale=1):
362
+ show_hrrr = gr.Checkbox(
363
+ value=True,
364
+ label="πŸ›°οΈ Show HRRR Forecast",
365
+ info="Green/cyan color tint"
366
+ )
367
+ hrrr_opacity = gr.Slider(
368
+ minimum=0.0,
369
+ maximum=1.0,
370
+ value=0.5,
371
+ step=0.1,
372
+ label="HRRR Opacity",
373
+ info="Adjust for blending"
374
+ )
375
 
376
+ load_btn = gr.Button("πŸ”„ Load Comparison View", variant="primary", size="lg")
377
 
378
  with gr.Row():
379
+ map_output = gr.HTML(label="Comparison Map")
380
 
381
+ def load_map(run_time, forecast_hour, domain, show_nexrad, nexrad_opacity, show_hrrr, hrrr_opacity):
382
+ m = generate_map(run_time, int(forecast_hour), domain,
383
+ show_nexrad, nexrad_opacity, show_hrrr, hrrr_opacity)
384
  return m._repr_html_()
385
 
386
  load_btn.click(
387
  fn=load_map,
388
+ inputs=[run_time, forecast_hour, domain, show_nexrad, nexrad_opacity, show_hrrr, hrrr_opacity],
389
  outputs=map_output
390
  )
391
 
392
  # Auto-load on startup
393
  demo.load(
394
  fn=load_map,
395
+ inputs=[run_time, forecast_hour, domain, show_nexrad, nexrad_opacity, show_hrrr, hrrr_opacity],
396
  outputs=map_output
397
  )
398
 
399
  gr.Markdown("""
400
  ---
401
+ ## πŸ“Š How to Use This Comparison Tool
 
 
 
402
 
403
+ ### Visual Alignment Check
404
 
405
+ 1. **Set Forecast Hour to 0** (F000 = HRRR analysis)
406
+ 2. **Enable both NEXRAD and HRRR** layers
407
+ 3. **Adjust opacity sliders** to see both layers clearly
408
+ 4. **Look for alignment:**
409
+ - **Perfect overlap** = HRRR correctly assimilated radar data
410
+ - **Offset/misalignment** = potential data issues or timing differences
411
+ - **Different intensities** = model vs. observation differences
412
 
413
+ ### Color Coding
414
 
415
+ - **πŸ”΄ NEXRAD (Standard Colors):** Red, purple, yellow = Real-time radar observations
416
+ - **🟒 HRRR (Green-Blue Tint):** Cyan, green = Model forecast with color shift
417
+ - **Mixed Areas:** Where both overlap, you'll see blended colors
 
 
 
 
 
418
 
419
+ ### Recommended Settings for Comparison
420
 
421
+ | Purpose | NEXRAD Opacity | HRRR Opacity | Notes |
422
+ |---------|---------------|--------------|-------|
423
+ | Check alignment | 0.6 | 0.5 | Balanced visibility |
424
+ | Focus on NEXRAD | 0.8 | 0.3 | HRRR as reference |
425
+ | Focus on HRRR | 0.3 | 0.7 | NEXRAD as reference |
426
+ | See differences | 0.5 | 0.5 | Equal blending |
427
 
428
+ ### Understanding Forecast Hours
429
 
430
+ - **F000**: HRRR analysis - should match NEXRAD closely (uses radar data assimilation)
431
+ - **F001-F003**: Very short-term forecast - minor divergence expected
432
+ - **F006-F012**: Short-term forecast - moderate divergence
433
+ - **F012-F018**: Medium-range forecast - larger divergence from real-time
434
 
435
+ ### Model Coverage
436
 
437
+ - **CONUS HRRR**: Continental US at 3km resolution, updated hourly
438
+ - **Alaska HRRR**: Alaska domain at 3km resolution
439
+ - **Hawaii HRRR**: Hawaiian Islands at 3km resolution
 
440
 
441
+ ### Data Availability
442
 
443
+ **HRRR Data:**
444
+ - Images may not always be available from rapidrefresh.noaa.gov
445
+ - Try recent run times (last 6-12 hours) for best availability
446
+ - GRIB2 data always available from NOMADS/AWS S3
447
 
448
+ **NEXRAD Data:**
449
+ - Real-time WMS service, always current
450
+ - Updates every ~5 minutes
451
+ - Covers CONUS, Alaska, Hawaii, Puerto Rico
452
 
453
+ ### πŸ”— References
454
 
455
+ - [HRRR Information](https://rapidrefresh.noaa.gov/hrrr/)
456
+ - [NEXRAD Documentation](https://www.ncei.noaa.gov/products/radar/next-generation-weather-radar)
457
+ - [HRRR on AWS](https://registry.opendata.aws/noaa-hrrr/)
 
458
 
459
  ---
460
 
461
  <p style='text-align: center; color: #666; font-size: 11px;'>
462
+ Data: NOAA | For research/educational purposes only | Not for operational use
463
  </p>
464
  """)
465
 
requirements.txt CHANGED
@@ -3,3 +3,4 @@ folium==0.18.0
3
  requests==2.32.3
4
  Pillow==10.4.0
5
  beautifulsoup4==4.12.3
 
 
3
  requests==2.32.3
4
  Pillow==10.4.0
5
  beautifulsoup4==4.12.3
6
+ numpy==1.26.4