nakas Claude commited on
Commit
85d2f0b
Β·
1 Parent(s): 842ad77

Replace noisy NEXRAD binary parsing with clean Iowa Mesonet composites

Browse files

- Add Iowa State Mesonet NEXRAD composite integration as top priority
- Implement fetch_iowa_nexrad_composites() for clean radar data
- Add crop_iowa_composite() for regional data extraction
- Fix radar site selection with better US/Canada border coverage (KBUF, KDTX)
- Add add_iowa_mesonet_overlay() for interactive map visualization
- Update data source priorities: Iowa Mesonet > Canadian MSC > NOAA direct
- Support both N0Q (8-bit) and N0R (4-bit) composite products
- Clean 5-minute interval radar updates instead of noisy binary processing

This replaces the problematic binary NEXRAD file parsing that was
producing noise with Iowa's professionally processed composites.

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

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

Files changed (1) hide show
  1. app.py +258 -23
app.py CHANGED
@@ -304,33 +304,32 @@ def fetch_real_time_radar_data(location="Toronto, ON"):
304
  lat, lon = 43.6532, -79.3832
305
  print(f"πŸ“ Using default location: Toronto ({lat:.2f}, {lon:.2f})")
306
 
307
- # PRIORITY 1: NOAA direct radar images for US locations (cleanest, most reliable)
308
- if 25 <= lat <= 50 and -125 <= lon <= -65: # US/Southern Canada coverage
309
- print("πŸ‡ΊπŸ‡Έ PRIORITY: Trying NOAA direct radar images (processed, not raw binary)...")
310
- noaa_data = fetch_noaa_nexrad_images(lat, lon)
311
- if noaa_data and len(noaa_data) >= 4:
312
- print(f"βœ… SUCCESS: Fetched {len(noaa_data)} clean NOAA radar images!")
313
- return noaa_data
314
  else:
315
- print("⚠️ NOAA direct images unavailable - will try other sources...")
316
 
317
  # PRIORITY 2: Canadian MSC GeoMet radar (excellent for Canadian locations)
318
- print("πŸ‡¨πŸ‡¦ Attempting Canadian MSC GeoMet radar data...")
319
- canadian_data = fetch_canadian_radar_data(lat, lon)
320
-
321
- if canadian_data and len(canadian_data) >= 4:
322
- print(f"βœ… Successfully fetched {len(canadian_data)} Canadian radar frames!")
323
- return canadian_data
 
324
 
325
- # PRIORITY 3: NEXRAD binary data (only as fallback for US locations)
326
  if 25 <= lat <= 50 and -125 <= lon <= -65: # US coverage
327
- print("πŸ‡ΊπŸ‡Έ FALLBACK: Trying NEXRAD binary data processing...")
328
- radar_site = find_nearest_nexrad_site(lat, lon)
329
- nexrad_data = fetch_nexrad_data(radar_site, num_scans=4)
330
-
331
- if nexrad_data and len(nexrad_data) >= 4:
332
- print(f"βœ… Successfully processed {len(nexrad_data)} NEXRAD binary scans!")
333
- return process_nexrad_to_images(nexrad_data, lat, lon, radar_site)
334
 
335
  # Try RainViewer as backup
336
  print("🌧️ Trying RainViewer global radar...")
@@ -486,13 +485,20 @@ def find_nearest_nexrad_site(lat, lon):
486
  """
487
  Find the nearest NEXRAD radar site to given coordinates
488
  """
489
- # Major NEXRAD sites with good coverage
490
  nexrad_sites = {
491
  'KTLX': (35.3331, -97.2778, 'Oklahoma City, OK'),
492
  'KOUN': (35.2356, -97.4619, 'Norman, OK'),
493
  'KOKX': (40.8656, -72.8644, 'New York, NY'),
494
  'KDOX': (38.8256, -75.4400, 'Philadelphia, PA'),
495
  'KLOT': (41.6044, -88.0844, 'Chicago, IL'),
 
 
 
 
 
 
 
496
  'KEWX': (29.7036, -98.0289, 'San Antonio, TX'),
497
  'KBMX': (33.1722, -86.7697, 'Birmingham, AL'),
498
  'KMLB': (28.1133, -80.6544, 'Melbourne, FL'),
@@ -921,6 +927,152 @@ def fetch_noaa_nexrad_images(lat, lon):
921
  print(f"❌ NOAA NEXRAD image fetch failed: {e}")
922
  return None
923
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
924
  def fetch_rainviewer_data(lat, lon):
925
  """
926
  Fetch radar data from RainViewer API (public weather radar)
@@ -1137,6 +1289,9 @@ def create_radar_map(forecast_data=None, input_data=None):
1137
  if data_source == 'Canadian_MSC_GeoMet':
1138
  # Add Canadian radar WMS overlay
1139
  add_canadian_radar_overlay(m, geo_metadata)
 
 
 
1140
  elif data_source == 'NOAA_NEXRAD_Images':
1141
  # Add NOAA radar image overlay
1142
  add_noaa_radar_overlay(m, geo_metadata)
@@ -1194,6 +1349,7 @@ def create_radar_map(forecast_data=None, input_data=None):
1194
  # Add enhanced information box
1195
  data_source_name = {
1196
  'Canadian_MSC_GeoMet': 'πŸ‡¨πŸ‡¦ Canadian MSC GeoMet',
 
1197
  'NOAA_NEXRAD_Images': 'πŸ‡ΊπŸ‡Έ NOAA NEXRAD',
1198
  'RainViewer': '🌍 RainViewer Global',
1199
  'Temporal_Patterns': 'πŸ“Š Weather Patterns'
@@ -1310,6 +1466,81 @@ def add_noaa_radar_overlay(m, geo_metadata):
1310
  popup=f'πŸ‡ΊπŸ‡Έ NOAA Radar Coverage'
1311
  ).add_to(m)
1312
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1313
  def add_rainviewer_overlay(m, geo_metadata):
1314
  """Add RainViewer radar overlay to map"""
1315
  try:
@@ -1362,6 +1593,9 @@ def run_nowcast_prediction(use_real_time_data=True, location="Toronto, ON"):
1362
 
1363
  if data_source == 'Canadian_MSC_GeoMet':
1364
  data_info = f"βœ… REAL Canadian MSC GeoMet radar data ({location})\nπŸ‡¨πŸ‡¦ Layer: {geo_meta.get('layer', 'RADAR_1KM_RRAI')}\n⏱️ Updates every 10 minutes\nπŸ“‘ 1km resolution radar composite"
 
 
 
1365
  elif data_source == 'NOAA_NEXRAD_Images':
1366
  data_info = f"βœ… REAL NOAA NEXRAD radar images from {radar_site} ({location})\nπŸ‡ΊπŸ‡Έ Direct weather.gov radar images\nπŸ“‘ Base reflectivity (N0R) product\n⏱️ Updates every 5-10 minutes"
1367
  elif data_source == 'NEXRAD_REAL':
@@ -1659,6 +1893,7 @@ def create_interactive_map():
1659
  else:
1660
  data_source_name = {
1661
  'Canadian_MSC_GeoMet': 'πŸ‡¨πŸ‡¦ Canadian MSC GeoMet',
 
1662
  'NOAA_NEXRAD_Images': 'πŸ‡ΊπŸ‡Έ NOAA NEXRAD Images',
1663
  'RainViewer': '🌍 RainViewer Global',
1664
  'Temporal_Patterns': 'πŸ“Š Weather Patterns'
 
304
  lat, lon = 43.6532, -79.3832
305
  print(f"πŸ“ Using default location: Toronto ({lat:.2f}, {lon:.2f})")
306
 
307
+ # PRIORITY 1: Iowa Mesonet NEXRAD composites (cleanest US coverage)
308
+ if 25 <= lat <= 50 and -125 <= lon <= -65: # US coverage
309
+ print("πŸ‡ΊπŸ‡Έ PRIORITY: Fetching Iowa Mesonet NEXRAD composites (cleanest data)...")
310
+ iowa_data = fetch_iowa_nexrad_composites(lat, lon)
311
+ if iowa_data and len(iowa_data) >= 4:
312
+ print(f"βœ… SUCCESS: Fetched {len(iowa_data)} Iowa Mesonet radar composites!")
313
+ return iowa_data
314
  else:
315
+ print("⚠️ Iowa Mesonet unavailable - trying other sources...")
316
 
317
  # PRIORITY 2: Canadian MSC GeoMet radar (excellent for Canadian locations)
318
+ if lat >= 45: # Focus on Canadian locations
319
+ print("πŸ‡¨πŸ‡¦ Attempting Canadian MSC GeoMet radar data...")
320
+ canadian_data = fetch_canadian_radar_data(lat, lon)
321
+
322
+ if canadian_data and len(canadian_data) >= 4:
323
+ print(f"βœ… Successfully fetched {len(canadian_data)} Canadian radar frames!")
324
+ return canadian_data
325
 
326
+ # PRIORITY 3: NOAA direct radar images (site-specific)
327
  if 25 <= lat <= 50 and -125 <= lon <= -65: # US coverage
328
+ print("πŸ‡ΊπŸ‡Έ Trying NOAA direct radar site images...")
329
+ noaa_data = fetch_noaa_nexrad_images(lat, lon)
330
+ if noaa_data and len(noaa_data) >= 4:
331
+ print(f"βœ… SUCCESS: Fetched {len(noaa_data)} NOAA radar images!")
332
+ return noaa_data
 
 
333
 
334
  # Try RainViewer as backup
335
  print("🌧️ Trying RainViewer global radar...")
 
485
  """
486
  Find the nearest NEXRAD radar site to given coordinates
487
  """
488
+ # Major NEXRAD sites with good coverage, especially near US/Canada border
489
  nexrad_sites = {
490
  'KTLX': (35.3331, -97.2778, 'Oklahoma City, OK'),
491
  'KOUN': (35.2356, -97.4619, 'Norman, OK'),
492
  'KOKX': (40.8656, -72.8644, 'New York, NY'),
493
  'KDOX': (38.8256, -75.4400, 'Philadelphia, PA'),
494
  'KLOT': (41.6044, -88.0844, 'Chicago, IL'),
495
+ 'KBUF': (42.9488, -78.7369, 'Buffalo, NY'), # Close to Toronto
496
+ 'KDTX': (42.6997, -83.4719, 'Detroit, MI'), # Close to Toronto
497
+ 'KGRR': (42.8939, -85.5449, 'Grand Rapids, MI'), # Great Lakes region
498
+ 'KAPX': (44.9072, -84.7197, 'Gaylord, MI'), # Northern MI
499
+ 'KBGM': (42.1997, -75.9847, 'Binghamton, NY'), # Upstate NY
500
+ 'KTYX': (43.7556, -75.6800, 'Montague, NY'), # Central NY
501
+ 'KCBW': (46.0394, -67.8061, 'Houlton, ME'), # Near Canadian border
502
  'KEWX': (29.7036, -98.0289, 'San Antonio, TX'),
503
  'KBMX': (33.1722, -86.7697, 'Birmingham, AL'),
504
  'KMLB': (28.1133, -80.6544, 'Melbourne, FL'),
 
927
  print(f"❌ NOAA NEXRAD image fetch failed: {e}")
928
  return None
929
 
930
+ def fetch_iowa_nexrad_composites(lat, lon):
931
+ """
932
+ Fetch NEXRAD composites from Iowa Mesonet - cleanest radar data available
933
+ """
934
+ try:
935
+ print("🌽 Fetching Iowa Mesonet NEXRAD composites...")
936
+
937
+ radar_images = []
938
+ current_time = datetime.utcnow()
939
+
940
+ # Iowa Mesonet updates every 5 minutes, get last 4 frames (20 minutes)
941
+ for i in range(4):
942
+ # Go back in 5-minute intervals
943
+ time_offset = timedelta(minutes=i * 5)
944
+ target_time = current_time - time_offset
945
+
946
+ # Iowa Mesonet URL format: /data/gis/images/4326/USCOMP/n0r_YYYYMMDDHHMM.png
947
+ time_str = target_time.strftime('%Y%m%d%H%M')
948
+
949
+ # Try N0Q (8-bit, higher quality) first, fallback to N0R (4-bit)
950
+ composite_urls = [
951
+ f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0q_{time_str}.png", # 8-bit
952
+ f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0r_{time_str}.png" # 4-bit fallback
953
+ ]
954
+
955
+ frame_success = False
956
+ for composite_type, url in zip(['N0Q', 'N0R'], composite_urls):
957
+ try:
958
+ print(f"πŸ“₯ Fetching Iowa composite frame {4-i}/4 ({composite_type}) - {target_time.strftime('%H:%M')}...")
959
+
960
+ headers = {
961
+ 'User-Agent': 'DeepNowcast/1.0 (Research Application)',
962
+ 'Accept': 'image/png,image/*,*/*'
963
+ }
964
+
965
+ response = requests.get(url, timeout=15, headers=headers)
966
+
967
+ if response.status_code == 200 and len(response.content) > 5000:
968
+ # Got composite image - crop to area around location
969
+ composite_img = Image.open(io.BytesIO(response.content))
970
+
971
+ # Iowa composites are huge (6000x2600 or 12000x5200)
972
+ # Need to crop to region around our location
973
+ cropped_img = crop_iowa_composite(composite_img, lat, lon)
974
+
975
+ if cropped_img:
976
+ radar_images.append(cropped_img)
977
+ print(f"βœ… Successfully fetched Iowa composite {4-i}/4 ({composite_type})")
978
+ frame_success = True
979
+ break # Got this frame, move to next time
980
+
981
+ else:
982
+ print(f"⚠️ Iowa composite {composite_type} frame {4-i}/4 - HTTP {response.status_code}")
983
+
984
+ except Exception as frame_error:
985
+ print(f"⚠️ Error fetching Iowa {composite_type} frame {4-i}/4: {frame_error}")
986
+ continue
987
+
988
+ if not frame_success:
989
+ print(f"❌ Could not fetch Iowa composite for {target_time.strftime('%H:%M')}")
990
+
991
+ # Reverse to get chronological order (oldest first)
992
+ radar_images.reverse()
993
+
994
+ if len(radar_images) >= 2: # At least 2 frames
995
+ # Pad if needed
996
+ while len(radar_images) < 4:
997
+ radar_images.append(radar_images[-1])
998
+
999
+ # Store metadata
1000
+ globals()['_current_geo_metadata'] = {
1001
+ 'center_lat': lat,
1002
+ 'center_lon': lon,
1003
+ 'data_source': 'Iowa_Mesonet_NEXRAD',
1004
+ 'product_type': 'NEXRAD_Composite',
1005
+ 'bbox': {
1006
+ 'north': lat + 2.5,
1007
+ 'south': lat - 2.5,
1008
+ 'east': lon + 3.5,
1009
+ 'west': lon - 3.5
1010
+ },
1011
+ 'resolution': '1km',
1012
+ 'update_frequency': '5 minutes',
1013
+ 'coverage': 'CONUS'
1014
+ }
1015
+
1016
+ print(f"βœ… Successfully fetched {len(radar_images)} Iowa Mesonet NEXRAD composites!")
1017
+ return radar_images
1018
+ else:
1019
+ print("❌ Could not fetch sufficient Iowa Mesonet composites")
1020
+ return None
1021
+
1022
+ except Exception as e:
1023
+ print(f"❌ Iowa Mesonet composite fetch failed: {e}")
1024
+ return None
1025
+
1026
+ def crop_iowa_composite(composite_img, target_lat, target_lon):
1027
+ """
1028
+ Crop Iowa Mesonet composite image to region around target location
1029
+ """
1030
+ try:
1031
+ # Iowa composites cover CONUS: roughly 20Β°N to 50Β°N, 125Β°W to 65Β°W
1032
+ img_width, img_height = composite_img.size
1033
+
1034
+ # Geographic bounds of the composite (approximate)
1035
+ north_bound = 50.0
1036
+ south_bound = 20.0
1037
+ west_bound = -125.0
1038
+ east_bound = -65.0
1039
+
1040
+ # Calculate target region (roughly 500km x 500km around location)
1041
+ crop_size_deg = 5.0 # degrees (roughly 500km)
1042
+
1043
+ crop_north = min(target_lat + crop_size_deg/2, north_bound)
1044
+ crop_south = max(target_lat - crop_size_deg/2, south_bound)
1045
+ crop_west = max(target_lon - crop_size_deg/2, west_bound)
1046
+ crop_east = min(target_lon + crop_size_deg/2, east_bound)
1047
+
1048
+ # Convert geographic coordinates to pixel coordinates
1049
+ x_west = int((crop_west - west_bound) / (east_bound - west_bound) * img_width)
1050
+ x_east = int((crop_east - west_bound) / (east_bound - west_bound) * img_width)
1051
+ y_north = int((north_bound - crop_north) / (north_bound - south_bound) * img_height)
1052
+ y_south = int((north_bound - crop_south) / (north_bound - south_bound) * img_height)
1053
+
1054
+ # Ensure valid crop bounds
1055
+ x_west = max(0, min(x_west, img_width-1))
1056
+ x_east = max(x_west+1, min(x_east, img_width))
1057
+ y_north = max(0, min(y_north, img_height-1))
1058
+ y_south = max(y_north+1, min(y_south, img_height))
1059
+
1060
+ # Crop the image
1061
+ cropped = composite_img.crop((x_west, y_north, x_east, y_south))
1062
+
1063
+ # Resize to standard 256x256
1064
+ cropped = cropped.resize((256, 256), Image.Resampling.BILINEAR)
1065
+
1066
+ # Convert to grayscale if needed
1067
+ if cropped.mode != 'L':
1068
+ cropped = cropped.convert('L')
1069
+
1070
+ return cropped
1071
+
1072
+ except Exception as e:
1073
+ print(f"⚠️ Error cropping Iowa composite: {e}")
1074
+ return None
1075
+
1076
  def fetch_rainviewer_data(lat, lon):
1077
  """
1078
  Fetch radar data from RainViewer API (public weather radar)
 
1289
  if data_source == 'Canadian_MSC_GeoMet':
1290
  # Add Canadian radar WMS overlay
1291
  add_canadian_radar_overlay(m, geo_metadata)
1292
+ elif data_source == 'Iowa_Mesonet_NEXRAD':
1293
+ # Add Iowa Mesonet NEXRAD composite overlay
1294
+ add_iowa_mesonet_overlay(m, geo_metadata)
1295
  elif data_source == 'NOAA_NEXRAD_Images':
1296
  # Add NOAA radar image overlay
1297
  add_noaa_radar_overlay(m, geo_metadata)
 
1349
  # Add enhanced information box
1350
  data_source_name = {
1351
  'Canadian_MSC_GeoMet': 'πŸ‡¨πŸ‡¦ Canadian MSC GeoMet',
1352
+ 'Iowa_Mesonet_NEXRAD': '🌽 Iowa Mesonet NEXRAD',
1353
  'NOAA_NEXRAD_Images': 'πŸ‡ΊπŸ‡Έ NOAA NEXRAD',
1354
  'RainViewer': '🌍 RainViewer Global',
1355
  'Temporal_Patterns': 'πŸ“Š Weather Patterns'
 
1466
  popup=f'πŸ‡ΊπŸ‡Έ NOAA Radar Coverage'
1467
  ).add_to(m)
1468
 
1469
+ def add_iowa_mesonet_overlay(m, geo_metadata):
1470
+ """Add Iowa Mesonet NEXRAD composite overlay to map"""
1471
+ try:
1472
+ # Get current time for the most recent composite
1473
+ current_time = datetime.utcnow()
1474
+ time_str = current_time.strftime('%Y%m%d%H%M')
1475
+
1476
+ # Round to nearest 5-minute mark (Iowa updates every 5 minutes)
1477
+ minutes = current_time.minute
1478
+ rounded_minutes = (minutes // 5) * 5
1479
+ rounded_time = current_time.replace(minute=rounded_minutes, second=0, microsecond=0)
1480
+ time_str = rounded_time.strftime('%Y%m%d%H%M')
1481
+
1482
+ # Iowa Mesonet composite URL (try N0Q first, fallback to N0R)
1483
+ composite_urls = [
1484
+ f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0q_{time_str}.png",
1485
+ f"https://mesonet.agron.iastate.edu/data/gis/images/4326/USCOMP/n0r_{time_str}.png"
1486
+ ]
1487
+
1488
+ bbox = geo_metadata['bbox']
1489
+
1490
+ # Try to add the actual composite image overlay
1491
+ for product_type, url in zip(['N0Q', 'N0R'], composite_urls):
1492
+ try:
1493
+ # Iowa composites cover CONUS: roughly 20Β°N to 50Β°N, 125Β°W to 65Β°W
1494
+ composite_bounds = [
1495
+ [20.0, -125.0], # Southwest
1496
+ [50.0, -65.0] # Northeast
1497
+ ]
1498
+
1499
+ folium.raster_layers.ImageOverlay(
1500
+ image=url,
1501
+ bounds=composite_bounds,
1502
+ opacity=0.7,
1503
+ interactive=True,
1504
+ cross_origin=False,
1505
+ name=f"🌽 Iowa Mesonet {product_type}",
1506
+ ).add_to(m)
1507
+
1508
+ print(f"βœ… Added Iowa Mesonet {product_type} composite overlay")
1509
+ break # Success, no need to try fallback
1510
+
1511
+ except Exception as overlay_error:
1512
+ print(f"⚠️ Failed to add Iowa {product_type} overlay: {overlay_error}")
1513
+ continue
1514
+
1515
+ # Add coverage area indicator
1516
+ folium.Rectangle(
1517
+ bounds=[[bbox['south'], bbox['west']], [bbox['north'], bbox['east']]],
1518
+ color='green',
1519
+ fill=True,
1520
+ fillColor='green',
1521
+ fillOpacity=0.1,
1522
+ weight=2,
1523
+ popup='🌽 Iowa Mesonet NEXRAD Coverage'
1524
+ ).add_to(m)
1525
+
1526
+ print("βœ… Added Iowa Mesonet NEXRAD overlay to map")
1527
+
1528
+ except Exception as e:
1529
+ print(f"⚠️ Failed to add Iowa Mesonet overlay: {e}")
1530
+ # Add basic coverage indicator as fallback
1531
+ bbox = geo_metadata.get('bbox', {})
1532
+ if bbox:
1533
+ folium.Rectangle(
1534
+ bounds=[[bbox.get('south', 0), bbox.get('west', 0)],
1535
+ [bbox.get('north', 0), bbox.get('east', 0)]],
1536
+ color='green',
1537
+ fill=True,
1538
+ fillColor='green',
1539
+ fillOpacity=0.1,
1540
+ weight=2,
1541
+ popup='🌽 Iowa Mesonet Coverage'
1542
+ ).add_to(m)
1543
+
1544
  def add_rainviewer_overlay(m, geo_metadata):
1545
  """Add RainViewer radar overlay to map"""
1546
  try:
 
1593
 
1594
  if data_source == 'Canadian_MSC_GeoMet':
1595
  data_info = f"βœ… REAL Canadian MSC GeoMet radar data ({location})\nπŸ‡¨πŸ‡¦ Layer: {geo_meta.get('layer', 'RADAR_1KM_RRAI')}\n⏱️ Updates every 10 minutes\nπŸ“‘ 1km resolution radar composite"
1596
+ elif data_source == 'Iowa_Mesonet_NEXRAD':
1597
+ product_type = geo_meta.get('product_type', 'NEXRAD_Composite')
1598
+ data_info = f"βœ… REAL Iowa Mesonet NEXRAD composites ({location})\n🌽 {product_type} - Cleanest US radar data\n⏱️ Updates every 5 minutes\nπŸ“‘ 1km resolution CONUS coverage"
1599
  elif data_source == 'NOAA_NEXRAD_Images':
1600
  data_info = f"βœ… REAL NOAA NEXRAD radar images from {radar_site} ({location})\nπŸ‡ΊπŸ‡Έ Direct weather.gov radar images\nπŸ“‘ Base reflectivity (N0R) product\n⏱️ Updates every 5-10 minutes"
1601
  elif data_source == 'NEXRAD_REAL':
 
1893
  else:
1894
  data_source_name = {
1895
  'Canadian_MSC_GeoMet': 'πŸ‡¨πŸ‡¦ Canadian MSC GeoMet',
1896
+ 'Iowa_Mesonet_NEXRAD': '🌽 Iowa Mesonet NEXRAD',
1897
  'NOAA_NEXRAD_Images': 'πŸ‡ΊπŸ‡Έ NOAA NEXRAD Images',
1898
  'RainViewer': '🌍 RainViewer Global',
1899
  'Temporal_Patterns': 'πŸ“Š Weather Patterns'