TREM 震度速報
trem.intensity.v1 是 ExpTech TREM-net(TREM 地震觀測網)的震度速報:地震搖晃期間,依測站實際觀測到的震度,列出各震度級的鄉鎮。同一場搖晃會陸續發出多報(第 1 報、更新、最終報),每報都是當下的完整內容。資料格式是 XML。
連線方式、解碼與重連規則見 SSE 連線。
TREM 資料使用警語
TREM 資料是 ExpTech 自有觀測網(TREM-net)的自動化即時觀測結果,不是官方地震資訊。臺灣地震資訊的權威機構是交通部中央氣象署,一切以中央氣象署發布的內容為準。本警語所稱 TREM 資料,指 TREM-net 的觀測與檢知資訊;TREM EEW 只提供給經 ExpTech 個別同意或簽約的特定用戶,使用方式另依服務條款第四條之一。
使用 TREM 資料時,你必須:
- 以中央氣象署為準。地震報告、強震即時警報、震度與海嘯資訊,以中央氣象署發布者為準;TREM 資料與官方資訊不一致時,以官方資訊為準。
- 清楚標示非官方。在任何介面或內容中呈現 TREM 資料時,必須同時標示資料來源與「非官方資訊,以中央氣象署發布為準」。
- 遵守《氣象法》。依《氣象法》第 17 條第 1 項,地震之預報或警報由中央氣象主管機關統一發布;第 18 條並未開放其他機關、學校、團體或個人對不特定大眾發布地震預報或任何警報;提供給特定用戶不在此限。違者依同法第 24 條第 2 項,處新臺幣 20 萬元以上 100 萬元以下罰鍰,並得按次處罰。
- 不得用於違法用途。禁止利用 TREM 資料預測地震,或對外發布地震、海嘯警報;禁止散布未經證實的地震訊息。散播有關災害之謠言或不實訊息,足生損害於公眾或他人者,依《災害防救法》第 53 條第 3 項,處三年以下有期徒刑、拘役或新臺幣 100 萬元以下罰金;散佈謠言,足以影響公共之安寧者,依《社會秩序維護法》第 63 條第 1 項第 5 款,處三日以下拘留或新臺幣 3 萬元以下罰鍰。
- 了解資料限制。TREM 為自動化觀測,未經人工審核,可能因雜訊、設備異常或網路延遲而誤判、漏報或延遲。不得作為避難、停班停課或任何安全決策的唯一依據。
不需要 token(公開 topic)。帶有效 token 時,用量記在權限 trem.intensity 下,超過配額也不會中斷。詳見 SSE 連線:驗證。
- 多工入口:
/api/v1/trem/sse?topics=trem.intensity.v1,事件名稱trem.intensity.v1。 - 單一 topic 路徑:
tokenAPI Token(選填)。建議改用 Authorization: Bearer header。
curl -N --compressed "https://api.lb.exptech.dev/api/v1/trem/sse/intensity"curl -N --compressed "https://api.lb-tpe1.exptech.dev/api/v1/trem/sse/intensity"curl -N --compressed "https://api.lb-khh1.exptech.dev/api/v1/trem/sse/intensity"const res = await fetch("https://api.lb.exptech.dev/api/v1/trem/sse/intensity");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/intensity");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/intensity");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/intensity", 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/intensity", 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/intensity", 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(XML))
retry: 3000
event: infodata: {"location":"lb-tpe1"}
data: H4sIAAAAAAAC/...token 無效、過期或已撤銷
invalid tokenmode 對這個 topic 沒有影響,每一報都會送出。
每則 data 解碼後是一份 UTF-8 XML(沒有 <?xml ?> 宣告):
<Report> <Control> <Title>震度速報</Title> <PublishingOffice>ExpTech Studio</PublishingOffice> <PublishingRegion>core-tnn1</PublishingRegion> </Control> <Head> <ReportDateTime>2026-07-26T20:36:41.869+08:00</ReportDateTime> <EventID>20260726203631</EventID> <InfoType>發表</InfoType> <Serial>1</Serial> <RelatedEewEvents> <EventID>20260726203631</EventID> <EventID>20260726203639</EventID> </RelatedEewEvents> </Head> <Body> <Intensity> <Observation> <MaxInt>2</MaxInt> <Area level="2"> <Town code="100"></Town> <Town code="105"></Town> <Town code="220"></Town> </Area> <Area level="1"> <Town code="239"></Town> <Town code="260"></Town> <Town code="330"></Town> </Area> </Observation> </Intensity> </Body></Report>| 欄位 | 類型 | 說明 |
|---|---|---|
Control/Title | string | 固定為「震度速報」。 |
Control/PublishingOffice | string | 固定為 ExpTech Studio。 |
Control/PublishingRegion | string | 發布這一報的節點名稱,例如 core-tnn1。節點切換時會改變。 |
Head/ReportDateTime | string | 這一報的發布時間,ISO 8601 臺灣時間(+08:00),到毫秒。 |
Head/EventID | string | 這場搖晃的識別碼,格式 YYYYMMDDHHMMSS(臺灣時間),是引發這場搖晃的第一個地震事件的偵測時間。同一場搖晃的每一報都相同。 |
Head/InfoType | string | 發表(第 1 報)、更新、最終(這場搖晃的最後一報)。格式上另保留「取消」。 |
Head/Serial | int | 這場搖晃的第幾報,從 1 開始遞增。 |
Head/RelatedEewEvents/EventID | string | 造成這場搖晃的地震事件識別碼,格式同 Head/EventID,可能有多個,通常包含 Head/EventID 本身。沒有時整個 RelatedEewEvents 省略。 |
Body/Intensity/Observation/MaxInt | string | 這一報的最大震度,記號見下表。沒有任何鄉鎮達 1 級時為 0。 |
Observation/Area | element | 同一震度級的鄉鎮群組,依震度由大到小排列。只列 1 級以上。 |
@level | string | 這組的震度級,記號見下表。 |
Town@code | int | 鄉鎮代碼(3 碼郵遞區號,例如 100 是臺北市中正區),組內由小到大排列。 |
MaxInt 與 Area@level 使用下列記號:
| 記號 | 0 |
1 |
2 |
3 |
4 |
5- |
5+ |
6- |
6+ |
7 |
|---|---|---|---|---|---|---|---|---|---|---|
| 震度 | 0 級 | 1 級 | 2 級 | 3 級 | 4 級 | 5 弱 | 5 強 | 6 弱 | 6 強 | 7 級 |
Town@code 是 3 碼鄉鎮代碼(與郵遞區號相同)。代碼對應的縣市、鄉鎮名稱與座標,請使用 ExpTech 公開的 region.json。
瀏覽器用單一 topic 路徑接收:onmessage 收資料、解碼、以 DOMParser 解析 XML,並用 EventID + Serial 去重。
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 latestSerial = new Map(); // EventID -> 已處理的最新 Serial
const es = new EventSource("https://api.lb.exptech.dev/api/v1/trem/sse/intensity");es.onmessage = async (e) => { const doc = new DOMParser().parseFromString(await decodeData(e.data), "application/xml"); const eventId = doc.querySelector("Head > EventID").textContent; const serial = Number(doc.querySelector("Head > Serial").textContent); if (serial <= (latestSerial.get(eventId) ?? 0)) return; // 補送或重複 latestSerial.set(eventId, serial);
const infoType = doc.querySelector("Head > InfoType").textContent; const maxInt = doc.querySelector("MaxInt").textContent; console.log(`震度速報 ${eventId} 第 ${serial} 報(${infoType})最大震度 ${maxInt}`); for (const area of doc.querySelectorAll("Observation > Area")) { const towns = [...area.querySelectorAll("Town")].map((t) => Number(t.getAttribute("code"))); console.log(` 震度 ${area.getAttribute("level")}:${towns.join(", ")}`); }};- 連線時會補送。 新連線一建立,會先收到
EventID在 240 秒內的每場震度速報的最後一報,之後才是即時資料。斷線重連時可能收到已處理過的報,請用EventID+Serial去重,只處理比已知更新的Serial。 - 後到的報取代先到的。 每一報都是當下的完整內容,不是差分;同一場搖晃以
Serial最大的那一報為準。 - 一場搖晃可能對應多個地震事件。
RelatedEewEvents可能有多個EventID,不要假設只有一個。 - 只列 1 級以上的鄉鎮。 沒有出現在任何
Area的鄉鎮,表示這一報沒有觀測到 1 級以上的震度。 - 時間。
ReportDateTime與EventID都是臺灣時間(UTC+8)。
