地震報告推送
eq.report.v1 在伺服器取得中央氣象署(CWA)新的地震報告時推送報告內容,格式與 地震報告 API 的單筆報告相近。同一筆報告通常會收到兩則以上:先收到即時版本,寫入資料庫後再收到一則。適合用來做新地震報告通知。
連線方式、解碼與重連規則見 SSE 連線。
建議標示資料來源:中央氣象署、ExpTech Studio(exptech.dev)
上游條款
- 中央氣象署:另須遵守中央氣象署的資料使用規範(例如政府資料開放授權條款);氣象與地震資訊以中央氣象署發布的內容為準。
標示位置、範例與商業使用申請見資料授權與來源標示。
不需要 token(公開 topic)。帶有效 token 時,用量記在權限 eq.report 下,超過配額也不會中斷。詳見 SSE 連線:驗證。
- 多工入口:
/api/v1/trem/sse?topics=eq.report.v1,事件名稱eq.report.v1(前綴是eq,不是trem)。 - 單一 topic 路徑:
Query 參數
tokenAPI Token(選填)。建議改用 Authorization: Bearer header。
curl -N --compressed "https://api.lb.exptech.dev/api/v1/trem/sse/report"curl -N --compressed "https://api.lb-tpe1.exptech.dev/api/v1/trem/sse/report"curl -N --compressed "https://api.lb-khh1.exptech.dev/api/v1/trem/sse/report"const res = await fetch("https://api.lb.exptech.dev/api/v1/trem/sse/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);
// text/event-stream:逐段讀取const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();for (;;) { const { value, done } = await reader.read(); if (done) break; console.log(value);}const res = await fetch("https://api.lb-tpe1.exptech.dev/api/v1/trem/sse/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);
// text/event-stream:逐段讀取const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();for (;;) { const { value, done } = await reader.read(); if (done) break; console.log(value);}const res = await fetch("https://api.lb-khh1.exptech.dev/api/v1/trem/sse/report");if (!res.ok) throw new Error(`HTTP ${res.status}`);
// text/event-stream:逐段讀取const reader = res.body.pipeThrough(new TextDecoderStream()).getReader();for (;;) { const { value, done } = await reader.read(); if (done) break; console.log(value);}import requests
with requests.get( "https://api.lb.exptech.dev/api/v1/trem/sse/report", stream=True, timeout=30,) as res: res.raise_for_status() for line in res.iter_lines(decode_unicode=True): if line: print(line)import requests
with requests.get( "https://api.lb-tpe1.exptech.dev/api/v1/trem/sse/report", stream=True, timeout=30,) as res: res.raise_for_status() for line in res.iter_lines(decode_unicode=True): if line: print(line)import requests
with requests.get( "https://api.lb-khh1.exptech.dev/api/v1/trem/sse/report", stream=True, timeout=30,) as res: res.raise_for_status() for line in res.iter_lines(decode_unicode=True): if line: print(line)text/event-stream;data 是 base64(gzip(JSON))
retry: 3000
event: infodata: {"location":"lb-tpe1"}
data: H4sIAAAAAAAC/...token 無效、過期或已撤銷
invalid tokenmode 對這個 topic 沒有影響,每一則都會送出。
每則 data 解碼後是一個 JSON 物件:
{ "reason": 1, "data": { "id": "115079-2026-1109-122157", "lat": 23.62, "lon": 120.66, "depth": 10.5, "loc": "雲林縣政府東南東方 23.2 公里 (位於雲林縣古坑鄉)", "mag": 4.2, "time": 1794198117000, "list": { "雲林縣": { "int": 3, "town": { "草嶺": { "lat": 23.6, "lon": 120.68, "int": 3 } } } } }}| 欄位 | 類型 | 說明 |
|---|---|---|
reason | int | 這則訊息的來源與時機,0–3,見下表。 |
data.id | string | 報告 ID,格式 XXXXXX-YYYY-MMDD-HHMMSS,與 GET /api/v2/eq/report/{id} 相同。 |
data.lat | number | 震央緯度(度)。 |
data.lon | number | 震央經度(度)。 |
data.depth | number | 震源深度(公里)。 |
data.loc | string | 震央位置描述。 |
data.mag | number | 芮氏規模。 |
data.time | int | 發震時間,Unix 毫秒。 |
data.list | object | 各縣市震度,key 是縣市名稱。 |
<縣市>.int | int | 該縣市的最大震度,0–9。 |
<縣市>.town.<測站> | object | 測站的 lat、lon(度)與 int(震度 0–9)。 |
震度數值 0–9 的對照見 地震報告:震度表示法。訊息沒有 int(全臺最大震度)、md5、trem;需要全臺最大震度時,取 list 裡各縣市 int 的最大值。
reason
Section titled “reason”| 值 | 來源 | 何時送出 | 已可用 HTTP API 查到 |
|---|---|---|---|
0 |
氣象署即時發布 | 偵測到新報告時(最早的一則) | 不一定 |
1 |
氣象署報告頁面 | 當天發生的報告寫入資料庫時 | 是 |
2 |
氣象署報告頁面 | 補上之前漏掉的報告時 | 是 |
3 |
氣象署報告頁面 | 定期維護時寫入或回填報告(多半是舊報告) | 是 |
瀏覽器用單一 topic 路徑接收:onmessage 收資料並解碼,連上後用 HTTP API 補抓最近的報告,再以 data.id 去重。
async function decodeData(b64) { const bytes = Uint8Array.from(atob(b64), (c) => c.charCodeAt(0)); const stream = new Blob([bytes]).stream().pipeThrough(new DecompressionStream("gzip")); return await new Response(stream).text();}
const seen = new Set();
async function catchUp() { // 這個 topic 不補送:連上後用 HTTP API 補抓最近的報告 const res = await fetch("https://api.core.exptech.dev/api/v2/eq/report?limit=10&minIntensity=0"); for (const r of await res.json()) seen.add(r.id);}
const es = new EventSource("https://api.lb.exptech.dev/api/v1/trem/sse/report");es.addEventListener("info", () => catchUp());es.onmessage = async (e) => { const { reason, data } = JSON.parse(await decodeData(e.data)); if (reason === 3 && Date.now() - data.time > 24 * 3600 * 1000) return; // 回填的舊報告 const isNew = !seen.has(data.id); seen.add(data.id); const maxInt = Math.max(0, ...Object.values(data.list).map((a) => a.int)); console.log(isNew ? "新地震報告" : "報告更新", data.id, `M${data.mag}`, `最大震度 ${maxInt}`);};- 不補送。 連線(或重連)時不會送出之前的報告。斷線期間的報告請在重連後呼叫
GET /api/v2/eq/report補齊;要包含震度 0 的報告請帶minIntensity=0,需要各地震度時再查單筆報告。 - 同一筆報告通常會收到兩則以上。 先收到
reason為0的一則,寫入資料庫後再收到1、2或3。請用data.id去重,後到的內容取代先到的。 reason為0時,HTTP API 可能還查不到。 這時查GET /api/v2/eq/report/{id}可能回404,收到其他reason後就查得到。- 兩則的
list可能略有不同。reason為0的一則與之後的一則資料來源不同,測站與座標可能有差異;之後那則的座標四捨五入到小數 2 位。 reason為3多半是舊報告,但不要直接忽略。 例如補不到的報告會在定期維護時以3送出。只需要近期報告時,請依data.time過濾。- 時間單位。
data.time是 Unix 毫秒。
