CWA_opendata / app.py
cwadayi's picture
Update app.py
c4af178 verified
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("<br>資料來源:[交通部中央氣象署](https://opendata.cwa.gov.tw/index)")
demo.launch()