cwadayi commited on
Commit
959c7bb
·
verified ·
1 Parent(s): 4b69c3b

Update src/streamlit_app.py

Browse files
Files changed (1) hide show
  1. src/streamlit_app.py +120 -83
src/streamlit_app.py CHANGED
@@ -2,13 +2,9 @@ import streamlit as st
2
  import pandas as pd
3
  import numpy as np
4
 
5
- # ---核心計算函式 (與前一個範例相同)---
6
 
7
  def calculate_theoretical_pga(magnitude, distance):
8
- """
9
- 根據給定的地震動預估式(GMPE)計算理論PGA值
10
- PGA = 1.657 * e^(0.533*M) * r^(-1.607)
11
- """
12
  if distance <= 0:
13
  return np.inf
14
  exp_term = np.exp(0.533 * magnitude)
@@ -16,109 +12,150 @@ def calculate_theoretical_pga(magnitude, distance):
16
  return 1.657 * exp_term * dist_term
17
 
18
  def calculate_site_amplification(df):
19
- """
20
- 計算每個測站的平均場址放大因子 S
21
- """
22
  df_copy = df.copy()
23
-
24
- # 計算理論PGA
25
  df_copy['theoretical_pga'] = df_copy.apply(
26
  lambda row: calculate_theoretical_pga(row['magnitude'], row['distance_km']),
27
  axis=1
28
  )
29
-
30
- # 計算單次放大效應
31
  df_copy['single_event_amplification'] = df_copy['observed_pga'] / df_copy['theoretical_pga']
32
-
33
- # 計算各站點平均放大因子
34
  site_amplification_factors = df_copy.groupby('station_id')['single_event_amplification'].mean().reset_index()
35
  site_amplification_factors.rename(columns={'single_event_amplification': 'site_amplification_factor_S'}, inplace=True)
36
-
37
  return df_copy, site_amplification_factors
38
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
  # --- Streamlit 介面 ---
40
 
41
- st.set_page_config(page_title="場址放大因子計算器", layout="wide")
42
 
43
- st.title("🌋 場址放大因子 (S) 計算器")
44
- st.markdown("""
45
- 這是一個根據地震觀測紀錄,計算各地震測站「場址放大因子 (Site Amplification Factor, S)」的工具。
46
- 其原理是比較 **實際觀測到的地動值 (PGA)** 與 **理論上的預估值** 之間的差異,從而量化特定場址因局部地質條件對地震波的放大程度。
47
- """)
48
 
49
- # --- 側邊欄:資料來源選擇 ---
50
 
51
- st.sidebar.header("Step 1: 選擇資料來源")
52
- data_source = st.sidebar.radio(
53
- "請選擇您的資料來源:",
54
- ("使用內建範例資料", "上傳自己的 CSV 檔案")
55
- )
56
 
57
- # 準備資料 DataFrame
58
  input_df = None
59
-
60
  if data_source == "使用內建範例資料":
61
- st.sidebar.info("您已選擇使用程式內建的範例資料進行計算。")
62
  data = {
63
- 'station_id': ['STA1', 'STA2', 'STA3', 'STA1', 'STA2', 'STA3', 'STA1', 'STA2', 'STA3', 'STA1', 'STA2', 'STA3', 'STA1', 'STA2', 'STA3'],
64
- 'earthquake_id': ['EQ1', 'EQ1', 'EQ1', 'EQ2', 'EQ2', 'EQ2', 'EQ3', 'EQ3', 'EQ3', 'EQ4', 'EQ4', 'EQ4', 'EQ5', 'EQ5', 'EQ5'],
65
- 'magnitude': [6.2, 6.2, 6.2, 5.5, 5.5, 5.5, 7.0, 7.0, 7.0, 4.8, 4.8, 4.8, 6.5, 6.5, 6.5],
66
- 'distance_km': [50, 80, 75, 30, 25, 45, 120, 90, 100, 60, 80, 70, 40, 65, 55],
67
- 'observed_pga': [80, 150, 90, 100, 180, 110, 60, 130, 75, 25, 50, 30, 150, 280, 180]
 
 
 
68
  }
69
  input_df = pd.DataFrame(data)
70
-
71
  else:
72
- uploaded_file = st.sidebar.file_uploader("請上傳您的 CSV 檔案", type=["csv"])
73
- if uploaded_file is not None:
74
  input_df = pd.read_csv(uploaded_file)
75
- st.sidebar.success("檔案上傳成功!")
76
- else:
77
- st.sidebar.warning("請上傳一個 CSV 檔。")
78
-
79
- # --- 主畫面 ---
80
 
 
81
  if input_df is not None:
82
- st.header("1. 原始觀測資料")
83
- st.dataframe(input_df)
84
-
85
- # 檢查必要欄位是否存在
86
- required_columns = ['station_id', 'magnitude', 'distance_km', 'observed_pga']
87
- if not all(col in input_df.columns for col in required_columns):
88
- st.error(f"錯誤:您上傳的檔案缺少必要的欄位。請確保檔案包含以下欄位:{', '.join(required_columns)}")
89
- else:
90
- st.sidebar.header("Step 2: 開始計算")
91
- if st.sidebar.button("🚀 計算場址放大因子 (S)"):
92
- with st.spinner('正在進行計算中,請稍候...'):
93
- # 執行計算
94
  intermediate_df, final_factors_df = calculate_site_amplification(input_df)
95
-
96
- st.header("2. 計算過程詳情")
97
- st.markdown("下方表格增加了 `theoretical_pga` (理論預估PGA) 與 `single_event_amplification` (單次放大效應) 欄位。")
98
- st.dataframe(intermediate_df)
99
-
100
- st.header("3. 最終計算結果")
101
- st.markdown("各測站的平均場址放大因子 (S) 如下:")
102
-
103
- # 使用欄位並排顯示表格和圖表
104
- col1, col2 = st.columns([0.4, 0.6])
105
-
106
- with col1:
107
- st.dataframe(final_factors_df)
108
-
109
- with col2:
110
- st.markdown("#### 結果視覺化比較")
111
- # 將 station_id 設為 index 以便繪圖
112
- chart_data = final_factors_df.set_index('station_id')
113
- st.bar_chart(chart_data['site_amplification_factor_S'])
114
-
115
- # 解讀結果
116
- st.info("""
117
- **結果解讀:**
118
- - **S > 1**: 表示該測站的平均地動比理論值大,存在「放大效應」。S值越大,放大效應越顯著。
119
- - **S ≈ 1**: 表示該測站的平均地動與理論值接近。
120
- - **S < 1**: 表示該測站的平均地動比理論值小,可能存在「減弱效應」。
121
- """)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
122
  else:
123
- st.info("請在左方側邊欄選擇或上傳資料以開始。")
124
 
 
2
  import pandas as pd
3
  import numpy as np
4
 
5
+ # --- 核心計算函式 (保持不變) ---
6
 
7
  def calculate_theoretical_pga(magnitude, distance):
 
 
 
 
8
  if distance <= 0:
9
  return np.inf
10
  exp_term = np.exp(0.533 * magnitude)
 
12
  return 1.657 * exp_term * dist_term
13
 
14
  def calculate_site_amplification(df):
 
 
 
15
  df_copy = df.copy()
 
 
16
  df_copy['theoretical_pga'] = df_copy.apply(
17
  lambda row: calculate_theoretical_pga(row['magnitude'], row['distance_km']),
18
  axis=1
19
  )
 
 
20
  df_copy['single_event_amplification'] = df_copy['observed_pga'] / df_copy['theoretical_pga']
 
 
21
  site_amplification_factors = df_copy.groupby('station_id')['single_event_amplification'].mean().reset_index()
22
  site_amplification_factors.rename(columns={'single_event_amplification': 'site_amplification_factor_S'}, inplace=True)
 
23
  return df_copy, site_amplification_factors
24
 
25
+ # --- 創意功能函式 ---
26
+
27
+ def get_s_factor_interpretation(s_value):
28
+ """將 S 因子轉換成白話文解說"""
29
+ if s_value > 2.0:
30
+ return f"🔴 **極顯著放大 ({s_value:.2f})**: 此處地質鬆軟,搖晃程度可能是堅硬岩盤地區的 **2倍以上**!需特別注意建築結構安全。"
31
+ elif s_value > 1.5:
32
+ return f"🟠 **顯著放大 ({s_value:.2f})**: 此處有明顯的場址放大效應,搖晃會比理論值強烈很多。"
33
+ elif s_value > 1.1:
34
+ return f"🟡 **輕微放大 ({s_value:.2f})**: 搖晃程度略高於理論值,存在一定的放大效應。"
35
+ elif s_value < 0.9:
36
+ return f"🔵 **減弱效應 ({s_value:.2f})**: 此處地質可能較為堅硬,搖晃程度反而比理論值小。"
37
+ else:
38
+ return f"🟢 **接近基準 ({s_value:.2f})**: 此處的搖晃程度與標準岩盤地區接近。"
39
+
40
+ def predict_pga_with_s(magnitude, distance, s_factor):
41
+ """使用S因子預測一個假想地震的PGA"""
42
+ base_pga = calculate_theoretical_pga(magnitude, distance)
43
+ return base_pga * s_factor
44
+
45
  # --- Streamlit 介面 ---
46
 
47
+ st.set_page_config(page_title="創意場址放大因子儀表板", layout="wide")
48
 
49
+ # 1. 標題與介紹
50
+ st.title("🌋 創意場址放大因子儀表板")
51
+ st.image("https://media.giphy.com/media/l41lGvinEgARjB2HC/giphy.gif", caption="地震波在地層中傳遞示意圖")
52
+ st.markdown("不僅計算 S 因子,更透過地圖、情境模擬,讓您『看見』並『感受』場址效應的威力!")
 
53
 
 
54
 
55
+ # 2. 側邊欄
56
+ st.sidebar.header("Step 1: 載入資料")
57
+ data_source = st.sidebar.radio("請選擇資料來源:", ("使用內建範例資料", "上傳自己的 CSV 檔案"))
 
 
58
 
 
59
  input_df = None
 
60
  if data_source == "使用內建範例資料":
61
+ st.sidebar.info("範例資料已加入經緯度(lat, lon),以用於地圖視覺化。")
62
  data = {
63
+ 'station_id': ['TPE', 'KHH', 'HUA', 'TPE', 'KHH', 'HUA', 'TPE', 'KHH', 'HUA', 'TPE', 'KHH', 'HUA'],
64
+ 'lat': [25.0330, 22.6273, 23.9739, 25.0330, 22.6273, 23.9739, 25.0330, 22.6273, 23.9739, 25.0330, 22.6273, 23.9739],
65
+ 'lon': [121.5654, 120.3014, 121.6059, 121.5654, 120.3014, 121.6059, 121.5654, 120.3014, 121.6059, 121.5654, 120.3014, 121.6059],
66
+ 'earthquake_id': ['EQ1', 'EQ1', 'EQ1', 'EQ2', 'EQ2', 'EQ2', 'EQ3', 'EQ3', 'EQ3', 'EQ4', 'EQ4', 'EQ4'],
67
+ 'magnitude': [6.2, 6.2, 6.2, 5.5, 5.5, 5.5, 7.0, 7.0, 7.0, 6.5, 6.5, 6.5],
68
+ 'distance_km': [50, 200, 30, 80, 150, 60, 120, 250, 40, 40, 180, 25],
69
+ # KHH(高雄)的地質條件較軟,預期會有較大的放大效應
70
+ 'observed_pga': [80, 40, 150, 30, 25, 60, 60, 35, 180, 150, 55, 280]
71
  }
72
  input_df = pd.DataFrame(data)
 
73
  else:
74
+ uploaded_file = st.sidebar.file_uploader("上傳 CSV (需含 lat, lon 欄位)", type=["csv"])
75
+ if uploaded_file:
76
  input_df = pd.read_csv(uploaded_file)
 
 
 
 
 
77
 
78
+ # 3. 主畫面
79
  if input_df is not None:
80
+ st.sidebar.header("Step 2: 執行計算")
81
+ if st.sidebar.button("🚀 點我開始分析!"):
82
+ with st.spinner('科學計算中,請稍候...'):
83
+ required_cols = ['station_id', 'lat', 'lon', 'magnitude', 'distance_km', 'observed_pga']
84
+ if not all(col in input_df.columns for col in required_cols):
85
+ st.error(f"資料格式錯誤!請確保您的 CSV 包含以下欄位: {required_cols}")
86
+ else:
 
 
 
 
 
87
  intermediate_df, final_factors_df = calculate_site_amplification(input_df)
88
+ # 將 station 的經緯度資訊合併到最終結果中
89
+ station_locations = input_df[['station_id', 'lat', 'lon']].drop_duplicates().set_index('station_id')
90
+ st.session_state.final_results = final_factors_df.join(station_locations, on='station_id')
91
+ st.session_state.intermediate_results = intermediate_df
92
+ st.success("計算完成!請查看下方分頁結果。")
93
+
94
+ if 'final_results' in st.session_state:
95
+ # 使用分頁呈現結果
96
+ tab1, tab2, tab3, tab4 = st.tabs(["📊 結果總覽", "🗺️ 地圖視覺化", "🔬 What-If 模擬器", "📄 資料詳情"])
97
+
98
+ final_df = st.session_state.final_results
99
+
100
+ with tab1:
101
+ st.header("📊 各測站平均場址放大因子 (S)")
102
+ col1, col2 = st.columns([0.5, 0.5])
103
+ with col1:
104
+ st.dataframe(final_df[['station_id', 'site_amplification_factor_S']].style.format({'site_amplification_factor_S': "{:.2f}"}))
105
+ st.bar_chart(final_df.set_index('station_id')['site_amplification_factor_S'])
106
+
107
+ with col2:
108
+ st.subheader("💬 結果白話文解說")
109
+ for index, row in final_df.iterrows():
110
+ st.markdown(f"**{row['station_id']} 測站:**")
111
+ st.markdown(get_s_factor_interpretation(row['site_amplification_factor_S']))
112
+ st.markdown("---")
113
+
114
+ with tab2:
115
+ st.header("🗺️ 場址放大效應地理分佈")
116
+ st.markdown("地圖上的點越大,���表該地的場址放大效應越強烈。")
117
+
118
+ # 為了讓地圖上的點大小差異更明顯,進行正規化
119
+ min_s = final_df['site_amplification_factor_S'].min()
120
+ max_s = final_df['site_amplification_factor_S'].max()
121
+ # 避免 max_s == min_s 的情況
122
+ if (max_s - min_s) > 0:
123
+ final_df['size'] = 50 + ((final_df['site_amplification_factor_S'] - min_s) / (max_s - min_s)) * 500
124
+ else:
125
+ final_df['size'] = 100
126
+
127
+ st.map(final_df, latitude='lat', longitude='lon', size='size', zoom=6)
128
+
129
+ with tab3:
130
+ st.header("🔬 What-If 情境模擬器")
131
+ st.markdown("如果現在發生一場地震,各地搖晃程度會是多少?")
132
+
133
+ sim_col1, sim_col2 = st.columns(2)
134
+ with sim_col1:
135
+ sim_mag = st.slider("設定假想地震規模 (M)", min_value=4.0, max_value=8.0, value=6.5, step=0.1)
136
+ with sim_col2:
137
+ sim_dist = st.number_input("設定您與震央的距離 (公里)", min_value=10, max_value=300, value=50)
138
+
139
+ st.markdown(f"#### 模擬結果:規模 **{sim_mag}**、距離 **{sim_dist}** 公里的地震")
140
+
141
+ sim_results = []
142
+ for index, row in final_df.iterrows():
143
+ predicted_pga = predict_pga_with_s(sim_mag, sim_dist, row['site_amplification_factor_S'])
144
+ sim_results.append({
145
+ "測站": row['station_id'],
146
+ "場址放大因子 (S)": f"{row['site_amplification_factor_S']:.2f}",
147
+ "預估搖晃程度 (PGA)": f"{predicted_pga:.2f}"
148
+ })
149
+
150
+ st.table(pd.DataFrame(sim_results))
151
+ st.info("💡 注意:PGA 越高,代表地表加速度越大,感受到的搖晃越劇烈。")
152
+
153
+ with tab4:
154
+ st.header("📄 詳細資料與計算過程")
155
+ with st.expander("點此查看原始觀測資料"):
156
+ st.dataframe(input_df)
157
+ with st.expander("點此查看詳細計算過程 (含單次放大效應)"):
158
+ st.dataframe(st.session_state.intermediate_results)
159
  else:
160
+ st.info("請在左方側邊欄選擇資料來源,然後點擊按鈕開始分析。")
161