fcastrain / app.py
rickyt
tomorrow
8e2d880
import os
import requests
import pandas as pd
import gradio as gr
from datetime import datetime, timedelta, timezone
from zoneinfo import ZoneInfo
# --- CONFIG ---
TOMORROW_API_KEY = os.getenv("TOMORROW_API_KEY", "teKj9Rkys1UzWxKBEs36pAR8paCXnPW6")
METEOMATICS_USERNAME = os.getenv("METEO_USERNAME", "ptbukitteknologidigital_tudjuka_ricky")
METEOMATICS_PASSWORD = os.getenv("METEO_PASSWORD", "u5n25MwaviEG3O3v8q94")
DEFAULT_LAT = 0.46876
DEFAULT_LON = 116.16879
LOCAL_TZ = ZoneInfo("Asia/Kuala_Lumpur") # ✅ changed from Asia/Jakarta to Asia/Kuala_Lumpur
MM_VAR_RAIN = "precip_1h:mm"
MM_VAR_PROB = "prob_precip_1h:p"
DEFAULT_MODEL = "ecmwf-ifs"
# --- Tomorrow.io fetch ---
def fetch_tomorrow(lat, lon, hours=24):
if not TOMORROW_API_KEY:
raise RuntimeError("Missing TOMORROW_API_KEY")
url = "https://api.tomorrow.io/v4/weather/forecast"
params = {
"location": f"{lat},{lon}",
"units": "metric",
"fields": "rainIntensity,precipitationProbability",
"apikey": TOMORROW_API_KEY,
}
r = requests.get(url, params=params, timeout=25)
if r.status_code != 200:
raise RuntimeError(f"Tomorrow.io API error: {r.status_code} {r.text}")
data = r.json().get("timelines", {}).get("hourly", [])
if not data:
raise RuntimeError("Tomorrow.io: no hourly data returned")
rows = []
for p in data[:hours]:
ts = pd.to_datetime(p["time"], utc=True).tz_convert(LOCAL_TZ)
vals = p.get("values", {})
rain = float(vals.get("rainIntensity", 0.0) or 0.0)
prob = float(vals.get("precipitationProbability", 0.0) or 0.0)
rows.append({"time": ts, "tio_rain": rain, "tio_prob": prob})
return pd.DataFrame(rows)
# --- Meteomatics fetch ---
def fetch_meteomatics(lat, lon, hours=24):
if not METEOMATICS_USERNAME or not METEOMATICS_PASSWORD:
raise RuntimeError("Missing METEOMATICS credentials")
start = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0)
end = start + timedelta(hours=hours - 1)
timepath = f"{start.isoformat().replace('+00:00','Z')}--{end.isoformat().replace('+00:00','Z')}:PT1H"
url = f"https://api.meteomatics.com/{timepath}/{MM_VAR_RAIN},{MM_VAR_PROB}/{lat},{lon}/json"
params = {"model": DEFAULT_MODEL}
r = requests.get(url, auth=(METEOMATICS_USERNAME, METEOMATICS_PASSWORD), params=params, timeout=25)
if r.status_code != 200:
raise RuntimeError(f"Meteomatics API error: {r.status_code} {r.text}")
js = r.json().get("data", [])
if not js:
raise RuntimeError("Meteomatics: no data returned")
time_map = {}
for entry in js:
var = entry.get("parameter")
for d in entry["coordinates"][0]["dates"]:
ts = pd.to_datetime(d["date"], utc=True).tz_convert(LOCAL_TZ)
val = float(d.get("value", 0.0) or 0.0)
rec = time_map.setdefault(ts, {})
if var == MM_VAR_RAIN:
rec["mm_rain"] = val
elif var == MM_VAR_PROB:
rec["mm_prob"] = val
df = pd.DataFrame([
{"time": t, "mm_rain": rec.get("mm_rain", 0.0), "mm_prob": rec.get("mm_prob", 0.0)}
for t, rec in sorted(time_map.items())
])
return df
# --- Combine & table ---
def build_table(lat, lon):
df_tio = fetch_tomorrow(lat, lon, 24)
df_mm = fetch_meteomatics(lat, lon, 24)
df = pd.merge(df_tio, df_mm, on="time", how="outer").sort_values("time")
table = pd.DataFrame({
"Time (Asia/Kuala_Lumpur)": df["time"].dt.strftime("%Y-%m-%d %H:%M"),
"Tomorrow.io Rain (mm/hr)": df["tio_rain"].round(3),
"Tomorrow.io Prob (%)": df["tio_prob"].round(1),
"Meteomatics Rain (mm/hr)": df["mm_rain"].round(3),
"Meteomatics Prob (%)": df["mm_prob"].round(1),
})
return table
def run_table(lat, lon):
try:
table = build_table(lat, lon)
info = f"<small>Location: {lat:.5f}, {lon:.5f} | Model: <b>{DEFAULT_MODEL}</b> | Timezone: <b>Asia/Kuala_Lumpur</b> | Units: <b>mm/hr</b> | Horizon: 24 hours</small>"
return table, info
except Exception as e:
return None, f"<p><b>Error:</b> {e}</p>"
# --- Gradio UI ---
with gr.Blocks(title="Next 24 Hours — Tomorrow.io + Meteomatics Table (mm/hr)") as demo:
gr.Markdown("## 🌧️ Next 24 Hours — **Tomorrow.io** & **Meteomatics** (All in mm/hr)\n"
"Side-by-side hourly rain and probability forecasts for the next 24 hours.")
with gr.Row():
lat = gr.Number(label="Latitude", value=DEFAULT_LAT)
lon = gr.Number(label="Longitude", value=DEFAULT_LON)
btn = gr.Button("Fetch 24h Table")
table_out = gr.Dataframe(interactive=False, wrap=True)
info_out = gr.HTML()
btn.click(run_table, inputs=[lat, lon], outputs=[table_out, info_out])
# if __name__ == "__main__":
# os.environ["GRADIO_ANALYTICS_ENABLED"]="false"
# os.environ["HF_HUB_DISABLE_TELEMETRY"]="1"
# demo.launch(server_name="127.0.0.1", server_port=7861, show_error=True, inbrowser=False, debug=True)
if __name__ == "__main__":
demo.launch()