Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -180,4 +180,142 @@ def plot_map_path(df):
|
|
| 180 |
d["Magnitude"] = pd.to_numeric(d["Magnitude"], errors="coerce").fillna(0).clip(lower=0)
|
| 181 |
d["Depth_km"] = pd.to_numeric(d["Depth_km"], errors="coerce")
|
| 182 |
|
| 183 |
-
#
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 180 |
d["Magnitude"] = pd.to_numeric(d["Magnitude"], errors="coerce").fillna(0).clip(lower=0)
|
| 181 |
d["Depth_km"] = pd.to_numeric(d["Depth_km"], errors="coerce")
|
| 182 |
|
| 183 |
+
# 自動範圍 + padding
|
| 184 |
+
pad = 0.5
|
| 185 |
+
lon_min, lon_max = d["Lon"].min() - pad, d["Lon"].max() + pad
|
| 186 |
+
lat_min, lat_max = d["Lat"].min() - pad, d["Lat"].max() + pad
|
| 187 |
+
|
| 188 |
+
# 點大小(可依喜好調係數)
|
| 189 |
+
size = (d["Magnitude"] + 2) ** 3
|
| 190 |
+
|
| 191 |
+
# 繪圖
|
| 192 |
+
fig, ax = plt.subplots(figsize=(6, 6))
|
| 193 |
+
ax.set_xlim(lon_min, lon_max)
|
| 194 |
+
ax.set_ylim(lat_min, lat_max)
|
| 195 |
+
|
| 196 |
+
sc = ax.scatter(d["Lon"], d["Lat"], s=size, c=d["Depth_km"],
|
| 197 |
+
edgecolor="black", alpha=0.9)
|
| 198 |
+
|
| 199 |
+
cb = plt.colorbar(sc, ax=ax, fraction=0.046, pad=0.04)
|
| 200 |
+
cb.set_label("Depth (km)")
|
| 201 |
+
|
| 202 |
+
ax.set_xlabel("Longitude (°E)")
|
| 203 |
+
ax.set_ylabel("Latitude (°N)")
|
| 204 |
+
ax.set_title("Epicenters (auto region, simple map)")
|
| 205 |
+
ax.grid(True, linestyle="--", alpha=0.3)
|
| 206 |
+
|
| 207 |
+
return _save_fig_to_tmp(fig)
|
| 208 |
+
|
| 209 |
+
# -----------------------------
|
| 210 |
+
# 表格輸出(tabulate 可選)
|
| 211 |
+
# -----------------------------
|
| 212 |
+
def _format_taipei(series):
|
| 213 |
+
try:
|
| 214 |
+
if series.dt.tz is None:
|
| 215 |
+
s = series.dt.tz_localize(TAIPEI_TZ)
|
| 216 |
+
else:
|
| 217 |
+
s = series.dt.tz_convert(TAIPEI_TZ)
|
| 218 |
+
return s.dt.strftime("%Y-%m-%d %H:%M:%S %Z")
|
| 219 |
+
except Exception:
|
| 220 |
+
return series.astype(str)
|
| 221 |
+
|
| 222 |
+
def _to_simple_md_table(df: pd.DataFrame) -> str:
|
| 223 |
+
cols = list(df.columns)
|
| 224 |
+
header = "|" + "|".join(cols) + "|\n"
|
| 225 |
+
sep = "|" + "|".join(["---"] * len(cols)) + "|\n"
|
| 226 |
+
rows = []
|
| 227 |
+
for _, r in df.iterrows():
|
| 228 |
+
cells = []
|
| 229 |
+
for c in cols:
|
| 230 |
+
v = r.get(c, "")
|
| 231 |
+
cells.append("" if pd.isna(v) else str(v))
|
| 232 |
+
rows.append("|" + "|".join(cells) + "|")
|
| 233 |
+
return header + sep + "\n".join(rows)
|
| 234 |
+
|
| 235 |
+
def df_to_markdown(df, top_n=100):
|
| 236 |
+
if df.empty:
|
| 237 |
+
return "(查無資料)"
|
| 238 |
+
cols = ["OriginTime", "Magnitude", "Depth_km", "Lat", "Lon", "Location", "ReportURL"]
|
| 239 |
+
cols = [c for c in cols if c in df.columns]
|
| 240 |
+
slim = df[cols].head(top_n).copy()
|
| 241 |
+
if "OriginTime" in slim.columns:
|
| 242 |
+
slim["OriginTime"] = _format_taipei(slim["OriginTime"])
|
| 243 |
+
header = f"共 {len(df)} 筆,顯示前 {min(len(slim), top_n)} 筆\n\n"
|
| 244 |
+
if HAS_TABULATE:
|
| 245 |
+
table = slim.to_markdown(index=False)
|
| 246 |
+
else:
|
| 247 |
+
table = _to_simple_md_table(slim.reset_index(drop=True))
|
| 248 |
+
return header + table
|
| 249 |
+
|
| 250 |
+
# -----------------------------
|
| 251 |
+
# 主流程
|
| 252 |
+
# -----------------------------
|
| 253 |
+
def query_and_render(time_from, time_to, sort_order):
|
| 254 |
+
try:
|
| 255 |
+
raw = fetch_reports(time_from, time_to)
|
| 256 |
+
df = parse_ea0015(raw)
|
| 257 |
+
if df.empty:
|
| 258 |
+
return "(查無資料)", None, None, None
|
| 259 |
+
|
| 260 |
+
if sort_order == "OriginTime (舊→新)":
|
| 261 |
+
df = df.sort_values("OriginTime", ascending=True, na_position="last").reset_index(drop=True)
|
| 262 |
+
|
| 263 |
+
md = df_to_markdown(df)
|
| 264 |
+
trend_path = plot_trend_path(df)
|
| 265 |
+
map_path = plot_map_path(df)
|
| 266 |
+
|
| 267 |
+
csv_bytes = df.to_csv(index=False).encode("utf-8-sig")
|
| 268 |
+
csv_path = tempfile.NamedTemporaryFile(delete=False, suffix=".csv", prefix="CWA_E-A0015-001_").name
|
| 269 |
+
with open(csv_path, "wb") as f:
|
| 270 |
+
f.write(csv_bytes)
|
| 271 |
+
|
| 272 |
+
return md, trend_path, map_path, csv_path
|
| 273 |
+
except Exception as e:
|
| 274 |
+
return f"錯誤:{e}", None, None, None
|
| 275 |
+
|
| 276 |
+
# -----------------------------
|
| 277 |
+
# 介面
|
| 278 |
+
# -----------------------------
|
| 279 |
+
default_from, default_to = set_time_range(days=3)
|
| 280 |
+
|
| 281 |
+
with gr.Blocks(fill_height=True) as demo:
|
| 282 |
+
gr.Markdown("## CWA 顯著有感地震報告 (E-A0015-001)\n預設查詢最近 3 天(台北時間)")
|
| 283 |
+
|
| 284 |
+
with gr.Column():
|
| 285 |
+
time_from = gr.Textbox(label="timeFrom yyyy-MM-ddTHH:mm:ss", value=default_from)
|
| 286 |
+
time_to = gr.Textbox(label="timeTo yyyy-MM-ddTHH:mm:ss", value=default_to)
|
| 287 |
+
|
| 288 |
+
with gr.Row():
|
| 289 |
+
btn_12h = gr.Button("最近 12 小時")
|
| 290 |
+
btn_24h = gr.Button("最近 24 小時")
|
| 291 |
+
btn_3d = gr.Button("最近 3 天")
|
| 292 |
+
btn_5d = gr.Button("最近 5 天")
|
| 293 |
+
|
| 294 |
+
sort_dd = gr.Dropdown(
|
| 295 |
+
choices=["OriginTime (新→舊)", "OriginTime (舊→新)"],
|
| 296 |
+
value="OriginTime (新→舊)",
|
| 297 |
+
label="排序",
|
| 298 |
+
)
|
| 299 |
+
|
| 300 |
+
run_btn = gr.Button("查詢", variant="primary")
|
| 301 |
+
|
| 302 |
+
table_out = gr.Markdown("(尚未查詢)")
|
| 303 |
+
trend_out = gr.Image(label="趨勢圖", type="filepath")
|
| 304 |
+
map_out = gr.Image(label="台灣範圍圖(簡易)", type="filepath")
|
| 305 |
+
dl_btn = gr.DownloadButton(label="下載 CSV") # 回傳路徑即可
|
| 306 |
+
|
| 307 |
+
# 快速鍵
|
| 308 |
+
btn_12h.click(lambda: set_time_range(hours=12), outputs=[time_from, time_to])
|
| 309 |
+
btn_24h.click(lambda: set_time_range(hours=24), outputs=[time_from, time_to])
|
| 310 |
+
btn_3d.click(lambda: set_time_range(days=3), outputs=[time_from, time_to])
|
| 311 |
+
btn_5d.click(lambda: set_time_range(days=5), outputs=[time_from, time_to])
|
| 312 |
+
|
| 313 |
+
# ���詢
|
| 314 |
+
run_btn.click(
|
| 315 |
+
query_and_render,
|
| 316 |
+
inputs=[time_from, time_to, sort_dd],
|
| 317 |
+
outputs=[table_out, trend_out, map_out, dl_btn],
|
| 318 |
+
)
|
| 319 |
+
|
| 320 |
+
if __name__ == "__main__":
|
| 321 |
+
demo.launch()
|