TREM 震度速報
trem.intensity.v1 は、ExpTech の TREM-net(TREM 地震観測網)の震度速報です。揺れのあいだ、観測点が実際に観測した震度に基づき、震度階級ごとの郷鎮を並べます。同じ揺れでは第 1 報、更新、最終報が順に出ます。各報はその時点の完全な内容です。データ形式は XML です。
接続方法、復号、再接続の規則は SSE 接続 を参照してください。
TREM データ利用上の注意
TREM データは、ExpTech が独自に運用する観測網(TREM-net)による自動・リアルタイムの観測結果であり、公式の地震情報ではありません。台湾の地震情報における権威ある機関は交通部中央気象署(CWA)であり、すべて中央気象署の発表内容が優先されます。本注意事項でいう TREM データとは、TREM-net の観測・検知情報を指します。TREM EEW は ExpTech が個別に同意した、または契約した特定の利用者にのみ提供し、その利用は利用規約第四条の一に従います。
TREM データを利用する際は、次を守ってください。
- 中央気象署の発表を優先する。地震情報、強震即時警報、震度、津波情報は、中央気象署の発表に従ってください。TREM データと公式情報が異なる場合は、公式情報に従ってください。
- 非公式であることを明示する。TREM データを表示・利用する際は、必ず出典とあわせて「非公式情報。中央気象署の発表が優先されます」と明示してください。
- 気象法を遵守する。台湾の気象法第 17 条第 1 項により、地震の予報・警報は中央気象主管機関が一元的に発表します。第 18 条は、ほかの機関・学校・団体・個人が不特定多数に向けて地震予報や警報を発表することを認めていません。特定の利用者への提供は制限されません。違反した場合は同法第 24 条第 2 項により新台湾ドル 20 万元以上 100 万元以下の過料が科され、違反ごとに繰り返し科されることがあります。
- 違法な目的に使わない。TREM データを使って地震を予測したり、地震・津波警報を発表したりすることは禁止です。未確認の地震情報の拡散も禁止です。災害に関するデマや虚偽情報を流布し、公衆または他人に損害を生じさせた場合は、災害防救法第 53 条第 3 項により 3 年以下の有期懲役、拘留または新台湾ドル 100 万元以下の罰金が、公共の安寧に影響するデマを流布した場合は、社会秩序維護法第 63 条第 1 項第 5 号により 3 日以下の拘留または新台湾ドル 3 万元以下の過料が科されます。
- データの限界を理解する。TREM は自動観測で、人による確認を経ていません。ノイズ、機器の異常、ネットワーク遅延により、誤検知・見逃し・遅延が生じることがあります。避難、休業・休校など安全に関わる判断の唯一の根拠にしないでください。
データライセンス
Section titled “データライセンス”推奨表記データ出典:ExpTech Studio TREM(exptech.dev)・非商用利用・非公式情報。中央気象署の発表が優先されます
表記の場所、例、商用利用の申請はデータの利用条件と出典表記をご覧ください。
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 に影響せず、すべての報が送られます。
メッセージ形式
Section titled “メッセージ形式”各 data を復号すると、1 つの 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 に達した郷鎮が 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が最大の報を正としてください。 - 1 回の揺れが複数の地震イベントに対応することがあります。
RelatedEewEventsには複数のEventIDがあることがあります。1 つだけとは仮定しないでください。 - 1 以上の郷鎮だけを列挙します。 どの
Areaにも出てこない郷鎮は、この報で 1 以上の震度が観測されていないことを示します。 - 時刻。
ReportDateTimeとEventIDはどちらも台湾時間(UTC+8)です。
