cwadayi commited on
Commit
8d60232
·
verified ·
1 Parent(s): 10d3b19

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +4 -166
app.py CHANGED
@@ -12,7 +12,7 @@ from datetime import datetime, timedelta, timezone
12
  # -----------------------------
13
  TAIPEI_TZ = timezone(timedelta(hours=8))
14
 
15
- def fmt_dt(dt: datetime) -> str:
16
  return dt.strftime("%Y-%m-%dT%H:%M:%S")
17
 
18
  def set_time_range(hours=None, days=None):
@@ -24,7 +24,7 @@ def set_time_range(hours=None, days=None):
24
  t_from = now - timedelta(days=days)
25
  else:
26
  t_from = now - timedelta(days=3)
27
- return fmt_dt(t_from), fmt_dt(now)
28
 
29
  # -----------------------------
30
  # 取 API 資料
@@ -35,167 +35,5 @@ def fetch_reports(time_from, time_to):
35
  api_key = os.getenv("CWA_API_KEY", "").strip()
36
  if not api_key:
37
  raise RuntimeError("請在環境變數設定 CWA_API_KEY")
38
- params = {
39
- "Authorization": api_key,
40
- "timeFrom": time_from,
41
- "timeTo": time_to
42
- }
43
- r = requests.get(API_URL, params=params, timeout=30)
44
- r.raise_for_status()
45
- return r.json()
46
-
47
- # -----------------------------
48
- # 解析 JSON → DataFrame
49
- # -----------------------------
50
- def safe_get(d, *keys, default=None):
51
- cur = d
52
- for k in keys:
53
- if isinstance(cur, dict) and k in cur:
54
- cur = cur[k]
55
- else:
56
- return default
57
- return cur
58
-
59
- def _to_float(x):
60
- try:
61
- if x is None or x == "":
62
- return None
63
- return float(str(x).strip())
64
- except Exception:
65
- return None
66
-
67
- def parse_reports(data):
68
- records = data.get("records") or {}
69
- quakes = records.get("earthquake") or []
70
- rows = []
71
- for q in quakes:
72
- origin = safe_get(q, "originTime")
73
- lat = safe_get(q, "epicenter", "epicenterLat")
74
- lon = safe_get(q, "epicenter", "epicenterLon")
75
- depth = safe_get(q, "depth")
76
- mag = safe_get(q, "magnitude", "magnitudeValue")
77
- loc = safe_get(q, "epicenter", "location")
78
- url = safe_get(q, "reportURL")
79
- rows.append({
80
- "OriginTime": origin,
81
- "Lat": _to_float(lat),
82
- "Lon": _to_float(lon),
83
- "Depth_km": _to_float(depth),
84
- "Magnitude": _to_float(mag),
85
- "Location": loc,
86
- "ReportURL": url
87
- })
88
- df = pd.DataFrame(rows)
89
- if not df.empty:
90
- df["OriginTime"] = pd.to_datetime(df["OriginTime"], errors="coerce")
91
- df = df.sort_values("OriginTime", ascending=False).reset_index(drop=True)
92
- return df
93
-
94
- # -----------------------------
95
- # 視覺化
96
- # -----------------------------
97
- def plot_trend(df):
98
- if df.empty:
99
- return None
100
- fig, ax = plt.subplots(figsize=(6, 4))
101
- ax.scatter(df["OriginTime"], df["Magnitude"])
102
- ax.set_xlabel("Origin Time (Taipei)")
103
- ax.set_ylabel("Magnitude")
104
- ax.grid(True, linestyle="--", alpha=0.4)
105
- fig.autofmt_xdate()
106
- buf = io.BytesIO()
107
- fig.savefig(buf, format="png", dpi=150, bbox_inches="tight")
108
- plt.close(fig)
109
- buf.seek(0)
110
- return buf.getvalue()
111
-
112
- def plot_map(df):
113
- if df.empty:
114
- return None
115
- lon_min, lon_max, lat_min, lat_max = 119, 123, 21, 26
116
- fig, ax = plt.subplots(figsize=(6, 6))
117
- ax.set_xlim(lon_min, lon_max)
118
- ax.set_ylim(lat_min, lat_max)
119
- mags = df["Magnitude"].fillna(0)
120
- sizes = (mags + 2) ** 3
121
- ax.scatter(df["Lon"], df["Lat"], s=sizes, alpha=0.6, edgecolor="black")
122
- for m in [3, 4, 5, 6]:
123
- ax.scatter([], [], s=((m + 2) ** 3), alpha=0.6, edgecolor="black", label=f"M {m}")
124
- ax.legend(title="Magnitude")
125
- ax.grid(True, linestyle="--", alpha=0.3)
126
- buf = io.BytesIO()
127
- fig.savefig(buf, format="png", dpi=150, bbox_inches="tight")
128
- plt.close(fig)
129
- buf.seek(0)
130
- return buf.getvalue()
131
-
132
- # -----------------------------
133
- # 主流程
134
- # -----------------------------
135
- def df_to_markdown(df, top_n=100):
136
- if df.empty:
137
- return "(查無資料)"
138
- show_cols = ["OriginTime", "Magnitude", "Depth_km", "Lat", "Lon", "Location", "ReportURL"]
139
- exist_cols = [c for c in show_cols if c in df.columns]
140
- slim = df[exist_cols].head(top_n).copy()
141
- if "OriginTime" in slim.columns:
142
- slim["OriginTime"] = slim["OriginTime"].dt.tz_localize("UTC").dt.tz_convert(TAIPEI_TZ).dt.strftime("%Y-%m-%d %H:%M:%S %Z")
143
- return slim.to_markdown(index=False)
144
-
145
- def query_and_render(time_from, time_to, sort_order):
146
- try:
147
- raw = fetch_reports(time_from, time_to)
148
- df = parse_reports(raw)
149
- if df.empty:
150
- return "(查無資料)", None, None, None
151
- if sort_order == "OriginTime (舊→新)":
152
- df = df.sort_values("OriginTime", ascending=True).reset_index(drop=True)
153
- md = df_to_markdown(df)
154
- trend_png = plot_trend(df)
155
- map_png = plot_map(df)
156
- csv_bytes = df.to_csv(index=False).encode("utf-8-sig")
157
- # 建立臨時檔讓 DownloadButton 可以下載
158
- tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="CWA_E-A0015-001_")
159
- tmp.write(csv_bytes)
160
- tmp.flush()
161
- tmp.close()
162
- return md, trend_png, map_png, tmp.name
163
- except Exception as e:
164
- return f"錯誤:{e}", None, None, None
165
-
166
- # -----------------------------
167
- # 介面
168
- # -----------------------------
169
- default_from, default_to = set_time_range(days=3)
170
-
171
- with gr.Blocks(fill_height=True) as demo:
172
- gr.Markdown("## CWA 顯著有感地震報告 (E-A0015-001)\n預設查詢最近 3 天(台北時間)")
173
-
174
- with gr.Column():
175
- time_from = gr.Textbox(label="timeFrom yyyy-MM-ddTHH:mm:ss", value=default_from)
176
- time_to = gr.Textbox(label="timeTo yyyy-MM-ddTHH:mm:ss", value=default_to)
177
-
178
- with gr.Row():
179
- btn_12h = gr.Button("最近 12 小時")
180
- btn_24h = gr.Button("最近 24 小時")
181
- btn_3d = gr.Button("最近 3 天")
182
- btn_5d = gr.Button("最近 5 天")
183
-
184
- sort_dd = gr.Dropdown(choices=["OriginTime (新→舊)", "OriginTime (舊→新)"], value="OriginTime (新→舊)", label="排序")
185
-
186
- run_btn = gr.Button("查詢", variant="primary")
187
-
188
- table_out = gr.Markdown("(尚未查詢)")
189
- trend_out = gr.Image(label="趨勢圖", type="numpy")
190
- map_out = gr.Image(label="台灣範圍圖", type="numpy")
191
- dl_btn = gr.DownloadButton(label="下載 CSV") # 不加 file_name
192
-
193
- btn_12h.click(lambda: set_time_range(hours=12), outputs=[time_from, time_to])
194
- btn_24h.click(lambda: set_time_range(hours=24), outputs=[time_from, time_to])
195
- btn_3d.click(lambda: set_time_range(days=3), outputs=[time_from, time_to])
196
- btn_5d.click(lambda: set_time_range(days=5), outputs=[time_from, time_to])
197
-
198
- run_btn.click(query_and_render, inputs=[time_from, time_to, sort_dd], outputs=[table_out, trend_out, map_out, dl_btn])
199
-
200
- if __name__ == "__main__":
201
- demo.launch()
 
12
  # -----------------------------
13
  TAIPEI_TZ = timezone(timedelta(hours=8))
14
 
15
+ def _fmt(dt: datetime) -> str:
16
  return dt.strftime("%Y-%m-%dT%H:%M:%S")
17
 
18
  def set_time_range(hours=None, days=None):
 
24
  t_from = now - timedelta(days=days)
25
  else:
26
  t_from = now - timedelta(days=3)
27
+ return _fmt(t_from), _fmt(now)
28
 
29
  # -----------------------------
30
  # 取 API 資料
 
35
  api_key = os.getenv("CWA_API_KEY", "").strip()
36
  if not api_key:
37
  raise RuntimeError("請在環境變數設定 CWA_API_KEY")
38
+ params = {"Authorization": api_key, "timeFrom": time_from, "timeTo": time_to}
39
+ r = requests.get(API_URL, params=params, timeout=30