2025年7月27日 星期日

使用 Python TKinetr 去擷取 空氣品質指標(AQI) 的資料

使用 Python TKinetr 去擷取 空氣品質指標(AQI) 的資料




import tkinter as tk

from tkinter import ttk

import requests


class AirQualityApp:

    def __init__(self, root):

        self.root = root

        self.root.title("台灣空氣品質監測")

        self.root.geometry("1200x700") # 調整視窗大小以容納更多欄位


        self.api_url = "https://data.moenv.gov.tw/api/v2/aqx_p_432?api_key=221974dd-667c-4243-b308-61b60bc29986&limit=1000&sort=ImportDate%20desc&format=JSON"


        # 定義您想顯示的欄位及其對應的 API 鍵名

        # 關鍵修正:將 "site_name" 改為 "sitename"

        self.display_columns_map = {

            "測站名稱": "sitename", # <-- 這裡從 "site_name" 改為 "sitename"

            "縣市": "county",

            "空氣品質指標(AQI)": "aqi",

            "空氣污染指標物": "pollutant",

            "狀態": "status",

            "二氧化硫(ppb)": "so2",

            "一氧化碳(ppm)": "co",

            "臭氧(ppb)": "o3",

            "臭氧8小時移動平均(ppb)": "o3_8hr",

            "懸浮微粒(μg/m3)": "pm10",

            "細懸浮微粒(μg/m3)": "pm25",

            "二氧化氮(ppb)": "no2",

            "氮氧化物(ppb)": "nox",

            "測站編號": "siteid",

            "發布時間": "publishtime"

        }


        self.create_widgets()

        self.fetch_and_display_data()


    def create_widgets(self):

        # 控制按鈕區塊

        control_frame = tk.Frame(self.root)

        control_frame.pack(pady=10)


        refresh_button = tk.Button(control_frame, text="重新整理資料", command=self.fetch_and_display_data)

        refresh_button.pack(side=tk.LEFT, padx=5)


        # Treeview 顯示資料

        self.tree = ttk.Treeview(self.root, show="headings")

        self.tree.pack(fill=tk.BOTH, expand=True, padx=10, pady=10)


        # 設定 Treeview 的 columns

        # 這裡使用 display_columns_map 的 keys() 作為 Treeview 的欄位標題

        self.tree["columns"] = list(self.display_columns_map.keys())


        # 設定每個欄位的標題和屬性

        for col_name in self.display_columns_map.keys():

            self.tree.heading(col_name, text=col_name, command=lambda c=col_name: self.sort_treeview(self.tree, c, False))

            self.tree.column(col_name, width=120, anchor='center') # 調整預設欄寬


        # 捲軸

        vsb = ttk.Scrollbar(self.tree, orient="vertical", command=self.tree.yview)

        vsb.pack(side='right', fill='y')

        self.tree.configure(yscrollcommand=vsb.set)


        hsb = ttk.Scrollbar(self.tree, orient="horizontal", command=self.tree.xview)

        hsb.pack(side='bottom', fill='x')

        self.tree.configure(xscrollcommand=hsb.set)

        

    def fetch_and_display_data(self):

        try:

            response = requests.get(self.api_url)

            response.raise_for_status()  # 檢查 HTTP 錯誤

            data = response.json()

            records = data.get("records", [])


            # 根據測站名稱 (sitename) 排序資料

            # 這裡的 "sitename" 是 API 返回的實際鍵名

            records.sort(key=lambda x: x.get("sitename", "")) # <-- 這裡從 "site_name" 改為 "sitename"


            self.clear_treeview()


            if not records:

                self.tree.heading("#0", text="無資料")

                return


            # 插入新資料

            for record in records:

                # 遍歷 display_columns_map 的值(即 API 的鍵名),並依序從 record 中取值

                row_values = [record.get(api_key, "") for api_key in self.display_columns_map.values()]

                self.tree.insert("", "end", values=row_values)


        except requests.exceptions.RequestException as e:

            self.clear_treeview()

            self.tree.heading("#0", text=f"錯誤: 無法取得資料 - {e}")

        except ValueError as e:

            self.clear_treeview()

            self.tree.heading("#0", text=f"錯誤: 資料解析失敗 - {e}")

        except Exception as e:

            self.clear_treeview()

            self.tree.heading("#0", text=f"發生未知錯誤: {e}")


    def clear_treeview(self):

        # 清除現有資料

        for item in self.tree.get_children():

            self.tree.delete(item)

        self.tree.heading("#0", text="") # 清除預設標題


    # 排序功能 (可選,但很有用)

    def sort_treeview(self, tree, col, reverse):

        # 找到點擊的欄位在 Treeview 欄位列表中的索引

        try:

            col_index = list(self.display_columns_map.keys()).index(col)

        except ValueError:

            # 如果欄位名稱不在 display_columns_map 的 keys 中,則不執行排序

            return


        l = []

        for item_id in tree.get_children(''):

            item_values = tree.item(item_id, 'values')

            if col_index < len(item_values):

                value = item_values[col_index]

            else:

                value = "" # 如果資料不完整,則視為空值


            l.append((value, item_id))


        # 嘗試將數值型資料轉換為 float 以進行正確排序

        def sort_key_func(item):

            val = item[0]

            if isinstance(val, str) and val.replace('.', '', 1).isdigit() and val != '':

                try:

                    return float(val)

                except ValueError:

                    return val # 無法轉換為數字時,按字串排序

            return val


        l.sort(key=sort_key_func, reverse=reverse)


        # 重新排列項目

        for index, (val, k) in enumerate(l):

            tree.move(k, '', index)

        

        # 翻轉下次排序的順序

        tree.heading(col, command=lambda: self.sort_treeview(tree, col, not reverse))



if __name__ == "__main__":

    root = tk.Tk()

    app = AirQualityApp(root)

    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...