Spaces:
Sleeping
Sleeping
| 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("請在左側設定時間範圍與參數,然後點擊按鈕以開始分析。") |