import streamlit as st from obspy import UTCDateTime import pandas as pd from datetime import datetime, timedelta # 匯入我們自己寫的模組 import earthquake_fetcher import earthquake_analyzer import visualizer # --- 頁面設定 --- st.set_page_config( page_title="即時地震監測儀表板", page_icon="🌊", layout="wide" ) st.title("即時地震監測與定位儀表板") st.markdown(""" 本應用程式從 [IRIS](http://ds.iris.edu/ds/nodes/dmc/) FDSN 服務獲取台灣 (TW) 測站的即時地震波形資料, 自動偵測 P 波並使用 Geiger 方法定位震源。 """) # --- 側邊欄控制項 (FR6) --- with st.sidebar: st.header("⚙️ 分析參數設定") # --- START OF CHANGE --- # FR1: 時間範圍設定 (使用固定的預設值,並增加提示) st.subheader("時間範圍設定") # 設定一個固定的、可說明的預設時間範圍,避免使用者誤解 default_start_datetime = datetime(2025, 7, 20, 7, 0, 0) default_end_datetime = datetime(2025, 7, 20, 7, 15, 0) col1, col2 = st.columns(2) with col1: start_date = st.date_input("起始日期", value=default_start_datetime.date()) start_time_input = st.time_input("起始時間", value=default_start_datetime.time()) with col2: end_date = st.date_input("終止日期", value=default_end_datetime.date()) end_time_input = st.time_input("終止時間", value=default_end_datetime.time()) # 增加提示文字,確保使用者了解其靈活性 st.caption("提示:您可以自由調整上方的日期與時間,查詢任何時間範圍的資料。") # --- END OF CHANGE --- # FR2: STA/LTA 參數設定 st.subheader("P波撿拾 (STA/LTA) 參數") sta_window = st.slider("STA 窗長 (秒)", min_value=0.1, max_value=5.0, value=1.0, step=0.1) lta_window = st.slider("LTA 窗長 (秒)", min_value=5.0, max_value=60.0, value=10.0, step=1.0) trigger_threshold = st.slider("觸發門檻值", min_value=1.5, max_value=10.0, value=3.0, step=0.5) off_threshold = st.slider("關閉門檻值", min_value=0.1, max_value=2.0, value=1.5, step=0.1) # 初始化 session state if 'analysis_results' not in st.session_state: st.session_state.analysis_results = None # 執行按鈕 if st.button("獲取指定範圍地震資料並分析", type="primary"): with st.spinner("正在執行分析...請稍候..."): # 將日期和時間輸入組合成 datetime 物件 start_datetime_local = datetime.combine(start_date, start_time_input) end_datetime_local = datetime.combine(end_date, end_time_input) # 轉換為 obspy 的 UTCDateTime 物件 start_time = UTCDateTime(start_datetime_local) end_time = UTCDateTime(end_datetime_local) # 為了快取,將 UTCDateTime 物件轉為字串傳遞 stream, inventory = earthquake_fetcher.get_earthquake_data( start_time_str=str(start_time), end_time_str=str(end_time) ) if stream and inventory: # 2. P波自動撿拾 (FR2) p_picks = earthquake_analyzer.pick_p_waves(stream, sta_window, lta_window, trigger_threshold, off_threshold) # 4. 震源定位 (FR4) location_result = None if len(p_picks) >= 4: st.write(f"找到 {len(p_picks)} 個 P 波觸發,開始定位...") location_result = earthquake_analyzer.locate_hypocenter(p_picks, inventory) else: st.write(f"P 波觸發測站數 ({len(p_picks)}) 不足 4 個,無法進行定位。") # 將所有結果存儲到 session state st.session_state.analysis_results = { "stream": stream, "inventory": inventory, "p_picks": p_picks, "location_result": location_result } else: st.session_state.analysis_results = None # 清除舊結果 st.success("分析完成!") # --- 主畫面結果展示 --- if st.session_state.analysis_results: results = st.session_state.analysis_results p_picks = results["p_picks"] location_result = results["location_result"] inventory = results["inventory"] st.header("📊 分析結果") # 3. 波形視覺化 (FR3) if p_picks: st.subheader("測站波形與 P 波初達") waveform_fig = visualizer.plot_waveforms_with_picks(p_picks) st.plotly_chart(waveform_fig, use_container_width=True) else: st.info("在所有獲取的波形中,均未成功觸發 P 波。請嘗試調整 STA/LTA 參數或擴大時間範圍。") # 5. 定位結果視覺化 (FR5) st.subheader("震源定位結果") if len(p_picks) < 4: st.warning("P 波撿拾測站不足(少於4個),無法進行定位。") elif not location_result: st.error("定位計算失敗,可能是測站分佈不佳或資料品質問題。") else: col1, col2 = st.columns([2, 1]) # 地圖佔2/3,文字佔1/3 with col1: st.pydeck_chart(visualizer.plot_event_map(inventory, p_picks, location_result)) with col2: st.markdown("#### **定位資訊**") origin_utc = UTCDateTime(location_result['origin_time']) st.metric(label="發震時刻 (UTC)", value=origin_utc.strftime("%Y-%m-%d %H:%M:%S")) st.metric(label="震央位置", value=f"{location_result['latitude']:.3f}°N, {location_result['longitude']:.3f}°E") st.metric(label="深度", value=f"{location_result['depth_km']:.1f} km") st.metric(label="使用測站數", value=len(p_picks)) st.markdown("---") st.markdown("#### **參與定位測站**") station_list = [p['station'] for p in p_picks] st.text(", ".join(station_list)) else: st.info("請在左側設定時間範圍與參數,然後點擊按鈕以開始分析。")