Python实例题:基于 Python 的简单天气查询应用

目录

Python实例题

题目

要求:

解题思路:

代码实现:

Python实例题

题目

基于 Python 的简单天气查询应用

要求

  • 使用 Python 构建一个天气查询应用,支持以下功能:
    • 通过城市名称查询实时天气信息
    • 显示天气状况、温度、湿度、风速等基本信息
    • 支持多日天气预报查询
    • 显示天气趋势图表(温度变化、降水概率等)
    • 支持摄氏度 / 华氏度单位切换
  • 使用tkinter构建图形用户界面。
  • 调用公开天气 API 获取数据(如 OpenWeatherMap)。

解题思路

  • 使用tkinter构建界面,包括输入框、查询按钮和结果显示区域。
  • 通过requests库调用 OpenWeatherMap API 获取天气数据。
  • 使用matplotlib绘制天气趋势图表。

代码实现

import tkinter as tk
from tkinter import ttk, messagebox, scrolledtext
import requests
import json
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import datetime
import threading
import time

# 设置中文字体
plt.rcParams["font.family"] = ["SimHei", "WenQuanYi Micro Hei", "Heiti TC"]
plt.rcParams['axes.unicode_minus'] = False  # 解决负号显示问题

class WeatherApp:
    def __init__(self, root):
        self.root = root
        self.root.title("天气查询应用")
        self.root.geometry("800x600")
        
        # OpenWeatherMap API密钥(需要替换为自己的API密钥)
        self.api_key = "YOUR_API_KEY"
        self.base_url = "http://api.openweathermap.org/data/2.5/"
        
        # 温度单位(C或F)
        self.temp_unit = "C"
        
        # 创建主界面
        self.create_main_window()
    
    def create_main_window(self):
        """创建主窗口"""
        # 创建顶部框架 - 搜索区域
        top_frame = ttk.Frame(self.root, padding=10)
        top_frame.pack(fill=tk.X)
        
        # 城市输入框
        ttk.Label(top_frame, text="城市:").pack(side=tk.LEFT, padx=5)
        self.city_var = tk.StringVar()
        city_entry = ttk.Entry(top_frame, textvariable=self.city_var, width=30)
        city_entry.pack(side=tk.LEFT, padx=5)
        city_entry.bind("<Return>", lambda event: self.get_weather())
        
        # 查询按钮
        ttk.Button(top_frame, text="查询", command=self.get_weather).pack(side=tk.LEFT, padx=5)
        
        # 单位切换按钮
        self.unit_var = tk.StringVar(value="摄氏度")
        unit_cb = ttk.Combobox(top_frame, textvariable=self.unit_var, values=["摄氏度", "华氏度"], width=8)
        unit_cb.pack(side=tk.LEFT, padx=5)
        unit_cb.bind("<<ComboboxSelected>>", self.change_unit)
        
        # 创建主框架 - 结果显示区域
        main_frame = ttk.Frame(self.root, padding=10)
        main_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建左侧面板 - 当前天气信息
        left_frame = ttk.LabelFrame(main_frame, text="当前天气", padding=10)
        left_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 城市和日期
        self.city_date_var = tk.StringVar(value="")
        ttk.Label(left_frame, textvariable=self.city_date_var, font=('SimHei', 16, 'bold')).pack(anchor=tk.W)
        
        # 天气图标
        self.weather_icon = tk.Label(left_frame, text="☀️", font=('SimHei', 48))
        self.weather_icon.pack(pady=10)
        
        # 温度
        self.temp_var = tk.StringVar(value="")
        ttk.Label(left_frame, textvariable=self.temp_var, font=('SimHei', 24)).pack(pady=5)
        
        # 天气描述
        self.desc_var = tk.StringVar(value="")
        ttk.Label(left_frame, textvariable=self.desc_var, font=('SimHei', 14)).pack(pady=5)
        
        # 其他信息
        self.other_info = scrolledtext.ScrolledText(left_frame, width=30, height=10, wrap=tk.WORD)
        self.other_info.pack(fill=tk.BOTH, expand=True, pady=10)
        
        # 创建右侧面板 - 预报信息
        right_frame = ttk.LabelFrame(main_frame, text="天气预报", padding=10)
        right_frame.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=5, pady=5)
        
        # 创建图表框架
        self.chart_frame = ttk.Frame(right_frame)
        self.chart_frame.pack(fill=tk.BOTH, expand=True)
        
        # 创建预报信息框架
        self.forecast_frame = ttk.Frame(right_frame)
        self.forecast_frame.pack(fill=tk.X, pady=10)
        
        # 创建状态栏
        self.status_var = tk.StringVar(value="就绪")
        ttk.Label(self.root, textvariable=self.status_var, relief=tk.SUNKEN, anchor=tk.W).pack(side=tk.BOTTOM, fill=tk.X)
    
    def get_weather(self):
        """获取天气信息"""
        city = self.city_var.get().strip()
        
        if not city:
            messagebox.showinfo("提示", "请输入城市名称")
            return
        
        # 清空之前的结果
        self.other_info.delete(1.0, tk.END)
        
        # 在单独的线程中获取天气数据
        self.status_var.set(f"正在查询 {city} 的天气...")
        threading.Thread(target=self._get_weather_thread, args=(city,)).start()
    
    def _get_weather_thread(self, city):
        """在单独的线程中获取天气数据"""
        try:
            # 获取当前天气
            current_weather_url = f"{self.base_url}weather?q={city}&appid={self.api_key}&units=metric"
            current_response = requests.get(current_weather_url)
            current_data = json.loads(current_response.text)
            
            if current_response.status_code != 200:
                error_msg = current_data.get('message', '未知错误')
                self.root.after(0, lambda: messagebox.showerror("错误", f"获取天气失败: {error_msg}"))
                self.root.after(0, lambda: self.status_var.set("就绪"))
                return
            
            # 获取预报数据
            forecast_url = f"{self.base_url}forecast?q={city}&appid={self.api_key}&units=metric"
            forecast_response = requests.get(forecast_url)
            forecast_data = json.loads(forecast_response.text)
            
            if forecast_response.status_code != 200:
                error_msg = forecast_data.get('message', '未知错误')
                self.root.after(0, lambda: messagebox.showerror("错误", f"获取预报失败: {error_msg}"))
                self.root.after(0, lambda: self.status_var.set("就绪"))
                return
            
            # 更新UI
            self.root.after(0, lambda: self._update_ui(current_data, forecast_data))
            
        except Exception as e:
            self.root.after(0, lambda: messagebox.showerror("错误", f"发生异常: {str(e)}"))
        finally:
            self.root.after(0, lambda: self.status_var.set("就绪"))
    
    def _update_ui(self, current_data, forecast_data):
        """更新UI显示"""
        # 更新城市和日期
        city_name = current_data['name']
        country = current_data['sys']['country']
        current_time = datetime.datetime.fromtimestamp(current_data['dt']).strftime('%Y-%m-%d %H:%M:%S')
        self.city_date_var.set(f"{city_name}, {country} - {current_time}")
        
        # 更新天气图标和描述
        weather = current_data['weather'][0]
        weather_desc = weather['description'].capitalize()
        self.desc_var.set(weather_desc)
        
        # 设置天气图标(使用表情符号)
        weather_id = weather['id']
        if weather_id < 300:  # 雷雨
            self.weather_icon.config(text="⛈️")
        elif weather_id < 600:  # 雨
            self.weather_icon.config(text="🌧️")
        elif weather_id < 700:  # 雪
            self.weather_icon.config(text="❄️")
        elif weather_id < 800:  # 雾/霾
            self.weather_icon.config(text="🌫️")
        elif weather_id == 800:  # 晴天
            self.weather_icon.config(text="☀️")
        elif weather_id <= 804:  # 多云
            self.weather_icon.config(text="☁️")
        else:
            self.weather_icon.config(text="❓")
        
        # 更新温度
        temp = current_data['main']['temp']
        feels_like = current_data['main']['feels_like']
        temp_min = current_data['main']['temp_min']
        temp_max = current_data['main']['temp_max']
        
        if self.temp_unit == "F":
            temp = self.celsius_to_fahrenheit(temp)
            feels_like = self.celsius_to_fahrenheit(feels_like)
            temp_min = self.celsius_to_fahrenheit(temp_min)
            temp_max = self.celsius_to_fahrenheit(temp_max)
        
        temp_text = f"{temp:.1f}°{'C' if self.temp_unit == 'C' else 'F'}"
        self.temp_var.set(temp_text)
        
        # 更新其他信息
        other_info_text = (
            f"体感温度: {feels_like:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
            f"最低温度: {temp_min:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
            f"最高温度: {temp_max:.1f}°{'C' if self.temp_unit == 'C' else 'F'}\n"
            f"湿度: {current_data['main']['humidity']}%\n"
            f"气压: {current_data['main']['pressure']} hPa\n"
            f"风速: {current_data['wind']['speed']} m/s\n"
            f"能见度: {current_data.get('visibility', 'N/A')} m"
        )
        
        self.other_info.delete(1.0, tk.END)
        self.other_info.insert(tk.END, other_info_text)
        
        # 绘制温度趋势图表
        self._plot_temperature_trend(forecast_data)
        
        # 显示未来几天的预报
        self._show_forecast(forecast_data)
    
    def _plot_temperature_trend(self, forecast_data):
        """绘制温度趋势图表"""
        # 清除之前的图表
        for widget in self.chart_frame.winfo_children():
            widget.destroy()
        
        # 准备数据
        dates = []
        temps = []
        temp_mins = []
        temp_maxs = []
        
        for item in forecast_data['list'][:8]:  # 显示未来8个时段(约3天)
            date = datetime.datetime.fromtimestamp(item['dt']).strftime('%m-%d %H:%M')
            dates.append(date)
            
            temp = item['main']['temp']
            temp_min = item['main']['temp_min']
            temp_max = item['main']['temp_max']
            
            if self.temp_unit == "F":
                temp = self.celsius_to_fahrenheit(temp)
                temp_min = self.celsius_to_fahrenheit(temp_min)
                temp_max = self.celsius_to_fahrenheit(temp_max)
            
            temps.append(temp)
            temp_mins.append(temp_min)
            temp_maxs.append(temp_max)
        
        # 创建图表
        fig, ax = plt.subplots(figsize=(8, 4))
        ax.plot(dates, temps, 'o-', label='温度')
        ax.plot(dates, temp_mins, '^-', label='最低温度')
        ax.plot(dates, temp_maxs, 'v-', label='最高温度')
        
        # 设置图表属性
        ax.set_title('温度趋势')
        ax.set_xlabel('日期和时间')
        ax.set_ylabel(f'温度 (°{"C" if self.temp_unit == "C" else "F"})')
        ax.legend()
        plt.xticks(rotation=45)
        plt.tight_layout()
        
        # 将图表嵌入到Tkinter窗口
        canvas = FigureCanvasTkAgg(fig, master=self.chart_frame)
        canvas.draw()
        canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
    
    def _show_forecast(self, forecast_data):
        """显示未来几天的预报"""
        # 清除之前的预报
        for widget in self.forecast_frame.winfo_children():
            widget.destroy()
        
        # 按天分组预报数据
        daily_forecasts = {}
        
        for item in forecast_data['list']:
            date = datetime.datetime.fromtimestamp(item['dt']).strftime('%Y-%m-%d')
            
            if date not in daily_forecasts:
                daily_forecasts[date] = []
            
            daily_forecasts[date].append(item)
        
        # 只保留每天的中午预报(约12:00)
        daily_representatives = []
        
        for date, items in daily_forecasts.items():
            # 找到最接近中午的预报
            closest_item = min(items, key=lambda x: abs(12 - datetime.datetime.fromtimestamp(x['dt']).hour))
            daily_representatives.append(closest_item)
        
        # 显示未来3天的预报
        for i, item in enumerate(daily_representatives[:3]):
            day_frame = ttk.Frame(self.forecast_frame, relief=tk.RAISED, borderwidth=1)
            day_frame.pack(side=tk.LEFT, fill=tk.BOTH, expand=True, padx=5, pady=5)
            
            # 日期
            date = datetime.datetime.fromtimestamp(item['dt']).strftime('%m-%d')
            ttk.Label(day_frame, text=date, font=('SimHei', 12, 'bold')).pack(pady=5)
            
            # 天气图标
            weather_id = item['weather'][0]['id']
            if weather_id < 300:  # 雷雨
                icon = "⛈️"
            elif weather_id < 600:  # 雨
                icon = "🌧️"
            elif weather_id < 700:  # 雪
                icon = "❄️"
            elif weather_id < 800:  # 雾/霾
                icon = "🌫️"
            elif weather_id == 800:  # 晴天
                icon = "☀️"
            elif weather_id <= 804:  # 多云
                icon = "☁️"
            else:
                icon = "❓"
            
            ttk.Label(day_frame, text=icon, font=('SimHei', 24)).pack(pady=5)
            
            # 温度
            temp_min = item['main']['temp_min']
            temp_max = item['main']['temp_max']
            
            if self.temp_unit == "F":
                temp_min = self.celsius_to_fahrenheit(temp_min)
                temp_max = self.celsius_to_fahrenheit(temp_max)
            
            ttk.Label(day_frame, text=f"{temp_min:.1f}° - {temp_max:.1f}°{'C' if self.temp_unit == 'C' else 'F'}").pack(pady=5)
            
            # 天气描述
            desc = item['weather'][0]['description'].capitalize()
            ttk.Label(day_frame, text=desc).pack(pady=5)
    
    def change_unit(self, event=None):
        """切换温度单位"""
        new_unit = self.unit_var.get()
        self.temp_unit = "C" if new_unit == "摄氏度" else "F"
        
        # 如果已有天气数据,刷新显示
        if self.city_date_var.get():
            city = self.city_var.get().strip()
            if city:
                self.get_weather()
    
    def celsius_to_fahrenheit(self, celsius):
        """将摄氏度转换为华氏度"""
        return (celsius * 9/5) + 32

if __name__ == "__main__":
    root = tk.Tk()
    app = WeatherApp(root)
    root.mainloop()
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值