Spaces:
Runtime error
Runtime error
| 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" | |
| 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) | |