@sayue_ltr/fleq 1.49.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +898 -0
- package/LICENSE +21 -0
- package/README.md +535 -0
- package/assets/icons/.gitkeep +0 -0
- package/assets/sounds/.gitkeep +0 -0
- package/assets/sounds/cancel.mp3 +0 -0
- package/assets/sounds/critical.mp3 +0 -0
- package/assets/sounds/info.mp3 +0 -0
- package/assets/sounds/normal.mp3 +0 -0
- package/assets/sounds/warning.mp3 +0 -0
- package/dist/config.js +638 -0
- package/dist/dmdata/connection-manager.js +2 -0
- package/dist/dmdata/endpoint-selector.js +185 -0
- package/dist/dmdata/multi-connection-manager.js +158 -0
- package/dist/dmdata/rest-client.js +281 -0
- package/dist/dmdata/telegram-parser.js +704 -0
- package/dist/dmdata/volcano-parser.js +647 -0
- package/dist/dmdata/ws-client.js +336 -0
- package/dist/engine/cli/cli-init.js +266 -0
- package/dist/engine/cli/cli-run.js +121 -0
- package/dist/engine/cli/cli.js +121 -0
- package/dist/engine/eew/eew-logger.js +355 -0
- package/dist/engine/eew/eew-tracker.js +229 -0
- package/dist/engine/messages/message-router.js +261 -0
- package/dist/engine/messages/tsunami-state.js +96 -0
- package/dist/engine/messages/volcano-state.js +131 -0
- package/dist/engine/messages/volcano-vfvo53-aggregator.js +173 -0
- package/dist/engine/monitor/monitor.js +118 -0
- package/dist/engine/monitor/repl-coordinator.js +63 -0
- package/dist/engine/monitor/shutdown.js +114 -0
- package/dist/engine/notification/node-notifier-loader.js +19 -0
- package/dist/engine/notification/notifier.js +338 -0
- package/dist/engine/notification/sound-player.js +230 -0
- package/dist/engine/notification/volcano-presentation.js +166 -0
- package/dist/engine/startup/config-resolver.js +139 -0
- package/dist/engine/startup/tsunami-initializer.js +91 -0
- package/dist/engine/startup/update-checker.js +229 -0
- package/dist/engine/startup/volcano-initializer.js +89 -0
- package/dist/index.js +24 -0
- package/dist/logger.js +95 -0
- package/dist/types.js +61 -0
- package/dist/ui/earthquake-formatter.js +871 -0
- package/dist/ui/eew-formatter.js +335 -0
- package/dist/ui/formatter.js +689 -0
- package/dist/ui/repl.js +2059 -0
- package/dist/ui/test-samples.js +880 -0
- package/dist/ui/theme.js +516 -0
- package/dist/ui/volcano-formatter.js +667 -0
- package/dist/ui/waiting-tips.js +227 -0
- package/dist/utils/intensity.js +13 -0
- package/dist/utils/secrets.js +14 -0
- package/package.json +69 -0
|
@@ -0,0 +1,647 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.normalizeVolcanoBodyText = normalizeVolcanoBodyText;
|
|
37
|
+
exports.parseVolcanoTelegram = parseVolcanoTelegram;
|
|
38
|
+
const telegram_parser_1 = require("./telegram-parser");
|
|
39
|
+
const log = __importStar(require("../logger"));
|
|
40
|
+
// ── 共通ヘルパー ──
|
|
41
|
+
/**
|
|
42
|
+
* 行頭ラベル部分(コロンの前)の全角スペースだけを除去する。
|
|
43
|
+
* 市町村区切り等の全角スペースは保持する。
|
|
44
|
+
*
|
|
45
|
+
* "火 山:浅間山" → "火山:浅間山"
|
|
46
|
+
* "鹿児島市 日置市 いちき串木野市" → そのまま
|
|
47
|
+
*/
|
|
48
|
+
const LABEL_PREFIX_RE = /^(\s*(?:[0-90-9]+[..、]\s*)?)([^::\n]+)([::])(.*)$/u;
|
|
49
|
+
function normalizeVolcanoBodyLine(line) {
|
|
50
|
+
const trimmedRight = line.replace(/[ \t\u3000]+$/gu, "");
|
|
51
|
+
if (!trimmedRight)
|
|
52
|
+
return "";
|
|
53
|
+
const match = trimmedRight.match(LABEL_PREFIX_RE);
|
|
54
|
+
if (!match)
|
|
55
|
+
return trimmedRight;
|
|
56
|
+
const [, prefix, rawLabel, colon, rest] = match;
|
|
57
|
+
// ラベル部に全角/半角スペースがなければそのまま
|
|
58
|
+
if (!/[ \u3000]/u.test(rawLabel))
|
|
59
|
+
return trimmedRight;
|
|
60
|
+
// 説明文っぽい内容(句読点・括弧を含む)はラベル扱いしない
|
|
61
|
+
if (/[、。,,.()()]/u.test(rawLabel))
|
|
62
|
+
return trimmedRight;
|
|
63
|
+
const label = rawLabel.replace(/[ \u3000]+/gu, "");
|
|
64
|
+
return `${prefix}${label}${colon}${rest}`;
|
|
65
|
+
}
|
|
66
|
+
function normalizeVolcanoBodyText(text) {
|
|
67
|
+
return text
|
|
68
|
+
.replace(/\r\n?/g, "\n")
|
|
69
|
+
.split("\n")
|
|
70
|
+
.map(normalizeVolcanoBodyLine)
|
|
71
|
+
.join("\n")
|
|
72
|
+
.replace(/\n{3,}/g, "\n\n")
|
|
73
|
+
.trim();
|
|
74
|
+
}
|
|
75
|
+
/** Report ノードを取得 */
|
|
76
|
+
function findReport(parsed) {
|
|
77
|
+
return ((0, telegram_parser_1.dig)(parsed, "Report") ||
|
|
78
|
+
(0, telegram_parser_1.dig)(parsed, "jmx:Report") ||
|
|
79
|
+
(0, telegram_parser_1.dig)(parsed, "jmx_seis:Report"));
|
|
80
|
+
}
|
|
81
|
+
/** レベルコード → 数値 ("11"→1, "12"→2, ..., "15"→5, それ以外→null) */
|
|
82
|
+
function levelCodeToNumber(code) {
|
|
83
|
+
if (!code)
|
|
84
|
+
return null;
|
|
85
|
+
const map = {
|
|
86
|
+
"11": 1, "12": 2, "13": 3, "14": 4, "15": 5,
|
|
87
|
+
};
|
|
88
|
+
return map[code] ?? null;
|
|
89
|
+
}
|
|
90
|
+
/** XML の Condition + infoType → VolcanoAction への正規化 */
|
|
91
|
+
function conditionToAction(condition, infoType) {
|
|
92
|
+
if (infoType === "取消")
|
|
93
|
+
return "cancel";
|
|
94
|
+
const normalized = condition.replace(/\s+/g, "");
|
|
95
|
+
if (normalized === "引上げ")
|
|
96
|
+
return "raise";
|
|
97
|
+
if (normalized === "引下げ")
|
|
98
|
+
return "lower";
|
|
99
|
+
if (normalized === "解除")
|
|
100
|
+
return "release";
|
|
101
|
+
if (normalized === "継続")
|
|
102
|
+
return "continue";
|
|
103
|
+
if (normalized === "発表" || normalized === "特別警報")
|
|
104
|
+
return "issue";
|
|
105
|
+
return "issue";
|
|
106
|
+
}
|
|
107
|
+
/** 火山名の VolcanoInfo (type="...(対象火山)") から火山情報を抽出 */
|
|
108
|
+
function extractVolcanoBase(report, headType, msg) {
|
|
109
|
+
const head = (0, telegram_parser_1.dig)(report, "Head");
|
|
110
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
111
|
+
const infoType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "InfoType"));
|
|
112
|
+
const titleRaw = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "Title"));
|
|
113
|
+
// Head > Title は「火山名 御嶽山 噴火速報」のように「火山名+山名」がプレフィックスされる場合がある
|
|
114
|
+
const title = titleRaw.replace(/^火山名\s+\S+\s+/, "");
|
|
115
|
+
const reportDateTime = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "ReportDateTime"));
|
|
116
|
+
const headlineRaw = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "Headline", "Text"));
|
|
117
|
+
const headline = headlineRaw ? normalizeVolcanoBodyText(headlineRaw) : null;
|
|
118
|
+
const publishingOffice = msg.xmlReport?.control?.publishingOffice || "";
|
|
119
|
+
// 火山名・コード・座標を Body > VolcanoInfo から取得
|
|
120
|
+
let volcanoName = "";
|
|
121
|
+
let volcanoCode = "";
|
|
122
|
+
let coordinate = null;
|
|
123
|
+
let eventDateTime = null;
|
|
124
|
+
const volcanoInfos = (0, telegram_parser_1.dig)(body, "VolcanoInfo");
|
|
125
|
+
const infos = Array.isArray(volcanoInfos) ? volcanoInfos : volcanoInfos ? [volcanoInfos] : [];
|
|
126
|
+
for (const vi of infos) {
|
|
127
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
128
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
129
|
+
for (const item of itemList) {
|
|
130
|
+
const areas = (0, telegram_parser_1.dig)(item, "Areas");
|
|
131
|
+
const codeType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(areas, "@_codeType"));
|
|
132
|
+
if (codeType === "火山名") {
|
|
133
|
+
const areaList = (0, telegram_parser_1.dig)(areas, "Area");
|
|
134
|
+
const area = Array.isArray(areaList) ? areaList[0] : areaList;
|
|
135
|
+
if (area) {
|
|
136
|
+
volcanoName = volcanoName || (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Name"));
|
|
137
|
+
volcanoCode = volcanoCode || (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Code"));
|
|
138
|
+
if (!coordinate) {
|
|
139
|
+
const coord = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Coordinate", "#text") ||
|
|
140
|
+
(0, telegram_parser_1.dig)(area, "Coordinate"));
|
|
141
|
+
coordinate = coord || null;
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
// EventTime
|
|
146
|
+
if (!eventDateTime) {
|
|
147
|
+
const et = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(item, "EventTime", "EventDateTime", "#text") || (0, telegram_parser_1.dig)(item, "EventTime", "EventDateTime"));
|
|
148
|
+
if (et)
|
|
149
|
+
eventDateTime = et;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
// TargetDateTime をフォールバック
|
|
154
|
+
if (!eventDateTime) {
|
|
155
|
+
const target = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "TargetDateTime"));
|
|
156
|
+
if (target)
|
|
157
|
+
eventDateTime = target;
|
|
158
|
+
}
|
|
159
|
+
return {
|
|
160
|
+
domain: "volcano",
|
|
161
|
+
infoType,
|
|
162
|
+
title,
|
|
163
|
+
reportDateTime,
|
|
164
|
+
eventDateTime,
|
|
165
|
+
headline,
|
|
166
|
+
publishingOffice,
|
|
167
|
+
volcanoName,
|
|
168
|
+
volcanoCode,
|
|
169
|
+
coordinate,
|
|
170
|
+
isTest: msg.head.test,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
/** 噴煙観測データを抽出 */
|
|
174
|
+
function extractPlumeObservation(body) {
|
|
175
|
+
const obs = (0, telegram_parser_1.dig)(body, "VolcanoObservation");
|
|
176
|
+
const colorPlume = (0, telegram_parser_1.dig)(obs, "ColorPlume");
|
|
177
|
+
let plumeHeight = null;
|
|
178
|
+
let plumeHeightUnknown = false;
|
|
179
|
+
let plumeDirection = null;
|
|
180
|
+
if (colorPlume) {
|
|
181
|
+
// 火口上噴煙高度
|
|
182
|
+
const heightNode = (0, telegram_parser_1.dig)(colorPlume, "jmx_eb:PlumeHeightAboveCrater") ||
|
|
183
|
+
(0, telegram_parser_1.dig)(colorPlume, "PlumeHeightAboveCrater");
|
|
184
|
+
const heightVal = typeof heightNode === "object"
|
|
185
|
+
? (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(heightNode, "#text"))
|
|
186
|
+
: (0, telegram_parser_1.str)(heightNode);
|
|
187
|
+
const heightCondition = typeof heightNode === "object"
|
|
188
|
+
? (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(heightNode, "@_condition"))
|
|
189
|
+
: "";
|
|
190
|
+
if (heightCondition === "不明" || heightVal === "" || heightVal === "不明") {
|
|
191
|
+
plumeHeightUnknown = true;
|
|
192
|
+
}
|
|
193
|
+
else {
|
|
194
|
+
const parsed = parseInt(heightVal, 10);
|
|
195
|
+
if (!isNaN(parsed))
|
|
196
|
+
plumeHeight = parsed;
|
|
197
|
+
}
|
|
198
|
+
// 流向
|
|
199
|
+
const dirNode = (0, telegram_parser_1.dig)(colorPlume, "jmx_eb:PlumeDirection") ||
|
|
200
|
+
(0, telegram_parser_1.dig)(colorPlume, "PlumeDirection");
|
|
201
|
+
const dirVal = typeof dirNode === "object"
|
|
202
|
+
? (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(dirNode, "#text"))
|
|
203
|
+
: (0, telegram_parser_1.str)(dirNode);
|
|
204
|
+
if (dirVal && dirVal !== "流向不明") {
|
|
205
|
+
plumeDirection = dirVal;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
// 火口名
|
|
209
|
+
let craterName = null;
|
|
210
|
+
// VolcanoInfo 内から取得
|
|
211
|
+
const volcanoInfos = (0, telegram_parser_1.dig)(body, "VolcanoInfo");
|
|
212
|
+
const infos = Array.isArray(volcanoInfos) ? volcanoInfos : volcanoInfos ? [volcanoInfos] : [];
|
|
213
|
+
for (const vi of infos) {
|
|
214
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
215
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
216
|
+
for (const item of itemList) {
|
|
217
|
+
const areas = (0, telegram_parser_1.dig)(item, "Areas");
|
|
218
|
+
const areaList = (0, telegram_parser_1.dig)(areas, "Area");
|
|
219
|
+
const area = Array.isArray(areaList) ? areaList[0] : areaList;
|
|
220
|
+
if (area) {
|
|
221
|
+
const cn = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "CraterName"));
|
|
222
|
+
if (cn) {
|
|
223
|
+
craterName = cn;
|
|
224
|
+
break;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
if (craterName)
|
|
229
|
+
break;
|
|
230
|
+
}
|
|
231
|
+
// VolcanoObservation > OtherObservation の "火口:" からも取得
|
|
232
|
+
if (!craterName) {
|
|
233
|
+
const otherObs = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(obs, "OtherObservation"));
|
|
234
|
+
const match = otherObs.match(/火口[::](.+?)[\n\r]/);
|
|
235
|
+
if (match)
|
|
236
|
+
craterName = match[1].trim();
|
|
237
|
+
}
|
|
238
|
+
return { plumeHeight, plumeHeightUnknown, plumeDirection, craterName };
|
|
239
|
+
}
|
|
240
|
+
// ── 電文タイプ別パーサ ──
|
|
241
|
+
/** VFVO50 / VFSVii: 噴火警報・予報 / 海上警報 */
|
|
242
|
+
function parseVolcanoAlert(report, headType, msg) {
|
|
243
|
+
const base = extractVolcanoBase(report, headType, msg);
|
|
244
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
245
|
+
// Head > Headline > Information (対象火山) から Kind/LastKind を取得
|
|
246
|
+
const headInfo = (0, telegram_parser_1.dig)(report, "Head", "Headline", "Information");
|
|
247
|
+
const headInfoList = Array.isArray(headInfo) ? headInfo : headInfo ? [headInfo] : [];
|
|
248
|
+
let alertLevelCode = null;
|
|
249
|
+
let previousLevelCode = null;
|
|
250
|
+
let condition = "";
|
|
251
|
+
let warningKind = "";
|
|
252
|
+
let isMarine = false;
|
|
253
|
+
// Body > VolcanoInfo (対象火山) からも取得
|
|
254
|
+
const volcanoInfos = (0, telegram_parser_1.dig)(body, "VolcanoInfo");
|
|
255
|
+
const infos = Array.isArray(volcanoInfos) ? volcanoInfos : volcanoInfos ? [volcanoInfos] : [];
|
|
256
|
+
for (const vi of infos) {
|
|
257
|
+
const viType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(vi, "@_type"));
|
|
258
|
+
if (viType.includes("対象火山")) {
|
|
259
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
260
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
261
|
+
const item = itemList[0];
|
|
262
|
+
if (item) {
|
|
263
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
264
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
265
|
+
alertLevelCode = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code")) || null;
|
|
266
|
+
condition = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Condition"));
|
|
267
|
+
warningKind = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
268
|
+
const lastKind = (0, telegram_parser_1.dig)(item, "LastKind");
|
|
269
|
+
const lastKindObj = Array.isArray(lastKind) ? lastKind[0] : lastKind;
|
|
270
|
+
previousLevelCode = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(lastKindObj, "Code")) || null;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
// 海上警報判定
|
|
274
|
+
if (viType.includes("海上")) {
|
|
275
|
+
isMarine = true;
|
|
276
|
+
}
|
|
277
|
+
}
|
|
278
|
+
// Head の Information からも海上判定
|
|
279
|
+
for (const info of headInfoList) {
|
|
280
|
+
const infoType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(info, "@_type"));
|
|
281
|
+
if (infoType.includes("海上")) {
|
|
282
|
+
isMarine = true;
|
|
283
|
+
// 海上電文の場合、alertLevelCode を Head から補完
|
|
284
|
+
if (!alertLevelCode) {
|
|
285
|
+
const items = (0, telegram_parser_1.dig)(info, "Item");
|
|
286
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
287
|
+
const item = itemList[0];
|
|
288
|
+
if (item) {
|
|
289
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
290
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
291
|
+
alertLevelCode = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code")) || null;
|
|
292
|
+
condition = condition || (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Condition"));
|
|
293
|
+
warningKind = warningKind || (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
294
|
+
const lastKind = (0, telegram_parser_1.dig)(item, "LastKind");
|
|
295
|
+
const lastKindObj = Array.isArray(lastKind) ? lastKind[0] : lastKind;
|
|
296
|
+
previousLevelCode = previousLevelCode || (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(lastKindObj, "Code")) || null;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const alertLevel = alertLevelCode ? levelCodeToNumber(alertLevelCode) : null;
|
|
302
|
+
const action = conditionToAction(condition, base.infoType);
|
|
303
|
+
// 対象市町村
|
|
304
|
+
const municipalities = [];
|
|
305
|
+
// 対象海上予報区
|
|
306
|
+
const marineAreas = [];
|
|
307
|
+
let marineWarningKind = null;
|
|
308
|
+
let marineAlertLevelCode = null;
|
|
309
|
+
for (const vi of infos) {
|
|
310
|
+
const viType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(vi, "@_type"));
|
|
311
|
+
if (viType.includes("対象市町村等")) {
|
|
312
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
313
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
314
|
+
for (const item of itemList) {
|
|
315
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
316
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
317
|
+
const kindName = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
318
|
+
const areas = (0, telegram_parser_1.dig)(item, "Areas");
|
|
319
|
+
const areaList = (0, telegram_parser_1.dig)(areas, "Area");
|
|
320
|
+
const areaArr = Array.isArray(areaList) ? areaList : areaList ? [areaList] : [];
|
|
321
|
+
for (const area of areaArr) {
|
|
322
|
+
municipalities.push({
|
|
323
|
+
name: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Name")),
|
|
324
|
+
code: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Code")),
|
|
325
|
+
kind: kindName,
|
|
326
|
+
});
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
if (viType.includes("対象海上予報区")) {
|
|
331
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
332
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
333
|
+
for (const item of itemList) {
|
|
334
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
335
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
336
|
+
const kindName = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
337
|
+
const kindCode = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code"));
|
|
338
|
+
if (kindName)
|
|
339
|
+
marineWarningKind = marineWarningKind || kindName;
|
|
340
|
+
if (kindCode)
|
|
341
|
+
marineAlertLevelCode = marineAlertLevelCode || kindCode;
|
|
342
|
+
const areas = (0, telegram_parser_1.dig)(item, "Areas");
|
|
343
|
+
const areaList = (0, telegram_parser_1.dig)(areas, "Area");
|
|
344
|
+
const areaArr = Array.isArray(areaList) ? areaList : areaList ? [areaList] : [];
|
|
345
|
+
for (const area of areaArr) {
|
|
346
|
+
marineAreas.push({
|
|
347
|
+
name: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Name")),
|
|
348
|
+
code: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Code")),
|
|
349
|
+
kind: kindName,
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
}
|
|
354
|
+
}
|
|
355
|
+
// VolcanoInfoContent
|
|
356
|
+
const content = (0, telegram_parser_1.dig)(body, "VolcanoInfoContent");
|
|
357
|
+
const bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(content, "VolcanoActivity")));
|
|
358
|
+
const preventionText = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(content, "VolcanoPrevention"));
|
|
359
|
+
return {
|
|
360
|
+
...base,
|
|
361
|
+
kind: "alert",
|
|
362
|
+
type: headType,
|
|
363
|
+
alertLevel,
|
|
364
|
+
alertLevelCode,
|
|
365
|
+
action,
|
|
366
|
+
previousLevelCode,
|
|
367
|
+
warningKind,
|
|
368
|
+
municipalities,
|
|
369
|
+
marineAreas,
|
|
370
|
+
marineWarningKind,
|
|
371
|
+
marineAlertLevelCode,
|
|
372
|
+
bodyText,
|
|
373
|
+
preventionText,
|
|
374
|
+
isMarine,
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
/** VFVO52 / VFVO56: 噴火に関する火山観測報 / 噴火速報 */
|
|
378
|
+
function parseVolcanoEruption(report, headType, msg) {
|
|
379
|
+
const base = extractVolcanoBase(report, headType, msg);
|
|
380
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
381
|
+
// Kind (現象コード)
|
|
382
|
+
let phenomenonCode = "";
|
|
383
|
+
let phenomenonName = "";
|
|
384
|
+
const volcanoInfos = (0, telegram_parser_1.dig)(body, "VolcanoInfo");
|
|
385
|
+
const infos = Array.isArray(volcanoInfos) ? volcanoInfos : volcanoInfos ? [volcanoInfos] : [];
|
|
386
|
+
for (const vi of infos) {
|
|
387
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
388
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
389
|
+
for (const item of itemList) {
|
|
390
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
391
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
392
|
+
const code = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code"));
|
|
393
|
+
if (code) {
|
|
394
|
+
phenomenonCode = code;
|
|
395
|
+
phenomenonName = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
396
|
+
break;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
if (phenomenonCode)
|
|
400
|
+
break;
|
|
401
|
+
}
|
|
402
|
+
const plume = extractPlumeObservation(body);
|
|
403
|
+
// VolcanoInfoContent
|
|
404
|
+
const content = (0, telegram_parser_1.dig)(body, "VolcanoInfoContent");
|
|
405
|
+
const bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(content, "VolcanoActivity")));
|
|
406
|
+
return {
|
|
407
|
+
...base,
|
|
408
|
+
kind: "eruption",
|
|
409
|
+
type: headType,
|
|
410
|
+
phenomenonCode,
|
|
411
|
+
phenomenonName,
|
|
412
|
+
craterName: plume.craterName,
|
|
413
|
+
plumeHeight: plume.plumeHeight,
|
|
414
|
+
plumeHeightUnknown: plume.plumeHeightUnknown,
|
|
415
|
+
plumeDirection: plume.plumeDirection,
|
|
416
|
+
isFlashReport: headType === "VFVO56",
|
|
417
|
+
bodyText,
|
|
418
|
+
};
|
|
419
|
+
}
|
|
420
|
+
/** VFVO53 / VFVO54 / VFVO55: 降灰予報 */
|
|
421
|
+
function parseVolcanoAshfall(report, headType, msg) {
|
|
422
|
+
const base = extractVolcanoBase(report, headType, msg);
|
|
423
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
424
|
+
const subKindMap = {
|
|
425
|
+
VFVO53: "scheduled",
|
|
426
|
+
VFVO54: "rapid",
|
|
427
|
+
VFVO55: "detailed",
|
|
428
|
+
};
|
|
429
|
+
// AshInfos から降灰予報データを抽出
|
|
430
|
+
const ashForecasts = [];
|
|
431
|
+
const ashInfos = (0, telegram_parser_1.dig)(body, "AshInfos");
|
|
432
|
+
if (ashInfos) {
|
|
433
|
+
const ashInfoList = (0, telegram_parser_1.dig)(ashInfos, "AshInfo");
|
|
434
|
+
const ashArr = Array.isArray(ashInfoList) ? ashInfoList : ashInfoList ? [ashInfoList] : [];
|
|
435
|
+
for (const ashInfo of ashArr) {
|
|
436
|
+
const startTime = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(ashInfo, "StartTime"));
|
|
437
|
+
const endTime = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(ashInfo, "EndTime"));
|
|
438
|
+
const areas = [];
|
|
439
|
+
const items = (0, telegram_parser_1.dig)(ashInfo, "Item");
|
|
440
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
441
|
+
for (const item of itemList) {
|
|
442
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
443
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
444
|
+
const ashName = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Name"));
|
|
445
|
+
const ashCode = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code"));
|
|
446
|
+
const sizeVal = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Property", "Size", "#text") || (0, telegram_parser_1.dig)(kindObj, "Property", "Size"));
|
|
447
|
+
const thickness = sizeVal ? parseFloat(sizeVal) : null;
|
|
448
|
+
const itemAreas = (0, telegram_parser_1.dig)(item, "Areas");
|
|
449
|
+
const areaList = (0, telegram_parser_1.dig)(itemAreas, "Area");
|
|
450
|
+
const areaArr = Array.isArray(areaList) ? areaList : areaList ? [areaList] : [];
|
|
451
|
+
for (const area of areaArr) {
|
|
452
|
+
areas.push({
|
|
453
|
+
name: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Name")),
|
|
454
|
+
code: (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(area, "Code")),
|
|
455
|
+
ashCode,
|
|
456
|
+
ashName,
|
|
457
|
+
thickness: thickness != null && !isNaN(thickness) ? thickness : null,
|
|
458
|
+
});
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
if (startTime || endTime) {
|
|
462
|
+
ashForecasts.push({ startTime, endTime, areas });
|
|
463
|
+
}
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
// 噴煙情報
|
|
467
|
+
const plume = extractPlumeObservation(body);
|
|
468
|
+
// VolcanoInfoContent
|
|
469
|
+
const content = (0, telegram_parser_1.dig)(body, "VolcanoInfoContent");
|
|
470
|
+
const bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(content, "VolcanoActivity") || (0, telegram_parser_1.dig)(content, "VolcanoHeadline")));
|
|
471
|
+
return {
|
|
472
|
+
...base,
|
|
473
|
+
kind: "ashfall",
|
|
474
|
+
type: headType,
|
|
475
|
+
subKind: subKindMap[headType],
|
|
476
|
+
craterName: plume.craterName,
|
|
477
|
+
ashForecasts,
|
|
478
|
+
plumeHeight: plume.plumeHeight,
|
|
479
|
+
plumeDirection: plume.plumeDirection,
|
|
480
|
+
bodyText,
|
|
481
|
+
};
|
|
482
|
+
}
|
|
483
|
+
/** VZVO40 / VFVO51: 火山に関するお知らせ / 火山の状況に関する解説情報 */
|
|
484
|
+
function parseVolcanoText(report, headType, msg) {
|
|
485
|
+
const base = extractVolcanoBase(report, headType, msg);
|
|
486
|
+
const head = (0, telegram_parser_1.dig)(report, "Head");
|
|
487
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
488
|
+
// VZVO40 は Body > Text 直下にテキスト
|
|
489
|
+
// VFVO51 は Body > VolcanoInfoContent > VolcanoActivity にテキスト
|
|
490
|
+
let bodyText = "";
|
|
491
|
+
if (headType === "VZVO40") {
|
|
492
|
+
bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(body, "Text")));
|
|
493
|
+
}
|
|
494
|
+
else {
|
|
495
|
+
const content = (0, telegram_parser_1.dig)(body, "VolcanoInfoContent");
|
|
496
|
+
bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(content, "VolcanoActivity")));
|
|
497
|
+
}
|
|
498
|
+
// レベル情報 (VFVO51 のみ)
|
|
499
|
+
let alertLevelCode = null;
|
|
500
|
+
if (headType === "VFVO51") {
|
|
501
|
+
const headInfo = (0, telegram_parser_1.dig)(head, "Headline", "Information");
|
|
502
|
+
const headInfoList = Array.isArray(headInfo) ? headInfo : headInfo ? [headInfo] : [];
|
|
503
|
+
for (const info of headInfoList) {
|
|
504
|
+
const infoType = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(info, "@_type"));
|
|
505
|
+
if (infoType.includes("対象火山")) {
|
|
506
|
+
const items = (0, telegram_parser_1.dig)(info, "Item");
|
|
507
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
508
|
+
// 最も高いレベルを採用(複数火山のケース)
|
|
509
|
+
// レベルコード 11〜15 のみ比較対象とし、海上警報コード等は除外
|
|
510
|
+
for (const item of itemList) {
|
|
511
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
512
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
513
|
+
const code = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code"));
|
|
514
|
+
if (code && levelCodeToNumber(code) != null) {
|
|
515
|
+
if (!alertLevelCode || Number(code) > Number(alertLevelCode)) {
|
|
516
|
+
alertLevelCode = code;
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
const alertLevel = alertLevelCode ? levelCodeToNumber(alertLevelCode) : null;
|
|
524
|
+
// 臨時判定: タイトルに「臨時」を含むか InfoKind に「臨時」を含む
|
|
525
|
+
const infoKind = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(head, "InfoKind"));
|
|
526
|
+
const isExtraordinary = base.title.includes("臨時") || infoKind.includes("臨時");
|
|
527
|
+
// NextAdvisory
|
|
528
|
+
const nextAdvisory = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(body, "NextAdvisory"));
|
|
529
|
+
return {
|
|
530
|
+
...base,
|
|
531
|
+
kind: "text",
|
|
532
|
+
type: headType,
|
|
533
|
+
alertLevel,
|
|
534
|
+
alertLevelCode,
|
|
535
|
+
isExtraordinary,
|
|
536
|
+
bodyText,
|
|
537
|
+
nextAdvisory: nextAdvisory ? nextAdvisory.trim() : null,
|
|
538
|
+
};
|
|
539
|
+
}
|
|
540
|
+
/** VFVO60: 推定噴煙流向報 */
|
|
541
|
+
function parseVolcanoPlume(report, msg) {
|
|
542
|
+
const base = extractVolcanoBase(report, "VFVO60", msg);
|
|
543
|
+
const body = (0, telegram_parser_1.dig)(report, "Body");
|
|
544
|
+
// Kind (現象コード)
|
|
545
|
+
let phenomenonCode = "";
|
|
546
|
+
const volcanoInfos = (0, telegram_parser_1.dig)(body, "VolcanoInfo");
|
|
547
|
+
const infos = Array.isArray(volcanoInfos) ? volcanoInfos : volcanoInfos ? [volcanoInfos] : [];
|
|
548
|
+
for (const vi of infos) {
|
|
549
|
+
const items = (0, telegram_parser_1.dig)(vi, "Item");
|
|
550
|
+
const itemList = Array.isArray(items) ? items : items ? [items] : [];
|
|
551
|
+
for (const item of itemList) {
|
|
552
|
+
const kind = (0, telegram_parser_1.dig)(item, "Kind");
|
|
553
|
+
const kindObj = Array.isArray(kind) ? kind[0] : kind;
|
|
554
|
+
const code = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(kindObj, "Code"));
|
|
555
|
+
if (code) {
|
|
556
|
+
phenomenonCode = code;
|
|
557
|
+
break;
|
|
558
|
+
}
|
|
559
|
+
}
|
|
560
|
+
if (phenomenonCode)
|
|
561
|
+
break;
|
|
562
|
+
}
|
|
563
|
+
const plume = extractPlumeObservation(body);
|
|
564
|
+
// WindAboveCrater
|
|
565
|
+
const windProfile = [];
|
|
566
|
+
const obs = (0, telegram_parser_1.dig)(body, "VolcanoObservation");
|
|
567
|
+
const windAbove = (0, telegram_parser_1.dig)(obs, "WindAboveCrater");
|
|
568
|
+
if (windAbove) {
|
|
569
|
+
const elements = (0, telegram_parser_1.dig)(windAbove, "WindAboveCraterElements");
|
|
570
|
+
const elemArr = Array.isArray(elements) ? elements : elements ? [elements] : [];
|
|
571
|
+
for (const elem of elemArr) {
|
|
572
|
+
const altitude = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(elem, "jmx_eb:WindHeightAboveSeaLevel", "@_description") ||
|
|
573
|
+
(0, telegram_parser_1.dig)(elem, "WindHeightAboveSeaLevel", "@_description") ||
|
|
574
|
+
(0, telegram_parser_1.dig)(elem, "@_description"));
|
|
575
|
+
const degreeVal = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(elem, "jmx_eb:WindDegree", "#text") ||
|
|
576
|
+
(0, telegram_parser_1.dig)(elem, "jmx_eb:WindDegree") ||
|
|
577
|
+
(0, telegram_parser_1.dig)(elem, "WindDegree", "#text") ||
|
|
578
|
+
(0, telegram_parser_1.dig)(elem, "WindDegree"));
|
|
579
|
+
const speedVal = (0, telegram_parser_1.str)((0, telegram_parser_1.dig)(elem, "jmx_eb:WindSpeed", "#text") ||
|
|
580
|
+
(0, telegram_parser_1.dig)(elem, "jmx_eb:WindSpeed") ||
|
|
581
|
+
(0, telegram_parser_1.dig)(elem, "WindSpeed", "#text") ||
|
|
582
|
+
(0, telegram_parser_1.dig)(elem, "WindSpeed"));
|
|
583
|
+
const degree = degreeVal ? parseInt(degreeVal, 10) : null;
|
|
584
|
+
const speed = speedVal ? parseInt(speedVal, 10) : null;
|
|
585
|
+
windProfile.push({
|
|
586
|
+
altitude: altitude || "",
|
|
587
|
+
degree: degree != null && !isNaN(degree) ? degree : null,
|
|
588
|
+
speed: speed != null && !isNaN(speed) ? speed : null,
|
|
589
|
+
});
|
|
590
|
+
}
|
|
591
|
+
}
|
|
592
|
+
const bodyText = normalizeVolcanoBodyText((0, telegram_parser_1.str)((0, telegram_parser_1.dig)(body, "Text")));
|
|
593
|
+
return {
|
|
594
|
+
...base,
|
|
595
|
+
kind: "plume",
|
|
596
|
+
type: "VFVO60",
|
|
597
|
+
phenomenonCode,
|
|
598
|
+
craterName: plume.craterName,
|
|
599
|
+
plumeHeight: plume.plumeHeight,
|
|
600
|
+
plumeDirection: plume.plumeDirection,
|
|
601
|
+
windProfile,
|
|
602
|
+
bodyText,
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
// ── 公開 API ──
|
|
606
|
+
/** 火山電文をパース (全10種類の head.type に対応) */
|
|
607
|
+
function parseVolcanoTelegram(msg) {
|
|
608
|
+
try {
|
|
609
|
+
const xmlStr = (0, telegram_parser_1.decodeBody)(msg);
|
|
610
|
+
const parsed = (0, telegram_parser_1.parseXml)(xmlStr);
|
|
611
|
+
const report = findReport(parsed);
|
|
612
|
+
if (!report) {
|
|
613
|
+
log.debug("Report ノードが見つかりません (火山電文)");
|
|
614
|
+
return null;
|
|
615
|
+
}
|
|
616
|
+
const headType = msg.head.type;
|
|
617
|
+
switch (headType) {
|
|
618
|
+
case "VFVO50":
|
|
619
|
+
return parseVolcanoAlert(report, "VFVO50", msg);
|
|
620
|
+
case "VFSVii":
|
|
621
|
+
return parseVolcanoAlert(report, "VFSVii", msg);
|
|
622
|
+
case "VFVO52":
|
|
623
|
+
return parseVolcanoEruption(report, "VFVO52", msg);
|
|
624
|
+
case "VFVO56":
|
|
625
|
+
return parseVolcanoEruption(report, "VFVO56", msg);
|
|
626
|
+
case "VFVO53":
|
|
627
|
+
return parseVolcanoAshfall(report, "VFVO53", msg);
|
|
628
|
+
case "VFVO54":
|
|
629
|
+
return parseVolcanoAshfall(report, "VFVO54", msg);
|
|
630
|
+
case "VFVO55":
|
|
631
|
+
return parseVolcanoAshfall(report, "VFVO55", msg);
|
|
632
|
+
case "VZVO40":
|
|
633
|
+
return parseVolcanoText(report, "VZVO40", msg);
|
|
634
|
+
case "VFVO51":
|
|
635
|
+
return parseVolcanoText(report, "VFVO51", msg);
|
|
636
|
+
case "VFVO60":
|
|
637
|
+
return parseVolcanoPlume(report, msg);
|
|
638
|
+
default:
|
|
639
|
+
log.debug(`未対応の火山電文タイプ: ${headType}`);
|
|
640
|
+
return null;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
643
|
+
catch (err) {
|
|
644
|
+
log.error(`火山電文パースエラー: ${err instanceof Error ? err.message : err}`);
|
|
645
|
+
return null;
|
|
646
|
+
}
|
|
647
|
+
}
|