Spaces:
Sleeping
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>
|
@@ -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:
|
| 308 |
-
if 25 <= lat <= 50 and -125 <= lon <= -65: # US
|
| 309 |
-
print("πΊπΈ PRIORITY:
|
| 310 |
-
|
| 311 |
-
if
|
| 312 |
-
print(f"β
SUCCESS: Fetched {len(
|
| 313 |
-
return
|
| 314 |
else:
|
| 315 |
-
print("β οΈ
|
| 316 |
|
| 317 |
# PRIORITY 2: Canadian MSC GeoMet radar (excellent for Canadian locations)
|
| 318 |
-
|
| 319 |
-
|
| 320 |
-
|
| 321 |
-
|
| 322 |
-
|
| 323 |
-
|
|
|
|
| 324 |
|
| 325 |
-
# PRIORITY 3:
|
| 326 |
if 25 <= lat <= 50 and -125 <= lon <= -65: # US coverage
|
| 327 |
-
print("πΊπΈ
|
| 328 |
-
|
| 329 |
-
|
| 330 |
-
|
| 331 |
-
|
| 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'
|