Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -8,7 +8,7 @@ import pandas as pd
|
|
| 8 |
import matplotlib.pyplot as plt
|
| 9 |
import gradio as gr
|
| 10 |
|
| 11 |
-
#
|
| 12 |
try:
|
| 13 |
import tabulate as _tabulate # noqa: F401
|
| 14 |
HAS_TABULATE = True
|
|
@@ -42,7 +42,7 @@ def set_time_range(hours=None, days=None):
|
|
| 42 |
return _fmt(t_from), _fmt(now)
|
| 43 |
|
| 44 |
# -----------------------------
|
| 45 |
-
#
|
| 46 |
# -----------------------------
|
| 47 |
API_URL = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/E-A0015-001"
|
| 48 |
|
|
@@ -56,7 +56,7 @@ def fetch_reports(time_from, time_to):
|
|
| 56 |
return r.json()
|
| 57 |
|
| 58 |
# -----------------------------
|
| 59 |
-
#
|
| 60 |
# -----------------------------
|
| 61 |
def _safe_get(d, *keys, default=None):
|
| 62 |
cur = d
|
|
@@ -77,45 +77,44 @@ def _to_float(x):
|
|
| 77 |
|
| 78 |
def parse_ea0015(obj):
|
| 79 |
"""
|
| 80 |
-
|
| 81 |
-
|
|
|
|
| 82 |
"""
|
| 83 |
records = obj.get("records") or obj.get("Records") or {}
|
| 84 |
-
quakes = records.get("earthquake") or records.get("Earthquake") or
|
| 85 |
if not isinstance(quakes, list):
|
| 86 |
quakes = []
|
| 87 |
|
| 88 |
rows = []
|
| 89 |
for q in quakes:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 90 |
origin = (
|
| 91 |
-
|
| 92 |
-
or
|
| 93 |
-
or _safe_get(q, "earthquakeInfo", "originTime")
|
| 94 |
-
or _safe_get(q, "EarthquakeInfo", "OriginTime")
|
| 95 |
)
|
| 96 |
-
lat =
|
| 97 |
-
lon =
|
| 98 |
-
depth =
|
| 99 |
mag = (
|
| 100 |
-
|
| 101 |
-
or
|
| 102 |
-
or _safe_get(q, "magnitude", "magnitude")
|
| 103 |
-
or _safe_get(q, "Magnitude", "Magnitude")
|
| 104 |
-
)
|
| 105 |
-
loc = _safe_get(q, "epicenter", "location") or _safe_get(q, "Epicenter", "Location")
|
| 106 |
-
url = _safe_get(q, "reportURL") or _safe_get(q, "ReportURL")
|
| 107 |
-
|
| 108 |
-
rows.append(
|
| 109 |
-
{
|
| 110 |
-
"OriginTime": origin,
|
| 111 |
-
"Lat": _to_float(lat),
|
| 112 |
-
"Lon": _to_float(lon),
|
| 113 |
-
"Depth_km": _to_float(depth),
|
| 114 |
-
"Magnitude": _to_float(mag),
|
| 115 |
-
"Location": loc,
|
| 116 |
-
"ReportURL": url,
|
| 117 |
-
}
|
| 118 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 119 |
|
| 120 |
df = pd.DataFrame(rows)
|
| 121 |
if not df.empty:
|
|
@@ -145,63 +144,58 @@ def plot_trend_path(df):
|
|
| 145 |
|
| 146 |
def plot_map_path(df):
|
| 147 |
"""
|
| 148 |
-
優先使用 PyGMT
|
| 149 |
範圍:119/123, 21/26;顏色=深度(km),大小=規模
|
| 150 |
"""
|
| 151 |
lon_min, lon_max, lat_min, lat_max = 119, 123, 21, 26
|
| 152 |
|
| 153 |
-
#
|
| 154 |
if HAS_PYGMT and not df.empty:
|
| 155 |
d = df.dropna(subset=["Lon", "Lat"]).copy()
|
| 156 |
-
if d.empty:
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
|
| 165 |
-
|
| 166 |
-
|
| 167 |
-
|
| 168 |
-
|
| 169 |
-
|
| 170 |
-
|
| 171 |
-
|
| 172 |
-
|
| 173 |
-
|
| 174 |
-
|
| 175 |
-
|
| 176 |
-
|
| 177 |
-
|
| 178 |
-
|
| 179 |
-
|
| 180 |
-
|
| 181 |
-
|
| 182 |
-
|
| 183 |
-
|
| 184 |
-
|
| 185 |
-
|
| 186 |
-
|
| 187 |
-
|
| 188 |
-
outpath = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 189 |
-
fig.savefig(outpath, dpi=220)
|
| 190 |
-
return outpath
|
| 191 |
-
|
| 192 |
-
# ---- 備援:Matplotlib 版本 ----
|
| 193 |
if df.empty:
|
| 194 |
return None
|
| 195 |
fig, ax = plt.subplots(figsize=(6, 6))
|
| 196 |
ax.set_xlim(lon_min, lon_max)
|
| 197 |
ax.set_ylim(lat_min, lat_max)
|
| 198 |
-
|
| 199 |
-
|
| 200 |
-
|
|
|
|
|
|
|
| 201 |
cb = plt.colorbar(sc, ax=ax, fraction=0.046, pad=0.04)
|
| 202 |
cb.set_label("Depth (km)")
|
| 203 |
-
ax.set_xlabel("Longitude (°E)")
|
| 204 |
-
ax.set_ylabel("Latitude (°N)")
|
| 205 |
ax.set_title("Epicenters in Taiwan Region (119–123E, 21–26N)")
|
| 206 |
ax.grid(True, linestyle="--", alpha=0.3)
|
| 207 |
return _save_fig_to_tmp(fig)
|
|
@@ -302,7 +296,7 @@ with gr.Blocks(fill_height=True) as demo:
|
|
| 302 |
table_out = gr.Markdown("(尚未查詢)")
|
| 303 |
trend_out = gr.Image(label="趨勢圖", type="filepath")
|
| 304 |
map_out = gr.Image(label="台灣範圍圖(PyGMT)", 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])
|
|
|
|
| 8 |
import matplotlib.pyplot as plt
|
| 9 |
import gradio as gr
|
| 10 |
|
| 11 |
+
# ---------- 可選依賴偵測(沒裝也能跑) ----------
|
| 12 |
try:
|
| 13 |
import tabulate as _tabulate # noqa: F401
|
| 14 |
HAS_TABULATE = True
|
|
|
|
| 42 |
return _fmt(t_from), _fmt(now)
|
| 43 |
|
| 44 |
# -----------------------------
|
| 45 |
+
# 呼叫 CWA API
|
| 46 |
# -----------------------------
|
| 47 |
API_URL = "https://opendata.cwa.gov.tw/api/v1/rest/datastore/E-A0015-001"
|
| 48 |
|
|
|
|
| 56 |
return r.json()
|
| 57 |
|
| 58 |
# -----------------------------
|
| 59 |
+
# JSON 解析(修正為讀 EarthquakeInfo)
|
| 60 |
# -----------------------------
|
| 61 |
def _safe_get(d, *keys, default=None):
|
| 62 |
cur = d
|
|
|
|
| 77 |
|
| 78 |
def parse_ea0015(obj):
|
| 79 |
"""
|
| 80 |
+
CWA E-A0015-001 結構:
|
| 81 |
+
records.earthquake[] 裡主要欄位在 EarthquakeInfo.*
|
| 82 |
+
取出:OriginTime, Lat, Lon, Depth_km, Magnitude, Location, ReportURL
|
| 83 |
"""
|
| 84 |
records = obj.get("records") or obj.get("Records") or {}
|
| 85 |
+
quakes = records.get("earthquake") or records.get("Earthquake") or []
|
| 86 |
if not isinstance(quakes, list):
|
| 87 |
quakes = []
|
| 88 |
|
| 89 |
rows = []
|
| 90 |
for q in quakes:
|
| 91 |
+
ei = q.get("EarthquakeInfo") or q.get("earthquakeInfo") or {}
|
| 92 |
+
epic = ei.get("Epicenter") or ei.get("epicenter") or {}
|
| 93 |
+
magobj = ei.get("Magnitude") or ei.get("magnitude") or {}
|
| 94 |
+
|
| 95 |
origin = (
|
| 96 |
+
ei.get("OriginTime") or ei.get("originTime")
|
| 97 |
+
or q.get("OriginTime") or q.get("originTime")
|
|
|
|
|
|
|
| 98 |
)
|
| 99 |
+
lat = epic.get("EpicenterLat") or epic.get("epicenterLat")
|
| 100 |
+
lon = epic.get("EpicenterLon") or epic.get("epicenterLon")
|
| 101 |
+
depth = ei.get("Depth") or ei.get("depth")
|
| 102 |
mag = (
|
| 103 |
+
magobj.get("MagnitudeValue") or magobj.get("magnitudeValue")
|
| 104 |
+
or magobj.get("Magnitude") or magobj.get("magnitude")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 105 |
)
|
| 106 |
+
loc = epic.get("Location") or epic.get("location")
|
| 107 |
+
url = q.get("Web") or q.get("ReportURL") or q.get("reportURL")
|
| 108 |
+
|
| 109 |
+
rows.append({
|
| 110 |
+
"OriginTime": origin,
|
| 111 |
+
"Lat": _to_float(lat),
|
| 112 |
+
"Lon": _to_float(lon),
|
| 113 |
+
"Depth_km": _to_float(depth),
|
| 114 |
+
"Magnitude": _to_float(mag),
|
| 115 |
+
"Location": loc,
|
| 116 |
+
"ReportURL": url,
|
| 117 |
+
})
|
| 118 |
|
| 119 |
df = pd.DataFrame(rows)
|
| 120 |
if not df.empty:
|
|
|
|
| 144 |
|
| 145 |
def plot_map_path(df):
|
| 146 |
"""
|
| 147 |
+
優先使用 PyGMT 畫台灣地圖;若不可用則退回 matplotlib。
|
| 148 |
範圍:119/123, 21/26;顏色=深度(km),大小=規模
|
| 149 |
"""
|
| 150 |
lon_min, lon_max, lat_min, lat_max = 119, 123, 21, 26
|
| 151 |
|
| 152 |
+
# --- PyGMT 版 ---
|
| 153 |
if HAS_PYGMT and not df.empty:
|
| 154 |
d = df.dropna(subset=["Lon", "Lat"]).copy()
|
| 155 |
+
if not d.empty:
|
| 156 |
+
mag = d["Magnitude"].fillna(0).clip(lower=0)
|
| 157 |
+
size_cm = 0.06 * (mag + 1.5) # 每點大小(cm)
|
| 158 |
+
depth = d["Depth_km"].fillna(0)
|
| 159 |
+
|
| 160 |
+
vmin = float(max(0, float(depth.min()) if len(depth) else 0))
|
| 161 |
+
vmax = float(max(100, float(depth.max()) if len(depth) else 100))
|
| 162 |
+
cmap = "roma"
|
| 163 |
+
|
| 164 |
+
fig = pygmt.Figure()
|
| 165 |
+
region = [lon_min, lon_max, lat_min, lat_max]
|
| 166 |
+
fig.coast(
|
| 167 |
+
region=region, projection="M12c",
|
| 168 |
+
land="lightgray", water="white",
|
| 169 |
+
shorelines="0.5p,black", borders="1/0.6p,black",
|
| 170 |
+
frame=["WSen", "xaf", "yaf"]
|
| 171 |
+
)
|
| 172 |
+
# per-point size
|
| 173 |
+
fig.plot(
|
| 174 |
+
x=d["Lon"].to_list(), y=d["Lat"].to_list(),
|
| 175 |
+
style=["c{}c".format(s) for s in size_cm.to_list()],
|
| 176 |
+
color=depth.to_list(), cmap=cmap, pen="0.25p,black"
|
| 177 |
+
)
|
| 178 |
+
fig.colorbar(frame=["x+lDepth (km)"], cmap=True, position="JMR+w7c/0.4c+o0.6c/0c")
|
| 179 |
+
fig.basemap(map_scale="jBL+w50k+o0.6c/0.6c+f+lkm")
|
| 180 |
+
|
| 181 |
+
outpath = tempfile.NamedTemporaryFile(delete=False, suffix=".png").name
|
| 182 |
+
fig.savefig(outpath, dpi=220)
|
| 183 |
+
return outpath
|
| 184 |
+
|
| 185 |
+
# --- Matplotlib 備援 ---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
if df.empty:
|
| 187 |
return None
|
| 188 |
fig, ax = plt.subplots(figsize=(6, 6))
|
| 189 |
ax.set_xlim(lon_min, lon_max)
|
| 190 |
ax.set_ylim(lat_min, lat_max)
|
| 191 |
+
sc = ax.scatter(
|
| 192 |
+
df["Lon"], df["Lat"],
|
| 193 |
+
s=(df["Magnitude"].fillna(0).clip(lower=0) + 2) ** 3,
|
| 194 |
+
c=df["Depth_km"], edgecolor="black", alpha=0.85
|
| 195 |
+
)
|
| 196 |
cb = plt.colorbar(sc, ax=ax, fraction=0.046, pad=0.04)
|
| 197 |
cb.set_label("Depth (km)")
|
| 198 |
+
ax.set_xlabel("Longitude (°E)"); ax.set_ylabel("Latitude (°N)")
|
|
|
|
| 199 |
ax.set_title("Epicenters in Taiwan Region (119–123E, 21–26N)")
|
| 200 |
ax.grid(True, linestyle="--", alpha=0.3)
|
| 201 |
return _save_fig_to_tmp(fig)
|
|
|
|
| 296 |
table_out = gr.Markdown("(尚未查詢)")
|
| 297 |
trend_out = gr.Image(label="趨勢圖", type="filepath")
|
| 298 |
map_out = gr.Image(label="台灣範圍圖(PyGMT)", type="filepath")
|
| 299 |
+
dl_btn = gr.DownloadButton(label="下載 CSV") # 回傳路徑即可
|
| 300 |
|
| 301 |
# 快速鍵
|
| 302 |
btn_12h.click(lambda: set_time_range(hours=12), outputs=[time_from, time_to])
|