Skip to content

Commit 91d5213

Browse files
authored
Add files via upload
1 parent 1a003e0 commit 91d5213

1 file changed

Lines changed: 228 additions & 0 deletions

File tree

weather-forecast-app.py

Lines changed: 228 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,228 @@
1+
import tkinter as tk
2+
import customtkinter as ctk
3+
import requests
4+
from PIL import Image, ImageTk
5+
import io
6+
from datetime import datetime
7+
8+
ctk.set_appearance_mode("dark")
9+
ctk.set_default_color_theme("blue")
10+
11+
GEOCODING_URL = "https://geocoding-api.open-meteo.com/v1/search"
12+
WEATHER_URL = "https://api.open-meteo.com/v1/forecast"
13+
14+
class GlowLabel(ctk.CTkLabel):
15+
def __init__(self, master, **kwargs):
16+
super().__init__(master, **kwargs)
17+
self.glow_frames = []
18+
self.current_frame = 0
19+
self.is_glowing = False
20+
21+
def start_glow(self):
22+
if not self.is_glowing:
23+
self.is_glowing = True
24+
self.glow_animation()
25+
26+
def stop_glow(self):
27+
self.is_glowing = False
28+
29+
def glow_animation(self):
30+
if self.is_glowing:
31+
self.current_frame = (self.current_frame + 1) % 20
32+
glow_intensity = abs((self.current_frame - 10) / 10)
33+
glow_color = self.rgb_to_hex((int(100 + 155 * glow_intensity), int(100 + 155 * glow_intensity), 255))
34+
self.configure(text_color=glow_color)
35+
self.after(50, self.glow_animation)
36+
37+
@staticmethod
38+
def rgb_to_hex(rgb):
39+
return "#{:02x}{:02x}{:02x}".format(rgb[0], rgb[1], rgb[2])
40+
41+
class WeatherApp(ctk.CTk):
42+
def __init__(self):
43+
super().__init__()
44+
self.title("Weather Forecast")
45+
self.geometry("500x750") # Increased height to accommodate copyright self.grid_columnconfigure(0, weight=1)
46+
self.grid_rowconfigure(0, weight=1)
47+
48+
self.main_frame = ctk.CTkFrame(self)
49+
self.main_frame.grid(row=0, column=0, padx=20, pady=20, sticky="nsew")
50+
self.main_frame.grid_rowconfigure(1, weight=1) # Make weather frame expandable
51+
52+
53+
54+
55+
self.setup_ui()
56+
57+
def setup_ui(self):
58+
# Search frame
59+
search_frame = ctk.CTkFrame(self.main_frame)
60+
search_frame.grid(row=0, column=0, padx=10, pady=10, sticky="ew")
61+
search_frame.grid_columnconfigure(0, weight=1)
62+
63+
self.city_entry = ctk.CTkEntry(search_frame, placeholder_text="Enter City")
64+
self.city_entry.grid(row=0, column=0, padx=(10, 5), pady=10, sticky="ew")
65+
66+
search_button = ctk.CTkButton(search_frame, text="Search", command=self.search_weather)
67+
search_button.grid(row=0, column=1, padx=(5, 10), pady=10)
68+
69+
# Weather info frame
70+
self.weather_frame = ctk.CTkFrame(self.main_frame)
71+
self.weather_frame.grid(row=1, column=0, padx=10, pady=10, sticky="nsew")
72+
self.weather_frame.grid_columnconfigure(0, weight=1)
73+
74+
self.location_label = GlowLabel(self.weather_frame, text="", font=("Helvetica", 24, "bold"))
75+
self.location_label.grid(row=0, column=0, padx=10, pady=(20, 10))
76+
77+
self.weather_icon = ctk.CTkLabel(self.weather_frame, text="")
78+
self.weather_icon.grid(row=1, column=0, padx=10, pady=10)
79+
80+
self.weather_label = GlowLabel(self.weather_frame, text="", font=("Helvetica", 18))
81+
self.weather_label.grid(row=2, column=0, padx=10, pady=5)
82+
83+
self.temp_label = ctk.CTkLabel(self.weather_frame, text="", font=("Helvetica", 36, "bold"))
84+
self.temp_label.grid(row=3, column=0, padx=10, pady=5)
85+
86+
self.wind_label = ctk.CTkLabel(self.weather_frame, text="", font=("Helvetica", 16))
87+
self.wind_label.grid(row=4, column=0, padx=10, pady=5)
88+
89+
self.time_label = ctk.CTkLabel(self.weather_frame, text="", font=("Helvetica", 14))
90+
self.time_label.grid(row=5, column=0, padx=10, pady=(20, 10))
91+
92+
# Copyright label
93+
copyright_label = ctk.CTkLabel(self.main_frame, text="© 2024 Daniel Monteiro", font=("Helvetica", 10))
94+
copyright_label.grid(row=2, column=0, pady=(0, 5), sticky="s")
95+
96+
# Initially hide the weather frame
97+
self.weather_frame.grid_remove()
98+
99+
def get_coordinates(self, city):
100+
params = {"name": city, "count": 1, "language": "en", "format": "json"}
101+
response = requests.get(GEOCODING_URL, params=params)
102+
data = response.json()
103+
104+
if "results" in data and len(data["results"]) > 0:
105+
result = data["results"][0]
106+
return result["latitude"], result["longitude"], result["name"], result["country"]
107+
return None
108+
109+
def get_weather(self, lat, lon):
110+
params = {
111+
"latitude": lat,
112+
"longitude": lon,
113+
"current_weather": "true",
114+
"temperature_unit": "celsius",
115+
"windspeed_unit": "ms",
116+
"timezone": "auto"
117+
}
118+
response = requests.get(WEATHER_URL, params=params)
119+
data = response.json()
120+
121+
if "current_weather" in data:
122+
weather = data["current_weather"]
123+
return {
124+
"temperature": f"{weather['temperature']}°C",
125+
"windspeed": f"{weather['windspeed']} m/s",
126+
"weathercode": weather['weathercode'],
127+
"time": weather['time']
128+
}
129+
return None
130+
131+
def get_weather_description(self, code):
132+
weather_codes = {
133+
0: "Clear sky", 1: "Mainly clear", 2: "Partly cloudy", 3: "Overcast",
134+
45: "Fog", 48: "Depositing rime fog",
135+
51: "Light drizzle", 53: "Moderate drizzle", 55: "Dense drizzle",
136+
61: "Slight rain", 63: "Moderate rain", 65: "Heavy rain",
137+
71: "Slight snow fall", 73: "Moderate snow fall", 75: "Heavy snow fall",
138+
77: "Snow grains",
139+
80: "Slight rain showers", 81: "Moderate rain showers", 82: "Violent rain showers",
140+
85: "Slight snow showers", 86: "Heavy snow showers",
141+
95: "Thunderstorm", 96: "Thunderstorm with slight hail", 99: "Thunderstorm with heavy hail"
142+
}
143+
return weather_codes.get(code, "Unknown")
144+
145+
def get_weather_icon(self, code):
146+
if code == 0:
147+
icon_name = "01d" # Clear sky
148+
elif code in [1, 2, 3]:
149+
icon_name = "02d" # Partly cloudy
150+
elif code in [45, 48]:
151+
icon_name = "50d" # Fog
152+
elif code in [51, 53, 55, 61, 63, 65, 80, 81, 82]:
153+
icon_name = "10d" # Rain
154+
elif code in [71, 73, 75, 77, 85, 86]:
155+
icon_name = "13d" # Snow
156+
elif code in [95, 96, 99]:
157+
icon_name = "11d" # Thunderstorm
158+
else:
159+
icon_name = "03d" # Default to cloudy
160+
161+
icon_url = f"http://openweathermap.org/img/wn/{icon_name}@2x.png"
162+
response = requests.get(icon_url)
163+
icon_data = response.content
164+
icon_image = Image.open(io.BytesIO(icon_data))
165+
return ImageTk.PhotoImage(icon_image)
166+
167+
def search_weather(self):
168+
city = self.city_entry.get()
169+
if not city:
170+
self.show_error("Please enter a city name.")
171+
return
172+
173+
coord_data = self.get_coordinates(city)
174+
if coord_data:
175+
lat, lon, city_name, country = coord_data
176+
weather_data = self.get_weather(lat, lon)
177+
if weather_data:
178+
self.update_weather_display(city_name, country, weather_data)
179+
else:
180+
self.show_error("Unable to fetch weather data. Please try again.")
181+
else:
182+
self.show_error("City not found. Please check the spelling and try again.")
183+
184+
def update_weather_display(self, city_name, country, weather_data):
185+
self.weather_frame.grid()
186+
self.location_label.configure(text=f"{city_name}, {country}")
187+
self.location_label.start_glow()
188+
189+
weather_icon_img = self.get_weather_icon(weather_data['weathercode'])
190+
self.weather_icon.configure(image=weather_icon_img)
191+
self.weather_icon.image = weather_icon_img
192+
193+
weather_description = self.get_weather_description(weather_data['weathercode'])
194+
self.weather_label.configure(text=weather_description)
195+
self.weather_label.start_glow()
196+
197+
self.temp_label.configure(text=weather_data['temperature'])
198+
self.wind_label.configure(text=f"Wind Speed: {weather_data['windspeed']}")
199+
200+
time = datetime.fromisoformat(weather_data['time'])
201+
self.time_label.configure(text=f"Last updated: {time.strftime('%Y-%m-%d %H:%M:%S')}")
202+
203+
self.animate_weather_display()
204+
205+
def animate_weather_display(self):
206+
def fade_in(widget, alpha=0):
207+
if alpha < 1:
208+
alpha += 0.1
209+
widget.configure(fg_color=self.blend_colors("#1a1a1a", "#2b2b2b", alpha))
210+
self.after(50, lambda: fade_in(widget, alpha))
211+
212+
fade_in(self.weather_frame)
213+
214+
@staticmethod
215+
def blend_colors(color1, color2, alpha):
216+
r1, g1, b1 = int(color1[1:3], 16), int(color1[3:5], 16), int(color1[5:7], 16)
217+
r2, g2, b2 = int(color2[1:3], 16), int(color2[3:5], 16), int(color2[5:7], 16)
218+
r = int(r1 * (1 - alpha) + r2 * alpha)
219+
g = int(g1 * (1 - alpha) + g2 * alpha)
220+
b = int(b1 * (1 - alpha) + b2 * alpha)
221+
return f"#{r:02x}{g:02x}{b:02x}"
222+
223+
def show_error(self, message):
224+
ctk.CTkMessagebox(title="Error", message=message, icon="cancel")
225+
226+
if __name__ == "__main__":
227+
app = WeatherApp()
228+
app.mainloop()

0 commit comments

Comments
 (0)