earthquake_monitoring / src /streamlit_app.py
cwadayi's picture
Update src/streamlit_app.py
6709c71 verified
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("請在左側設定時間範圍與參數,然後點擊按鈕以開始分析。")