rickyt commited on
Commit
be94c44
·
1 Parent(s): 7d80fff
Files changed (1) hide show
  1. app.py +115 -180
app.py CHANGED
@@ -1,193 +1,128 @@
1
- # app.py
2
- # Gradio app: Tomorrow.io hourly (next 24 hours)
3
- # Requirements: pip install gradio requests pandas python-dateutil python-dotenv
4
-
5
  import os
6
  import requests
7
  import pandas as pd
8
  import gradio as gr
 
9
  from zoneinfo import ZoneInfo
10
- from dotenv import load_dotenv
11
- import numpy as np
12
- from html import escape
13
-
14
- load_dotenv()
15
-
16
- # -------------------
17
- # Helper functions
18
- # -------------------
19
-
20
- def _wx_emoji(txt):
21
- """Safely format weather text with emoji icons"""
22
- if txt is None or (isinstance(txt, float) and np.isnan(txt)):
23
- return "—"
24
- txt = str(txt) # ensure string
25
- t = txt.lower()
26
- if "thunder" in t: return "⛈️ " + txt
27
- if "shower" in t or "rain" in t or "drizzle" in t: return "🌧️ " + txt
28
- if "fog" in t or "mist" in t: return "🌫️ " + txt
29
- if "cloud" in t or "overcast" in t: return "☁️ " + txt
30
- if "clear" in t or "sun" in t: return "☀️ " + txt
31
- return txt
32
-
33
- def _fmt(x, nd=1):
34
- if x is None or (isinstance(x, float) and np.isnan(x)): return "—"
35
- if isinstance(x, (int, np.integer)): return f"{x:d}"
36
- if isinstance(x, (float, np.floating)): return f"{x:.{nd}f}"
37
- return escape(str(x))
38
-
39
- def _heat(value, vmax, hue="blue"):
40
- """Inline, subtle heat-tint"""
41
- try:
42
- v = float(value or 0.0)
43
- except:
44
- v = 0.0
45
- pct = max(0.0, min(1.0, v / max(vmax, 1e-9)))
46
- if hue == "blue":
47
- return f"background: rgba(59, 130, 246, {0.18*pct});"
48
- if hue == "green":
49
- return f"background: rgba(34, 197, 94, {0.18*pct});"
50
- return ""
51
-
52
- def build_pretty_table(df: pd.DataFrame) -> str:
53
- if df is None or len(df) == 0:
54
- return "<p>No data.</p>"
55
-
56
- vmax_pop = max(70, np.nanmax(df["TMRW_RAIN_PROB_%"]))
57
- vmax_mm = max(2.0, np.nanmax(df["TMRW_PrecipRate_mmph"]))
58
-
59
- rows_html = []
60
- rows_html.append(
61
- "<thead><tr>"
62
- "<th>Local Time</th>"
63
- "<th>Weather</th>"
64
- "<th>Rain Prob. %</th>"
65
- "<th>Rain mm/h</th>"
66
- "<th>Humidity %</th>"
67
- "<th>Visibility km</th>"
68
- "<th>Cloud %</th>"
69
- "</tr></thead>"
70
- )
71
-
72
- body = []
73
- for _, r in df.iterrows():
74
- prob = r.get("TMRW_RAIN_PROB_%") or 0
75
- rain = r.get("TMRW_PrecipRate_mmph") or 0
76
-
77
- # ✅ Apply green highlight rule
78
- row_class = ""
79
- try:
80
- if (rain >= 1) or (prob > 50 and rain >= 1):
81
- row_class = "highlight-green"
82
- except Exception:
83
- row_class = ""
84
-
85
- cells = [
86
- f"<td>{escape(str(r.get('Local Time','')))}</td>",
87
- f"<td>{_wx_emoji(r.get('TMRW_Weather'))}</td>",
88
- f"<td style='{_heat(prob, vmax_pop, 'green')}'>{_fmt(prob)}</td>",
89
- f"<td style='{_heat(rain, vmax_mm, 'blue')}'>{_fmt(rain)}</td>",
90
- f"<td>{_fmt(r.get('TMRW_Humidity_%'))}</td>",
91
- f"<td>{_fmt(r.get('TMRW_Visibility_km'))}</td>",
92
- f"<td>{_fmt(r.get('TMRW_Cloud_%'))}</td>",
93
- ]
94
- body.append(f"<tr class='{row_class}'>" + "".join(cells) + "</tr>")
95
-
96
- table_html = f"""
97
- <style>
98
- .wx-table {{ width: 100%; border-collapse: collapse; font-family: ui-sans-serif, system-ui; }}
99
- .wx-table th, .wx-table td {{
100
- padding: 8px; border-bottom: 1px solid #ccc; text-align: center;
101
- }}
102
- /* ✅ Full-row green highlight */
103
- tr.highlight-green td {{
104
- background: rgba(34,197,94,0.35) !important;
105
- }}
106
- </style>
107
- <table class="wx-table">
108
- {''.join(rows_html)}
109
- <tbody>
110
- {''.join(body)}
111
- </tbody>
112
- </table>
113
- """
114
- return table_html
115
-
116
-
117
- # -------------------
118
- # API Configs
119
- # -------------------
120
- TMRW_BASE = "https://api.tomorrow.io/v4/weather/forecast"
121
- TMRW_API_KEY = os.getenv("TOMORROW_API_KEY") or "teKj9Rkys1UzWxKBEs36pAR8paCXnPW6"
122
-
123
- TMRW_WEATHER_CODE = {
124
- 0: "Unknown", 1000: "Clear", 1100: "Mostly Clear", 1101: "Partly Cloudy", 1102: "Mostly Cloudy",
125
- 1001: "Cloudy", 2000: "Fog", 2100: "Light Fog",
126
- 4000: "Drizzle", 4001: "Rain", 4200: "Light Rain", 4201: "Heavy Rain",
127
- 5000: "Snow", 5001: "Flurries", 5100: "Light Snow", 5101: "Heavy Snow",
128
- 6000: "Freezing Drizzle", 6001: "Freezing Rain", 6200: "Light Freezing Rain",
129
- 6201: "Heavy Freezing Rain", 7000: "Ice Pellets",
130
- 7101: "Heavy Ice Pellets", 7102: "Light Ice Pellets",
131
- 8000: "Thunderstorm"
132
- }
133
-
134
- def _safe_to_timestamp(iso: str):
135
- if not iso: return None
136
- try: return pd.to_datetime(iso)
137
- except Exception: return None
138
-
139
- # ---------- Tomorrow.io ----------
140
- def fetch_tomorrow_hourly(latlon: str, hours: int = 24):
141
- params = {"location": latlon.strip(), "timesteps": "hourly", "units": "metric", "apikey": TMRW_API_KEY}
142
- r = requests.get(TMRW_BASE, params=params, timeout=20)
143
- r.raise_for_status()
144
- data = r.json()
145
- hourly = (data.get("timelines") or {}).get("hourly") or []
146
  rows = []
147
- for h in hourly[:hours]:
148
- t_utc = _safe_to_timestamp(h.get("time"))
149
- v = h.get("values") or {}
150
- code = v.get("weatherCode")
151
- rows.append({
152
- "time_utc": t_utc,
153
- "TMRW_Weather": TMRW_WEATHER_CODE.get(code, str(code) if code is not None else None),
154
- "TMRW_Humidity_%": v.get("humidity"),
155
- "TMRW_RAIN_PROB_%": v.get("precipitationProbability"),
156
- "TMRW_PrecipRate_mmph": v.get("rainIntensity"),
157
- "TMRW_Visibility_km": v.get("visibility"),
158
- "TMRW_Cloud_%": v.get("cloudCover"),
159
- })
160
- df = pd.DataFrame(rows)
161
- local_tz = ZoneInfo("Asia/Singapore")
162
- df["Local Time"] = (
163
- pd.to_datetime(df["time_utc"])
164
- .dt.tz_convert(local_tz)
165
- .dt.strftime("%Y-%m-%d %H:%M %Z")
166
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
167
  return df
168
 
169
- # ---------- Gradio UI ----------
170
- with gr.Blocks(fill_height=True) as demo:
171
- gr.Markdown("## Tomorrow.io Hourly Forecast (Next 24h)")
172
- with gr.Row():
173
- loc = gr.Textbox(label="Location (lat,lon)", placeholder="e.g., -6.21,106.85", value="0.46876,116.16879")
174
- btn = gr.Button("Get Forecast", variant="primary")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
175
 
176
- out_table = gr.HTML(label="Hourly forecast table")
177
- out_info = gr.Markdown()
178
- dl_btn = gr.DownloadButton(label="Download CSV", value=None)
 
 
 
 
 
179
 
180
- def _run(loc_s):
181
- try:
182
- df = fetch_tomorrow_hourly(loc_s, hours=24)
183
- html = build_pretty_table(df)
184
- csv_path = "forecast_tomorrow.csv"
185
- df.to_csv(csv_path, index=False)
186
- return html, f"**Location:** {loc_s} • Rows: {len(df)} • Source: Tomorrow.io", csv_path
187
- except Exception as ex:
188
- return f"<pre>{escape(str(ex))}</pre>", "", None
189
 
190
- btn.click(_run, inputs=[loc], outputs=[out_table, out_info, dl_btn])
191
 
192
  if __name__ == "__main__":
193
- demo.launch()
 
 
 
 
 
 
 
1
  import os
2
  import requests
3
  import pandas as pd
4
  import gradio as gr
5
+ from datetime import datetime, timedelta, timezone
6
  from zoneinfo import ZoneInfo
7
+
8
+ # --- CONFIG ---
9
+ TOMORROW_API_KEY = os.getenv("TOMORROW_API_KEY", "teKj9Rkys1UzWxKBEs36pAR8paCXnPW6")
10
+ METEOMATICS_USERNAME = os.getenv("METEO_USERNAME", "ptbukitteknologidigital_tudjuka_ricky")
11
+ METEOMATICS_PASSWORD = os.getenv("METEO_PASSWORD", "u5n25MwaviEG3O3v8q94")
12
+
13
+ DEFAULT_LAT = 0.46876
14
+ DEFAULT_LON = 116.16879
15
+ LOCAL_TZ = ZoneInfo("Asia/Kuala_Lumpur") # ✅ changed from Asia/Jakarta to Asia/Kuala_Lumpur
16
+
17
+ MM_VAR_RAIN = "precip_1h:mm"
18
+ MM_VAR_PROB = "prob_precip_1h:p"
19
+ DEFAULT_MODEL = "ecmwf-ifs"
20
+
21
+ # --- Tomorrow.io fetch ---
22
+ def fetch_tomorrow(lat, lon, hours=24):
23
+ if not TOMORROW_API_KEY:
24
+ raise RuntimeError("Missing TOMORROW_API_KEY")
25
+
26
+ url = "https://api.tomorrow.io/v4/weather/forecast"
27
+ params = {
28
+ "location": f"{lat},{lon}",
29
+ "units": "metric",
30
+ "fields": "rainIntensity,precipitationProbability",
31
+ "apikey": TOMORROW_API_KEY,
32
+ }
33
+ r = requests.get(url, params=params, timeout=25)
34
+ if r.status_code != 200:
35
+ raise RuntimeError(f"Tomorrow.io API error: {r.status_code} {r.text}")
36
+
37
+ data = r.json().get("timelines", {}).get("hourly", [])
38
+ if not data:
39
+ raise RuntimeError("Tomorrow.io: no hourly data returned")
40
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  rows = []
42
+ for p in data[:hours]:
43
+ ts = pd.to_datetime(p["time"], utc=True).tz_convert(LOCAL_TZ)
44
+ vals = p.get("values", {})
45
+ rain = float(vals.get("rainIntensity", 0.0) or 0.0)
46
+ prob = float(vals.get("precipitationProbability", 0.0) or 0.0)
47
+ rows.append({"time": ts, "tio_rain": rain, "tio_prob": prob})
48
+ return pd.DataFrame(rows)
49
+
50
+ # --- Meteomatics fetch ---
51
+ def fetch_meteomatics(lat, lon, hours=24):
52
+ if not METEOMATICS_USERNAME or not METEOMATICS_PASSWORD:
53
+ raise RuntimeError("Missing METEOMATICS credentials")
54
+
55
+ start = datetime.now(timezone.utc).replace(minute=0, second=0, microsecond=0)
56
+ end = start + timedelta(hours=hours - 1)
57
+ timepath = f"{start.isoformat().replace('+00:00','Z')}--{end.isoformat().replace('+00:00','Z')}:PT1H"
58
+ url = f"https://api.meteomatics.com/{timepath}/{MM_VAR_RAIN},{MM_VAR_PROB}/{lat},{lon}/json"
59
+ params = {"model": DEFAULT_MODEL}
60
+
61
+ r = requests.get(url, auth=(METEOMATICS_USERNAME, METEOMATICS_PASSWORD), params=params, timeout=25)
62
+ if r.status_code != 200:
63
+ raise RuntimeError(f"Meteomatics API error: {r.status_code} {r.text}")
64
+
65
+ js = r.json().get("data", [])
66
+ if not js:
67
+ raise RuntimeError("Meteomatics: no data returned")
68
+
69
+ time_map = {}
70
+ for entry in js:
71
+ var = entry.get("parameter")
72
+ for d in entry["coordinates"][0]["dates"]:
73
+ ts = pd.to_datetime(d["date"], utc=True).tz_convert(LOCAL_TZ)
74
+ val = float(d.get("value", 0.0) or 0.0)
75
+ rec = time_map.setdefault(ts, {})
76
+ if var == MM_VAR_RAIN:
77
+ rec["mm_rain"] = val
78
+ elif var == MM_VAR_PROB:
79
+ rec["mm_prob"] = val
80
+
81
+ df = pd.DataFrame([
82
+ {"time": t, "mm_rain": rec.get("mm_rain", 0.0), "mm_prob": rec.get("mm_prob", 0.0)}
83
+ for t, rec in sorted(time_map.items())
84
+ ])
85
  return df
86
 
87
+ # --- Combine & table ---
88
+ def build_table(lat, lon):
89
+ df_tio = fetch_tomorrow(lat, lon, 24)
90
+ df_mm = fetch_meteomatics(lat, lon, 24)
91
+ df = pd.merge(df_tio, df_mm, on="time", how="outer").sort_values("time")
92
+
93
+ table = pd.DataFrame({
94
+ "Time (Asia/Kuala_Lumpur)": df["time"].dt.strftime("%Y-%m-%d %H:%M"),
95
+ "Tomorrow.io Rain (mm/hr)": df["tio_rain"].round(3),
96
+ "Tomorrow.io Prob (%)": df["tio_prob"].round(1),
97
+ "Meteomatics Rain (mm/hr)": df["mm_rain"].round(3),
98
+ "Meteomatics Prob (%)": df["mm_prob"].round(1),
99
+ })
100
+ return table
101
+
102
+ def run_table(lat, lon):
103
+ try:
104
+ table = build_table(lat, lon)
105
+ 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>"
106
+ return table, info
107
+ except Exception as e:
108
+ return None, f"<p><b>Error:</b> {e}</p>"
109
 
110
+ # --- Gradio UI ---
111
+ with gr.Blocks(title="Next 24 Hours — Tomorrow.io + Meteomatics Table (mm/hr)") as demo:
112
+ gr.Markdown("## 🌧️ Next 24 Hours — **Tomorrow.io** & **Meteomatics** (All in mm/hr)\n"
113
+ "Side-by-side hourly rain and probability forecasts for the next 24 hours.")
114
+
115
+ with gr.Row():
116
+ lat = gr.Number(label="Latitude", value=DEFAULT_LAT)
117
+ lon = gr.Number(label="Longitude", value=DEFAULT_LON)
118
 
119
+ btn = gr.Button("Fetch 24h Table")
120
+ table_out = gr.Dataframe(interactive=False, wrap=True)
121
+ info_out = gr.HTML()
 
 
 
 
 
 
122
 
123
+ btn.click(run_table, inputs=[lat, lon], outputs=[table_out, info_out])
124
 
125
  if __name__ == "__main__":
126
+ os.environ["GRADIO_ANALYTICS_ENABLED"]="false"
127
+ os.environ["HF_HUB_DISABLE_TELEMETRY"]="1"
128
+ demo.launch(server_name="127.0.0.1", server_port=7861, show_error=True, inbrowser=False, debug=True)