Spaces:
Running
Running
File size: 8,098 Bytes
09f0340 ceca998 f19be1c f83a6ae 838cae9 15b52e7 cf9fd0b a5edd38 7950f1f a5edd38 822b205 2499a01 822b205 2499a01 f347b00 7950f1f f83a6ae 7950f1f 15b52e7 521a6b0 cf9fd0b 521a6b0 cf9fd0b 521a6b0 cf9fd0b 4b783ed 521a6b0 cf9fd0b 521a6b0 cf9fd0b 521a6b0 15b52e7 7950f1f 521a6b0 ba976b4 521a6b0 15b52e7 b09ffd7 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 | import gradio as gr
import pandas as pd
import requests
import urllib.parse
from datetime import datetime
import pytz
import time
import folium
from folium.features import DivIcon
import re
# 1. LOAD DATASET
try:
drift_df = pd.read_csv("weather_drift.csv")
drift_df['Date'] = drift_df['Date'].astype(str)
except Exception:
drift_df = None
def extract_drift(target_date):
if drift_df is not None:
match = drift_df[drift_df['Date'] == target_date]
if not match.empty:
return float(match.iloc[0]['Drift_Fahrenheit'])
return 0.0
# 2. TIMEZONE ENGINE (Deep Navy, No Black)
def get_timezone_string():
fmt = "%H:%M"
pt = datetime.now(pytz.timezone('America/Los_Angeles')).strftime(fmt)
mt = datetime.now(pytz.timezone('America/Denver')).strftime(fmt)
ct = datetime.now(pytz.timezone('America/Chicago')).strftime(fmt)
et = datetime.now(pytz.timezone('America/New_York')).strftime(fmt)
return f"<div style='text-align: right; font-size: 14px; font-weight: 800; color: #1e3a8a; font-family: Arial, sans-serif;'>PT: {pt} | MT: {mt} | CT: {ct} | ET: {et}</div>"
# 3. DUAL GEOCODING (ZIP + CITY)
def geocode_query(query):
query = str(query).strip()
if re.match(r'^\d{5}$', query):
try:
resp = requests.get(f"https://api.zippopotam.us/us/{query}", timeout=3).json()
if "places" in resp:
return float(resp["places"][0]["latitude"]), float(resp["places"][0]["longitude"]), resp["places"][0]["place name"]
except: pass
try:
safe_query = urllib.parse.quote(query.split(',')[0].strip())
resp = requests.get(f"https://geocoding-api.open-meteo.com/v1/search?name={safe_query}&count=1&format=json", timeout=3).json()
if resp.get("results"):
return resp["results"][0]["latitude"], resp["results"][0]["longitude"], resp["results"][0]["name"]
except: pass
return None, None, None
def get_real_city_name(lat, lon):
try:
resp = requests.get(f"https://api.bigdatacloud.net/data/reverse-geocode-client?latitude={lat}&longitude={lon}&localityLanguage=en", timeout=2).json()
return resp.get('city') or resp.get('locality') if resp.get('countryCode') else None
except: return None
# 4. WEATHER MAP ENGINE (100% OPAQUE, HIGH CONTRAST)
def generate_folium_map(lat, lon, date_str, base_max, base_min, wind_val, show_wind=False, loc_name="Target"):
if lat is None: return folium.Map(location=[39.8, -98.5], zoom_start=4, tiles="CartoDB positron")._repr_html_()
m = folium.Map(location=[lat, lon], zoom_start=8, tiles="CartoDB positron")
folium.raster_layers.TileLayer(
tiles='https://mesonet.agron.iastate.edu/cache/tile.py/1.0.0/nexrad-n0q-900913/{z}/{x}/{y}.png',
attr='NEXRAD', overlay=True, opacity=0.4
).add_to(m)
valid_points = [{"lat": lat, "lon": lon, "name": loc_name}]
offsets = [(0.4, 0), (-0.4, 0), (0, 0.5), (0, -0.5)]
for dlat, dlon in offsets:
plat, plon = lat + dlat, lon + dlon
real_name = get_real_city_name(plat, plon)
if real_name and not any(p['name'] == real_name for p in valid_points):
valid_points.append({"lat": plat, "lon": plon, "name": real_name})
drift = extract_drift(date_str)
for pt in valid_points:
var = (pt['lat']-lat)*2 - (pt['lon']-lon)*1.5
hi, lo = round(base_max+drift+var), round(base_min+drift+var)
wind = f" <span style='color:#475569; font-size:12px;'>💨{round(wind_val+abs(var))}mph</span>" if show_wind else ""
# Solid White Background, Deep Navy Border, High-Contrast Red & Blue
label_html = f"""
<div style="background-color: #ffffff; border: 2px solid #1e3a8a; border-radius: 6px; padding: 6px 12px; text-align: center; font-family: Arial, sans-serif; box-shadow: 0 4px 8px rgba(30,58,138,0.2); white-space: nowrap;">
<span style='font-size:14px; font-weight:900; color:#1e3a8a;'>{pt['name'].upper()}:</span>
<span style='font-size:16px; font-weight:900;'>
<span style='color:#cc0000;'>{hi}°</span>
<span style='color:#94a3b8;'>/</span>
<span style='color:#0055cc;'>{lo}°</span>
</span>{wind}
</div>"""
folium.Marker([pt['lat'], pt['lon']], icon=DivIcon(icon_size=(220,40), icon_anchor=(110,20), html=label_html)).add_to(m)
return m._repr_html_()
# 5. CORE ENGINE (RENAMED TO "ACTUAL FORECAST")
def process_search(query):
lat, lon, name = geocode_query(query)
if not lat: yield "", pd.DataFrame(), get_timezone_string(); return
resp = requests.get(f"https://api.open-meteo.com/v1/forecast?latitude={lat}&longitude={lon}&daily=temperature_2m_max,temperature_2m_min,windspeed_10m_max&temperature_unit=fahrenheit&timezone=auto").json()
dates, maxs, mins, winds = resp["daily"]["time"], resp["daily"]["temperature_2m_max"], resp["daily"]["temperature_2m_min"], resp["daily"]["windspeed_10m_max"]
results = []
for i in range(min(len(dates), 7)):
d = extract_drift(dates[i])
results.append({
"Date": dates[i],
"Aqua Forecast": f"{round(maxs[i]+d)}°F / {round(mins[i]+d)}°F",
"Actual Forecast": f"{round(maxs[i])}°F / {round(mins[i])}°F",
"Drift Correction": f"{d}°F"
})
df = pd.DataFrame(results)
yield generate_folium_map(lat, lon, dates[0], maxs[0], mins[0], winds[0], False, name), df, get_timezone_string()
time.sleep(4)
yield generate_folium_map(lat, lon, dates[0], maxs[0], mins[0], winds[0], True, name), df, get_timezone_string()
def on_load(request: gr.Request):
default = "88220"
for m, d, t in process_search(default): yield default, m, d, t
# 6. PROFESSIONAL UI CSS
custom_css = """
body, .gradio-container { background-color: #f1f5f9 !important; color: #1e3a8a !important; font-family: Arial, sans-serif !important; }
#title_row { border-bottom: 2px solid #cbd5e1; padding-bottom: 15px; margin-bottom: 20px; align-items: center; }
#title_text { font-size: 26px !important; font-weight: 900 !important; color: #1e3a8a !important; }
/* Thick, prominent search bar */
#search_box input { border: 2px solid #1e3a8a !important; border-radius: 6px !important; font-weight: 800 !important; color: #1e3a8a !important; background: #ffffff !important; font-size: 16px !important; padding: 10px !important; }
/* Clean Map Border */
#main_map { border: 2px solid #1e3a8a !important; border-radius: 8px; overflow: hidden; height: 500px; box-shadow: 0 4px 12px rgba(30,58,138,0.1); }
#main_map iframe { width: 100%; height: 500px; border: none; }
#forecast_title { font-size: 20px !important; font-weight: 900 !important; color: #1e3a8a !important; margin-top: 25px; margin-bottom: 10px; }
/* High Contrast Data Table */
table { border: 2px solid #1e3a8a !important; background: #ffffff !important; border-radius: 6px !important; overflow: hidden; }
th { background: #e2e8f0 !important; color: #1e3a8a !important; border-bottom: 2px solid #1e3a8a !important; font-weight: 900 !important; text-transform: uppercase; font-size: 14px !important; padding: 12px !important; }
td { border-bottom: 1px solid #cbd5e1 !important; font-weight: 800 !important; color: #1e3a8a !important; padding: 12px !important; }
"""
with gr.Blocks(css=custom_css) as demo:
with gr.Row(elem_id="title_row"):
gr.HTML("<div id='title_text'>🌍 Aqua Forecast</div>", scale=2)
loc_input = gr.Textbox(placeholder="Enter ZIP or City...", show_label=False, elem_id="search_box", scale=2)
tz_display = gr.HTML(scale=2)
spatial_map = gr.HTML(elem_id="main_map")
gr.HTML("<div id='forecast_title'>📅 7-Day Forecast</div>")
main_table = gr.Dataframe(headers=["Date", "Aqua Forecast", "Actual Forecast", "Drift Correction"], interactive=False)
demo.load(on_load, None, [loc_input, spatial_map, main_table, tz_display])
loc_input.submit(process_search, loc_input, [spatial_map, main_table, tz_display])
demo.launch()
|