weather-forcast / app.py
zeeshan4801's picture
Create app.py
7eb6dd2 verified
import requests
import pandas as pd
from datetime import date, timedelta
from functools import lru_cache
import gradio as gr
GEOCODE_URL = "https://geocoding-api.open-meteo.com/v1/search"
FORECAST_URL = "https://api.open-meteo.com/v1/forecast"
@lru_cache(maxsize=128)
def geocode_city(city_name: str):
"""Return (name, latitude, longitude, country) or raise ValueError if not found."""
params = {"name": city_name, "count": 5, "language": "en", "format": "json"}
r = requests.get(GEOCODE_URL, params=params, timeout=10)
r.raise_for_status()
data = r.json()
results = data.get("results")
if not results:
raise ValueError(f"City '{city_name}' not found.")
# choose the best match (first)
best = results[0]
return {
"name": best.get("name"),
"latitude": best.get("latitude"),
"longitude": best.get("longitude"),
"country": best.get("country")
}
def fetch_forecast(lat: float, lon: float, days: int = 3):
"""Fetch daily forecast for given days (1..7). Returns pandas.DataFrame."""
if days < 1 or days > 16:
raise ValueError("Days must be between 1 and 16 (Open-Meteo supports up to 16).")
start = date.today()
end = start + timedelta(days=days-1)
params = {
"latitude": lat,
"longitude": lon,
"daily": "temperature_2m_max,temperature_2m_min,precipitation_sum,weathercode",
"timezone": "auto",
"start_date": start.isoformat(),
"end_date": end.isoformat()
}
r = requests.get(FORECAST_URL, params=params, timeout=10)
r.raise_for_status()
data = r.json()
daily = data.get("daily", {})
df = pd.DataFrame({
"date": daily.get("time", []),
"temp_max_C": daily.get("temperature_2m_max", []),
"temp_min_C": daily.get("temperature_2m_min", []),
"precip_mm": daily.get("precipitation_sum", []),
"weathercode": daily.get("weathercode", [])
})
# Convert date column to datetime
df["date"] = pd.to_datetime(df["date"]).dt.date
# Add a human readable weather description from weathercode
df["weather_description"] = df["weathercode"].apply(map_weathercode)
return df
def map_weathercode(code):
"""Simple mapping of Open-Meteo weather codes to text."""
# This is a compact mapping for common codes; not exhaustive.
mapping = {
0: "Clear sky",
1: "Mainly clear",
2: "Partly cloudy",
3: "Overcast",
45: "Fog",
48: "Depositing rime fog",
51: "Light drizzle",
53: "Moderate drizzle",
55: "Dense drizzle",
56: "Light freezing drizzle",
57: "Dense freezing drizzle",
61: "Slight rain",
63: "Moderate rain",
65: "Heavy rain",
66: "Light freezing rain",
67: "Heavy freezing rain",
71: "Slight snow fall",
73: "Moderate snow fall",
75: "Heavy snow fall",
77: "Snow grains",
80: "Slight rain showers",
81: "Moderate rain showers",
82: "Violent rain showers",
85: "Slight snow showers",
86: "Heavy snow showers",
95: "Thunderstorm",
96: "Thunderstorm with slight hail",
99: "Thunderstorm with heavy hail"
}
return mapping.get(code, f"Code {code}")
def get_weather(city: str, days: int):
"""Main wrapper for UI: returns (summary_str, dataframe)"""
city = city.strip()
if not city:
return "Please enter a city name.", None
try:
geo = geocode_city(city)
except Exception as e:
return f"Error: {e}", None
try:
df = fetch_forecast(geo["latitude"], geo["longitude"], days)
except Exception as e:
return f"Error fetching forecast: {e}", None
# Basic summary
start = df.iloc[0]["date"]
end = df.iloc[-1]["date"]
summary = (
f"Forecast for {geo['name']}, {geo['country']} ({geo['latitude']:.3f}, {geo['longitude']:.3f})\n"
f"Period: {start}{end}\n"
f"Days shown: {len(df)}"
)
# Nicely format temperatures to 1 decimal
df_display = df.copy()
df_display["temp_max_C"] = df_display["temp_max_C"].round(1)
df_display["temp_min_C"] = df_display["temp_min_C"].round(1)
df_display["precip_mm"] = df_display["precip_mm"].round(1)
# Reorder columns for display
df_display = df_display[["date", "weather_description", "temp_min_C", "temp_max_C", "precip_mm"]]
df_display.columns = ["Date", "Weather", "Min (°C)", "Max (°C)", "Precip (mm)"]
return summary, df_display
# Build Gradio UI
with gr.Blocks() as demo:
gr.Markdown("## 🌤️ Simple Weather Forecast (Open-Meteo) — Gradio Demo")
gr.Markdown("Enter a city name and select number of days (1–7). Data comes from Open-Meteo (no API key).")
with gr.Row():
city_input = gr.Textbox(label="City (e.g., Karachi, London, New York)", placeholder="Type a city name...", value="Karachi")
days_input = gr.Slider(minimum=1, maximum=7, step=1, label="Days of forecast", value=3)
with gr.Row():
run_btn = gr.Button("Get Forecast")
summary_out = gr.Textbox(label="Summary", interactive=False)
table_out = gr.Dataframe(headers=["Date", "Weather", "Min (°C)", "Max (°C)", "Precip (mm)"], interactive=False)
def _wrapped(city, days):
summary, df = get_weather(city, days)
# gradio expects either dataframe or None
return summary, df
run_btn.click(_wrapped, inputs=[city_input, days_input], outputs=[summary_out, table_out])
if __name__ == "__main__":
demo.launch(server_name="0.0.0.0", server_port=7860)