@sayue_ltr/fleq 1.50.1 → 2.0.0
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 +174 -0
- package/README.md +42 -6
- package/dist/config.js +37 -4
- package/dist/dmdata/rest-client.js +58 -3
- package/dist/dmdata/telegram-parser.js +115 -64
- package/dist/dmdata/ws-client.js +49 -18
- package/dist/engine/cli/cli-run.js +88 -3
- package/dist/engine/cli/cli.js +12 -0
- package/dist/engine/eew/eew-tracker.js +41 -15
- package/dist/engine/filter/compile-filter.js +21 -0
- package/dist/engine/filter/compiler.js +188 -0
- package/dist/engine/filter/errors.js +41 -0
- package/dist/engine/filter/field-registry.js +78 -0
- package/dist/engine/filter/index.js +15 -0
- package/dist/engine/filter/parser.js +137 -0
- package/dist/engine/filter/rank-maps.js +34 -0
- package/dist/engine/filter/tokenizer.js +121 -0
- package/dist/engine/filter/type-checker.js +104 -0
- package/dist/engine/filter/types.js +2 -0
- package/dist/engine/filter-template/pipeline-controller.js +73 -0
- package/dist/engine/filter-template/pipeline.js +16 -0
- package/dist/engine/messages/display-callbacks.js +7 -0
- package/dist/engine/messages/message-router.js +114 -182
- package/dist/engine/messages/summary-tracker.js +106 -0
- package/dist/engine/messages/telegram-stats.js +103 -0
- package/dist/engine/messages/volcano-route-handler.js +122 -0
- package/dist/engine/monitor/monitor.js +52 -4
- package/dist/engine/monitor/shutdown.js +1 -0
- package/dist/engine/notification/notifier.js +21 -4
- package/dist/engine/notification/sound-player.js +398 -36
- package/dist/engine/presentation/diff-store.js +158 -0
- package/dist/engine/presentation/diff-types.js +2 -0
- package/dist/engine/presentation/events/from-earthquake.js +53 -0
- package/dist/engine/presentation/events/from-eew.js +72 -0
- package/dist/engine/presentation/events/from-lg-observation.js +58 -0
- package/dist/engine/presentation/events/from-nankai-trough.js +39 -0
- package/dist/engine/presentation/events/from-raw.js +35 -0
- package/dist/engine/presentation/events/from-seismic-text.js +37 -0
- package/dist/engine/presentation/events/from-tsunami.js +51 -0
- package/dist/engine/presentation/events/from-volcano.js +88 -0
- package/dist/engine/presentation/events/to-presentation-event.js +32 -0
- package/dist/engine/presentation/level-helpers.js +118 -0
- package/dist/engine/presentation/processors/process-earthquake.js +36 -0
- package/dist/engine/presentation/processors/process-eew.js +105 -0
- package/dist/engine/presentation/processors/process-lg-observation.js +30 -0
- package/dist/engine/presentation/processors/process-message.js +53 -0
- package/dist/engine/presentation/processors/process-nankai-trough.js +30 -0
- package/dist/engine/presentation/processors/process-raw.js +22 -0
- package/dist/engine/presentation/processors/process-seismic-text.js +30 -0
- package/dist/engine/presentation/processors/process-tsunami.js +42 -0
- package/dist/engine/presentation/processors/process-volcano.js +41 -0
- package/dist/engine/presentation/types.js +2 -0
- package/dist/engine/startup/config-resolver.js +2 -0
- package/dist/engine/template/compile-template.js +18 -0
- package/dist/engine/template/compiler.js +105 -0
- package/dist/engine/template/field-accessor.js +31 -0
- package/dist/engine/template/filters.js +100 -0
- package/dist/engine/template/index.js +5 -0
- package/dist/engine/template/parser.js +185 -0
- package/dist/engine/template/tokenizer.js +96 -0
- package/dist/engine/template/types.js +2 -0
- package/dist/types.js +3 -2
- package/dist/ui/display-adapter.js +60 -0
- package/dist/ui/earthquake-formatter.js +22 -5
- package/dist/ui/eew-formatter.js +25 -10
- package/dist/ui/formatter.js +116 -32
- package/dist/ui/minimap/grid-layout.js +91 -0
- package/dist/ui/minimap/index.js +16 -0
- package/dist/ui/minimap/minimap-renderer.js +277 -0
- package/dist/ui/minimap/pref-mapping.js +82 -0
- package/dist/ui/minimap/types.js +2 -0
- package/dist/ui/night-overlay.js +56 -0
- package/dist/ui/repl-handlers/command-definitions.js +327 -0
- package/dist/ui/repl-handlers/index.js +11 -0
- package/dist/ui/repl-handlers/info-handlers.js +633 -0
- package/dist/ui/repl-handlers/operation-handlers.js +233 -0
- package/dist/ui/repl-handlers/settings-handlers.js +927 -0
- package/dist/ui/repl-handlers/types.js +10 -0
- package/dist/ui/repl.js +81 -1752
- package/dist/ui/statistics-formatter.js +258 -0
- package/dist/ui/status-line.js +80 -0
- package/dist/ui/summary/index.js +5 -0
- package/dist/ui/summary/summary-line.js +18 -0
- package/dist/ui/summary/summary-model.js +31 -0
- package/dist/ui/summary/token-builders.js +317 -0
- package/dist/ui/summary/types.js +2 -0
- package/dist/ui/summary/width-fit.js +41 -0
- package/dist/ui/summary-interval-formatter.js +72 -0
- package/dist/ui/test-samples.js +6 -0
- package/dist/ui/theme.js +43 -5
- package/dist/ui/tip-shuffler.js +81 -0
- package/dist/ui/volcano-formatter.js +15 -13
- package/dist/ui/waiting-tips-eew.js +63 -0
- package/dist/ui/waiting-tips-info-systems.js +81 -0
- package/dist/ui/waiting-tips-seismology.js +97 -0
- package/dist/ui/waiting-tips-tsunami.js +72 -0
- package/dist/ui/waiting-tips-weather.js +189 -0
- package/dist/ui/waiting-tips.js +420 -249
- package/package.json +1 -1
package/dist/ui/formatter.js
CHANGED
|
@@ -61,10 +61,13 @@ exports.visualPadEnd = visualPadEnd;
|
|
|
61
61
|
exports.renderFrameTable = renderFrameTable;
|
|
62
62
|
exports.wrapFrameLines = wrapFrameLines;
|
|
63
63
|
exports.wrapTextLines = wrapTextLines;
|
|
64
|
+
exports.renderGroupedItemList = renderGroupedItemList;
|
|
65
|
+
exports.renderSimpleNameList = renderSimpleNameList;
|
|
64
66
|
exports.collectHighlightSpans = collectHighlightSpans;
|
|
65
67
|
exports.highlightAndWrap = highlightAndWrap;
|
|
66
68
|
exports.formatTimestamp = formatTimestamp;
|
|
67
69
|
exports.formatElapsedTime = formatElapsedTime;
|
|
70
|
+
exports.formatUptime = formatUptime;
|
|
68
71
|
exports.intensityColor = intensityColor;
|
|
69
72
|
exports.lgIntensityColor = lgIntensityColor;
|
|
70
73
|
exports.lgIntToNumeric = lgIntToNumeric;
|
|
@@ -311,30 +314,28 @@ function sanitizeForTerminal(str) {
|
|
|
311
314
|
// eslint-disable-next-line no-control-regex
|
|
312
315
|
return stripAnsi(str).replace(/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F]/g, "");
|
|
313
316
|
}
|
|
317
|
+
/** コードポイントが CJK 等の全角文字かどうかを判定する */
|
|
318
|
+
function isWideChar(cp) {
|
|
319
|
+
return ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
320
|
+
(cp >= 0x2E80 && cp <= 0x303E) || // CJK部首・記号
|
|
321
|
+
(cp >= 0x3041 && cp <= 0x33BF) || // ひらがな・カタカナ・CJK互換
|
|
322
|
+
(cp >= 0x3400 && cp <= 0x4DBF) || // CJK統合漢字拡張A
|
|
323
|
+
(cp >= 0x4E00 && cp <= 0xA4CF) || // CJK統合漢字 + Yi
|
|
324
|
+
(cp >= 0xAC00 && cp <= 0xD7AF) || // Hangul Syllables
|
|
325
|
+
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK互換漢字
|
|
326
|
+
(cp >= 0xFE30 && cp <= 0xFE4F) || // CJK互換形
|
|
327
|
+
(cp >= 0xFF01 && cp <= 0xFF60) || // 全角ASCII・半角カタカナ
|
|
328
|
+
(cp >= 0xFFE0 && cp <= 0xFFE6) || // 全角記号
|
|
329
|
+
(cp >= 0x20000 && cp <= 0x2FA1F) // CJK統合漢字拡張B-F
|
|
330
|
+
);
|
|
331
|
+
}
|
|
314
332
|
/** 文字列の視覚的な幅を計算(全角文字を2として数える) */
|
|
315
333
|
function visualWidth(str) {
|
|
316
334
|
const plain = stripAnsi(str);
|
|
317
335
|
let width = 0;
|
|
318
336
|
for (const ch of plain) {
|
|
319
337
|
const cp = ch.codePointAt(0) ?? 0;
|
|
320
|
-
|
|
321
|
-
if ((cp >= 0x1100 && cp <= 0x115F) || // Hangul Jamo
|
|
322
|
-
(cp >= 0x2E80 && cp <= 0x303E) || // CJK部首・記号
|
|
323
|
-
(cp >= 0x3041 && cp <= 0x33BF) || // ひらがな・カタカナ・CJK互換
|
|
324
|
-
(cp >= 0x3400 && cp <= 0x4DBF) || // CJK統合漢字拡張A
|
|
325
|
-
(cp >= 0x4E00 && cp <= 0xA4CF) || // CJK統合漢字 + Yi
|
|
326
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) || // Hangul Syllables
|
|
327
|
-
(cp >= 0xF900 && cp <= 0xFAFF) || // CJK互換漢字
|
|
328
|
-
(cp >= 0xFE30 && cp <= 0xFE4F) || // CJK互換形
|
|
329
|
-
(cp >= 0xFF01 && cp <= 0xFF60) || // 全角ASCII・半角カタカナ
|
|
330
|
-
(cp >= 0xFFE0 && cp <= 0xFFE6) || // 全角記号
|
|
331
|
-
(cp >= 0x20000 && cp <= 0x2FA1F) // CJK統合漢字拡張B-F
|
|
332
|
-
) {
|
|
333
|
-
width += 2;
|
|
334
|
-
}
|
|
335
|
-
else {
|
|
336
|
-
width += 1;
|
|
337
|
-
}
|
|
338
|
+
width += isWideChar(cp) ? 2 : 1;
|
|
338
339
|
}
|
|
339
340
|
return width;
|
|
340
341
|
}
|
|
@@ -467,20 +468,7 @@ function wrapTextLines(text, maxWidth) {
|
|
|
467
468
|
let currentWidth = 0;
|
|
468
469
|
for (const ch of text) {
|
|
469
470
|
const cp = ch.codePointAt(0) ?? 0;
|
|
470
|
-
|
|
471
|
-
if ((cp >= 0x1100 && cp <= 0x115F) ||
|
|
472
|
-
(cp >= 0x2E80 && cp <= 0x303E) ||
|
|
473
|
-
(cp >= 0x3041 && cp <= 0x33BF) ||
|
|
474
|
-
(cp >= 0x3400 && cp <= 0x4DBF) ||
|
|
475
|
-
(cp >= 0x4E00 && cp <= 0xA4CF) ||
|
|
476
|
-
(cp >= 0xAC00 && cp <= 0xD7AF) ||
|
|
477
|
-
(cp >= 0xF900 && cp <= 0xFAFF) ||
|
|
478
|
-
(cp >= 0xFE30 && cp <= 0xFE4F) ||
|
|
479
|
-
(cp >= 0xFF01 && cp <= 0xFF60) ||
|
|
480
|
-
(cp >= 0xFFE0 && cp <= 0xFFE6) ||
|
|
481
|
-
(cp >= 0x20000 && cp <= 0x2FA1F)) {
|
|
482
|
-
charWidth = 2;
|
|
483
|
-
}
|
|
471
|
+
const charWidth = isWideChar(cp) ? 2 : 1;
|
|
484
472
|
if (currentWidth + charWidth > maxWidth) {
|
|
485
473
|
lines.push(currentLine);
|
|
486
474
|
currentLine = ch;
|
|
@@ -496,6 +484,54 @@ function wrapTextLines(text, maxWidth) {
|
|
|
496
484
|
}
|
|
497
485
|
return lines;
|
|
498
486
|
}
|
|
487
|
+
/**
|
|
488
|
+
* 震度別・階級別にグループ化された項目リストを描画する。
|
|
489
|
+
* 各グループは `prefix + item, item, ...` 形式で、2行目以降は prefix 幅分のハンギングインデント。
|
|
490
|
+
* badges は primary の直後に連結される(先頭スペースは呼び出し側で付与済み想定)。
|
|
491
|
+
*/
|
|
492
|
+
function renderGroupedItemList(options) {
|
|
493
|
+
const { level, width, groups, itemSeparator = ", " } = options;
|
|
494
|
+
const out = options.buf
|
|
495
|
+
? (line) => options.buf.push(line)
|
|
496
|
+
: (line) => console.log(line);
|
|
497
|
+
for (const group of groups) {
|
|
498
|
+
if (group.items.length === 0)
|
|
499
|
+
continue;
|
|
500
|
+
const prefix = group.prefix;
|
|
501
|
+
const indentWidth = visualWidth(stripAnsi(prefix));
|
|
502
|
+
// 各項目を "primary + badges" 形式の文字列に組み立て
|
|
503
|
+
const itemTexts = group.items.map((item) => {
|
|
504
|
+
let text = item.primary;
|
|
505
|
+
if (item.badges != null && item.badges.length > 0) {
|
|
506
|
+
text += item.badges.join("");
|
|
507
|
+
}
|
|
508
|
+
return text;
|
|
509
|
+
});
|
|
510
|
+
const content = prefix + itemTexts.join(itemSeparator);
|
|
511
|
+
for (const line of wrapFrameLines(level, content, width, indentWidth)) {
|
|
512
|
+
out(line);
|
|
513
|
+
}
|
|
514
|
+
}
|
|
515
|
+
}
|
|
516
|
+
/**
|
|
517
|
+
* 単純な名前リストを描画する。
|
|
518
|
+
* `label + name, name, ...` 形式。2行目以降は label 幅分のハンギングインデント。
|
|
519
|
+
* label 指定時は ` label ` の形式(先頭・末尾にスペース)で gray 表示される。
|
|
520
|
+
*/
|
|
521
|
+
function renderSimpleNameList(options) {
|
|
522
|
+
const { level, width, items, label, separator = ", " } = options;
|
|
523
|
+
if (items.length === 0)
|
|
524
|
+
return;
|
|
525
|
+
const out = options.buf
|
|
526
|
+
? (line) => options.buf.push(line)
|
|
527
|
+
: (line) => console.log(line);
|
|
528
|
+
const prefix = label ? ` ${chalk_1.default.gray(label)} ` : "";
|
|
529
|
+
const indentWidth = label ? visualWidth(stripAnsi(prefix)) : 0;
|
|
530
|
+
const content = prefix + items.join(separator);
|
|
531
|
+
for (const line of wrapFrameLines(level, content, width, indentWidth)) {
|
|
532
|
+
out(line);
|
|
533
|
+
}
|
|
534
|
+
}
|
|
499
535
|
/** 元の行からマッチspanを収集する */
|
|
500
536
|
function collectHighlightSpans(line, rules) {
|
|
501
537
|
const spans = [];
|
|
@@ -584,6 +620,54 @@ function formatElapsedTime(ms) {
|
|
|
584
620
|
const ss = totalSec % 60;
|
|
585
621
|
return `${String(hh).padStart(2, "0")}:${String(mm).padStart(2, "0")}:${String(ss).padStart(2, "0")}`;
|
|
586
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* 稼働時間を "DDD:HH:MM:SS" 形式に整形する。
|
|
625
|
+
* 未使用の上位ゼロ桁は dim (gray) 表示にして画面ヤケを軽減する。
|
|
626
|
+
* 日数が 3 桁を超える場合は桁数が増える。
|
|
627
|
+
*/
|
|
628
|
+
function formatUptime(ms) {
|
|
629
|
+
const safeMs = Math.max(0, ms);
|
|
630
|
+
const totalSec = Math.floor(safeMs / 1000);
|
|
631
|
+
const dd = Math.floor(totalSec / 86400);
|
|
632
|
+
const hh = Math.floor((totalSec % 86400) / 3600);
|
|
633
|
+
const mm = Math.floor((totalSec % 3600) / 60);
|
|
634
|
+
const ss = totalSec % 60;
|
|
635
|
+
const ddStr = String(dd).padStart(3, "0");
|
|
636
|
+
const hhStr = String(hh).padStart(2, "0");
|
|
637
|
+
const mmStr = String(mm).padStart(2, "0");
|
|
638
|
+
const ssStr = String(ss).padStart(2, "0");
|
|
639
|
+
const raw = `${ddStr}:${hhStr}:${mmStr}:${ssStr}`;
|
|
640
|
+
// セグメント境界: DDD(0-2) : HH(4-5) : MM(7-8) : SS(10-11)
|
|
641
|
+
// segStarts は dd < 1000 (3桁) の通常ケース用。
|
|
642
|
+
// dd >= 1000 では firstNonZero=0 → activeSegStart=0 で全体 white になるため問題なし。
|
|
643
|
+
const segStarts = [0, 4, 7, 10];
|
|
644
|
+
// 先頭から連続するゼロと区切り文字をスキャン
|
|
645
|
+
let firstNonZero = -1;
|
|
646
|
+
for (let i = 0; i < raw.length; i++) {
|
|
647
|
+
if (raw[i] !== "0" && raw[i] !== ":") {
|
|
648
|
+
firstNonZero = i;
|
|
649
|
+
break;
|
|
650
|
+
}
|
|
651
|
+
}
|
|
652
|
+
// dim 境界を決定
|
|
653
|
+
// - 日部分 (index 0-2): 文字レベルで先頭ゼロをグレーアウト
|
|
654
|
+
// - 時刻部分 (HH/MM/SS): セグメント境界でグレーアウト
|
|
655
|
+
// - 全桁ゼロ: SS セグメントから通常表示
|
|
656
|
+
let dimEnd;
|
|
657
|
+
if (firstNonZero === -1) {
|
|
658
|
+
dimEnd = segStarts[segStarts.length - 1];
|
|
659
|
+
}
|
|
660
|
+
else if (firstNonZero <= 2) {
|
|
661
|
+
dimEnd = firstNonZero;
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
dimEnd = segStarts.filter((s) => s <= firstNonZero).pop();
|
|
665
|
+
}
|
|
666
|
+
if (dimEnd === 0) {
|
|
667
|
+
return chalk_1.default.white(raw);
|
|
668
|
+
}
|
|
669
|
+
return chalk_1.default.gray(raw.slice(0, dimEnd)) + chalk_1.default.white(raw.slice(dimEnd));
|
|
670
|
+
}
|
|
587
671
|
/** 区切り線 (後方互換用、新コードではフレーム関数を使用) */
|
|
588
672
|
function separator(char = "─", len = 60) {
|
|
589
673
|
return chalk_1.default.gray(char.repeat(len));
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS = exports.GRID_COLS = exports.GRID_ROWS = void 0;
|
|
4
|
+
exports.GRID_ROWS = 12;
|
|
5
|
+
exports.GRID_COLS = 13;
|
|
6
|
+
/** Helper to create a placement */
|
|
7
|
+
function p(id, cells, anchor) {
|
|
8
|
+
return {
|
|
9
|
+
id,
|
|
10
|
+
cells: cells.map(([row, col]) => ({ row, col })),
|
|
11
|
+
anchor: { row: anchor[0], col: anchor[1] },
|
|
12
|
+
};
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* All 47 prefecture placements on the 12×13 grid.
|
|
16
|
+
*
|
|
17
|
+
* Grid layout (from spec):
|
|
18
|
+
* Col: 0 1 2 3 4 5 6 7 8 9 10 11 12
|
|
19
|
+
* r00: HK *HK HK HK
|
|
20
|
+
* r01: HK HK HK HK *AO AO
|
|
21
|
+
* r02: AK IT
|
|
22
|
+
* r03: YG MG
|
|
23
|
+
* r04: OK *FS FS
|
|
24
|
+
* r05: *IS *NI NI TC IB
|
|
25
|
+
* r06: TM IS *NA GI GU ST
|
|
26
|
+
* r07: SM TT *HG KY *FI FI NA YN TY *CB
|
|
27
|
+
* r08: YA HS OY HG SI *ME AI SZ KN CB
|
|
28
|
+
* r09: NS SG FO OS NR ME
|
|
29
|
+
* r10: KU OI EH KA WA
|
|
30
|
+
* r11: KG MZ *KO KO TK
|
|
31
|
+
*/
|
|
32
|
+
exports.PREF_PLACEMENTS = [
|
|
33
|
+
// ── 北海道 (4×2) ──
|
|
34
|
+
p("HK", [[0, 3], [0, 4], [0, 5], [0, 6], [1, 3], [1, 4], [1, 5], [1, 6]], [0, 4]),
|
|
35
|
+
// ── 東北 ──
|
|
36
|
+
p("AO", [[1, 11], [1, 12]], [1, 11]),
|
|
37
|
+
p("AK", [[2, 11]], [2, 11]),
|
|
38
|
+
p("IT", [[2, 12]], [2, 12]),
|
|
39
|
+
p("YG", [[3, 11]], [3, 11]),
|
|
40
|
+
p("MG", [[3, 12]], [3, 12]),
|
|
41
|
+
p("FS", [[4, 11], [4, 12]], [4, 11]),
|
|
42
|
+
// ── 関東 ──
|
|
43
|
+
p("IB", [[5, 12]], [5, 12]),
|
|
44
|
+
p("TC", [[5, 11]], [5, 11]),
|
|
45
|
+
p("GU", [[6, 11]], [6, 11]),
|
|
46
|
+
p("ST", [[6, 12]], [6, 12]),
|
|
47
|
+
p("CB", [[7, 12], [8, 12]], [7, 12]),
|
|
48
|
+
p("TY", [[7, 11]], [7, 11]),
|
|
49
|
+
p("KN", [[8, 11]], [8, 11]),
|
|
50
|
+
// ── 中部 ──
|
|
51
|
+
p("NI", [[5, 9], [5, 10]], [5, 9]),
|
|
52
|
+
p("TM", [[6, 7]], [6, 7]),
|
|
53
|
+
p("IS", [[5, 8], [6, 8]], [5, 8]),
|
|
54
|
+
p("FI", [[7, 7], [7, 8]], [7, 7]),
|
|
55
|
+
p("YN", [[7, 10]], [7, 10]),
|
|
56
|
+
p("NA", [[6, 9], [7, 9]], [6, 9]),
|
|
57
|
+
p("GI", [[6, 10]], [6, 10]),
|
|
58
|
+
p("SZ", [[8, 10]], [8, 10]),
|
|
59
|
+
p("AI", [[8, 9]], [8, 9]),
|
|
60
|
+
// ── 近畿 ──
|
|
61
|
+
p("ME", [[8, 8], [9, 8]], [8, 8]),
|
|
62
|
+
p("SI", [[8, 6]], [8, 6]),
|
|
63
|
+
p("KY", [[7, 6]], [7, 6]),
|
|
64
|
+
p("OS", [[9, 6]], [9, 6]),
|
|
65
|
+
p("HG", [[7, 5], [8, 5]], [7, 5]),
|
|
66
|
+
p("NR", [[9, 7]], [9, 7]),
|
|
67
|
+
p("WA", [[10, 7]], [10, 7]),
|
|
68
|
+
// ── 中国 ──
|
|
69
|
+
p("TT", [[7, 4]], [7, 4]),
|
|
70
|
+
p("SM", [[7, 3]], [7, 3]),
|
|
71
|
+
p("OY", [[8, 4]], [8, 4]),
|
|
72
|
+
p("HS", [[8, 3]], [8, 3]),
|
|
73
|
+
p("YA", [[8, 2]], [8, 2]),
|
|
74
|
+
// ── 四国 ──
|
|
75
|
+
p("TK", [[11, 5]], [11, 5]),
|
|
76
|
+
p("KA", [[10, 5]], [10, 5]),
|
|
77
|
+
p("EH", [[10, 4]], [10, 4]),
|
|
78
|
+
p("KO", [[11, 3], [11, 4]], [11, 3]),
|
|
79
|
+
// ── 九州 ──
|
|
80
|
+
p("FO", [[9, 2]], [9, 2]),
|
|
81
|
+
p("SG", [[9, 1]], [9, 1]),
|
|
82
|
+
p("NS", [[9, 0]], [9, 0]),
|
|
83
|
+
p("KU", [[10, 1]], [10, 1]),
|
|
84
|
+
p("OI", [[10, 2]], [10, 2]),
|
|
85
|
+
p("MZ", [[11, 2]], [11, 2]),
|
|
86
|
+
p("KG", [[11, 1]], [11, 1]),
|
|
87
|
+
// ── 沖縄 ──
|
|
88
|
+
p("OK", [[4, 0]], [4, 0]),
|
|
89
|
+
];
|
|
90
|
+
/** All prefecture IDs in definition order */
|
|
91
|
+
exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS.map((pp) => pp.id);
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.renderMinimapForEvent = exports.shouldShowMinimap = exports.buildMinimapCells = exports.renderMinimap = exports.GRID_COLS = exports.GRID_ROWS = exports.ALL_PREF_IDS = exports.PREF_PLACEMENTS = exports.mapAreaToPref = exports.PREF_DEFS = void 0;
|
|
4
|
+
var pref_mapping_1 = require("./pref-mapping");
|
|
5
|
+
Object.defineProperty(exports, "PREF_DEFS", { enumerable: true, get: function () { return pref_mapping_1.PREF_DEFS; } });
|
|
6
|
+
Object.defineProperty(exports, "mapAreaToPref", { enumerable: true, get: function () { return pref_mapping_1.mapAreaToPref; } });
|
|
7
|
+
var grid_layout_1 = require("./grid-layout");
|
|
8
|
+
Object.defineProperty(exports, "PREF_PLACEMENTS", { enumerable: true, get: function () { return grid_layout_1.PREF_PLACEMENTS; } });
|
|
9
|
+
Object.defineProperty(exports, "ALL_PREF_IDS", { enumerable: true, get: function () { return grid_layout_1.ALL_PREF_IDS; } });
|
|
10
|
+
Object.defineProperty(exports, "GRID_ROWS", { enumerable: true, get: function () { return grid_layout_1.GRID_ROWS; } });
|
|
11
|
+
Object.defineProperty(exports, "GRID_COLS", { enumerable: true, get: function () { return grid_layout_1.GRID_COLS; } });
|
|
12
|
+
var minimap_renderer_1 = require("./minimap-renderer");
|
|
13
|
+
Object.defineProperty(exports, "renderMinimap", { enumerable: true, get: function () { return minimap_renderer_1.renderMinimap; } });
|
|
14
|
+
Object.defineProperty(exports, "buildMinimapCells", { enumerable: true, get: function () { return minimap_renderer_1.buildMinimapCells; } });
|
|
15
|
+
Object.defineProperty(exports, "shouldShowMinimap", { enumerable: true, get: function () { return minimap_renderer_1.shouldShowMinimap; } });
|
|
16
|
+
Object.defineProperty(exports, "renderMinimapForEvent", { enumerable: true, get: function () { return minimap_renderer_1.renderMinimapForEvent; } });
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.renderMinimap = renderMinimap;
|
|
7
|
+
exports.buildMinimapCells = buildMinimapCells;
|
|
8
|
+
exports.shouldShowMinimap = shouldShowMinimap;
|
|
9
|
+
exports.renderMinimapForEvent = renderMinimapForEvent;
|
|
10
|
+
const chalk_1 = __importDefault(require("chalk"));
|
|
11
|
+
const formatter_1 = require("../formatter");
|
|
12
|
+
const grid_layout_1 = require("./grid-layout");
|
|
13
|
+
const pref_mapping_1 = require("./pref-mapping");
|
|
14
|
+
// ── Constants ──
|
|
15
|
+
/** Width of a single cell including trailing space: "AA:xx " = 6 chars */
|
|
16
|
+
const CELL_WIDTH = 6;
|
|
17
|
+
/** Continuation marker for multi-cell prefectures */
|
|
18
|
+
const CONTINUATION = "·····";
|
|
19
|
+
// ── Legend ──
|
|
20
|
+
/** Intensity values in display order */
|
|
21
|
+
const INTENSITY_LEGEND = ["1", "2", "3", "4", "5-", "5+", "6-", "6+", "7"];
|
|
22
|
+
/** Tsunami abbreviations in display order */
|
|
23
|
+
const TSUNAMI_LEGEND = [
|
|
24
|
+
{ abbrev: "MJ", label: "MJ", color: chalk_1.default.redBright },
|
|
25
|
+
{ abbrev: "WN", label: "WN", color: chalk_1.default.red },
|
|
26
|
+
{ abbrev: "AD", label: "AD", color: chalk_1.default.yellow },
|
|
27
|
+
];
|
|
28
|
+
/**
|
|
29
|
+
* Build legend lines to overlay in the upper-left empty area.
|
|
30
|
+
* Returns an array of { row, text } for rows 0-3.
|
|
31
|
+
*/
|
|
32
|
+
function buildLegend() {
|
|
33
|
+
const lines = [];
|
|
34
|
+
// Row 0: "震度:" header + first 4 values
|
|
35
|
+
const intLine1 = INTENSITY_LEGEND.slice(0, 4)
|
|
36
|
+
.map((v) => (0, formatter_1.intensityColor)(v)(v.padEnd(2)))
|
|
37
|
+
.join(" ");
|
|
38
|
+
lines.push({ row: 0, text: `震度: ${intLine1}` });
|
|
39
|
+
// Row 1: next 4 values
|
|
40
|
+
const intLine2 = INTENSITY_LEGEND.slice(4, 8)
|
|
41
|
+
.map((v) => (0, formatter_1.intensityColor)(v)(v.padEnd(2)))
|
|
42
|
+
.join(" ");
|
|
43
|
+
lines.push({ row: 1, text: ` ${intLine2}` });
|
|
44
|
+
// Row 2: last value (7)
|
|
45
|
+
const intLine3 = (0, formatter_1.intensityColor)("7")("7 ");
|
|
46
|
+
lines.push({ row: 2, text: ` ${intLine3}` });
|
|
47
|
+
// Row 3: tsunami header + values
|
|
48
|
+
const tsunamiText = TSUNAMI_LEGEND.map((t) => t.color(t.label)).join(" ");
|
|
49
|
+
lines.push({ row: 3, text: `津波: ${tsunamiText}` });
|
|
50
|
+
return lines;
|
|
51
|
+
}
|
|
52
|
+
// ── ANSI strip helper ──
|
|
53
|
+
function stripAnsi(str) {
|
|
54
|
+
// eslint-disable-next-line no-control-regex
|
|
55
|
+
return str.replace(/\u001b\[[0-9;]*m/g, "");
|
|
56
|
+
}
|
|
57
|
+
// ── Rendering ──
|
|
58
|
+
/**
|
|
59
|
+
* Render the minimap from a set of cells.
|
|
60
|
+
* Returns 12 lines of text representing the ASCII minimap with legend overlay.
|
|
61
|
+
*/
|
|
62
|
+
function renderMinimap(cells) {
|
|
63
|
+
// Build cell lookup
|
|
64
|
+
const cellMap = new Map();
|
|
65
|
+
for (const cell of cells) {
|
|
66
|
+
cellMap.set(cell.prefId, cell);
|
|
67
|
+
}
|
|
68
|
+
// Build placement lookup: (row,col) -> { prefId, isAnchor }
|
|
69
|
+
const gridMap = new Map();
|
|
70
|
+
for (const placement of grid_layout_1.PREF_PLACEMENTS) {
|
|
71
|
+
for (const pos of placement.cells) {
|
|
72
|
+
const key = `${pos.row},${pos.col}`;
|
|
73
|
+
gridMap.set(key, {
|
|
74
|
+
prefId: placement.id,
|
|
75
|
+
isAnchor: pos.row === placement.anchor.row && pos.col === placement.anchor.col,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// Build legend
|
|
80
|
+
const legend = buildLegend();
|
|
81
|
+
const legendByRow = new Map();
|
|
82
|
+
for (const entry of legend) {
|
|
83
|
+
legendByRow.set(entry.row, entry.text);
|
|
84
|
+
}
|
|
85
|
+
// Render each row
|
|
86
|
+
const lines = [];
|
|
87
|
+
for (let row = 0; row < grid_layout_1.GRID_ROWS; row++) {
|
|
88
|
+
let line = "";
|
|
89
|
+
// Find the last occupied column in this row
|
|
90
|
+
let lastCol = -1;
|
|
91
|
+
for (let col = 0; col < grid_layout_1.GRID_COLS; col++) {
|
|
92
|
+
if (gridMap.has(`${row},${col}`))
|
|
93
|
+
lastCol = col;
|
|
94
|
+
}
|
|
95
|
+
for (let col = 0; col < grid_layout_1.GRID_COLS; col++) {
|
|
96
|
+
const key = `${row},${col}`;
|
|
97
|
+
const entry = gridMap.get(key);
|
|
98
|
+
if (entry == null) {
|
|
99
|
+
// Empty cell (ocean) — check if legend should be rendered here
|
|
100
|
+
if (legendByRow.has(row) && col === 0) {
|
|
101
|
+
const legendText = legendByRow.get(row);
|
|
102
|
+
line += legendText;
|
|
103
|
+
// Calculate how many columns the legend text spans
|
|
104
|
+
const legendVisualLen = stripAnsi(legendText).length;
|
|
105
|
+
const colsSpanned = Math.ceil(legendVisualLen / CELL_WIDTH);
|
|
106
|
+
col += colsSpanned - 1; // skip ahead (loop will increment)
|
|
107
|
+
continue;
|
|
108
|
+
}
|
|
109
|
+
if (col <= lastCol) {
|
|
110
|
+
line += " ".repeat(CELL_WIDTH);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
else {
|
|
114
|
+
const cell = cellMap.get(entry.prefId);
|
|
115
|
+
const content = cell?.content ?? "..";
|
|
116
|
+
const isMatch = content !== "..";
|
|
117
|
+
if (entry.isAnchor) {
|
|
118
|
+
// Anchor cell: show "AA:xx"
|
|
119
|
+
const padded = content.length >= 2 ? content.slice(0, 2) : content.padEnd(2);
|
|
120
|
+
const text = `${entry.prefId}:${padded}`;
|
|
121
|
+
if (isMatch && cell?.color) {
|
|
122
|
+
line += cell.color(text) + " ";
|
|
123
|
+
}
|
|
124
|
+
else {
|
|
125
|
+
line += chalk_1.default.dim(text) + " ";
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
else {
|
|
129
|
+
// Continuation cell: show "·····"
|
|
130
|
+
if (isMatch && cell?.color) {
|
|
131
|
+
line += cell.color(CONTINUATION) + " ";
|
|
132
|
+
}
|
|
133
|
+
else {
|
|
134
|
+
line += chalk_1.default.dim(CONTINUATION) + " ";
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
lines.push(line.trimEnd());
|
|
140
|
+
}
|
|
141
|
+
return lines;
|
|
142
|
+
}
|
|
143
|
+
// ── Building cells from PresentationEvent ──
|
|
144
|
+
/** Tsunami warning kind abbreviations */
|
|
145
|
+
function tsunamiKindAbbrev(kind) {
|
|
146
|
+
if (kind.includes("大津波"))
|
|
147
|
+
return "MJ";
|
|
148
|
+
if (kind.includes("津波警報"))
|
|
149
|
+
return "WN";
|
|
150
|
+
if (kind.includes("注意報"))
|
|
151
|
+
return "AD";
|
|
152
|
+
return "??";
|
|
153
|
+
}
|
|
154
|
+
/** Tsunami warning color by abbreviation */
|
|
155
|
+
function tsunamiColor(abbrev) {
|
|
156
|
+
switch (abbrev) {
|
|
157
|
+
case "MJ":
|
|
158
|
+
return chalk_1.default.redBright;
|
|
159
|
+
case "WN":
|
|
160
|
+
return chalk_1.default.red;
|
|
161
|
+
case "AD":
|
|
162
|
+
return chalk_1.default.yellow;
|
|
163
|
+
default:
|
|
164
|
+
return chalk_1.default.white;
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
/** Tsunami kind priority (higher = more severe) */
|
|
168
|
+
function tsunamiKindPriority(abbrev) {
|
|
169
|
+
switch (abbrev) {
|
|
170
|
+
case "MJ":
|
|
171
|
+
return 3;
|
|
172
|
+
case "WN":
|
|
173
|
+
return 2;
|
|
174
|
+
case "AD":
|
|
175
|
+
return 1;
|
|
176
|
+
default:
|
|
177
|
+
return 0;
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
/**
|
|
181
|
+
* Build MinimapCell[] from a PresentationEvent.
|
|
182
|
+
* Assigns prefecture-level data (max intensity, tsunami kind) for each matched area.
|
|
183
|
+
*/
|
|
184
|
+
function buildMinimapCells(event) {
|
|
185
|
+
const prefData = new Map();
|
|
186
|
+
if (event.domain === "earthquake" || event.domain === "lgObservation" || event.domain === "eew") {
|
|
187
|
+
for (const item of event.areaItems) {
|
|
188
|
+
const prefId = (0, pref_mapping_1.mapAreaToPref)(item.name);
|
|
189
|
+
if (prefId == null)
|
|
190
|
+
continue;
|
|
191
|
+
const maxInt = item.maxInt ?? "..";
|
|
192
|
+
const rank = maxInt !== ".." ? (0, formatter_1.intensityToNumeric)(maxInt) : -1;
|
|
193
|
+
const existing = prefData.get(prefId);
|
|
194
|
+
if (!existing || rank > existing.priority) {
|
|
195
|
+
prefData.set(prefId, {
|
|
196
|
+
content: maxInt,
|
|
197
|
+
color: maxInt !== ".." ? (0, formatter_1.intensityColor)(maxInt) : undefined,
|
|
198
|
+
priority: rank,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
else if (event.domain === "tsunami") {
|
|
204
|
+
for (const item of event.areaItems) {
|
|
205
|
+
const prefId = (0, pref_mapping_1.mapAreaToPref)(item.name);
|
|
206
|
+
if (prefId == null)
|
|
207
|
+
continue;
|
|
208
|
+
const kind = item.kind ?? "";
|
|
209
|
+
const abbrev = tsunamiKindAbbrev(kind);
|
|
210
|
+
const priority = tsunamiKindPriority(abbrev);
|
|
211
|
+
const existing = prefData.get(prefId);
|
|
212
|
+
if (!existing || priority > existing.priority) {
|
|
213
|
+
prefData.set(prefId, {
|
|
214
|
+
content: abbrev,
|
|
215
|
+
color: tsunamiColor(abbrev),
|
|
216
|
+
priority,
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
// Build cells for all prefectures
|
|
222
|
+
const cells = [];
|
|
223
|
+
for (const prefId of grid_layout_1.ALL_PREF_IDS) {
|
|
224
|
+
const data = prefData.get(prefId);
|
|
225
|
+
if (data) {
|
|
226
|
+
cells.push({ prefId, content: data.content, color: data.color });
|
|
227
|
+
}
|
|
228
|
+
else {
|
|
229
|
+
cells.push({ prefId, content: ".." });
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return cells;
|
|
233
|
+
}
|
|
234
|
+
// ── Display conditions ──
|
|
235
|
+
/**
|
|
236
|
+
* Determine whether the minimap should be shown for this event.
|
|
237
|
+
*/
|
|
238
|
+
function shouldShowMinimap(event) {
|
|
239
|
+
const termWidth = process.stdout.columns ?? 80;
|
|
240
|
+
if (termWidth < 80)
|
|
241
|
+
return false;
|
|
242
|
+
if (event.isCancellation)
|
|
243
|
+
return false;
|
|
244
|
+
switch (event.domain) {
|
|
245
|
+
case "earthquake": {
|
|
246
|
+
if (event.areaCount === 0)
|
|
247
|
+
return false;
|
|
248
|
+
const rank = event.maxIntRank ?? 0;
|
|
249
|
+
return rank >= 4 || event.areaCount >= 4;
|
|
250
|
+
}
|
|
251
|
+
case "eew":
|
|
252
|
+
return event.forecastAreaCount > 0;
|
|
253
|
+
case "tsunami":
|
|
254
|
+
if (event.forecastAreaCount === 0 && event.areaCount === 0)
|
|
255
|
+
return false;
|
|
256
|
+
return ["critical", "warning", "normal"].includes(event.frameLevel);
|
|
257
|
+
case "lgObservation": {
|
|
258
|
+
if (event.areaCount === 0 && event.observationCount === 0)
|
|
259
|
+
return false;
|
|
260
|
+
const rank = event.maxIntRank ?? 0;
|
|
261
|
+
const count = event.observationCount > 0 ? event.observationCount : event.areaCount;
|
|
262
|
+
return rank >= 4 || count >= 4;
|
|
263
|
+
}
|
|
264
|
+
default:
|
|
265
|
+
return false;
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
/**
|
|
269
|
+
* Public API: render the minimap for a PresentationEvent.
|
|
270
|
+
* Returns the minimap lines, or null if the minimap should not be shown.
|
|
271
|
+
*/
|
|
272
|
+
function renderMinimapForEvent(event) {
|
|
273
|
+
if (!shouldShowMinimap(event))
|
|
274
|
+
return null;
|
|
275
|
+
const cells = buildMinimapCells(event);
|
|
276
|
+
return renderMinimap(cells);
|
|
277
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.PREF_DEFS = void 0;
|
|
4
|
+
exports.mapAreaToPref = mapAreaToPref;
|
|
5
|
+
/** All 47 prefecture definitions with match patterns */
|
|
6
|
+
exports.PREF_DEFS = [
|
|
7
|
+
{ id: "HK", name: "北海道", patterns: ["北海道"] },
|
|
8
|
+
{ id: "AO", name: "青森県", patterns: ["青森"] },
|
|
9
|
+
{ id: "IT", name: "岩手県", patterns: ["岩手"] },
|
|
10
|
+
{ id: "MG", name: "宮城県", patterns: ["宮城"] },
|
|
11
|
+
{ id: "AK", name: "秋田県", patterns: ["秋田"] },
|
|
12
|
+
{ id: "YG", name: "山形県", patterns: ["山形"] },
|
|
13
|
+
{ id: "FS", name: "福島県", patterns: ["福島"] },
|
|
14
|
+
{ id: "IB", name: "茨城県", patterns: ["茨城"] },
|
|
15
|
+
{ id: "TC", name: "栃木県", patterns: ["栃木"] },
|
|
16
|
+
{ id: "GU", name: "群馬県", patterns: ["群馬"] },
|
|
17
|
+
{ id: "ST", name: "埼玉県", patterns: ["埼玉"] },
|
|
18
|
+
{ id: "CB", name: "千葉県", patterns: ["千葉"] },
|
|
19
|
+
{ id: "TY", name: "東京都", patterns: ["東京島しょ", "小笠原", "東京"] },
|
|
20
|
+
{ id: "KN", name: "神奈川県", patterns: ["神奈川"] },
|
|
21
|
+
{ id: "NI", name: "新潟県", patterns: ["新潟"] },
|
|
22
|
+
{ id: "TM", name: "富山県", patterns: ["富山"] },
|
|
23
|
+
{ id: "IS", name: "石川県", patterns: ["石川"] },
|
|
24
|
+
{ id: "FI", name: "福井県", patterns: ["福井"] },
|
|
25
|
+
{ id: "YN", name: "山梨県", patterns: ["山梨"] },
|
|
26
|
+
{ id: "NA", name: "長野県", patterns: ["長野"] },
|
|
27
|
+
{ id: "GI", name: "岐阜県", patterns: ["岐阜"] },
|
|
28
|
+
{ id: "SZ", name: "静岡県", patterns: ["静岡"] },
|
|
29
|
+
{ id: "AI", name: "愛知県", patterns: ["愛知"] },
|
|
30
|
+
{ id: "ME", name: "三重県", patterns: ["三重"] },
|
|
31
|
+
{ id: "SI", name: "滋賀県", patterns: ["滋賀"] },
|
|
32
|
+
{ id: "KY", name: "京都府", patterns: ["京都"] },
|
|
33
|
+
{ id: "OS", name: "大阪府", patterns: ["大阪"] },
|
|
34
|
+
{ id: "HG", name: "兵庫県", patterns: ["兵庫"] },
|
|
35
|
+
{ id: "NR", name: "奈良県", patterns: ["奈良"] },
|
|
36
|
+
{ id: "WA", name: "和歌山県", patterns: ["和歌山"] },
|
|
37
|
+
{ id: "TT", name: "鳥取県", patterns: ["鳥取"] },
|
|
38
|
+
{ id: "SM", name: "島根県", patterns: ["島根"] },
|
|
39
|
+
{ id: "OY", name: "岡山県", patterns: ["岡山"] },
|
|
40
|
+
{ id: "HS", name: "広島県", patterns: ["広島"] },
|
|
41
|
+
{ id: "YA", name: "山口県", patterns: ["山口"] },
|
|
42
|
+
{ id: "TK", name: "徳島県", patterns: ["徳島"] },
|
|
43
|
+
{ id: "KA", name: "香川県", patterns: ["香川"] },
|
|
44
|
+
{ id: "EH", name: "愛媛県", patterns: ["愛媛"] },
|
|
45
|
+
{ id: "KO", name: "高知県", patterns: ["高知"] },
|
|
46
|
+
{ id: "FO", name: "福岡県", patterns: ["福岡"] },
|
|
47
|
+
{ id: "SG", name: "佐賀県", patterns: ["佐賀"] },
|
|
48
|
+
{ id: "NS", name: "長崎県", patterns: ["長崎"] },
|
|
49
|
+
{ id: "KU", name: "熊本県", patterns: ["熊本"] },
|
|
50
|
+
{ id: "OI", name: "大分県", patterns: ["大分"] },
|
|
51
|
+
{ id: "MZ", name: "宮崎県", patterns: ["宮崎"] },
|
|
52
|
+
{ id: "KG", name: "鹿児島県", patterns: ["奄美", "鹿児島"] },
|
|
53
|
+
{ id: "OK", name: "沖縄県", patterns: ["沖縄"] },
|
|
54
|
+
];
|
|
55
|
+
/**
|
|
56
|
+
* Build a sorted pattern index: longer patterns first to prevent false matches.
|
|
57
|
+
* e.g. "東京島しょ" is checked before "東京".
|
|
58
|
+
*/
|
|
59
|
+
function buildPatternIndex() {
|
|
60
|
+
const entries = [];
|
|
61
|
+
for (const def of exports.PREF_DEFS) {
|
|
62
|
+
for (const pattern of def.patterns) {
|
|
63
|
+
entries.push({ pattern, prefId: def.id });
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
entries.sort((a, b) => b.pattern.length - a.pattern.length);
|
|
67
|
+
return entries;
|
|
68
|
+
}
|
|
69
|
+
const patternIndex = buildPatternIndex();
|
|
70
|
+
/**
|
|
71
|
+
* Map an area name (e.g. "石川県能登地方") to its prefecture ID.
|
|
72
|
+
* Uses substring matching with longer patterns checked first.
|
|
73
|
+
* Returns null if no match is found.
|
|
74
|
+
*/
|
|
75
|
+
function mapAreaToPref(areaName) {
|
|
76
|
+
for (const { pattern, prefId } of patternIndex) {
|
|
77
|
+
if (areaName.includes(pattern)) {
|
|
78
|
+
return prefId;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
return null;
|
|
82
|
+
}
|