2025年7月29日 星期二

一般天氣預報-今明36小時天氣預報---Python TKInter

一般天氣預報-今明36小時天氣預報---Python TKInter









Python


import tkinter as tk
from tkinter import messagebox
import requests
import json

# 全局變數用於儲存所有縣市的原始天氣資料,避免重複下載
ALL_WEATHER_DATA = None

def fetch_weather_data():
    """從中央氣象署開放資料平台擷取天氣資料"""
    # 替換成你自己的 API 金鑰
    # (此處省略 URL,請確保您的程式碼中有正確的 URL 和 API 金鑰)
    url = "https://opendata.cwa.gov.tw/fileapi/v1/opendataapi/F-C0032-001?Authorization=YOUR_ACTUAL_API_KEY&format=JSON" # 請務必替換為您的金鑰
    try:
        response = requests.get(url)
        response.raise_for_status()  # 如果請求不成功,拋出 HTTPError
        data = response.json()
        return data
    except requests.exceptions.RequestException as e:
        messagebox.showerror("網路錯誤", f"無法連接到天氣資料服務:{e}")
        return None
    except requests.exceptions.JSONDecodeError as e:
        messagebox.showerror("資料解析錯誤", f"無法解析 API 回應為 JSON:{e}\n請檢查 API 金鑰或資料格式是否正確。")
        return None

def parse_and_filter_weather_data(data, target_location_name=None):
    """
    解析天氣資料並提取所需資訊。
    可選參數 target_location_name 用於只顯示特定縣市的資料。
    """
    if not data or "cwaopendata" not in data:
        return "資料格式錯誤或無資料,或頂層無 cwaopendata 鍵。"

    cwa_data = data["cwaopendata"]
    if "dataset" not in cwa_data:
        return "cwaopendata 內部無 dataset 鍵。"

    actual_dataset = cwa_data["dataset"]
    if "location" not in actual_dataset:
        return "dataset 內部無 location 鍵。"

    locations = actual_dataset["location"]
    weather_reports = []

    for loc in locations:
        location_name = loc.get("locationName", "未知地區")

        if target_location_name and location_name != target_location_name:
            continue

        weather_report_for_loc = f"地區:{location_name}\n"

        elements_by_time = {}
        for wp in loc.get("weatherElement", []):
            element_name = wp.get("elementName")
            if element_name:
                if element_name not in elements_by_time:
                    elements_by_time[element_name] = []

                for time_slot in wp.get('time', []):
                    start_time = time_slot.get('startTime', 'N/A')
                    end_time = time_slot.get('endTime', 'N/A')
                    parameter_name = time_slot.get('parameter', {}).get('parameterName', 'N/A')

                    elements_by_time[element_name].append({
                        'startTime': start_time,
                        'endTime': end_time,
                        'parameterName': parameter_name
                    })

        num_time_slots = 0
        if "Wx" in elements_by_time:
            num_time_slots = len(elements_by_time["Wx"])
        elif "PoP" in elements_by_time:
            num_time_slots = len(elements_by_time["PoP"])

        for i in range(num_time_slots):
            weather_report_for_loc += f"\n--- 預報時段 {i+1} ---\n"
            if "Wx" in elements_by_time and len(elements_by_time["Wx"]) > i:
                 start = elements_by_time["Wx"][i]['startTime']
                 end = elements_by_time["Wx"][i]['endTime']
                 weather_report_for_loc += f"時間:{start[5:16]}{end[5:16]}\n"

            if "Wx" in elements_by_time and len(elements_by_time["Wx"]) > i:
                weather_report_for_loc += f"天氣現象:{elements_by_time['Wx'][i]['parameterName']}\n"
            if "PoP" in elements_by_time and len(elements_by_time["PoP"]) > i:
                weather_report_for_loc += f"降雨機率:{elements_by_time['PoP'][i]['parameterName']}%\n"
            if "MinT" in elements_by_time and len(elements_by_time["MinT"]) > i:
                weather_report_for_loc += f"最低溫度:{elements_by_time['MinT'][i]['parameterName']}°C\n"
            if "MaxT" in elements_by_time and len(elements_by_time["MaxT"]) > i:
                weather_report_for_loc += f"最高溫度:{elements_by_time['MaxT'][i]['parameterName']}°C\n"
            if "CI" in elements_by_time and len(elements_by_time["CI"]) > i:
                weather_report_for_loc += f"舒適度:{elements_by_time['CI'][i]['parameterName']}\n"
        
        weather_report_for_loc += "====================\n"
        weather_reports.append(weather_report_for_loc)

    if not weather_reports:
        return f"找不到 {target_location_name} 的天氣資料,或資料處理失敗。"

    return "\n".join(weather_reports)

def display_selected_weather(location_name):
    """根據選擇的縣市名稱顯示天氣預報"""
    global ALL_WEATHER_DATA
    if ALL_WEATHER_DATA is None:
        messagebox.showwarning("資料未載入", "請稍候,天氣資料正在初始化中...")
        return

    parsed_data = parse_and_filter_weather_data(ALL_WEATHER_DATA, location_name)
    text_area.delete("1.0", tk.END)
    text_area.insert(tk.END, parsed_data)

def initialize_app():
    """初始化應用程式:下載資料並建立縣市按鈕"""
    global ALL_WEATHER_DATA
    text_area.insert(tk.END, "載入中,請稍候...\n") # 給予使用者反饋
    root.update_idletasks() # 強制更新 GUI 以顯示訊息

    ALL_WEATHER_DATA = fetch_weather_data()

    if ALL_WEATHER_DATA:
        try:
            locations = ALL_WEATHER_DATA["cwaopendata"]["dataset"]["location"]
            location_names = sorted([loc.get("locationName", "未知") for loc in locations])

            # 動態生成按鈕 (修改部分在此)
            max_cols = 8 # 每行最多 8 個按鈕
            row = 0
            col = 0
            for name in location_names:
                button = tk.Button(button_frame, text=name, command=lambda n=name: display_selected_weather(n), font=("Arial", 10), width=8, height=2) # 調整寬高
                button.grid(row=row, column=col, padx=3, pady=3) # 調整內邊距
                col += 1
                if col >= max_cols: # 如果達到最大列數,換行
                    col = 0
                    row += 1
            
            # 預設顯示第一個縣市的天氣
            if location_names:
                display_selected_weather(location_names[0])

        except KeyError as e:
            messagebox.showerror("資料結構錯誤", f"無法從 API 資料中找到預期的鍵:{e}\n請檢查 API 資料格式是否已變更。")
            text_area.insert(tk.END, "初始化失敗:資料結構不符預期。")
        except Exception as e:
            messagebox.showerror("初始化錯誤", f"應用程式初始化時發生未知錯誤:{e}")
            text_area.insert(tk.END, "初始化失敗。")
    else:
        text_area.insert(tk.END, "未能載入天氣資料,請檢查網路連線或 API 金鑰。")


# --- Tkinter 介面設定 ---
root = tk.Tk()
root.title("今明36小時天氣預報")
root.geometry("800x700")

# 縣市按鈕框架
button_frame = tk.Frame(root, padx=10, pady=10, bg="#e0e0e0")
button_frame.pack(side="top", fill="x")

# 顯示天氣資訊的文字區域
text_area = tk.Text(root, wrap="word", font=("Arial", 12), padx=10, pady=10)
text_area.pack(expand=True, fill="both")

# 捲軸
scrollbar = tk.Scrollbar(text_area)
scrollbar.pack(side="right", fill="y")
text_area.config(yscrollcommand=scrollbar.set)
scrollbar.config(command=text_area.yview)

# 應用程式啟動時,載入資料並建立按鈕
root.after(100, initialize_app) # 使用 after 延遲呼叫,確保 Tkinter 視窗已初始化

root.mainloop()

沒有留言:

張貼留言

ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite

 ESP32 (ESP-IDF in VS Code) MFRC522 + MQTT + PYTHON TKinter +SQLite  ESP32 VS Code 程式 ; PlatformIO Project Configuration File ; ;   Build op...