cwadayi commited on
Commit
4e1eae8
·
verified ·
1 Parent(s): 9243e7a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +70 -76
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
- # API 資料
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
- # 解析 JSON DataFrame(彈性容錯)
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
- 支援 records/Records、earthquake/Earthquake 等大小寫差異。
81
- 輸出欄位:OriginTime, Lat, Lon, Depth_km, Magnitude, Location, ReportURL
 
82
  """
83
  records = obj.get("records") or obj.get("Records") or {}
84
- quakes = records.get("earthquake") or records.get("Earthquake") or records.get("data") or []
85
  if not isinstance(quakes, list):
86
  quakes = []
87
 
88
  rows = []
89
  for q in quakes:
 
 
 
 
90
  origin = (
91
- _safe_get(q, "originTime")
92
- or _safe_get(q, "OriginTime")
93
- or _safe_get(q, "earthquakeInfo", "originTime")
94
- or _safe_get(q, "EarthquakeInfo", "OriginTime")
95
  )
96
- lat = _safe_get(q, "epicenter", "epicenterLat") or _safe_get(q, "Epicenter", "EpicenterLat")
97
- lon = _safe_get(q, "epicenter", "epicenterLon") or _safe_get(q, "Epicenter", "EpicenterLon")
98
- depth = _safe_get(q, "depth") or _safe_get(q, "Depth")
99
  mag = (
100
- _safe_get(q, "magnitude", "magnitudeValue")
101
- or _safe_get(q, "Magnitude", "MagnitudeValue")
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 畫台灣區域地圖;若 PyGMT 不可用,退回 Matplotlib 版。
149
  範圍:119/123, 21/26;顏色=深度(km),大小=規模
150
  """
151
  lon_min, lon_max, lat_min, lat_max = 119, 123, 21, 26
152
 
153
- # ---- PyGMT 版本 ----
154
  if HAS_PYGMT and not df.empty:
155
  d = df.dropna(subset=["Lon", "Lat"]).copy()
156
- if d.empty:
157
- return None
158
-
159
- mag = d["Magnitude"].fillna(0).clip(lower=0)
160
- size_cm = 0.06 * (mag + 1.5) # 各點大小(cm)
161
- depth = d["Depth_km"].fillna(0)
162
-
163
- vmin = float(max(0, np.nanmin(depth)))
164
- vmax = float(max(100, np.nanmax(depth)))
165
- cmap = "roma" # 或試 "turbo", "viridis"
166
-
167
- fig = pygmt.Figure()
168
- region = [lon_min, lon_max, lat_min, lat_max]
169
-
170
- fig.coast(
171
- region=region, projection="M12c",
172
- land="lightgray", water="white",
173
- shorelines="0.5p,black", borders="1/0.6p,black",
174
- frame=["WSen", "xaf", "yaf"]
175
- )
176
-
177
- # 畫震央
178
- # 這裡用 per-point size:把每個大小轉字串傳入 style
179
- fig.plot(
180
- x=d["Lon"].to_list(), y=d["Lat"].to_list(),
181
- style=["c{}c".format(s) for s in size_cm.to_list()],
182
- color=depth.to_list(), cmap=cmap, pen="0.25p,black"
183
- )
184
-
185
- fig.colorbar(frame=["x+lDepth (km)"], cmap=True, position="JMR+w7c/0.4c+o0.6c/0c")
186
- fig.basemap(map_scale="jBL+w50k+o0.6c/0.6c+f+lkm")
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
- mags = df["Magnitude"].fillna(0)
199
- sizes = (mags.clip(lower=0) + 2) ** 3
200
- sc = ax.scatter(df["Lon"], df["Lat"], s=sizes, c=df["Depth_km"], alpha=0.8, edgecolor="black")
 
 
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") # 傳路徑,不用 file_name 參數
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])