import gradio as gr import requests import os from datetime import datetime, timedelta # --- 從 Hugging Face Secrets 讀取 API KEY --- CWA_API_KEY = os.getenv("CWA_API_KEY") # --- API 設定 --- CWA_API_BASE_URL = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/" # (DATASET_IDS 和 TAIWAN_LOCATIONS 的定義與前一版完全相同) DATASET_IDS = {"顯著有感地震": "E-A0015-001", "小區域有感地震": "E-A0016-001", "一般天氣預報": "F-C0032-001", "自動氣象站-氣象觀測": "O-A0001-001", "自動雨量站-雨量觀測": "O-A0002-001", "現在天氣觀測報告": "O-A0003-001"} TAIWAN_LOCATIONS = ["臺北市", "新北市", "基隆市", "桃園市", "新竹縣", "新竹市", "苗栗縣", "臺中市", "彰化縣", "南投縣", "雲林縣", "嘉義縣", "嘉義市", "臺南市", "高雄市", "屏東縣", "宜蘭縣", "花蓮縣", "臺東縣", "澎湖縣", "金門縣", "連江縣"] # ============================================================================== # API 請求與資料解析函式 (與前一版完全相同) # ============================================================================== def make_api_request(data_id, params=None): if not CWA_API_KEY: raise ValueError("錯誤:尚未在 Hugging Face Space 的 Secrets 中設定 CWA_API_KEY。") api_url = f"{CWA_API_BASE_URL}{data_id}" base_params = {"Authorization": CWA_API_KEY, "format": "JSON"} if params: base_params.update(params) response = requests.get(api_url, params=base_params) if response.status_code != 200: raise ConnectionError(f"API 請求失敗,狀態碼:{response.status_code}\n錯誤訊息:{response.text}") data = response.json() if not data.get('success'): raise ValueError("API 回應不成功或資料格式有誤。") return data.get('records', {}) def parse_earthquake_data(records, query_type): earthquakes = records.get('earthquake', []) if not earthquakes: return f"API 請求成功,在指定的時間範圍內沒有「{query_type}」的紀錄。" result = f"查詢到 {len(earthquakes)} 筆「{query_type}」紀錄 (最多顯示前5筆):\n" result += "========================================\n" for eq in earthquakes[:5]: info = eq['earthquakeInfo'] result += (f"報告內容:{eq.get('reportContent', 'N/A')}\n" f"發生時間:{info.get('originTime', 'N/A')}\n" f"震央位置:{info.get('epiCenter', {}).get('location', 'N/A')}\n" f"芮氏規模:{info.get('magnitude', {}).get('magnitudeValue', 'N/A')}\n" f"地震深度:{info.get('depth', {}).get('value', 'N/A')} 公里\n" "----------------------------------------\n") return result def parse_general_forecast_data(records, location): location_records = records.get('location', []) if not location_records: return f"找不到「{location}」的一般天氣預報資料。" weather_elements = location_records[0].get('weatherElement', []) forecasts = {} for element in weather_elements: element_name = element['elementName'] for time_period in element['time']: time_key = f"{time_period['startTime']} ~ {time_period['endTime']}" if time_key not in forecasts: forecasts[time_key] = {} value = time_period['parameter']['parameterName'] unit = time_period['parameter'].get('parameterUnit', '') forecasts[time_key][element_name] = f"{value} {unit}".strip() result = f"📍 **{location}** 一般天氣預報\n" + "---------------------------------\n" for time_period, values in forecasts.items(): result += (f"**預報時段:** {time_period}\n" f" - 天氣現象 (Wx): {values.get('Wx', 'N/A')}\n" f" - 最高溫度 (MaxT): {values.get('MaxT', 'N/A')}\n" f" - 最低溫度 (MinT): {values.get('MinT', 'N/A')}\n" f" - 舒適度 (CI): {values.get('CI', 'N/A')}\n" f" - 降雨機率 (PoP): {values.get('PoP', 'N/A')}\n\n") return result def parse_observation_data(records, query_type, location): location_records = records.get('location', []) if not location_records: return f"在「{location}」找不到任何「{query_type}」的觀測站資料。" result = f"📍 **{location}** - **{query_type}** (僅列出部分站點)\n" + "---------------------------------\n" if "站" in query_type: for station in location_records[:5]: result += f"**觀測站:{station['stationId']} {station.get('locationName', '')}**\n" elements = {elem['elementName']: elem['elementValue'] for elem in station['weatherElement']} if "氣象觀測" in query_type: result += (f" - 溫度: {elements.get('TEMP', 'N/A')} °C\n" f" - 濕度: {float(elements.get('HUMD', '-1'))*100:.1f} %\n" f" - 風速/風向: {elements.get('WDSD', 'N/A')} m/s / {elements.get('WDIR', 'N/A')}°\n") elif "雨量觀測" in query_type: result += (f" - 10分鐘雨量: {elements.get('RAIN', 'N/A')} mm\n" f" - 1小時雨量: {elements.get('HOUR_1', 'N/A')} mm\n" f" - 24小時雨量: {elements.get('HOUR_24', 'N/A')} mm\n") result += "\n" elif "現在天氣" in query_type: for loc in location_records: result += f"**區域:{loc.get('locationName', '')}**\n" elements = {elem['elementName']: elem['elementValue'] for elem in loc['weatherElement']} result += (f" - 天氣現象: {elements.get('Weather', 'N/A')}\n" f" - 溫度: {elements.get('Temperature', 'N/A')} °C\n" f" - 濕度: {float(elements.get('RelativeHumidity', '-1'))*100:.1f} %\n\n") return result # ============================================================================== # Gradio 觸發函式 # ============================================================================== # --- **這裡是修改的關鍵** --- def handle_earthquake_query(query_type, start_date_str, end_date_str): try: datetime.strptime(start_date_str, "%Y-%m-%d") datetime.strptime(end_date_str, "%Y-%m-%d") params = { "timeFrom": f"{start_date_str}T00:00:00", "timeTo": f"{end_date_str}T23:59:59", "limit": 10, "sort": "OriginTime" # 強制API依時間篩選與排序 } records = make_api_request(DATASET_IDS[query_type], params) return parse_earthquake_data(records, query_type) except ValueError: return "日期格式錯誤,請輸入 YYYY-MM-DD 格式。" except Exception as e: return str(e) def handle_general_forecast_query(location): try: records = make_api_request(DATASET_IDS["一般天氣預報"], {"locationName": location}) return parse_general_forecast_data(records, location) except Exception as e: return str(e) def handle_observation_query(query_type, location): try: records = make_api_request(DATASET_IDS[query_type], {"locationName": location}) return parse_observation_data(records, query_type, location) except Exception as e: return str(e) # ============================================================================== # Gradio UI 介面 (與前一版完全相同) # ============================================================================== with gr.Blocks(title="CWA 開放資料查詢平台") as demo: gr.Markdown("# 🇹🇼 CWA 中央氣象署開放資料查詢平台") with gr.Tabs(): with gr.TabItem("一般天氣預報(縣市)"): gr.Markdown("### 查詢各縣市天氣預報") fc_location_dropdown = gr.Dropdown(label="請選擇縣市", choices=TAIWAN_LOCATIONS, value="臺北市") fc_btn = gr.Button("查詢天氣") fc_output = gr.Textbox(lines=15, label="查詢結果") fc_btn.click(fn=handle_general_forecast_query, inputs=fc_location_dropdown, outputs=fc_output) with gr.TabItem("即時觀測"): gr.Markdown("### 查詢即時觀測資料") with gr.Row(): obs_type_dropdown = gr.Dropdown(label="觀測類型", choices=["自動氣象站-氣象觀測", "自動雨量站-雨量觀測", "現在天氣觀測報告"], value="自動氣象站-氣象觀測") obs_location_dropdown = gr.Dropdown(label="縣市", choices=TAIWAN_LOCATIONS, value="臺北市") obs_btn = gr.Button("查詢觀測資料") obs_output = gr.Textbox(lines=15, label="查詢結果") obs_btn.click(fn=handle_observation_query, inputs=[obs_type_dropdown, obs_location_dropdown], outputs=obs_output) with gr.TabItem("地震查詢"): gr.Markdown("### 查詢地震報告") eq_type_dropdown = gr.Dropdown(label="查詢類型", choices=["顯著有感地震", "小區域有感地震"], value="顯著有感地震") with gr.Row(): start_date_input = gr.Textbox(label="開始日期 (格式 YYYY-MM-DD)", value=(datetime.now() - timedelta(days=7)).strftime("%Y-%m-%d")) end_date_input = gr.Textbox(label="結束日期 (格式 YYYY-MM-DD)", value=datetime.now().strftime("%Y-%m-%d")) eq_btn = gr.Button("查詢地震") eq_output = gr.Textbox(lines=20, label="查詢結果") eq_btn.click(fn=handle_earthquake_query, inputs=[eq_type_dropdown, start_date_input, end_date_input], outputs=eq_output) gr.Markdown("
資料來源:[交通部中央氣象署](https://opendata.cwa.gov.tw/index)") demo.launch()