Spaces:
Paused
Paused
Create app.py
Browse files
app.py
ADDED
@@ -0,0 +1,218 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import dash
|
2 |
+
from dash import dcc, html, Input, Output, State
|
3 |
+
import dash_bootstrap_components as dbc
|
4 |
+
import plotly.graph_objs as go
|
5 |
+
import requests
|
6 |
+
import pandas as pd
|
7 |
+
from datetime import datetime
|
8 |
+
import os
|
9 |
+
from dotenv import load_dotenv
|
10 |
+
import threading
|
11 |
+
|
12 |
+
# Load environment variables
|
13 |
+
load_dotenv()
|
14 |
+
|
15 |
+
# Initialize the Dash app
|
16 |
+
app = dash.Dash(__name__, external_stylesheets=[dbc.themes.BOOTSTRAP])
|
17 |
+
server = app.server
|
18 |
+
|
19 |
+
# AccuWeather API key
|
20 |
+
API_KEY = os.getenv('ACCUWEATHER_API_KEY')
|
21 |
+
|
22 |
+
# Base URL for AccuWeather API
|
23 |
+
BASE_URL = "http://dataservice.accuweather.com"
|
24 |
+
|
25 |
+
def get_location_key(lat, lon):
|
26 |
+
url = f"{BASE_URL}/locations/v1/cities/geoposition/search"
|
27 |
+
params = {
|
28 |
+
"apikey": API_KEY,
|
29 |
+
"q": f"{lat},{lon}",
|
30 |
+
}
|
31 |
+
response = requests.get(url, params=params)
|
32 |
+
data = response.json()
|
33 |
+
return data["Key"]
|
34 |
+
|
35 |
+
def get_current_conditions(location_key):
|
36 |
+
url = f"{BASE_URL}/currentconditions/v1/{location_key}"
|
37 |
+
params = {
|
38 |
+
"apikey": API_KEY,
|
39 |
+
"details": "true",
|
40 |
+
}
|
41 |
+
response = requests.get(url, params=params)
|
42 |
+
return response.json()[0]
|
43 |
+
|
44 |
+
def get_forecast(location_key):
|
45 |
+
url = f"{BASE_URL}/forecasts/v1/daily/5day/{location_key}"
|
46 |
+
params = {
|
47 |
+
"apikey": API_KEY,
|
48 |
+
"metric": "true",
|
49 |
+
}
|
50 |
+
response = requests.get(url, params=params)
|
51 |
+
return response.json()
|
52 |
+
|
53 |
+
def get_indices(location_key):
|
54 |
+
url = f"{BASE_URL}/indices/v1/daily/5day/{location_key}"
|
55 |
+
params = {
|
56 |
+
"apikey": API_KEY,
|
57 |
+
}
|
58 |
+
response = requests.get(url, params=params)
|
59 |
+
return response.json()
|
60 |
+
|
61 |
+
def get_alerts(location_key):
|
62 |
+
url = f"{BASE_URL}/alerts/v1/{location_key}"
|
63 |
+
params = {
|
64 |
+
"apikey": API_KEY,
|
65 |
+
}
|
66 |
+
response = requests.get(url, params=params)
|
67 |
+
return response.json()
|
68 |
+
|
69 |
+
# App layout
|
70 |
+
app.layout = dbc.Container([
|
71 |
+
dbc.Row([
|
72 |
+
dbc.Col([
|
73 |
+
html.H1("Weather Dashboard", className="text-center mb-4"),
|
74 |
+
html.Div(id="loading-output"),
|
75 |
+
html.Div(id="alert-output"),
|
76 |
+
dbc.Row([
|
77 |
+
dbc.Col([
|
78 |
+
html.Div(id="current-weather-output"),
|
79 |
+
], width=6),
|
80 |
+
dbc.Col([
|
81 |
+
html.Div(id="forecast-output"),
|
82 |
+
], width=6),
|
83 |
+
]),
|
84 |
+
html.Div(id="indices-output"),
|
85 |
+
], width=12)
|
86 |
+
]),
|
87 |
+
dcc.Store(id="location-store"),
|
88 |
+
dcc.Interval(id="location-interval", interval=1000, max_intervals=1),
|
89 |
+
], fluid=True, className="mt-4")
|
90 |
+
|
91 |
+
@app.callback(
|
92 |
+
Output("location-store", "data"),
|
93 |
+
Input("location-interval", "n_intervals")
|
94 |
+
)
|
95 |
+
def update_location(n):
|
96 |
+
return {"latitude": 0, "longitude": 0} # Replace with actual browser location data
|
97 |
+
|
98 |
+
@app.callback(
|
99 |
+
[Output("loading-output", "children"),
|
100 |
+
Output("current-weather-output", "children"),
|
101 |
+
Output("forecast-output", "children"),
|
102 |
+
Output("indices-output", "children"),
|
103 |
+
Output("alert-output", "children")],
|
104 |
+
Input("location-store", "data")
|
105 |
+
)
|
106 |
+
def update_weather(location):
|
107 |
+
if not location:
|
108 |
+
return "Waiting for location data...", "", "", "", ""
|
109 |
+
|
110 |
+
lat, lon = location["latitude"], location["longitude"]
|
111 |
+
|
112 |
+
loading_output = html.Div([
|
113 |
+
html.P("Loading weather data...", className="text-center"),
|
114 |
+
dbc.Spinner(color="primary", type="grow"),
|
115 |
+
])
|
116 |
+
|
117 |
+
def fetch_weather_data():
|
118 |
+
nonlocal loading_output
|
119 |
+
try:
|
120 |
+
location_key = get_location_key(lat, lon)
|
121 |
+
current = get_current_conditions(location_key)
|
122 |
+
forecast = get_forecast(location_key)
|
123 |
+
indices = get_indices(location_key)
|
124 |
+
alerts = get_alerts(location_key)
|
125 |
+
|
126 |
+
current_weather = create_current_weather_card(current)
|
127 |
+
forecast_output = create_forecast_card(forecast)
|
128 |
+
indices_output = create_indices_card(indices)
|
129 |
+
alert_output = create_alert_card(alerts)
|
130 |
+
|
131 |
+
loading_output = ""
|
132 |
+
|
133 |
+
return current_weather, forecast_output, indices_output, alert_output
|
134 |
+
|
135 |
+
except Exception as e:
|
136 |
+
loading_output = html.Div(f"Error fetching weather data: {str(e)}", className="text-danger")
|
137 |
+
return "", "", "", ""
|
138 |
+
|
139 |
+
# Use threading to fetch data asynchronously
|
140 |
+
thread = threading.Thread(target=fetch_weather_data)
|
141 |
+
thread.start()
|
142 |
+
thread.join(timeout=10) # Wait for up to 10 seconds
|
143 |
+
|
144 |
+
if thread.is_alive():
|
145 |
+
loading_output = html.Div("Weather data fetch timed out. Please try again.", className="text-warning")
|
146 |
+
return loading_output, "", "", "", ""
|
147 |
+
|
148 |
+
current_weather, forecast_output, indices_output, alert_output = fetch_weather_data()
|
149 |
+
return loading_output, current_weather, forecast_output, indices_output, alert_output
|
150 |
+
|
151 |
+
def create_current_weather_card(current):
|
152 |
+
return dbc.Card([
|
153 |
+
dbc.CardBody([
|
154 |
+
html.H4("Current Weather", className="card-title"),
|
155 |
+
html.P(f"Temperature: {current['Temperature']['Metric']['Value']}°C"),
|
156 |
+
html.P(f"Condition: {current['WeatherText']}"),
|
157 |
+
html.P(f"Feels Like: {current['RealFeelTemperature']['Metric']['Value']}°C"),
|
158 |
+
html.P(f"Wind: {current['Wind']['Speed']['Metric']['Value']} km/h"),
|
159 |
+
html.P(f"Humidity: {current['RelativeHumidity']}%"),
|
160 |
+
])
|
161 |
+
], className="mb-4")
|
162 |
+
|
163 |
+
def create_forecast_card(forecast):
|
164 |
+
daily_forecasts = forecast['DailyForecasts']
|
165 |
+
|
166 |
+
fig = go.Figure()
|
167 |
+
|
168 |
+
dates = [datetime.strptime(day['Date'], "%Y-%m-%dT%H:%M:%S%z").strftime("%m-%d") for day in daily_forecasts]
|
169 |
+
max_temps = [day['Temperature']['Maximum']['Value'] for day in daily_forecasts]
|
170 |
+
min_temps = [day['Temperature']['Minimum']['Value'] for day in daily_forecasts]
|
171 |
+
|
172 |
+
fig.add_trace(go.Scatter(x=dates, y=max_temps, name="Max Temp", line=dict(color="red")))
|
173 |
+
fig.add_trace(go.Scatter(x=dates, y=min_temps, name="Min Temp", line=dict(color="blue")))
|
174 |
+
|
175 |
+
fig.update_layout(
|
176 |
+
title="5-Day Temperature Forecast",
|
177 |
+
xaxis_title="Date",
|
178 |
+
yaxis_title="Temperature (°C)",
|
179 |
+
legend_title="Temperature",
|
180 |
+
height=400
|
181 |
+
)
|
182 |
+
|
183 |
+
return dbc.Card([
|
184 |
+
dbc.CardBody([
|
185 |
+
html.H4("5-Day Forecast", className="card-title"),
|
186 |
+
dcc.Graph(figure=fig)
|
187 |
+
])
|
188 |
+
], className="mb-4")
|
189 |
+
|
190 |
+
def create_indices_card(indices):
|
191 |
+
return dbc.Card([
|
192 |
+
dbc.CardBody([
|
193 |
+
html.H4("Weather Indices", className="card-title"),
|
194 |
+
html.Div([
|
195 |
+
html.P(f"{index['Name']}: {index['Category']}")
|
196 |
+
for index in indices[:5] # Display first 5 indices
|
197 |
+
])
|
198 |
+
])
|
199 |
+
], className="mb-4")
|
200 |
+
|
201 |
+
def create_alert_card(alerts):
|
202 |
+
if not alerts:
|
203 |
+
return html.Div()
|
204 |
+
|
205 |
+
return dbc.Card([
|
206 |
+
dbc.CardBody([
|
207 |
+
html.H4("Weather Alerts", className="card-title text-danger"),
|
208 |
+
html.Div([
|
209 |
+
html.P(alert['Text'])
|
210 |
+
for alert in alerts
|
211 |
+
])
|
212 |
+
])
|
213 |
+
], className="mb-4")
|
214 |
+
|
215 |
+
if __name__ == '__main__':
|
216 |
+
print("Starting the Dash application...")
|
217 |
+
app.run(debug=True, host='0.0.0.0', port=7860)
|
218 |
+
print("Dash application has finished running.")
|