atmosx-nwws-parser 1.0.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/LICENSE +17 -0
- package/README.md +6 -0
- package/dist/cjs/bootstrap.cjs +1009 -0
- package/dist/cjs/database.cjs +1114 -0
- package/dist/cjs/dictionaries/awips.cjs +379 -0
- package/dist/cjs/dictionaries/events.cjs +139 -0
- package/dist/cjs/dictionaries/icao.cjs +265 -0
- package/dist/cjs/dictionaries/offshore.cjs +40 -0
- package/dist/cjs/dictionaries/signatures.cjs +132 -0
- package/dist/cjs/eas.cjs +2857 -0
- package/dist/cjs/helper.cjs +3014 -0
- package/dist/cjs/parsers/events.cjs +2857 -0
- package/dist/cjs/parsers/stanza.cjs +1108 -0
- package/dist/cjs/parsers/text.cjs +1142 -0
- package/dist/cjs/parsers/types/api.cjs +2857 -0
- package/dist/cjs/parsers/types/cap.cjs +2857 -0
- package/dist/cjs/parsers/types/text.cjs +2857 -0
- package/dist/cjs/parsers/types/ugc.cjs +2857 -0
- package/dist/cjs/parsers/types/vtec.cjs +2857 -0
- package/dist/cjs/parsers/ugc.cjs +1139 -0
- package/dist/cjs/parsers/vtec.cjs +1060 -0
- package/dist/cjs/types.cjs +17 -0
- package/dist/cjs/utils.cjs +2857 -0
- package/dist/cjs/xmpp.cjs +2857 -0
- package/dist/esm/bootstrap.mjs +972 -0
- package/dist/esm/database.mjs +1079 -0
- package/dist/esm/dictionaries/awips.mjs +355 -0
- package/dist/esm/dictionaries/events.mjs +111 -0
- package/dist/esm/dictionaries/icao.mjs +241 -0
- package/dist/esm/dictionaries/offshore.mjs +16 -0
- package/dist/esm/dictionaries/signatures.mjs +106 -0
- package/dist/esm/eas.mjs +2824 -0
- package/dist/esm/helper.mjs +2974 -0
- package/dist/esm/parsers/events.mjs +2824 -0
- package/dist/esm/parsers/stanza.mjs +1072 -0
- package/dist/esm/parsers/text.mjs +1106 -0
- package/dist/esm/parsers/types/api.mjs +2824 -0
- package/dist/esm/parsers/types/cap.mjs +2824 -0
- package/dist/esm/parsers/types/text.mjs +2824 -0
- package/dist/esm/parsers/types/ugc.mjs +2824 -0
- package/dist/esm/parsers/types/vtec.mjs +2824 -0
- package/dist/esm/parsers/ugc.mjs +1104 -0
- package/dist/esm/parsers/vtec.mjs +1025 -0
- package/dist/esm/types.mjs +0 -0
- package/dist/esm/utils.mjs +2824 -0
- package/dist/esm/xmpp.mjs +2824 -0
- package/package.json +47 -0
- package/shapefiles/FireCounties.dbf +0 -0
- package/shapefiles/FireCounties.shp +0 -0
- package/shapefiles/FireZones.dbf +0 -0
- package/shapefiles/FireZones.shp +0 -0
- package/shapefiles/ForecastZones.dbf +0 -0
- package/shapefiles/ForecastZones.shp +0 -0
- package/shapefiles/Marine.dbf +0 -0
- package/shapefiles/Marine.shp +0 -0
- package/shapefiles/OffShoreZones.dbf +0 -0
- package/shapefiles/OffShoreZones.shp +0 -0
- package/shapefiles/USCounties.dbf +0 -0
- package/shapefiles/USCounties.shp +0 -0
- package/src/bootstrap.ts +171 -0
- package/src/database.ts +99 -0
- package/src/dictionaries/awips.ts +351 -0
- package/src/dictionaries/events.ts +109 -0
- package/src/dictionaries/icao.ts +237 -0
- package/src/dictionaries/offshore.ts +12 -0
- package/src/dictionaries/signatures.ts +103 -0
- package/src/eas.ts +428 -0
- package/src/helper.ts +167 -0
- package/src/parsers/events.ts +289 -0
- package/src/parsers/stanza.ts +103 -0
- package/src/parsers/text.ts +167 -0
- package/src/parsers/types/api.ts +94 -0
- package/src/parsers/types/cap.ts +89 -0
- package/src/parsers/types/text.ts +54 -0
- package/src/parsers/types/ugc.ts +85 -0
- package/src/parsers/types/vtec.ts +60 -0
- package/src/parsers/ugc.ts +148 -0
- package/src/parsers/vtec.ts +66 -0
- package/src/types.ts +187 -0
- package/src/utils.ts +216 -0
- package/src/xmpp.ts +123 -0
- package/test.js +1 -0
- package/tsconfig.json +14 -0
- package/tsup.config.ts +11 -0
|
@@ -0,0 +1,2857 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __defProps = Object.defineProperties;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropDescs = Object.getOwnPropertyDescriptors;
|
|
6
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
7
|
+
var __getOwnPropSymbols = Object.getOwnPropertySymbols;
|
|
8
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
9
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
10
|
+
var __propIsEnum = Object.prototype.propertyIsEnumerable;
|
|
11
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
12
|
+
var __spreadValues = (a, b) => {
|
|
13
|
+
for (var prop in b || (b = {}))
|
|
14
|
+
if (__hasOwnProp.call(b, prop))
|
|
15
|
+
__defNormalProp(a, prop, b[prop]);
|
|
16
|
+
if (__getOwnPropSymbols)
|
|
17
|
+
for (var prop of __getOwnPropSymbols(b)) {
|
|
18
|
+
if (__propIsEnum.call(b, prop))
|
|
19
|
+
__defNormalProp(a, prop, b[prop]);
|
|
20
|
+
}
|
|
21
|
+
return a;
|
|
22
|
+
};
|
|
23
|
+
var __spreadProps = (a, b) => __defProps(a, __getOwnPropDescs(b));
|
|
24
|
+
var __export = (target, all) => {
|
|
25
|
+
for (var name in all)
|
|
26
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
27
|
+
};
|
|
28
|
+
var __copyProps = (to, from, except, desc) => {
|
|
29
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
30
|
+
for (let key of __getOwnPropNames(from))
|
|
31
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
32
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
33
|
+
}
|
|
34
|
+
return to;
|
|
35
|
+
};
|
|
36
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
37
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
38
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
39
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
40
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
41
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
42
|
+
mod
|
|
43
|
+
));
|
|
44
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
45
|
+
var __async = (__this, __arguments, generator) => {
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
var fulfilled = (value) => {
|
|
48
|
+
try {
|
|
49
|
+
step(generator.next(value));
|
|
50
|
+
} catch (e) {
|
|
51
|
+
reject(e);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
var rejected = (value) => {
|
|
55
|
+
try {
|
|
56
|
+
step(generator.throw(value));
|
|
57
|
+
} catch (e) {
|
|
58
|
+
reject(e);
|
|
59
|
+
}
|
|
60
|
+
};
|
|
61
|
+
var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected);
|
|
62
|
+
step((generator = generator.apply(__this, __arguments)).next());
|
|
63
|
+
});
|
|
64
|
+
};
|
|
65
|
+
|
|
66
|
+
// src/parsers/types/text.ts
|
|
67
|
+
var text_exports = {};
|
|
68
|
+
__export(text_exports, {
|
|
69
|
+
UGCAlerts: () => UGCAlerts2,
|
|
70
|
+
default: () => text_default2
|
|
71
|
+
});
|
|
72
|
+
module.exports = __toCommonJS(text_exports);
|
|
73
|
+
|
|
74
|
+
// src/bootstrap.ts
|
|
75
|
+
var fs = __toESM(require("fs"));
|
|
76
|
+
var path = __toESM(require("path"));
|
|
77
|
+
var events = __toESM(require("events"));
|
|
78
|
+
var xmpp = __toESM(require("@xmpp/client"));
|
|
79
|
+
var shapefile = __toESM(require("shapefile"));
|
|
80
|
+
var xml2js = __toESM(require("xml2js"));
|
|
81
|
+
var cron = __toESM(require("node-cron"));
|
|
82
|
+
var import_better_sqlite3 = __toESM(require("better-sqlite3"));
|
|
83
|
+
var import_axios = __toESM(require("axios"));
|
|
84
|
+
var import_crypto = __toESM(require("crypto"));
|
|
85
|
+
var import_os = __toESM(require("os"));
|
|
86
|
+
var import_say = __toESM(require("say"));
|
|
87
|
+
|
|
88
|
+
// src/dictionaries/events.ts
|
|
89
|
+
var EVENTS = {
|
|
90
|
+
"AF": "Ashfall",
|
|
91
|
+
"AS": "Air Stagnation",
|
|
92
|
+
"BH": "Beach Hazard",
|
|
93
|
+
"BW": "Brisk Wind",
|
|
94
|
+
"BZ": "Blizzard",
|
|
95
|
+
"CF": "Coastal Flood",
|
|
96
|
+
"DF": "Debris Flow",
|
|
97
|
+
"DS": "Dust Storm",
|
|
98
|
+
"EC": "Extreme Cold",
|
|
99
|
+
"EH": "Excessive Heat",
|
|
100
|
+
"XH": "Extreme Heat",
|
|
101
|
+
"EW": "Extreme Wind",
|
|
102
|
+
"FA": "Areal Flood",
|
|
103
|
+
"FF": "Flash Flood",
|
|
104
|
+
"FG": "Dense Fog",
|
|
105
|
+
"FL": "Flood",
|
|
106
|
+
"FR": "Frost",
|
|
107
|
+
"FW": "Fire Weather",
|
|
108
|
+
"FZ": "Freeze",
|
|
109
|
+
"GL": "Gale",
|
|
110
|
+
"HF": "Hurricane Force Wind",
|
|
111
|
+
"HT": "Heat",
|
|
112
|
+
"HU": "Hurricane",
|
|
113
|
+
"HW": "High Wind",
|
|
114
|
+
"HY": "Hydrologic",
|
|
115
|
+
"HZ": "Hard Freeze",
|
|
116
|
+
"IS": "Ice Storm",
|
|
117
|
+
"LE": "Lake Effect Snow",
|
|
118
|
+
"LO": "Low Water",
|
|
119
|
+
"LS": "Lakeshore Flood",
|
|
120
|
+
"LW": "Lake Wind",
|
|
121
|
+
"MA": "Special Marine",
|
|
122
|
+
"EQ": "Earthquake",
|
|
123
|
+
"MF": "Dense Fog",
|
|
124
|
+
"MH": "Ashfall",
|
|
125
|
+
"MS": "Dense Smoke",
|
|
126
|
+
"RB": "Small Craft for Rough Bar",
|
|
127
|
+
"RP": "Rip Current Risk",
|
|
128
|
+
"SC": "Small Craft",
|
|
129
|
+
"SE": "Hazardous Seas",
|
|
130
|
+
"SI": "Small Craft for Winds",
|
|
131
|
+
"SM": "Dense Smoke",
|
|
132
|
+
"SQ": "Snow Squall",
|
|
133
|
+
"SR": "Storm",
|
|
134
|
+
"SS": "Storm Surge",
|
|
135
|
+
"SU": "High Surf",
|
|
136
|
+
"SV": "Severe Thunderstorm",
|
|
137
|
+
"SW": "Small Craft for Hazardous Seas",
|
|
138
|
+
"TO": "Tornado",
|
|
139
|
+
"TR": "Tropical Storm",
|
|
140
|
+
"TS": "Tsunami",
|
|
141
|
+
"TY": "Typhoon",
|
|
142
|
+
"SP": "Special Weather",
|
|
143
|
+
"UP": "Heavy Freezing Spray",
|
|
144
|
+
"WC": "Wind Chill",
|
|
145
|
+
"WI": "Wind",
|
|
146
|
+
"WS": "Winter Storm",
|
|
147
|
+
"WW": "Winter Weather",
|
|
148
|
+
"ZF": "Freezing Fog",
|
|
149
|
+
"ZR": "Freezing Rain",
|
|
150
|
+
"ZY": "Freezing Spray"
|
|
151
|
+
};
|
|
152
|
+
var ACTIONS = {
|
|
153
|
+
"W": "Warning",
|
|
154
|
+
"F": "Forecast",
|
|
155
|
+
"A": "Watch",
|
|
156
|
+
"O": "Outlook",
|
|
157
|
+
"Y": "Advisory",
|
|
158
|
+
"N": "Synopsis",
|
|
159
|
+
"S": "Statement"
|
|
160
|
+
};
|
|
161
|
+
var STATUS = {
|
|
162
|
+
"NEW": "Issued",
|
|
163
|
+
"CON": "Updated",
|
|
164
|
+
"EXT": "Extended",
|
|
165
|
+
"EXA": "Extended",
|
|
166
|
+
"EXB": "Extended",
|
|
167
|
+
"UPG": "Upgraded",
|
|
168
|
+
"COR": "Correction",
|
|
169
|
+
"ROU": "Routine",
|
|
170
|
+
"CAN": "Cancelled",
|
|
171
|
+
"EXP": "Expired"
|
|
172
|
+
};
|
|
173
|
+
var TYPES = {
|
|
174
|
+
"O": "Operational Product",
|
|
175
|
+
"T": "Test Product",
|
|
176
|
+
"E": "Experimental Product",
|
|
177
|
+
"X": "Experimental Product (Non-Operational)"
|
|
178
|
+
};
|
|
179
|
+
var STATUS_CORRELATIONS = [
|
|
180
|
+
{ type: "Update", forward: "Updated", cancel: false },
|
|
181
|
+
{ type: "Cancel", forward: "Cancelled", cancel: true },
|
|
182
|
+
{ type: "Alert", forward: "Issued", cancel: false },
|
|
183
|
+
{ type: "Updated", forward: "Updated", cancel: false },
|
|
184
|
+
{ type: "Expired", forward: "Expired", cancel: true },
|
|
185
|
+
{ type: "Issued", forward: "Issued", cancel: false },
|
|
186
|
+
{ type: "Extended", forward: "Updated", cancel: false },
|
|
187
|
+
{ type: "Correction", forward: "Updated", cancel: false },
|
|
188
|
+
{ type: "Upgraded", forward: "Upgraded", cancel: false },
|
|
189
|
+
{ type: "Cancelled", forward: "Cancelled", cancel: true },
|
|
190
|
+
{ type: "Routine", forward: "Routine", cancel: false }
|
|
191
|
+
];
|
|
192
|
+
|
|
193
|
+
// src/dictionaries/offshore.ts
|
|
194
|
+
var OFFSHORE = {
|
|
195
|
+
"Special Weather Statement": "Special Weather Statement",
|
|
196
|
+
"Hurricane Warning": "Hurricane Warning",
|
|
197
|
+
"Hurricane Force Wind Warning": "Hurricane Force Wind Warning",
|
|
198
|
+
"Hurricane Watch": "Hurricane Watch",
|
|
199
|
+
"Tropical Storm Warning": "Tropical Storm Warning",
|
|
200
|
+
"Tropical Storm Watch": "Tropical Storm Watch",
|
|
201
|
+
"High Wind Warning": "High Wind Warning",
|
|
202
|
+
"Gale Warning": "Gale Warning",
|
|
203
|
+
"Small Craft Advisory": "Small Craft Advisory",
|
|
204
|
+
"Small Craft Warning": "Small Craft Warning"
|
|
205
|
+
};
|
|
206
|
+
|
|
207
|
+
// src/dictionaries/awips.ts
|
|
208
|
+
var AWIPS = {
|
|
209
|
+
ABV: `abv`,
|
|
210
|
+
ADA: `administrative-message`,
|
|
211
|
+
ADM: `administrative-message`,
|
|
212
|
+
ADR: `administrative-message`,
|
|
213
|
+
AHD: `area-hydrological-discussion`,
|
|
214
|
+
AHONT1: `high-density-observations-usaf-noaa`,
|
|
215
|
+
AHOPA1: `high-density-observations-usaf-noaa-west-pac`,
|
|
216
|
+
AHOPN1: `high-density-observations-usaf-noaa`,
|
|
217
|
+
AFD: `area-forecast-discussion`,
|
|
218
|
+
AQA: `air-quality-alert`,
|
|
219
|
+
AQI: `ground-level-ozone-forecast`,
|
|
220
|
+
AVA: `avalanche-watch`,
|
|
221
|
+
AVW: `avalanche-warning`,
|
|
222
|
+
AWU: `area-weather-update`,
|
|
223
|
+
AVG: `avalanche-weather-guidance`,
|
|
224
|
+
AWW: `airport-weather-warning`,
|
|
225
|
+
BLU: `blue-alert`,
|
|
226
|
+
CAE: `child-abduction-emergency`,
|
|
227
|
+
CDW: `civil-danger-warning`,
|
|
228
|
+
CEM: `civil-emergency-message`,
|
|
229
|
+
CFW: `coastal-hazard-message`,
|
|
230
|
+
CGR: `coastal-weather-observations`,
|
|
231
|
+
CLI: `daily-climate-report`,
|
|
232
|
+
CLM: `monthly-climate-report`,
|
|
233
|
+
CRF: `contingency-river-forecast`,
|
|
234
|
+
CWA: `center-weather-advisory`,
|
|
235
|
+
CWF: `coastal-waters-forecast`,
|
|
236
|
+
DGT: `drought-information`,
|
|
237
|
+
DSA: `tropical-disturbance-statement`,
|
|
238
|
+
DSW: `dust-storm-warning`,
|
|
239
|
+
EQI: `earthquake-information`,
|
|
240
|
+
EQR: `earthquake-report`,
|
|
241
|
+
EQW: `earthquake-warning`,
|
|
242
|
+
ESF: `hydrologic-outlook`,
|
|
243
|
+
EVI: `evacuation-immediate`,
|
|
244
|
+
EWW: `extreme-wind-warning`,
|
|
245
|
+
FFA: `flood-watch`,
|
|
246
|
+
FFG: `flash-flood-guidance`,
|
|
247
|
+
FFGMPD: `mesoscale-precipitation-discussion`,
|
|
248
|
+
FLS: `flood-advisory`,
|
|
249
|
+
FLW: `flood-warning`,
|
|
250
|
+
FFS: `flash-flood-statement`,
|
|
251
|
+
FFW: `flash-flood-warning`,
|
|
252
|
+
MWS: `marine-weather-statement`,
|
|
253
|
+
MWW: `marine-weather-warning`,
|
|
254
|
+
NMN: `network-message-notification`,
|
|
255
|
+
NOW: `short-term-forecast`,
|
|
256
|
+
NPW: `non-convective-advisory`,
|
|
257
|
+
NSH: `nearshore-marine-forecast`,
|
|
258
|
+
NUW: `nuclear-power-plant-warning`,
|
|
259
|
+
PMDAK: `alaska-discussion`,
|
|
260
|
+
PMDCA: `tropical-discussion`,
|
|
261
|
+
PMDEPD: `extended-forecast-discussion`,
|
|
262
|
+
PMDHI: `hawaii-discussion`,
|
|
263
|
+
PMDHMD: `model-diagnostic-discussion`,
|
|
264
|
+
PMDSA: `south-america-forecast-discussion`,
|
|
265
|
+
PMDSPD: `short-range-forecast-discussion`,
|
|
266
|
+
PNS: `public-information-statement`,
|
|
267
|
+
FRW: `fire-warning`,
|
|
268
|
+
FTM: `free-text-message`,
|
|
269
|
+
FWF: `fire-weather-planning-forecast`,
|
|
270
|
+
FWA: `fire-weather-administrative-message`,
|
|
271
|
+
FWS: `fire-weather-spot-forecast`,
|
|
272
|
+
GLF: `open-lake-forecast`,
|
|
273
|
+
HCM: `hydromet-coordination-message`,
|
|
274
|
+
HMT: `hmt`,
|
|
275
|
+
HMW: `hazardous-materials-warning`,
|
|
276
|
+
HPA: `high-pollution-advisory`,
|
|
277
|
+
HLS: `hurricane-local-statement`,
|
|
278
|
+
HMD: `rainfall-and-flood-outlook-product`,
|
|
279
|
+
HSF: `high-seas-forecast`,
|
|
280
|
+
HWO: `hazardous-weather-outlook`,
|
|
281
|
+
HYD: `supplementary-temp-and-precip`,
|
|
282
|
+
LAE: `local-area-emergency`,
|
|
283
|
+
LCO: `local-cooperative-observation`,
|
|
284
|
+
LEW: `law-enforcement-warning`,
|
|
285
|
+
LSR: `local-storm-report`,
|
|
286
|
+
ICE: `ice-outlook`,
|
|
287
|
+
MIS: `meteorological-impact-statement`,
|
|
288
|
+
OAV: `other-aviation-report`,
|
|
289
|
+
OEP: `taf-collaboration-product`,
|
|
290
|
+
OFF: `offshore-waters-forecast`,
|
|
291
|
+
OMR: `other-marine-reports`,
|
|
292
|
+
OSO: `weather-roundup`,
|
|
293
|
+
PFM: `point-forecast-matrices`,
|
|
294
|
+
PSH: `post-tropical-event-report`,
|
|
295
|
+
PWO: `public-severe-weather-outlook`,
|
|
296
|
+
QPFERD: `excessive-rainfall-discussion`,
|
|
297
|
+
QPFHSD: `heavy-snow-discussion`,
|
|
298
|
+
QPS: `quantitative-precipitation-forecast`,
|
|
299
|
+
REC: `recreational-forecast`,
|
|
300
|
+
REPNT0: `recco-observations-non-tropical-cyclone`,
|
|
301
|
+
REPNT1: `recco-observations-tropical-cyclone`,
|
|
302
|
+
REPNT2: `vortex-data-message`,
|
|
303
|
+
REPNTS: `supplementary-vortex-data-message`,
|
|
304
|
+
REPNT3: `dropsonde-observations`,
|
|
305
|
+
REPPN0: `recco-observations-non-tropical-cyclone`,
|
|
306
|
+
REPPN1: `recco-observations-tropical-cyclone`,
|
|
307
|
+
REPPN2: `vortex-data-message`,
|
|
308
|
+
REPPNS: `supplementary-vortex-data-message`,
|
|
309
|
+
REPPN3: `dropsonde-observations`,
|
|
310
|
+
REPPA0: `recco-observations-west-pac-non-tropical-cyclone`,
|
|
311
|
+
REPPA1: `recco-observations-west-pac-tropical-cyclone`,
|
|
312
|
+
REPPA2: `vortex-data-message-west-pac`,
|
|
313
|
+
REPPAS: `supplementary-vortex-data-message-west-pac`,
|
|
314
|
+
REPPA3: `dropsonde-observations-west-pac`,
|
|
315
|
+
REPRPD: `weather-reconnaissance-flights`,
|
|
316
|
+
RER: `record-event-report`,
|
|
317
|
+
RFD: `grassland-fire-danger`,
|
|
318
|
+
RFW: `red-flag-warning`,
|
|
319
|
+
RHW: `radiological-hazard-warning`,
|
|
320
|
+
RRM: `rainfall-storm-total`,
|
|
321
|
+
RTP: `regional-temperature-and-precipitation`,
|
|
322
|
+
RVA: `hydrologic-summary`,
|
|
323
|
+
RVD: `river-and-lake-summary`,
|
|
324
|
+
RVF: `river-forecast`,
|
|
325
|
+
RVS: `hydrologic-statement`,
|
|
326
|
+
RWR: `weather-roundup`,
|
|
327
|
+
RWS: `regional-weather-summary`,
|
|
328
|
+
SAB: `special-avalanche-bulletin`,
|
|
329
|
+
SCC: `storm-summary`,
|
|
330
|
+
SFT: `state-forecast-tabular-product`,
|
|
331
|
+
SIG: `convective-sigment`,
|
|
332
|
+
SMF: `smoke-management-weather-forecast`,
|
|
333
|
+
SMW: `special-marine-warning`,
|
|
334
|
+
SPS: `special-weather-statement`,
|
|
335
|
+
SPW: `shelter-in-place-warning`,
|
|
336
|
+
SQW: `snow-squall-warning`,
|
|
337
|
+
SRF: `surf-zone-forecast`,
|
|
338
|
+
STF: `tabular-state-forecast`,
|
|
339
|
+
STQ: `spot-forecast-request`,
|
|
340
|
+
SVR: `severe-thunderstorm-warning`,
|
|
341
|
+
SVS: `severe-weather-statement`,
|
|
342
|
+
SWOMCD: `mesoscale-convective-discussion`,
|
|
343
|
+
TAF: `terminal-aerodrome-forecast`,
|
|
344
|
+
TCD: `tropical-cyclone-discussion`,
|
|
345
|
+
TCE: `tropical-cyclone-position-estimate`,
|
|
346
|
+
TCM: `tropical-storm-forecast`,
|
|
347
|
+
TCU: `tropical-cyclone-update`,
|
|
348
|
+
TCV: `tropical-watch-warning-local-statement`,
|
|
349
|
+
TIB: `tsunami-information-statement`,
|
|
350
|
+
TID: `tide-data`,
|
|
351
|
+
TMA: `tsunami-message-acknowledgement`,
|
|
352
|
+
TOE: `telephone-outage-emergency`,
|
|
353
|
+
TOR: `tornado-warning`,
|
|
354
|
+
TWD: `tropical-weather-discussion`,
|
|
355
|
+
TWO: `tropical-weather-outlook`,
|
|
356
|
+
VAA: `volcanic-ash-advisory`,
|
|
357
|
+
VOW: `volcano-warning`,
|
|
358
|
+
WCN: `watch-county-notification`,
|
|
359
|
+
WRK: `work-product`,
|
|
360
|
+
WSV: `volcanic-ash-sigmet`,
|
|
361
|
+
WSW: `winter-weather-message`,
|
|
362
|
+
XTEUS: `wpc-contiguous-us-daily-max-min-temps`,
|
|
363
|
+
ZFP: `zone-forecast-package`,
|
|
364
|
+
RR1: `hydro-meteorological-data-report-pt1`,
|
|
365
|
+
RR2: `hydro-meteorological-data-report-pt2`,
|
|
366
|
+
RR3: `hydro-meteorological-data-report-pt3`,
|
|
367
|
+
RR4: `hydro-meteorological-data-report-pt4`,
|
|
368
|
+
RR5: `hydro-meteorological-data-report-pt5`,
|
|
369
|
+
RR6: `hydro-meteorological-data-report-pt6`,
|
|
370
|
+
RR7: `hydro-meteorological-data-report-pt7`,
|
|
371
|
+
RR8: `hydro-meteorological-data-report-pt8`,
|
|
372
|
+
RR9: `hydro-meteorological-data-report-pt9`,
|
|
373
|
+
SFP: `state-forecast-product`,
|
|
374
|
+
AFM: `area-forecast-matrices`,
|
|
375
|
+
AAG: `aag`,
|
|
376
|
+
ADV: `adv`,
|
|
377
|
+
AFP: `afp`,
|
|
378
|
+
AFW: `afw`,
|
|
379
|
+
AGO: `ago`,
|
|
380
|
+
AIR: `air`,
|
|
381
|
+
ALG: `alg`,
|
|
382
|
+
ALT: `alt`,
|
|
383
|
+
AVD: `avd`,
|
|
384
|
+
AWO: `awo`,
|
|
385
|
+
BOY: `boy`,
|
|
386
|
+
BRT: `brt`,
|
|
387
|
+
CF6: `cf6`,
|
|
388
|
+
CFP: `cfp`,
|
|
389
|
+
CLS: `cls`,
|
|
390
|
+
CMM: `cmm`,
|
|
391
|
+
COD: `cod`,
|
|
392
|
+
CRN: `crn`,
|
|
393
|
+
CUR: `cur`,
|
|
394
|
+
CWD: `cwd`,
|
|
395
|
+
CWS: `cws`,
|
|
396
|
+
DAY: `day`,
|
|
397
|
+
DDO: `ddo`,
|
|
398
|
+
DMO: `dmo`,
|
|
399
|
+
DSM: `dsm`,
|
|
400
|
+
ECD: `ecd`,
|
|
401
|
+
EFP: `efp`,
|
|
402
|
+
EOL: `eol`,
|
|
403
|
+
EOM: `eom`,
|
|
404
|
+
ESG: `esg`,
|
|
405
|
+
ESP: `esp`,
|
|
406
|
+
ESS: `ess`,
|
|
407
|
+
ETT: `ett`,
|
|
408
|
+
FA0: `fa0`,
|
|
409
|
+
FA7: `fa7`,
|
|
410
|
+
FA8: `fa8`,
|
|
411
|
+
FA9: `fa9`,
|
|
412
|
+
FD0: `fd0`,
|
|
413
|
+
FD1: `fd1`,
|
|
414
|
+
FD3: `fd3`,
|
|
415
|
+
FD5: `fd5`,
|
|
416
|
+
FD8: `fd8`,
|
|
417
|
+
FD9: `fd9`,
|
|
418
|
+
FDI: `fdi`,
|
|
419
|
+
FFH: `ffh`,
|
|
420
|
+
FFP: `ffp`,
|
|
421
|
+
FLR: `flr`,
|
|
422
|
+
FOP: `fop`,
|
|
423
|
+
FRH: `frh`,
|
|
424
|
+
FSH: `fsh`,
|
|
425
|
+
FTP: `ftp`,
|
|
426
|
+
FWD: `fwd`,
|
|
427
|
+
FWL: `fwl`,
|
|
428
|
+
FWM: `fwm`,
|
|
429
|
+
FWN: `fwn`,
|
|
430
|
+
FWO: `fwo`,
|
|
431
|
+
FZL: `fzl`,
|
|
432
|
+
GEN: `gen`,
|
|
433
|
+
GMT: `gmt`,
|
|
434
|
+
HD0: `hd0`,
|
|
435
|
+
HD2: `hd2`,
|
|
436
|
+
HD3: `hd3`,
|
|
437
|
+
HD4: `hd4`,
|
|
438
|
+
HD6: `hd6`,
|
|
439
|
+
HD8: `hd8`,
|
|
440
|
+
HML: `hml`,
|
|
441
|
+
HWR: `hwr`,
|
|
442
|
+
LAK: `lak`,
|
|
443
|
+
LEV: `lev`,
|
|
444
|
+
LLL: `lll`,
|
|
445
|
+
HP2: `hp2`,
|
|
446
|
+
HRR: `hrr`,
|
|
447
|
+
HYM: `hym`,
|
|
448
|
+
ICO: `ico`,
|
|
449
|
+
IDM: `idm`,
|
|
450
|
+
MAN: `man`,
|
|
451
|
+
MAP: `map`,
|
|
452
|
+
MAR: `mar`,
|
|
453
|
+
MAV: `mav`,
|
|
454
|
+
MCG: `mcg`,
|
|
455
|
+
MCX: `mcx`,
|
|
456
|
+
MET: `met`,
|
|
457
|
+
MEX: `mex`,
|
|
458
|
+
MFM: `mfm`,
|
|
459
|
+
MFP: `mfp`,
|
|
460
|
+
MHF: `mhf`,
|
|
461
|
+
MME: `mme`,
|
|
462
|
+
MMG: `mmg`,
|
|
463
|
+
MRP: `mrp`,
|
|
464
|
+
MSM: `msm`,
|
|
465
|
+
MTR: `mtr`,
|
|
466
|
+
MTT: `mtt`,
|
|
467
|
+
MVF: `mvf`,
|
|
468
|
+
NWR: `nwr`,
|
|
469
|
+
OFA: `ofa`,
|
|
470
|
+
OPU: `opu`,
|
|
471
|
+
OPW: `opw`,
|
|
472
|
+
OSB: `osb`,
|
|
473
|
+
PFW: `pfw`,
|
|
474
|
+
PLS: `pls`,
|
|
475
|
+
PMD: `pmd`,
|
|
476
|
+
POE: `poe`,
|
|
477
|
+
PRB: `prb`,
|
|
478
|
+
PTS: `pts`,
|
|
479
|
+
PWS: `pws`,
|
|
480
|
+
QPF: `qpf`,
|
|
481
|
+
QPG: `qpg`,
|
|
482
|
+
RBG: `rbg`,
|
|
483
|
+
REP: `rep`,
|
|
484
|
+
RFM: `rfm`,
|
|
485
|
+
RFR: `rfr`,
|
|
486
|
+
RRA: `rra`,
|
|
487
|
+
RRS: `rrs`,
|
|
488
|
+
RRY: `rry`,
|
|
489
|
+
RRZ: `rrz`,
|
|
490
|
+
RVC: `rvc`,
|
|
491
|
+
RVG: `rvg`,
|
|
492
|
+
RVK: `rvk`,
|
|
493
|
+
RVM: `rvm`,
|
|
494
|
+
RVO: `rvo`,
|
|
495
|
+
RVR: `rvr`,
|
|
496
|
+
RVU: `rvu`,
|
|
497
|
+
SAF: `saf`,
|
|
498
|
+
SAG: `sag`,
|
|
499
|
+
SAT: `sat`,
|
|
500
|
+
SAW: `saw`,
|
|
501
|
+
SCN: `scn`,
|
|
502
|
+
SCP: `scp`,
|
|
503
|
+
SCS: `scs`,
|
|
504
|
+
SCV: `scv`,
|
|
505
|
+
SEL: `sel`,
|
|
506
|
+
SGL: `sgl`,
|
|
507
|
+
SPE: `spe`,
|
|
508
|
+
SPN: `spn`,
|
|
509
|
+
SRG: `srg`,
|
|
510
|
+
SSM: `ssm`,
|
|
511
|
+
STA: `sta`,
|
|
512
|
+
SUM: `sum`,
|
|
513
|
+
SWE: `swe`,
|
|
514
|
+
SYN: `syn`,
|
|
515
|
+
TAP: `tap`,
|
|
516
|
+
TCA: `tca`,
|
|
517
|
+
TCP: `tcp`,
|
|
518
|
+
TCS: `tcs`,
|
|
519
|
+
TPT: `tpt`,
|
|
520
|
+
TST: `tst`,
|
|
521
|
+
TSU: `tsu`,
|
|
522
|
+
TUV: `tuv`,
|
|
523
|
+
TVL: `tvl`,
|
|
524
|
+
TWS: `tws`,
|
|
525
|
+
TXT: `txt`,
|
|
526
|
+
UVI: `uvi`,
|
|
527
|
+
VFT: `vft`,
|
|
528
|
+
WA0: `wa0`,
|
|
529
|
+
WA1: `wa1`,
|
|
530
|
+
WA2: `wa2`,
|
|
531
|
+
WA3: `wa3`,
|
|
532
|
+
WA4: `wa4`,
|
|
533
|
+
WA5: `wa5`,
|
|
534
|
+
WA6: `wa6`,
|
|
535
|
+
WA7: `wa7`,
|
|
536
|
+
WA8: `wa8`,
|
|
537
|
+
WA9: `wa9`,
|
|
538
|
+
WAR: `war`,
|
|
539
|
+
WAT: `wat`,
|
|
540
|
+
WCL: `wcl`,
|
|
541
|
+
WEE: `wee`,
|
|
542
|
+
WEK: `wek`,
|
|
543
|
+
WOU: `wou`,
|
|
544
|
+
WRM: `wrm`,
|
|
545
|
+
WS1: `ws1`,
|
|
546
|
+
WS2: `ws2`,
|
|
547
|
+
WS3: `ws3`,
|
|
548
|
+
WS4: `ws4`,
|
|
549
|
+
WS5: `ws5`,
|
|
550
|
+
WS6: `ws6`,
|
|
551
|
+
WSC: `wsc`,
|
|
552
|
+
WST: `wst`,
|
|
553
|
+
WTA: `wta`,
|
|
554
|
+
WWA: `wwa`,
|
|
555
|
+
WWP: `wwp`,
|
|
556
|
+
XF0: `xf0`,
|
|
557
|
+
XOB: `xob`
|
|
558
|
+
};
|
|
559
|
+
|
|
560
|
+
// src/dictionaries/signatures.ts
|
|
561
|
+
var TAGS = {
|
|
562
|
+
"A LARGE AND EXTREMELY DANGEROUS TORNADO": "Large and Dangerous Tornado",
|
|
563
|
+
"THIS IS A PARTICULARLY DANGEROUS SITUATION": "Particularly Dangerous Situation",
|
|
564
|
+
"RADAR INDICATED ROTATION": "Radar Indicated Tornado",
|
|
565
|
+
"WEATHER SPOTTERS CONFIRMED TORNADO": "Confirmed by Storm Spotters",
|
|
566
|
+
"A SEVERE THUNDERSTORM CAPABLE OF PRODUCING A TORNADO": "Developing Tornado",
|
|
567
|
+
"LAW ENFORCEMENT CONFIRMED TORNADO": "Reported by Law Enforcement",
|
|
568
|
+
"A TORNADO IS ON THE GROUND": "Confirmed Tornado",
|
|
569
|
+
"WEATHER SPOTTERS REPORTED FUNNEL CLOUD": "Confirmed Funnel Cloud by Storm Spotters",
|
|
570
|
+
"PUBLIC CONFIRMED TORNADO": "Public reports of Tornado",
|
|
571
|
+
"RADAR CONFIRMED": "Radar Confirmed",
|
|
572
|
+
"TORNADO WAS REPORTED BRIEFLY ON THE GROUND": "Tornado no longer on ground",
|
|
573
|
+
"SPOTTERS INDICATE THAT A FUNNEL CLOUD CONTINUES WITH THIS STORM": "Funnel Cloud Continues",
|
|
574
|
+
"A TORNADO MAY DEVELOP AT ANY TIME": "Potentional still exists for Tornado to form",
|
|
575
|
+
"LIFE-THREATENING SITUATION": "Life Threating Situation",
|
|
576
|
+
"COMPLETE DESTRUCTION IS POSSIBLE": "Extremly Damaging Tornado",
|
|
577
|
+
"POTENTIALLY DEADLY TORNADO": "Deadly Tornado",
|
|
578
|
+
"RADAR INDICATED": "Radar Indicated",
|
|
579
|
+
"HAIL DAMAGE TO VEHICLES IS EXPECTED": "Damaging to Vehicles",
|
|
580
|
+
"EXPECT WIND DAMAGE": "Wind Damage",
|
|
581
|
+
"FREQUENT LIGHTNING": "Frequent Lightning",
|
|
582
|
+
"PEOPLE AND ANIMALS OUTDOORS WILL BE INJURED": "Capable of Injuring People and Animals",
|
|
583
|
+
"TRAINED WEATHER SPOTTERS": "Confirmed by Storm Spotters",
|
|
584
|
+
"SOURCE...PUBLIC": "Confirmed by Public",
|
|
585
|
+
"SMALL CRAFT COULD BE DAMAGED": "Potential Damage to Small Craft",
|
|
586
|
+
"A TORNADO WATCH REMAINS IN EFFECT": "Active Tornado Watch",
|
|
587
|
+
"TENNIS BALL SIZE HAIL": "Tennis Ball Size Hail",
|
|
588
|
+
"BASEBALL SIZE HAIL": "Baseball Size Hail",
|
|
589
|
+
"GOLF BALL SIZE HAIL": "Golf Ball Size Hail",
|
|
590
|
+
"QUARTER SIZE HAIL": "Quarter Size Hail",
|
|
591
|
+
"PING PONG BALL SIZE HAIL": "Ping Pong Ball Size Hail",
|
|
592
|
+
"NICKEL SIZE HAIL": "Nickel Size Hail",
|
|
593
|
+
"DOPPLER RADAR.": "Confirmed by Radar",
|
|
594
|
+
"DOPPLER RADAR AND AUTOMATED GAUGES.": "Confirmed by Radar and Gauges",
|
|
595
|
+
"FLASH FLOODING CAUSED BY THUNDERSTORMS.": "Caused by Thunderstorm",
|
|
596
|
+
"SOURCE...EMERGENCY MANAGEMENT.": "Confirmed by Emergency Management",
|
|
597
|
+
"FLASH FLOODING CAUSED BY HEAVY RAIN.": "Caused by heavy rain",
|
|
598
|
+
"SOURCE...LAW ENFORCEMENT REPORTED.": "Confirmed by Law Enforcement"
|
|
599
|
+
};
|
|
600
|
+
var CANCEL_SIGNATURES = [
|
|
601
|
+
"THIS_MESSAGE_IS_FOR_TEST_PURPOSES_ONLY",
|
|
602
|
+
"this is a test",
|
|
603
|
+
"subsided sufficiently for the advisory to be cancelled",
|
|
604
|
+
"has been cancelled",
|
|
605
|
+
"will be allowed to expire",
|
|
606
|
+
"has diminished",
|
|
607
|
+
"and no longer",
|
|
608
|
+
"has been replaced",
|
|
609
|
+
"The threat has ended",
|
|
610
|
+
"has weakened below severe"
|
|
611
|
+
];
|
|
612
|
+
var MESSAGE_SIGNATURES = [
|
|
613
|
+
{ regex: /\r?\n/g, replacement: " " },
|
|
614
|
+
{ regex: /\s+/g, replacement: " " },
|
|
615
|
+
{ regex: /\*/g, replacement: "." },
|
|
616
|
+
{ regex: /\bUTC\b/g, replacement: "Coordinated Universal Time" },
|
|
617
|
+
{ regex: /\bGMT\b/g, replacement: "Greenwich Mean Time" },
|
|
618
|
+
{ regex: /\bEST\b/g, replacement: "Eastern Standard Time" },
|
|
619
|
+
{ regex: /\bEDT\b/g, replacement: "Eastern Daylight Time" },
|
|
620
|
+
{ regex: /\bCST\b/g, replacement: "Central Standard Time" },
|
|
621
|
+
{ regex: /\bCDT\b/g, replacement: "Central Daylight Time" },
|
|
622
|
+
{ regex: /\bMST\b/g, replacement: "Mountain Standard Time" },
|
|
623
|
+
{ regex: /\bMDT\b/g, replacement: "Mountain Daylight Time" },
|
|
624
|
+
{ regex: /\bPST\b/g, replacement: "Pacific Standard Time" },
|
|
625
|
+
{ regex: /\bPDT\b/g, replacement: "Pacific Daylight Time" },
|
|
626
|
+
{ regex: /\bAKST\b/g, replacement: "Alaska Standard Time" },
|
|
627
|
+
{ regex: /\bAKDT\b/g, replacement: "Alaska Daylight Time" },
|
|
628
|
+
{ regex: /\bHST\b/g, replacement: "Hawaii Standard Time" },
|
|
629
|
+
{ regex: /\bHDT\b/g, replacement: "Hawaii Daylight Time" },
|
|
630
|
+
{ regex: /\bmph\b/g, replacement: "miles per hour" },
|
|
631
|
+
{ regex: /\bkm\/h\b/g, replacement: "kilometers per hour" },
|
|
632
|
+
{ regex: /\bkmh\b/g, replacement: "kilometers per hour" },
|
|
633
|
+
{ regex: /\bkt\b/g, replacement: "knots" },
|
|
634
|
+
{ regex: /\bNE\b/g, replacement: "northeast" },
|
|
635
|
+
{ regex: /\bNW\b/g, replacement: "northwest" },
|
|
636
|
+
{ regex: /\bSE\b/g, replacement: "southeast" },
|
|
637
|
+
{ regex: /\bSW\b/g, replacement: "southwest" },
|
|
638
|
+
{ regex: /\bNM\b/g, replacement: "nautical miles" },
|
|
639
|
+
{ regex: /\bdeg\b/g, replacement: "degrees" },
|
|
640
|
+
{ regex: /\btstm\b/g, replacement: "thunderstorm" },
|
|
641
|
+
{ regex: /\bmm\b/g, replacement: "millimeters" },
|
|
642
|
+
{ regex: /\bcm\b/g, replacement: "centimeters" },
|
|
643
|
+
{ regex: /\bin\b/g, replacement: "inches" },
|
|
644
|
+
{ regex: /\bft\b/g, replacement: "feet" },
|
|
645
|
+
{ regex: /\bmi\b/g, replacement: "miles" },
|
|
646
|
+
{ regex: /\bhr\b/g, replacement: "hour" },
|
|
647
|
+
{ regex: /\bhourly\b/g, replacement: "per hour" },
|
|
648
|
+
{ regex: /\bkg\b/g, replacement: "kilograms" },
|
|
649
|
+
{ regex: /\bg\/kg\b/g, replacement: "grams per kilogram" },
|
|
650
|
+
{ regex: /\bmb\b/g, replacement: "millibars" },
|
|
651
|
+
{ regex: /\bhPa\b/g, replacement: "hectopascals" },
|
|
652
|
+
{ regex: /\bPa\b/g, replacement: "pascals" },
|
|
653
|
+
{ regex: /\bKPa\b/g, replacement: "kilopascals" },
|
|
654
|
+
{ regex: /\bC\/hr\b/g, replacement: "degrees Celsius per hour" },
|
|
655
|
+
{ regex: /\bF\/hr\b/g, replacement: "degrees Fahrenheit per hour" },
|
|
656
|
+
{ regex: /\bC\/min\b/g, replacement: "degrees Celsius per minute" },
|
|
657
|
+
{ regex: /\bF\/min\b/g, replacement: "degrees Fahrenheit per minute" },
|
|
658
|
+
{ regex: /\bC\b/g, replacement: "degrees Celsius" },
|
|
659
|
+
{ regex: /\bF\b/g, replacement: "degrees Fahrenheit" }
|
|
660
|
+
];
|
|
661
|
+
|
|
662
|
+
// src/dictionaries/icao.ts
|
|
663
|
+
var ICAOs = {
|
|
664
|
+
"KLCH": "Lake Charles, LA",
|
|
665
|
+
"TSTL": "St. Louis, MO",
|
|
666
|
+
"PABC": "Bethel, AK",
|
|
667
|
+
"TCMH": "Columbus, OH",
|
|
668
|
+
"KEPZ": "El Paso, TX",
|
|
669
|
+
"KCYS": "Cheyenne, WY",
|
|
670
|
+
"KJKL": "Jackson, KY",
|
|
671
|
+
"KPAH": "Paducah, KY",
|
|
672
|
+
"KEMX": "Tucson, AZ",
|
|
673
|
+
"KMHX": "Morehead City, NC",
|
|
674
|
+
"PAPD": "Fairbanks, AK",
|
|
675
|
+
"KDLH": "Duluth, MN",
|
|
676
|
+
"TADW": "Andrews Air Force Base, MD",
|
|
677
|
+
"KOKX": "Brookhaven, NY",
|
|
678
|
+
"KLZK": "Little Rock, AR",
|
|
679
|
+
"KHGX": "Houston, TX",
|
|
680
|
+
"TMSY": "New Orleans, LA",
|
|
681
|
+
"KDGX": "Jackson/Brandon, MS",
|
|
682
|
+
"KCTP": "Caribou, ME",
|
|
683
|
+
"KAMA": "Amarillo, TX",
|
|
684
|
+
"PGUA": "Andersen AFB, GU",
|
|
685
|
+
"KAPX": "Gaylord, MI",
|
|
686
|
+
"PAHG": "Kenai, AK",
|
|
687
|
+
"KLWX": "Sterling, VA",
|
|
688
|
+
"HWPA2": "Homer, AK",
|
|
689
|
+
"KGRK": "Fort Hood, TX",
|
|
690
|
+
"KAKQ": "Wakefield, VA",
|
|
691
|
+
"ROCO2": "Norman, OK",
|
|
692
|
+
"KCLX": "Charleston, SC",
|
|
693
|
+
"TPHX": "Phoenix, AZ",
|
|
694
|
+
"KNKX": "San Diego, CA",
|
|
695
|
+
"TDEN": "Denver, CO",
|
|
696
|
+
"TLAS": "Las Vegas, NV",
|
|
697
|
+
"KBUF": "Buffalo, NY",
|
|
698
|
+
"KTLX": "Norman, OK",
|
|
699
|
+
"KILX": "Lincoln, IL",
|
|
700
|
+
"KHDC": "Hammond, LA",
|
|
701
|
+
"KVWX": "Evansville, IN",
|
|
702
|
+
"TCLT": "Charlotte, NC",
|
|
703
|
+
"TEWR": "Newark, NJ",
|
|
704
|
+
"KFSD": "Sioux Falls, SD",
|
|
705
|
+
"KEAX": "Pleasant Hill, MO",
|
|
706
|
+
"KICX": "Cedar City, UT",
|
|
707
|
+
"KHTX": "Huntsville, AL",
|
|
708
|
+
"PACG": "Sitka, AK",
|
|
709
|
+
"KSOX": "Santa Ana Mountains, CA",
|
|
710
|
+
"TPBI": "West Palm Beach, FL",
|
|
711
|
+
"TSLC": "Salt Lake City, UT",
|
|
712
|
+
"KGLD": "Goodland, KS",
|
|
713
|
+
"TRDU": "Raleigh-Durham, NC",
|
|
714
|
+
"KATX": "Seattle, WA",
|
|
715
|
+
"TICH": "Wichita, KS",
|
|
716
|
+
"TSDF": "Louisville, KY",
|
|
717
|
+
"TBOS": "Boston, MA",
|
|
718
|
+
"TDCA": "Washington, DC",
|
|
719
|
+
"KUEX": "Grand Island, NE",
|
|
720
|
+
"TLKA2": "Talkeetna, AK",
|
|
721
|
+
"KBGM": "Binghamton, NY",
|
|
722
|
+
"TLVE": "Cleveland, OH",
|
|
723
|
+
"KCAE": "Columbia, SC",
|
|
724
|
+
"KDVN": "Quad Cities, IA",
|
|
725
|
+
"KABR": "Aberdeen, SD",
|
|
726
|
+
"KBYX": "Key West, FL",
|
|
727
|
+
"KMPX": "Minneapolis, MN",
|
|
728
|
+
"KCRP": "Corpus Christi, TX",
|
|
729
|
+
"KCBW": "Caribou, ME",
|
|
730
|
+
"KMRX": "Knoxville, TN",
|
|
731
|
+
"KSHV": "Shreveport, LA",
|
|
732
|
+
"KIWA": "Phoenix, AZ",
|
|
733
|
+
"KRGX": "Reno, NV",
|
|
734
|
+
"PHKM": "Kamuela, HI",
|
|
735
|
+
"KABX": "Albuquerque, NM",
|
|
736
|
+
"KBMX": "Birmingham, AL",
|
|
737
|
+
"TMDW": "Chicago Midway, IL",
|
|
738
|
+
"KVAX": "Moody AFB, GA",
|
|
739
|
+
"KHDX": "Holloman AFB, NM",
|
|
740
|
+
"KBRO": "Brownsville, TX",
|
|
741
|
+
"KTWX": "Topeka, KS",
|
|
742
|
+
"KRTX": "Portland, OR",
|
|
743
|
+
"KCXX": "Burlington, VT",
|
|
744
|
+
"KFCX": "Roanoke, VA",
|
|
745
|
+
"KFFC": "Atlanta, GA",
|
|
746
|
+
"KBOX": "Boston, MA",
|
|
747
|
+
"KTLH": "Tallahassee, FL",
|
|
748
|
+
"KPUX": "Pueblo, CO",
|
|
749
|
+
"KFDR": "Altus AFB, OK",
|
|
750
|
+
"KGJX": "Grand Junction, CO",
|
|
751
|
+
"KDTX": "Detroit, MI",
|
|
752
|
+
"PHWA": "Waimea, HI",
|
|
753
|
+
"KMQT": "Marquette, MI",
|
|
754
|
+
"KSJT": "San Angelo, TX",
|
|
755
|
+
"KUDX": "Rapid City, SD",
|
|
756
|
+
"TIAH": "Houston, TX",
|
|
757
|
+
"KSRX": "Fort Smith, AR",
|
|
758
|
+
"TJFK": "New York City, NY",
|
|
759
|
+
"KDDC": "Dodge City, KS",
|
|
760
|
+
"PAKC": "King Salmon, AK",
|
|
761
|
+
"PAIH": "Middleton Island, AK",
|
|
762
|
+
"RODN": "Kadena AB, JA",
|
|
763
|
+
"TBWI": "Baltimore/Washington, MD",
|
|
764
|
+
"KIWX": "Northern Indiana, IN",
|
|
765
|
+
"KFDX": "Cannon AFB, NM",
|
|
766
|
+
"TMIA": "Miami, FL",
|
|
767
|
+
"KICT": "Wichita, KS",
|
|
768
|
+
"TMKE": "Milwaukee, WI",
|
|
769
|
+
"TFLL": "Fort Lauderdale, FL",
|
|
770
|
+
"KARX": "La Crosse, WI",
|
|
771
|
+
"KLRX": "Elko, NV",
|
|
772
|
+
"KDAX": "Sacramento, CA",
|
|
773
|
+
"KGRB": "Green Bay, WI",
|
|
774
|
+
"KLGX": "Langley Hill, WA",
|
|
775
|
+
"KFTG": "Denver, CO",
|
|
776
|
+
"KMKX": "Milwaukee, WI",
|
|
777
|
+
"TTUL": "Tulsa, OK",
|
|
778
|
+
"TDFW": "Dallas/Fort Worth, TX",
|
|
779
|
+
"TTPA": "Tampa Bay, FL",
|
|
780
|
+
"TDAL": "Dallas Love Field, TX",
|
|
781
|
+
"KDFX": "Laughlin AFB, TX",
|
|
782
|
+
"KSFX": "Pocatello, ID",
|
|
783
|
+
"KMTX": "Salt Lake City, UT",
|
|
784
|
+
"PAEC": "Nome, AK",
|
|
785
|
+
"RKSG": "Camp Humphreys, KR",
|
|
786
|
+
"KOAX": "Omaha, NE",
|
|
787
|
+
"PHMO": "Molokai, HI",
|
|
788
|
+
"TDTW": "Detroit, MI",
|
|
789
|
+
"THOU": "Houston, TX",
|
|
790
|
+
"AWPA2": "Anchorage, AK",
|
|
791
|
+
"KTYX": "Fort Drum, NY",
|
|
792
|
+
"KCCX": "State College, PA",
|
|
793
|
+
"TMSP": "Minneapolis, MN",
|
|
794
|
+
"KMVX": "Grand Forks, ND",
|
|
795
|
+
"KBIS": "Bismarck, ND",
|
|
796
|
+
"KBBX": "Beale AFB, CA",
|
|
797
|
+
"KVBX": "Vandenberg AFB, CA",
|
|
798
|
+
"KPOE": "Fort Polk, LA",
|
|
799
|
+
"KMOB": "Mobile, AL",
|
|
800
|
+
"KJGX": "Robins AFB, GA",
|
|
801
|
+
"KMUX": "San Francisco, CA",
|
|
802
|
+
"TMCI": "Kansas City, MO",
|
|
803
|
+
"KLSX": "St. Louis, MO",
|
|
804
|
+
"KMAX": "Medford, OR",
|
|
805
|
+
"KRAX": "Raleigh/Durham, NC",
|
|
806
|
+
"KINX": "Tulsa, OK",
|
|
807
|
+
"RKJK": "Kunsan AB, KR",
|
|
808
|
+
"KSGF": "Springfield, MO",
|
|
809
|
+
"TDAY": "Dayton, OH",
|
|
810
|
+
"KDOX": "Dover AFB, DE",
|
|
811
|
+
"KGGW": "Glasgow, MT",
|
|
812
|
+
"KAMX": "Miami, FL",
|
|
813
|
+
"KENX": "Albany, NY",
|
|
814
|
+
"KTFX": "Great Falls, MT",
|
|
815
|
+
"KPBZ": "Pittsburgh, PA",
|
|
816
|
+
"KMAF": "Midland/Odessa, TX",
|
|
817
|
+
"KPDT": "Pendleton, OR",
|
|
818
|
+
"KLNX": "North Platte, NE",
|
|
819
|
+
"KEOX": "Fort Rucker, AL",
|
|
820
|
+
"KGSP": "Greer, SC",
|
|
821
|
+
"KHPX": "Fort Campbell, KY",
|
|
822
|
+
"KGRR": "Grand Rapids, MI",
|
|
823
|
+
"KLOT": "Chicago, IL",
|
|
824
|
+
"TPIT": "Pittsburgh, PA",
|
|
825
|
+
"KEYX": "Edwards AFB, CA",
|
|
826
|
+
"TIAD": "Dulles, VA",
|
|
827
|
+
"KFWS": "Dallas/Fort Worth, TX",
|
|
828
|
+
"KMLB": "Melbourne, FL",
|
|
829
|
+
"KMBX": "Minot AFB, ND",
|
|
830
|
+
"KDMX": "Des Moines, IA",
|
|
831
|
+
"KEVX": "Eglin AFB, FL",
|
|
832
|
+
"TBNA": "Nashville, TN",
|
|
833
|
+
"KDYX": "Dyess AFB, TX",
|
|
834
|
+
"TOKC": "Oklahoma City, OK",
|
|
835
|
+
"PHKI": "South Kauai, HI",
|
|
836
|
+
"TMCO": "Orlando, FL",
|
|
837
|
+
"KDIX": "Philadelphia, PA",
|
|
838
|
+
"TORD": "Chicago, IL",
|
|
839
|
+
"KYUX": "Yuma, AZ",
|
|
840
|
+
"KVNX": "Vance AFB, OK",
|
|
841
|
+
"TJUA": "San Juan, PR",
|
|
842
|
+
"TATL": "Atlanta, GA",
|
|
843
|
+
"KVTX": "Los Angeles, CA",
|
|
844
|
+
"KIND": "Indianapolis, IN",
|
|
845
|
+
"KCBX": "Boise, ID",
|
|
846
|
+
"KGYX": "Portland, ME",
|
|
847
|
+
"KMXX": "Maxwell AFB, AL",
|
|
848
|
+
"TSJU": "San Juan, PR",
|
|
849
|
+
"KHNX": "San Joaquin Valley, CA",
|
|
850
|
+
"KLVX": "Louisville, KY",
|
|
851
|
+
"KMSX": "Missoula, MT",
|
|
852
|
+
"KJAX": "Jacksonville, FL",
|
|
853
|
+
"KNQA": "Memphis, TN",
|
|
854
|
+
"KRIW": "Riverton/Lander, WY",
|
|
855
|
+
"TCVG": "Covington, KY",
|
|
856
|
+
"KBLX": "Billings, MT",
|
|
857
|
+
"TPHL": "Philadelphia, PA",
|
|
858
|
+
"KRLX": "Charleston, WV",
|
|
859
|
+
"TMEM": "Memphis, TN",
|
|
860
|
+
"KCLE": "Cleveland, OH",
|
|
861
|
+
"KBHX": "Eureka, CA",
|
|
862
|
+
"KLBB": "Lubbock, TX",
|
|
863
|
+
"KOTX": "Spokane, WA",
|
|
864
|
+
"KEWX": "Austin/San Antonio, TX",
|
|
865
|
+
"KGWX": "Columbus AFB, MS",
|
|
866
|
+
"KESX": "Las Vegas, NV",
|
|
867
|
+
"KTBW": "Tampa, FL",
|
|
868
|
+
"KOHX": "Nashville, TN",
|
|
869
|
+
"KLTX": "Wilmington, NC",
|
|
870
|
+
"KFSX": "Flagstaff, AZ",
|
|
871
|
+
"TIDS": "Indianapolis, IN",
|
|
872
|
+
"KILN": "Cincinnati, OH",
|
|
873
|
+
"PAFG": "Fairbanks, AK",
|
|
874
|
+
"KPQR": "Portland, OR",
|
|
875
|
+
"KILM": "Wilmington, NC",
|
|
876
|
+
"KEKA": "Eureka, CA",
|
|
877
|
+
"KCHS": "Charleston, SC",
|
|
878
|
+
"KPHI": "Philadelphia/Mt. Holly, NJ",
|
|
879
|
+
"KUNR": "Rapid City, SD",
|
|
880
|
+
"KMFL": "Miami, FL",
|
|
881
|
+
"TJSJ": "San Juan, PR",
|
|
882
|
+
"KFGF": "Grand Forks, ND",
|
|
883
|
+
"KSEW": "Seattle, WA",
|
|
884
|
+
"PAFC": "Anchorage, AK",
|
|
885
|
+
"KLMK": "Louisville, KY",
|
|
886
|
+
"PHFO": "Honolulu, HI",
|
|
887
|
+
"KLIX": "New Orleans/Baton Rouge, LA",
|
|
888
|
+
"KBOI": "Boise, ID",
|
|
889
|
+
"KPIH": "Pocatello, ID",
|
|
890
|
+
"KMTR": "San Francisco/Monterey, CA",
|
|
891
|
+
"KGJT": "Grand Junction, CO",
|
|
892
|
+
"PAAQ": "Anchorage, AK",
|
|
893
|
+
"KABQ": "Albuquerque, NM",
|
|
894
|
+
"KTAE": "Tallahassee, FL",
|
|
895
|
+
"KCAR": "Caribou, ME",
|
|
896
|
+
"KMFR": "Medford, OR",
|
|
897
|
+
"PGUM": "Guam, GU",
|
|
898
|
+
"PAJK": "Juneau, AK"
|
|
899
|
+
};
|
|
900
|
+
|
|
901
|
+
// src/bootstrap.ts
|
|
902
|
+
var packages = {
|
|
903
|
+
fs,
|
|
904
|
+
path,
|
|
905
|
+
events,
|
|
906
|
+
xmpp,
|
|
907
|
+
shapefile,
|
|
908
|
+
xml2js,
|
|
909
|
+
sqlite3: import_better_sqlite3.default,
|
|
910
|
+
cron,
|
|
911
|
+
axios: import_axios.default,
|
|
912
|
+
crypto: import_crypto.default,
|
|
913
|
+
os: import_os.default,
|
|
914
|
+
say: import_say.default
|
|
915
|
+
};
|
|
916
|
+
var cache = {
|
|
917
|
+
isReady: true,
|
|
918
|
+
sigHalt: false,
|
|
919
|
+
isConnected: false,
|
|
920
|
+
attemptingReconnect: false,
|
|
921
|
+
totalReconnects: 0,
|
|
922
|
+
lastStanza: null,
|
|
923
|
+
session: null,
|
|
924
|
+
lastConnect: null,
|
|
925
|
+
db: null,
|
|
926
|
+
events: new events.EventEmitter(),
|
|
927
|
+
isProcessingAudioQueue: false,
|
|
928
|
+
audioQueue: []
|
|
929
|
+
};
|
|
930
|
+
var settings = {
|
|
931
|
+
database: path.join(process.cwd(), "shapefiles.db"),
|
|
932
|
+
isNWWS: true,
|
|
933
|
+
NoaaWeatherWireService: {
|
|
934
|
+
clientReconnections: {
|
|
935
|
+
canReconnect: true,
|
|
936
|
+
currentInterval: 60
|
|
937
|
+
},
|
|
938
|
+
clientCredentials: {
|
|
939
|
+
username: null,
|
|
940
|
+
password: null,
|
|
941
|
+
nickname: "AtmosphericX Standalone Parser"
|
|
942
|
+
},
|
|
943
|
+
cache: {
|
|
944
|
+
read: false,
|
|
945
|
+
maxSizeMB: 5,
|
|
946
|
+
maxHistory: 5e3,
|
|
947
|
+
directory: null
|
|
948
|
+
},
|
|
949
|
+
alertPreferences: {
|
|
950
|
+
isCapOnly: false,
|
|
951
|
+
isShapefileUGC: false
|
|
952
|
+
}
|
|
953
|
+
},
|
|
954
|
+
NationalWeatherService: {
|
|
955
|
+
checkInterval: 15,
|
|
956
|
+
endpoint: "https://api.weather.gov/alerts/active"
|
|
957
|
+
},
|
|
958
|
+
global: {
|
|
959
|
+
useParentEvents: true,
|
|
960
|
+
betterEventParsing: true,
|
|
961
|
+
alertFiltering: {
|
|
962
|
+
filteredEvents: [],
|
|
963
|
+
filteredICOAs: [],
|
|
964
|
+
ignoredICOAs: [`KWNS`],
|
|
965
|
+
ignoredEvents: [`Unknown`, `Test Message`],
|
|
966
|
+
ugcFilter: [],
|
|
967
|
+
stateFilter: [],
|
|
968
|
+
checkExpired: true
|
|
969
|
+
},
|
|
970
|
+
easSettings: {
|
|
971
|
+
easAlerts: [],
|
|
972
|
+
easDirectory: null,
|
|
973
|
+
easIntroWav: null
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
};
|
|
977
|
+
var definitions = {
|
|
978
|
+
events: EVENTS,
|
|
979
|
+
actions: ACTIONS,
|
|
980
|
+
status: STATUS,
|
|
981
|
+
productTypes: TYPES,
|
|
982
|
+
correlations: STATUS_CORRELATIONS,
|
|
983
|
+
offshore: OFFSHORE,
|
|
984
|
+
awips: AWIPS,
|
|
985
|
+
cancelSignatures: CANCEL_SIGNATURES,
|
|
986
|
+
messageSignatures: MESSAGE_SIGNATURES,
|
|
987
|
+
tags: TAGS,
|
|
988
|
+
ICAO: ICAOs,
|
|
989
|
+
enhancedEvents: [
|
|
990
|
+
{ "Tornado Warning": {
|
|
991
|
+
"Tornado Emergency": { description: "tornado emergency", condition: (tornadoThreatTag) => tornadoThreatTag === "OBSERVED" },
|
|
992
|
+
"PDS Tornado Warning": { description: "particularly dangerous situation", condition: (damageThreatTag) => damageThreatTag === "CONSIDERABLE" },
|
|
993
|
+
"Confirmed Tornado Warning": { condition: (tornadoThreatTag) => tornadoThreatTag === "OBSERVED" },
|
|
994
|
+
"Radar Indicated Tornado Warning": { condition: (tornadoThreatTag) => tornadoThreatTag !== "OBSERVED" }
|
|
995
|
+
} },
|
|
996
|
+
{ "Tornado Watch": {
|
|
997
|
+
"PDS Tornado Watch": { description: "particularly dangerous situation" }
|
|
998
|
+
} },
|
|
999
|
+
{ "Flash Flood Warning": {
|
|
1000
|
+
"Flash Flood Emergency": { description: "flash flood emergency" },
|
|
1001
|
+
"Considerable Flash Flood Warning": { condition: (damageThreatTag) => damageThreatTag === "CONSIDERABLE" }
|
|
1002
|
+
} },
|
|
1003
|
+
{ "Severe Thunderstorm Warning": {
|
|
1004
|
+
"EDS Severe Thunderstorm Warning": { description: "extremely dangerous situation" },
|
|
1005
|
+
"Destructive Severe Thunderstorm Warning": { condition: (damageThreatTag) => damageThreatTag === "DESTRUCTIVE" },
|
|
1006
|
+
"Considerable Severe Thunderstorm Warning": { condition: (damageThreatTag) => damageThreatTag === "CONSIDERABLE" }
|
|
1007
|
+
} }
|
|
1008
|
+
],
|
|
1009
|
+
expressions: {
|
|
1010
|
+
vtec: `[OTEX].(NEW|CON|EXT|EXA|EXB|UPG|CAN|EXP|COR|ROU).[A-Z]{4}.[A-Z]{2}.[WAYSFON].[0-9]{4}.[0-9]{6}T[0-9]{4}Z-[0-9]{6}T[0-9]{4}Z`,
|
|
1011
|
+
wmo: `[A-Z0-9]{6}\\s[A-Z]{4}\\s\\d{6}`,
|
|
1012
|
+
ugc1: `(\\w{2}[CZ](\\d{3}((-|>)\\s?(
|
|
1013
|
+
|
|
1014
|
+
)?))+)`,
|
|
1015
|
+
ugc2: `(\\d{6}(-|>)\\s?(
|
|
1016
|
+
|
|
1017
|
+
)?)`,
|
|
1018
|
+
ugc3: `(\\d{6})(?=-|$)`,
|
|
1019
|
+
dateline: `/d{3,4}s*(AM|PM)?s*[A-Z]{2,4}s+[A-Z]{3,}s+[A-Z]{3,}s+d{1,2}s+d{4}`,
|
|
1020
|
+
wmoID: `[A-Z0-9]{2}([A-Z]{3})`
|
|
1021
|
+
},
|
|
1022
|
+
shapefiles: [
|
|
1023
|
+
{ id: `C`, file: `USCounties` },
|
|
1024
|
+
{ id: `Z`, file: `ForecastZones` },
|
|
1025
|
+
{ id: `Z`, file: `FireZones` },
|
|
1026
|
+
{ id: `Z`, file: `OffShoreZones` },
|
|
1027
|
+
{ id: `Z`, file: `FireCounties` },
|
|
1028
|
+
{ id: `Z`, file: `Marine` }
|
|
1029
|
+
],
|
|
1030
|
+
messages: {
|
|
1031
|
+
shapefile_creation: `[NOTICE] DO NOT CLOSE THIS PROJECT UNTIL THE SHAPEFILES ARE DONE COMPLETING!
|
|
1032
|
+
THIS COULD TAKE A WHILE DEPENDING ON THE SPEED OF YOUR STORAGE!!
|
|
1033
|
+
IF YOU CLOSE YOUR PROJECT, THE SHAPEFILES WILL NOT BE CREATED AND YOU WILL NEED TO DELETE ${settings.database} AND RESTART TO CREATE THEM AGAIN!`,
|
|
1034
|
+
shapefile_creation_finished: `[NOTICE] SHAPEFILES HAVE BEEN SUCCESSFULLY CREATED AND THE DATABASE IS READY FOR USE!`,
|
|
1035
|
+
not_ready: "[ERROR] You can NOT create another instance without shutting down the current one first, please make sure to call the stop() method first!",
|
|
1036
|
+
invalid_nickname: "[WARNING] The nickname you provided is invalid, please provide a valid nickname to continue.",
|
|
1037
|
+
eas_no_directory: "[WARNING] You have not set a directory for EAS audio files to be saved to, please set the 'easDirectory' setting in the global settings to enable EAS audio generation."
|
|
1038
|
+
}
|
|
1039
|
+
};
|
|
1040
|
+
|
|
1041
|
+
// src/parsers/text.ts
|
|
1042
|
+
var TextParser = class {
|
|
1043
|
+
/**
|
|
1044
|
+
* textProductToString extracts a specific value from a text-based weather product message based on a given key and optional removal strings.
|
|
1045
|
+
*
|
|
1046
|
+
* @public
|
|
1047
|
+
* @static
|
|
1048
|
+
* @param {string} message
|
|
1049
|
+
* @param {string} value
|
|
1050
|
+
* @param {string[]} [removal=[]]
|
|
1051
|
+
* @returns {(string | null)}
|
|
1052
|
+
*/
|
|
1053
|
+
static textProductToString(message, value, removal = []) {
|
|
1054
|
+
const lines = message.split("\n");
|
|
1055
|
+
for (const line of lines) {
|
|
1056
|
+
if (line.includes(value)) {
|
|
1057
|
+
let result = line.slice(line.indexOf(value) + value.length).trim();
|
|
1058
|
+
for (const str of removal) {
|
|
1059
|
+
result = result.split(str).join("");
|
|
1060
|
+
}
|
|
1061
|
+
result = result.replace(value, "").replace("<", "").trim();
|
|
1062
|
+
return result || null;
|
|
1063
|
+
}
|
|
1064
|
+
}
|
|
1065
|
+
return null;
|
|
1066
|
+
}
|
|
1067
|
+
/**
|
|
1068
|
+
* textProductToPolygon extracts geographical coordinates from a text-based weather product message and returns them as an array of [longitude, latitude] pairs.
|
|
1069
|
+
*
|
|
1070
|
+
* @public
|
|
1071
|
+
* @static
|
|
1072
|
+
* @param {string} message
|
|
1073
|
+
* @returns {[number, number][]}
|
|
1074
|
+
*/
|
|
1075
|
+
static textProductToPolygon(message) {
|
|
1076
|
+
const coordinates = [];
|
|
1077
|
+
const latLonMatch = message.match(/LAT\.{3}LON\s+([\d\s]+)/i);
|
|
1078
|
+
if (!latLonMatch || !latLonMatch[1]) return coordinates;
|
|
1079
|
+
const coordStrings = latLonMatch[1].replace(/\n/g, " ").trim().split(/\s+/);
|
|
1080
|
+
for (let i = 0; i < coordStrings.length - 1; i += 2) {
|
|
1081
|
+
const lat = parseFloat(coordStrings[i]) / 100;
|
|
1082
|
+
const lon = -parseFloat(coordStrings[i + 1]) / 100;
|
|
1083
|
+
if (!isNaN(lat) && !isNaN(lon)) {
|
|
1084
|
+
coordinates.push([lon, lat]);
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
if (coordinates.length > 2) {
|
|
1088
|
+
coordinates.push(coordinates[0]);
|
|
1089
|
+
}
|
|
1090
|
+
return coordinates;
|
|
1091
|
+
}
|
|
1092
|
+
/**
|
|
1093
|
+
* textProductToDescription extracts the main description from a text-based weather product message.
|
|
1094
|
+
*
|
|
1095
|
+
* @public
|
|
1096
|
+
* @static
|
|
1097
|
+
* @param {string} message
|
|
1098
|
+
* @param {string} [handle=null]
|
|
1099
|
+
* @returns {string}
|
|
1100
|
+
*/
|
|
1101
|
+
static textProductToDescription(message, handle = null) {
|
|
1102
|
+
const original = message;
|
|
1103
|
+
const dateRegex = /\d{3,4}\s*(AM|PM)?\s*[A-Z]{2,4}\s+[A-Z]{3,}\s+[A-Z]{3,}\s+\d{1,2}\s+\d{4}/gim;
|
|
1104
|
+
const discoveredDates = Array.from(message.matchAll(dateRegex));
|
|
1105
|
+
if (discoveredDates.length) {
|
|
1106
|
+
const lastMatch = discoveredDates[discoveredDates.length - 1][0];
|
|
1107
|
+
const startIdx = message.lastIndexOf(lastMatch);
|
|
1108
|
+
if (startIdx !== -1) {
|
|
1109
|
+
const endIdx = message.indexOf("&&", startIdx);
|
|
1110
|
+
message = message.substring(startIdx + lastMatch.length, endIdx !== -1 ? endIdx : void 0).trimStart();
|
|
1111
|
+
if (message.startsWith("/")) message = message.slice(1).trimStart();
|
|
1112
|
+
if (handle && message.includes(handle)) {
|
|
1113
|
+
const handleIdx = message.indexOf(handle);
|
|
1114
|
+
message = message.substring(handleIdx + handle.length).trimStart();
|
|
1115
|
+
if (message.startsWith("/")) message = message.slice(1).trimStart();
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
} else if (handle) {
|
|
1119
|
+
const handleIdx = message.indexOf(handle);
|
|
1120
|
+
if (handleIdx !== -1) {
|
|
1121
|
+
let afterHandle = message.substring(handleIdx + handle.length).trimStart();
|
|
1122
|
+
if (afterHandle.startsWith("/")) afterHandle = afterHandle.slice(1).trimStart();
|
|
1123
|
+
const latEnd = afterHandle.indexOf("&&");
|
|
1124
|
+
message = latEnd !== -1 ? afterHandle.substring(0, latEnd).trim() : afterHandle.trim();
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
return message.replace(/\s+/g, " ").trim().startsWith("ISSUED TIME...") ? original : message.trim();
|
|
1128
|
+
}
|
|
1129
|
+
/**
|
|
1130
|
+
* awipTextToEvent converts an AWIPS ID prefix from a text-based weather product message to its corresponding event type and prefix.
|
|
1131
|
+
*
|
|
1132
|
+
* @public
|
|
1133
|
+
* @static
|
|
1134
|
+
* @param {string} message
|
|
1135
|
+
* @returns {{ type: any; prefix: any; }}
|
|
1136
|
+
*/
|
|
1137
|
+
static awipTextToEvent(message) {
|
|
1138
|
+
for (const [prefix, type] of Object.entries(definitions.awips)) {
|
|
1139
|
+
if (message.startsWith(prefix)) {
|
|
1140
|
+
return { type, prefix };
|
|
1141
|
+
}
|
|
1142
|
+
}
|
|
1143
|
+
return { type: `XX`, prefix: `XX` };
|
|
1144
|
+
}
|
|
1145
|
+
/**
|
|
1146
|
+
* getXmlValues recursively searches a parsed XML object for specified keys and extracts their values.
|
|
1147
|
+
*
|
|
1148
|
+
* @public
|
|
1149
|
+
* @static
|
|
1150
|
+
* @param {*} parsed
|
|
1151
|
+
* @param {string[]} valuesToExtract
|
|
1152
|
+
* @returns {Record<string, any>}
|
|
1153
|
+
*/
|
|
1154
|
+
static getXmlValues(parsed, valuesToExtract) {
|
|
1155
|
+
const extracted = {};
|
|
1156
|
+
const findValueByKey = (obj, searchKey) => {
|
|
1157
|
+
const results = [];
|
|
1158
|
+
if (obj === null || typeof obj !== "object") {
|
|
1159
|
+
return results;
|
|
1160
|
+
}
|
|
1161
|
+
const searchKeyLower = searchKey.toLowerCase();
|
|
1162
|
+
for (const key in obj) {
|
|
1163
|
+
if (obj.hasOwnProperty(key) && key.toLowerCase() === searchKeyLower) {
|
|
1164
|
+
results.push(obj[key]);
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
if (Array.isArray(obj)) {
|
|
1168
|
+
for (const item of obj) {
|
|
1169
|
+
if (item.valueName && item.valueName.toLowerCase() === searchKeyLower && item.value !== void 0) {
|
|
1170
|
+
results.push(item.value);
|
|
1171
|
+
}
|
|
1172
|
+
const nestedResults = findValueByKey(item, searchKey);
|
|
1173
|
+
results.push(...nestedResults);
|
|
1174
|
+
}
|
|
1175
|
+
}
|
|
1176
|
+
for (const key in obj) {
|
|
1177
|
+
if (obj.hasOwnProperty(key)) {
|
|
1178
|
+
const nestedResults = findValueByKey(obj[key], searchKey);
|
|
1179
|
+
results.push(...nestedResults);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return results;
|
|
1183
|
+
};
|
|
1184
|
+
for (const key of valuesToExtract) {
|
|
1185
|
+
const values = findValueByKey(parsed.alert, key);
|
|
1186
|
+
const uniqueValues = [...new Set(values)];
|
|
1187
|
+
extracted[key] = uniqueValues.length === 0 ? null : uniqueValues.length === 1 ? uniqueValues[0] : uniqueValues;
|
|
1188
|
+
}
|
|
1189
|
+
return extracted;
|
|
1190
|
+
}
|
|
1191
|
+
};
|
|
1192
|
+
var text_default = TextParser;
|
|
1193
|
+
|
|
1194
|
+
// src/parsers/ugc.ts
|
|
1195
|
+
var UGCParser = class {
|
|
1196
|
+
/**
|
|
1197
|
+
* ugcExtractor extracts and parses UGC codes from a given message string.
|
|
1198
|
+
*
|
|
1199
|
+
* @public
|
|
1200
|
+
* @static
|
|
1201
|
+
* @async
|
|
1202
|
+
* @param {string} message
|
|
1203
|
+
* @returns {unknown}
|
|
1204
|
+
*/
|
|
1205
|
+
static ugcExtractor(message) {
|
|
1206
|
+
return __async(this, null, function* () {
|
|
1207
|
+
const header = this.getHeader(message);
|
|
1208
|
+
const zones = this.getZones(header);
|
|
1209
|
+
const expiry = this.getExpiry(message);
|
|
1210
|
+
const locations = yield this.getLocations(zones);
|
|
1211
|
+
const ugc = zones.length > 0 ? { zones, locations, expiry } : null;
|
|
1212
|
+
return ugc;
|
|
1213
|
+
});
|
|
1214
|
+
}
|
|
1215
|
+
/**
|
|
1216
|
+
* getHeader extracts the UGC header from a UGC message string.
|
|
1217
|
+
*
|
|
1218
|
+
* @public
|
|
1219
|
+
* @static
|
|
1220
|
+
* @param {string} message
|
|
1221
|
+
* @returns {*}
|
|
1222
|
+
*/
|
|
1223
|
+
static getHeader(message) {
|
|
1224
|
+
const start = message.search(new RegExp(definitions.expressions.ugc1, "gimu"));
|
|
1225
|
+
const end = message.substring(start).search(new RegExp(definitions.expressions.ugc2, "gimu"));
|
|
1226
|
+
const full = message.substring(start, start + end).replace(/\s+/g, "").slice(0, -1);
|
|
1227
|
+
return full;
|
|
1228
|
+
}
|
|
1229
|
+
/**
|
|
1230
|
+
* getExpiry extracts the expiry date and time from a UGC message and returns it as a Date object.
|
|
1231
|
+
*
|
|
1232
|
+
* @public
|
|
1233
|
+
* @static
|
|
1234
|
+
* @param {string} message
|
|
1235
|
+
* @returns {*}
|
|
1236
|
+
*/
|
|
1237
|
+
static getExpiry(message) {
|
|
1238
|
+
const start = message.match(new RegExp(definitions.expressions.ugc3, "gimu"));
|
|
1239
|
+
if (start != null) {
|
|
1240
|
+
const day = parseInt(start[0].substring(0, 2), 10);
|
|
1241
|
+
const hour = parseInt(start[0].substring(2, 4), 10);
|
|
1242
|
+
const minute = parseInt(start[0].substring(4, 6), 10);
|
|
1243
|
+
const now = /* @__PURE__ */ new Date();
|
|
1244
|
+
const expires = new Date(now.getUTCFullYear(), now.getUTCMonth(), day, hour, minute, 0);
|
|
1245
|
+
return expires;
|
|
1246
|
+
}
|
|
1247
|
+
return null;
|
|
1248
|
+
}
|
|
1249
|
+
/**
|
|
1250
|
+
* getLocations retrieves unique location names for the provided UGC zone codes from the database.
|
|
1251
|
+
*
|
|
1252
|
+
* @public
|
|
1253
|
+
* @static
|
|
1254
|
+
* @async
|
|
1255
|
+
* @param {String[]} zones
|
|
1256
|
+
* @returns {unknown}
|
|
1257
|
+
*/
|
|
1258
|
+
static getLocations(zones) {
|
|
1259
|
+
return __async(this, null, function* () {
|
|
1260
|
+
const locations = [];
|
|
1261
|
+
for (let i = 0; i < zones.length; i++) {
|
|
1262
|
+
const id = zones[i].trim();
|
|
1263
|
+
const located = yield cache.db.prepare(`SELECT location FROM shapefiles WHERE id = ?`).get(id);
|
|
1264
|
+
located != void 0 ? locations.push(located.location) : locations.push(id);
|
|
1265
|
+
}
|
|
1266
|
+
return Array.from(new Set(locations)).sort();
|
|
1267
|
+
});
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* getCoordinates retrieves geographical coordinates for the provided UGC zone codes from the database.
|
|
1271
|
+
*
|
|
1272
|
+
* @public
|
|
1273
|
+
* @static
|
|
1274
|
+
* @param {String[]} zones
|
|
1275
|
+
* @returns {{}}
|
|
1276
|
+
*/
|
|
1277
|
+
static getCoordinates(zones) {
|
|
1278
|
+
let coordinates = [];
|
|
1279
|
+
for (let i = 0; i < zones.length; i++) {
|
|
1280
|
+
const id = zones[i].trim();
|
|
1281
|
+
let located = cache.db.prepare(`SELECT geometry FROM shapefiles WHERE id = ?`).get(id);
|
|
1282
|
+
if (located != void 0) {
|
|
1283
|
+
let geometry = JSON.parse(located.geometry);
|
|
1284
|
+
if ((geometry == null ? void 0 : geometry.type) === "Polygon") {
|
|
1285
|
+
coordinates.push(...geometry.coordinates[0].map((coord) => [coord[0], coord[1]]));
|
|
1286
|
+
break;
|
|
1287
|
+
}
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
return coordinates;
|
|
1291
|
+
}
|
|
1292
|
+
/**
|
|
1293
|
+
* getZones parses a UGC header string and returns an array of individual UGC zone codes.
|
|
1294
|
+
*
|
|
1295
|
+
* @public
|
|
1296
|
+
* @static
|
|
1297
|
+
* @param {string} header
|
|
1298
|
+
* @returns {*}
|
|
1299
|
+
*/
|
|
1300
|
+
static getZones(header) {
|
|
1301
|
+
const ugcSplit = header.split("-");
|
|
1302
|
+
const zones = [];
|
|
1303
|
+
let state = ugcSplit[0].substring(0, 2);
|
|
1304
|
+
let format = ugcSplit[0].substring(2, 3);
|
|
1305
|
+
for (let i = 0; i < ugcSplit.length; i++) {
|
|
1306
|
+
if (/^[A-Z]/.test(ugcSplit[i])) {
|
|
1307
|
+
state = ugcSplit[i].substring(0, 2);
|
|
1308
|
+
if (ugcSplit[i].includes(">")) {
|
|
1309
|
+
let [start, end] = ugcSplit[i].split(">"), startNum = parseInt(start.substring(3), 10), endNum = parseInt(end, 10);
|
|
1310
|
+
for (let j = startNum; j <= endNum; j++) zones.push(`${state}${format}${j.toString().padStart(3, "0")}`);
|
|
1311
|
+
} else zones.push(ugcSplit[i]);
|
|
1312
|
+
continue;
|
|
1313
|
+
}
|
|
1314
|
+
if (ugcSplit[i].includes(">")) {
|
|
1315
|
+
let [start, end] = ugcSplit[i].split(">"), startNum = parseInt(start, 10), endNum = parseInt(end, 10);
|
|
1316
|
+
for (let j = startNum; j <= endNum; j++) zones.push(`${state}${format}${j.toString().padStart(3, "0")}`);
|
|
1317
|
+
} else zones.push(`${state}${format}${ugcSplit[i]}`);
|
|
1318
|
+
}
|
|
1319
|
+
return zones.filter((item) => item !== "");
|
|
1320
|
+
}
|
|
1321
|
+
};
|
|
1322
|
+
var ugc_default = UGCParser;
|
|
1323
|
+
|
|
1324
|
+
// src/parsers/vtec.ts
|
|
1325
|
+
var VtecParser = class {
|
|
1326
|
+
/**
|
|
1327
|
+
* vtecExtractor extracts and parses VTEC codes from a given message string.
|
|
1328
|
+
*
|
|
1329
|
+
* @public
|
|
1330
|
+
* @static
|
|
1331
|
+
* @async
|
|
1332
|
+
* @param {string} message
|
|
1333
|
+
* @returns {unknown}
|
|
1334
|
+
*/
|
|
1335
|
+
static vtecExtractor(message) {
|
|
1336
|
+
return __async(this, null, function* () {
|
|
1337
|
+
const vtecs = [];
|
|
1338
|
+
const matches = message.match(new RegExp(definitions.expressions.vtec, "g"));
|
|
1339
|
+
if (!matches) return null;
|
|
1340
|
+
for (let i = 0; i < matches.length; i++) {
|
|
1341
|
+
const vtec = matches[i];
|
|
1342
|
+
const parts = vtec.split(`.`);
|
|
1343
|
+
const dates = parts[6].split(`-`);
|
|
1344
|
+
vtecs.push({
|
|
1345
|
+
raw: vtec,
|
|
1346
|
+
type: definitions.productTypes[parts[0]],
|
|
1347
|
+
tracking: `${parts[2]}-${parts[3]}-${parts[4]}-${parts[5]}`,
|
|
1348
|
+
event: `${definitions.events[parts[3]]} ${definitions.actions[parts[4]]}`,
|
|
1349
|
+
status: definitions.status[parts[1]],
|
|
1350
|
+
wmo: message.match(new RegExp(definitions.expressions.wmo, "gimu")),
|
|
1351
|
+
expires: this.parseExpiryDate(dates)
|
|
1352
|
+
});
|
|
1353
|
+
}
|
|
1354
|
+
return vtecs;
|
|
1355
|
+
});
|
|
1356
|
+
}
|
|
1357
|
+
/**
|
|
1358
|
+
* parseExpiryDate converts VTEC expiry date format to a standard ISO 8601 format with timezone adjustment.
|
|
1359
|
+
*
|
|
1360
|
+
* @private
|
|
1361
|
+
* @static
|
|
1362
|
+
* @param {String[]} args
|
|
1363
|
+
* @returns {string}
|
|
1364
|
+
*/
|
|
1365
|
+
static parseExpiryDate(args) {
|
|
1366
|
+
if (args[1] == `000000T0000Z`) return `Invalid Date Format`;
|
|
1367
|
+
const expires = `${(/* @__PURE__ */ new Date()).getFullYear().toString().substring(0, 2)}${args[1].substring(0, 2)}-${args[1].substring(2, 4)}-${args[1].substring(4, 6)}T${args[1].substring(7, 9)}:${args[1].substring(9, 11)}:00`;
|
|
1368
|
+
const local = new Date(new Date(expires).getTime() - 4 * 60 * 6e4);
|
|
1369
|
+
const pad = (n) => n.toString().padStart(2, "0");
|
|
1370
|
+
return `${local.getFullYear()}-${pad(local.getMonth() + 1)}-${pad(local.getDate())}T${pad(local.getHours())}:${pad(local.getMinutes())}:00.000-04:00`;
|
|
1371
|
+
}
|
|
1372
|
+
};
|
|
1373
|
+
var vtec_default = VtecParser;
|
|
1374
|
+
|
|
1375
|
+
// src/parsers/types/vtec.ts
|
|
1376
|
+
var VTECAlerts = class {
|
|
1377
|
+
/**
|
|
1378
|
+
* event processes validated VTEC alert messages, extracting relevant information and compiling it into structured event objects.
|
|
1379
|
+
*
|
|
1380
|
+
* @public
|
|
1381
|
+
* @static
|
|
1382
|
+
* @async
|
|
1383
|
+
* @param {types.TypeCompiled} validated
|
|
1384
|
+
* @returns {*}
|
|
1385
|
+
*/
|
|
1386
|
+
static event(validated) {
|
|
1387
|
+
return __async(this, null, function* () {
|
|
1388
|
+
var _a;
|
|
1389
|
+
let processed = [];
|
|
1390
|
+
const messages = (_a = validated.message.split(/(?=\$\$)/g)) == null ? void 0 : _a.map((msg) => msg.trim());
|
|
1391
|
+
if (!messages || messages.length == 0) return;
|
|
1392
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1393
|
+
const tick = performance.now();
|
|
1394
|
+
const message = messages[i];
|
|
1395
|
+
const getVTEC = yield vtec_default.vtecExtractor(message);
|
|
1396
|
+
const getUGC = yield ugc_default.ugcExtractor(message);
|
|
1397
|
+
if (getVTEC != null && getUGC != null) {
|
|
1398
|
+
for (let j = 0; j < getVTEC.length; j++) {
|
|
1399
|
+
const vtec = getVTEC[j];
|
|
1400
|
+
const getBaseProperties = yield events_default.getBaseProperties(message, validated, getUGC, vtec);
|
|
1401
|
+
const getHeader = events_default.getHeader(__spreadValues(__spreadValues({}, validated.attributes), getBaseProperties.attributes), getBaseProperties, vtec);
|
|
1402
|
+
processed.push({
|
|
1403
|
+
preformance: performance.now() - tick,
|
|
1404
|
+
tracking: vtec.tracking,
|
|
1405
|
+
header: getHeader,
|
|
1406
|
+
vtec: vtec.raw,
|
|
1407
|
+
history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: vtec.status }],
|
|
1408
|
+
properties: __spreadValues({ event: vtec.event, parent: vtec.event, action_type: vtec.status }, getBaseProperties)
|
|
1409
|
+
});
|
|
1410
|
+
}
|
|
1411
|
+
}
|
|
1412
|
+
}
|
|
1413
|
+
events_default.validateEvents(processed);
|
|
1414
|
+
});
|
|
1415
|
+
}
|
|
1416
|
+
};
|
|
1417
|
+
var vtec_default2 = VTECAlerts;
|
|
1418
|
+
|
|
1419
|
+
// src/parsers/types/ugc.ts
|
|
1420
|
+
var UGCAlerts = class {
|
|
1421
|
+
/**
|
|
1422
|
+
* getTracking generates a unique tracking identifier based on the sender's ICAO code and a hash of the UGC zones.
|
|
1423
|
+
*
|
|
1424
|
+
* @private
|
|
1425
|
+
* @static
|
|
1426
|
+
* @param {types.BaseProperties} baseProperties
|
|
1427
|
+
* @param {string[]} zones
|
|
1428
|
+
* @returns {string}
|
|
1429
|
+
*/
|
|
1430
|
+
static getTracking(baseProperties, zones) {
|
|
1431
|
+
return `${baseProperties.sender_icao} (${packages.crypto.createHash("md5").update(zones.join(``)).digest("hex")}})`;
|
|
1432
|
+
}
|
|
1433
|
+
/**
|
|
1434
|
+
* getEvent determines the event name based on offshore definitions or formats it from the attributes.
|
|
1435
|
+
*
|
|
1436
|
+
* @private
|
|
1437
|
+
* @static
|
|
1438
|
+
* @param {string} message
|
|
1439
|
+
* @param {Record<string, any>} attributes
|
|
1440
|
+
* @returns {*}
|
|
1441
|
+
*/
|
|
1442
|
+
static getEvent(message, attributes) {
|
|
1443
|
+
const offshoreEvent = Object.keys(definitions.offshore).find((event) => message.toLowerCase().includes(event.toLowerCase()));
|
|
1444
|
+
if (offshoreEvent != void 0) return Object.keys(definitions.offshore).find((event) => message.toLowerCase().includes(event.toLowerCase()));
|
|
1445
|
+
return attributes.type.split(`-`).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `);
|
|
1446
|
+
}
|
|
1447
|
+
/**
|
|
1448
|
+
* event processes validated UGC alert messages, extracting relevant information and compiling it into structured event objects.
|
|
1449
|
+
*
|
|
1450
|
+
* @public
|
|
1451
|
+
* @static
|
|
1452
|
+
* @async
|
|
1453
|
+
* @param {types.TypeCompiled} validated
|
|
1454
|
+
* @returns {*}
|
|
1455
|
+
*/
|
|
1456
|
+
static event(validated) {
|
|
1457
|
+
return __async(this, null, function* () {
|
|
1458
|
+
var _a;
|
|
1459
|
+
let processed = [];
|
|
1460
|
+
const messages = (_a = validated.message.split(/(?=\$\$|ISSUED TIME...|=================================================)/g)) == null ? void 0 : _a.map((msg) => msg.trim());
|
|
1461
|
+
if (!messages || messages.length == 0) return;
|
|
1462
|
+
for (let i = 0; i < messages.length; i++) {
|
|
1463
|
+
const tick = performance.now();
|
|
1464
|
+
const message = messages[i];
|
|
1465
|
+
const getUGC = yield ugc_default.ugcExtractor(message);
|
|
1466
|
+
if (getUGC != null) {
|
|
1467
|
+
const getBaseProperties = yield events_default.getBaseProperties(message, validated, getUGC);
|
|
1468
|
+
const getHeader = events_default.getHeader(__spreadValues(__spreadValues({}, validated.attributes), getBaseProperties.attributes), getBaseProperties);
|
|
1469
|
+
const getEvent = this.getEvent(message, getBaseProperties.attributes.getAwip);
|
|
1470
|
+
processed.push({
|
|
1471
|
+
preformance: performance.now() - tick,
|
|
1472
|
+
tracking: this.getTracking(getBaseProperties, getUGC.zones),
|
|
1473
|
+
header: getHeader,
|
|
1474
|
+
vtec: `N/A`,
|
|
1475
|
+
history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: `Issued` }],
|
|
1476
|
+
properties: __spreadValues({ event: getEvent, parent: getEvent, action_type: `Issued` }, getBaseProperties)
|
|
1477
|
+
});
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
events_default.validateEvents(processed);
|
|
1481
|
+
});
|
|
1482
|
+
}
|
|
1483
|
+
};
|
|
1484
|
+
var ugc_default2 = UGCAlerts;
|
|
1485
|
+
|
|
1486
|
+
// src/parsers/types/cap.ts
|
|
1487
|
+
var CapAlerts = class {
|
|
1488
|
+
static getTracking(extracted) {
|
|
1489
|
+
return extracted.vtec ? (() => {
|
|
1490
|
+
const vtecValue = Array.isArray(extracted.vtec) ? extracted.vtec[0] : extracted.vtec;
|
|
1491
|
+
const splitVTEC = vtecValue.split(".");
|
|
1492
|
+
return `${splitVTEC[2]}-${splitVTEC[3]}-${splitVTEC[4]}-${splitVTEC[5]}`;
|
|
1493
|
+
})() : `${extracted.wmoidentifier} (${extracted.ugc})`;
|
|
1494
|
+
}
|
|
1495
|
+
static event(validated) {
|
|
1496
|
+
return __async(this, null, function* () {
|
|
1497
|
+
var _a;
|
|
1498
|
+
let processed = [];
|
|
1499
|
+
const messages = (_a = validated.message.match(/<\?xml[\s\S]*?<\/alert>/g)) == null ? void 0 : _a.map((msg) => msg.trim());
|
|
1500
|
+
if (messages == null || messages.length === 0) return;
|
|
1501
|
+
for (let message of messages) {
|
|
1502
|
+
const tick = performance.now();
|
|
1503
|
+
message = message.substring(message.indexOf(`<?xml version="1.0"`), message.lastIndexOf(`>`) + 1);
|
|
1504
|
+
const parser = new packages.xml2js.Parser({ explicitArray: false, mergeAttrs: true, trim: true });
|
|
1505
|
+
const parsed = yield parser.parseStringPromise(message);
|
|
1506
|
+
if (parsed == null || parsed.alert == null) continue;
|
|
1507
|
+
const extracted = text_default.getXmlValues(parsed, [
|
|
1508
|
+
`vtec`,
|
|
1509
|
+
`wmoidentifier`,
|
|
1510
|
+
`ugc`,
|
|
1511
|
+
`areadesc`,
|
|
1512
|
+
`expires`,
|
|
1513
|
+
`sent`,
|
|
1514
|
+
`msgtype`,
|
|
1515
|
+
`description`,
|
|
1516
|
+
`event`,
|
|
1517
|
+
`sendername`,
|
|
1518
|
+
`tornadodetection`,
|
|
1519
|
+
`polygon`,
|
|
1520
|
+
`maxHailSize`,
|
|
1521
|
+
`maxWindGust`,
|
|
1522
|
+
`thunderstormdamagethreat`,
|
|
1523
|
+
`tornadodamagethreat`,
|
|
1524
|
+
`waterspoutdetection`,
|
|
1525
|
+
`flooddetection`
|
|
1526
|
+
]);
|
|
1527
|
+
const getHeader = events_default.getHeader(__spreadValues({}, validated.attributes));
|
|
1528
|
+
const getSource = text_default.textProductToString(extracted.description, `SOURCE...`, [`.`]) || `N/A`;
|
|
1529
|
+
processed.push({
|
|
1530
|
+
preformance: performance.now() - tick,
|
|
1531
|
+
tracking: this.getTracking(extracted),
|
|
1532
|
+
header: getHeader,
|
|
1533
|
+
vtec: extracted.vtec || `N/A`,
|
|
1534
|
+
history: [{ description: extracted.description || `N/A`, issued: extracted.sent ? new Date(extracted.sent).toLocaleString() : `N/A`, type: extracted.msgtype || `N/A` }],
|
|
1535
|
+
properties: {
|
|
1536
|
+
locations: extracted.areadesc || `N/A`,
|
|
1537
|
+
event: extracted.event || `N/A`,
|
|
1538
|
+
issued: extracted.sent ? new Date(extracted.sent).toLocaleString() : `N/A`,
|
|
1539
|
+
expires: extracted.expires ? new Date(extracted.expires).toLocaleString() : `N/A`,
|
|
1540
|
+
parent: extracted.event || `N/A`,
|
|
1541
|
+
action_type: extracted.msgtype || `N/A`,
|
|
1542
|
+
description: extracted.description || `N/A`,
|
|
1543
|
+
sender_name: extracted.sendername || `N/A`,
|
|
1544
|
+
sender_icao: extracted.wmoidentifier ? extracted.wmoidentifier.substring(extracted.wmoidentifier.length - 4) : `N/A`,
|
|
1545
|
+
attributes: validated.attributes,
|
|
1546
|
+
geocode: {
|
|
1547
|
+
UGC: [extracted.ugc]
|
|
1548
|
+
},
|
|
1549
|
+
parameters: {
|
|
1550
|
+
wmo: extracted.wmoidentifier || `N/A`,
|
|
1551
|
+
source: getSource,
|
|
1552
|
+
max_hail_size: extracted.maxHailSize || `N/A`,
|
|
1553
|
+
max_wind_gust: extracted.maxWindGust || `N/A`,
|
|
1554
|
+
damage_threat: extracted.thunderstormdamagethreat || `N/A`,
|
|
1555
|
+
tornado_detection: extracted.tornadodetection || extracted.waterspoutdetection || `N/A`,
|
|
1556
|
+
flood_detection: extracted.flooddetection || `N/A`,
|
|
1557
|
+
discussion_tornado_intensity: `N/A`,
|
|
1558
|
+
discussion_wind_intensity: `N/A`,
|
|
1559
|
+
discussion_hail_intensity: `N/A`
|
|
1560
|
+
},
|
|
1561
|
+
geometry: extracted.polygon ? { type: `Polygon`, coordinates: extracted.polygon.split(` `).map((coord) => coord.split(`,`).map((num) => parseFloat(num))) } : null
|
|
1562
|
+
}
|
|
1563
|
+
});
|
|
1564
|
+
}
|
|
1565
|
+
events_default.validateEvents(processed);
|
|
1566
|
+
});
|
|
1567
|
+
}
|
|
1568
|
+
};
|
|
1569
|
+
var cap_default = CapAlerts;
|
|
1570
|
+
|
|
1571
|
+
// src/parsers/types/api.ts
|
|
1572
|
+
var APIAlerts = class {
|
|
1573
|
+
static getTracking(extracted) {
|
|
1574
|
+
return extracted.vtec ? (() => {
|
|
1575
|
+
const vtecValue = Array.isArray(extracted.vtec) ? extracted.vtec[0] : extracted.vtec;
|
|
1576
|
+
const splitVTEC = vtecValue.split(".");
|
|
1577
|
+
return `${splitVTEC[2]}-${splitVTEC[3]}-${splitVTEC[4]}-${splitVTEC[5]}`;
|
|
1578
|
+
})() : `${extracted.wmoidentifier} (${extracted.ugc})`;
|
|
1579
|
+
}
|
|
1580
|
+
static getICAO(vtec) {
|
|
1581
|
+
var _a, _b;
|
|
1582
|
+
const icao = vtec ? vtec.split(`.`)[2] : `N/A`;
|
|
1583
|
+
const name = (_b = (_a = definitions.ICAO) == null ? void 0 : _a[icao]) != null ? _b : `N/A`;
|
|
1584
|
+
return { icao, name };
|
|
1585
|
+
}
|
|
1586
|
+
static event(validated) {
|
|
1587
|
+
return __async(this, null, function* () {
|
|
1588
|
+
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n, _o, _p, _q, _r, _s, _t, _u, _v, _w, _x, _y, _z, _A, _B, _C, _D, _E, _F, _G, _H, _I, _J, _K, _L, _M, _N, _O, _P, _Q, _R, _S, _T, _U, _V, _W, _X, _Y, _Z, __, _$, _aa, _ba, _ca;
|
|
1589
|
+
let processed = [];
|
|
1590
|
+
const messages = Object.values(JSON.parse(validated.message).features);
|
|
1591
|
+
for (let feature of messages) {
|
|
1592
|
+
const tick = performance.now();
|
|
1593
|
+
const getVTEC = (_d = (_c = (_b = (_a = feature == null ? void 0 : feature.properties) == null ? void 0 : _a.parameters) == null ? void 0 : _b.VTEC) == null ? void 0 : _c[0]) != null ? _d : null;
|
|
1594
|
+
const getWmo = (_g = (_f = (_e = feature == null ? void 0 : feature.properties) == null ? void 0 : _e.parameters) == null ? void 0 : _f.WMOidentifier[0]) != null ? _g : null;
|
|
1595
|
+
const getUgc = (_j = (_i = (_h = feature == null ? void 0 : feature.properties) == null ? void 0 : _h.geocode) == null ? void 0 : _i.UGC) != null ? _j : null;
|
|
1596
|
+
const getHeadline = (_n = (_m = (_l = (_k = feature == null ? void 0 : feature.properties) == null ? void 0 : _k.parameters) == null ? void 0 : _l.NWSheadline) == null ? void 0 : _m[0]) != null ? _n : "";
|
|
1597
|
+
const getDescription = `${getHeadline} ${(_p = (_o = feature == null ? void 0 : feature.properties) == null ? void 0 : _o.description) != null ? _p : ``}`;
|
|
1598
|
+
const getAWIP = (_t = (_s = (_r = (_q = feature == null ? void 0 : feature.properties) == null ? void 0 : _q.parameters) == null ? void 0 : _r.AWIPSidentifier) == null ? void 0 : _s[0]) != null ? _t : null;
|
|
1599
|
+
const getHeader = events_default.getHeader(__spreadValues({}, { getAwip: { prefix: getAWIP == null ? void 0 : getAWIP.slice(0, -3) } }));
|
|
1600
|
+
const getSource = text_default.textProductToString(getDescription, `SOURCE...`, [`.`]) || `N/A`;
|
|
1601
|
+
const getOffice = this.getICAO(getVTEC || ``);
|
|
1602
|
+
processed.push({
|
|
1603
|
+
preformance: performance.now() - tick,
|
|
1604
|
+
tracking: this.getTracking({ vtec: getVTEC, wmoidentifier: getWmo, ugc: getUgc ? getUgc.join(`,`) : null }),
|
|
1605
|
+
header: getHeader,
|
|
1606
|
+
vtec: getVTEC || `N/A`,
|
|
1607
|
+
history: [{
|
|
1608
|
+
description: (_v = (_u = feature == null ? void 0 : feature.properties) == null ? void 0 : _u.description) != null ? _v : `N/A`,
|
|
1609
|
+
action: (_x = (_w = feature == null ? void 0 : feature.properties) == null ? void 0 : _w.messageType) != null ? _x : `N/A`,
|
|
1610
|
+
time: ((_y = feature == null ? void 0 : feature.properties) == null ? void 0 : _y.sent) ? new Date((_z = feature == null ? void 0 : feature.properties) == null ? void 0 : _z.sent).toLocaleString() : `N/A`
|
|
1611
|
+
}],
|
|
1612
|
+
properties: {
|
|
1613
|
+
locations: (_B = (_A = feature == null ? void 0 : feature.properties) == null ? void 0 : _A.areaDesc) != null ? _B : `N/A`,
|
|
1614
|
+
event: (_D = (_C = feature == null ? void 0 : feature.properties) == null ? void 0 : _C.event) != null ? _D : `N/A`,
|
|
1615
|
+
issued: ((_E = feature == null ? void 0 : feature.properties) == null ? void 0 : _E.sent) ? new Date((_F = feature == null ? void 0 : feature.properties) == null ? void 0 : _F.sent).toLocaleString() : `N/A`,
|
|
1616
|
+
expires: ((_G = feature == null ? void 0 : feature.properties) == null ? void 0 : _G.expires) ? new Date((_H = feature == null ? void 0 : feature.properties) == null ? void 0 : _H.expires).toLocaleString() : `N/A`,
|
|
1617
|
+
parent: (_J = (_I = feature == null ? void 0 : feature.properties) == null ? void 0 : _I.event) != null ? _J : `N/A`,
|
|
1618
|
+
action_type: (_L = (_K = feature == null ? void 0 : feature.properties) == null ? void 0 : _K.messageType) != null ? _L : `N/A`,
|
|
1619
|
+
description: (_N = (_M = feature == null ? void 0 : feature.properties) == null ? void 0 : _M.description) != null ? _N : `N/A`,
|
|
1620
|
+
sender_name: getOffice.name || `N/A`,
|
|
1621
|
+
sender_icao: getOffice.icao || `N/A`,
|
|
1622
|
+
attributes: validated.attributes,
|
|
1623
|
+
geocode: {
|
|
1624
|
+
UGC: (_Q = (_P = (_O = feature == null ? void 0 : feature.properties) == null ? void 0 : _O.geocode) == null ? void 0 : _P.UGC) != null ? _Q : [`XX000`]
|
|
1625
|
+
},
|
|
1626
|
+
parameters: {
|
|
1627
|
+
wmo: ((_T = (_S = (_R = feature == null ? void 0 : feature.properties) == null ? void 0 : _R.parameters) == null ? void 0 : _S.WMOidentifier) == null ? void 0 : _T[0]) || getWmo || `N/A`,
|
|
1628
|
+
source: getSource,
|
|
1629
|
+
max_hail_size: ((_V = (_U = feature == null ? void 0 : feature.properties) == null ? void 0 : _U.parameters) == null ? void 0 : _V.maxHailSize) || `N/A`,
|
|
1630
|
+
max_wind_gust: ((_X = (_W = feature == null ? void 0 : feature.properties) == null ? void 0 : _W.parameters) == null ? void 0 : _X.maxWindGust) || `N/A`,
|
|
1631
|
+
damage_threat: ((_Z = (_Y = feature == null ? void 0 : feature.properties) == null ? void 0 : _Y.parameters) == null ? void 0 : _Z.thunderstormDamageThreat) || [`N/A`],
|
|
1632
|
+
tornado_detection: ((_$ = (__ = feature == null ? void 0 : feature.properties) == null ? void 0 : __.parameters) == null ? void 0 : _$.tornadoDetection) || [`N/A`],
|
|
1633
|
+
flood_detection: ((_ba = (_aa = feature == null ? void 0 : feature.properties) == null ? void 0 : _aa.parameters) == null ? void 0 : _ba.floodDetection) || [`N/A`],
|
|
1634
|
+
discussion_tornado_intensity: "N/A",
|
|
1635
|
+
peakWindGust: `N/A`,
|
|
1636
|
+
peakHailSize: `N/A`
|
|
1637
|
+
},
|
|
1638
|
+
geometry: (_ca = feature == null ? void 0 : feature.geometry) != null ? _ca : null
|
|
1639
|
+
}
|
|
1640
|
+
});
|
|
1641
|
+
}
|
|
1642
|
+
events_default.validateEvents(processed);
|
|
1643
|
+
});
|
|
1644
|
+
}
|
|
1645
|
+
};
|
|
1646
|
+
var api_default = APIAlerts;
|
|
1647
|
+
|
|
1648
|
+
// src/parsers/stanza.ts
|
|
1649
|
+
var StanzaParser = class {
|
|
1650
|
+
/**
|
|
1651
|
+
* validate handles the validation of incoming XMPP stanzas to ensure they contain valid alert data.
|
|
1652
|
+
* You can also feed debug / cache data directly into this function by specifying the second parameter
|
|
1653
|
+
* which is the attributes object that would normally be parsed from the XMPP stanza.
|
|
1654
|
+
*
|
|
1655
|
+
* @public
|
|
1656
|
+
* @static
|
|
1657
|
+
* @param {*} stanza
|
|
1658
|
+
* @param {(boolean | types.TypeAttributes)} [isDebug=false]
|
|
1659
|
+
* @returns {{ message: any; attributes: types.TypeAttributes; isCap: any; isVtec: boolean; isCapDescription: any; awipsType: any; isApi: boolean; ignore: boolean; }}
|
|
1660
|
+
*/
|
|
1661
|
+
static validate(stanza, isDebug = false) {
|
|
1662
|
+
var _a;
|
|
1663
|
+
if (isDebug !== false) {
|
|
1664
|
+
const vTypes = isDebug;
|
|
1665
|
+
const message = stanza;
|
|
1666
|
+
const attributes = vTypes;
|
|
1667
|
+
const isCap = (_a = vTypes.isCap) != null ? _a : message.includes(`<?xml`);
|
|
1668
|
+
const isCapDescription = message.includes(`<areaDesc>`);
|
|
1669
|
+
const isVtec = message.match(definitions.expressions.vtec) != null;
|
|
1670
|
+
const isUGC = message.match(definitions.expressions.ugc1) != null;
|
|
1671
|
+
const awipsType = this.getType(attributes);
|
|
1672
|
+
return { message, attributes, isCap, isVtec, isUGC, isCapDescription, awipsType, isApi: false, ignore: false };
|
|
1673
|
+
}
|
|
1674
|
+
if (stanza.is(`message`)) {
|
|
1675
|
+
let cb = stanza.getChild(`x`);
|
|
1676
|
+
if (cb && cb.children) {
|
|
1677
|
+
let message = unescape(cb.children[0]);
|
|
1678
|
+
let attributes = cb.attrs;
|
|
1679
|
+
if (attributes.awipsid && attributes.awipsid.length > 1) {
|
|
1680
|
+
const isCap = message.includes(`<?xml`);
|
|
1681
|
+
const isCapDescription = message.includes(`<areaDesc>`);
|
|
1682
|
+
const isVtec = message.match(definitions.expressions.vtec) != null;
|
|
1683
|
+
const isUGC = message.match(definitions.expressions.ugc1) != null;
|
|
1684
|
+
const awipsType = this.getType(attributes);
|
|
1685
|
+
const isApi = false;
|
|
1686
|
+
this.cache({ message, attributes, isCap, isVtec, awipsType: awipsType.type, awipsPrefix: awipsType.prefix });
|
|
1687
|
+
return { message, attributes, isCap, isApi, isVtec, isUGC, isCapDescription, awipsType, ignore: false };
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
}
|
|
1691
|
+
return { message: null, attributes: null, isApi: null, isCap: null, isVtec: null, isUGC: null, isCapDescription: null, awipsType: null, ignore: true };
|
|
1692
|
+
}
|
|
1693
|
+
/**
|
|
1694
|
+
* getType determines the AWIPS type of the alert based on its attributes, specifically the awipsid.
|
|
1695
|
+
* If no matching type is found, it defaults to 'default'.
|
|
1696
|
+
*
|
|
1697
|
+
* @private
|
|
1698
|
+
* @static
|
|
1699
|
+
* @param {unknown} attributes
|
|
1700
|
+
* @returns {*}
|
|
1701
|
+
*/
|
|
1702
|
+
static getType(attributes) {
|
|
1703
|
+
const attrs = attributes;
|
|
1704
|
+
if (!attrs || !attrs.awipsid) return { type: `XX`, prefix: `XX` };
|
|
1705
|
+
for (const [prefix, type] of Object.entries(definitions.awips)) {
|
|
1706
|
+
if (attrs.awipsid.startsWith(prefix)) {
|
|
1707
|
+
return { type, prefix };
|
|
1708
|
+
}
|
|
1709
|
+
}
|
|
1710
|
+
return { type: `XX`, prefix: `XX` };
|
|
1711
|
+
}
|
|
1712
|
+
/**
|
|
1713
|
+
* cache stores the compiled alert data into a cache file if caching is enabled in the settings.
|
|
1714
|
+
*
|
|
1715
|
+
* @private
|
|
1716
|
+
* @static
|
|
1717
|
+
* @param {unknown} compiled
|
|
1718
|
+
*/
|
|
1719
|
+
static cache(compiled) {
|
|
1720
|
+
const data = compiled;
|
|
1721
|
+
const settings2 = settings;
|
|
1722
|
+
if (!settings2.NoaaWeatherWireService.cache.directory) return;
|
|
1723
|
+
if (!packages.fs.existsSync(settings2.NoaaWeatherWireService.cache.directory)) {
|
|
1724
|
+
packages.fs.mkdirSync(settings2.NoaaWeatherWireService.cache.directory, { recursive: true });
|
|
1725
|
+
}
|
|
1726
|
+
data.message = data.message.replace(/\$\$/g, `
|
|
1727
|
+
STANZA ATTRIBUTES...${JSON.stringify(data.attributes)}
|
|
1728
|
+
ISSUED TIME...${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1729
|
+
$$$
|
|
1730
|
+
`);
|
|
1731
|
+
if (!data.message.includes(`STANZA ATTRIBUTES...`)) {
|
|
1732
|
+
data.message += `
|
|
1733
|
+
STANZA ATTRIBUTES...${JSON.stringify(data.attributes)}
|
|
1734
|
+
ISSUED TIME...${(/* @__PURE__ */ new Date()).toISOString()}
|
|
1735
|
+
$$
|
|
1736
|
+
`;
|
|
1737
|
+
}
|
|
1738
|
+
packages.fs.appendFileSync(`${settings2.NoaaWeatherWireService.cache.directory}/category-${data.awipsPrefix}-${data.awipsType}s-${data.isCap ? `cap` : `raw`}${data.isVtec ? `-vtec` : ``}.bin`, `=================================================
|
|
1739
|
+
${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}
|
|
1740
|
+
=================================================
|
|
1741
|
+
${data.message}`, "utf8");
|
|
1742
|
+
packages.fs.appendFileSync(`${settings2.NoaaWeatherWireService.cache.directory}/cache-${data.isCap ? `cap` : `raw`}${data.isVtec ? `-vtec` : ``}.bin`, `=================================================
|
|
1743
|
+
${(/* @__PURE__ */ new Date()).toISOString().replace(/[:.]/g, "-")}
|
|
1744
|
+
=================================================
|
|
1745
|
+
${data.message}`, "utf8");
|
|
1746
|
+
}
|
|
1747
|
+
};
|
|
1748
|
+
var stanza_default = StanzaParser;
|
|
1749
|
+
|
|
1750
|
+
// src/database.ts
|
|
1751
|
+
var Database = class {
|
|
1752
|
+
/**
|
|
1753
|
+
* handleAlertCache stores a unique alert in the SQLite database and ensures the total number of alerts does not exceed 5000.
|
|
1754
|
+
*
|
|
1755
|
+
* @public
|
|
1756
|
+
* @static
|
|
1757
|
+
* @async
|
|
1758
|
+
* @param {*} alert
|
|
1759
|
+
* @returns {*}
|
|
1760
|
+
*/
|
|
1761
|
+
static stanzaCacheImport(stanza) {
|
|
1762
|
+
return __async(this, null, function* () {
|
|
1763
|
+
const settings2 = settings;
|
|
1764
|
+
cache.db.prepare(`INSERT OR IGNORE INTO stanzas (stanza) VALUES (?)`).run(stanza);
|
|
1765
|
+
const count = cache.db.prepare(`SELECT COUNT(*) as total FROM stanzas`).get();
|
|
1766
|
+
if (count.total > settings2.NoaaWeatherWireService.cache.maxHistory) {
|
|
1767
|
+
cache.db.prepare(`DELETE FROM stanzas WHERE rowid IN (SELECT rowid FROM stanzas ORDER BY rowid ASC LIMIT ?)`).run(count.total - settings2.NoaaWeatherWireService.cache.maxHistory / 2);
|
|
1768
|
+
}
|
|
1769
|
+
});
|
|
1770
|
+
}
|
|
1771
|
+
/**
|
|
1772
|
+
* loadDatabase initializes the SQLite database and imports shapefile data if the database or table does not exist.
|
|
1773
|
+
*
|
|
1774
|
+
* @public
|
|
1775
|
+
* @static
|
|
1776
|
+
* @async
|
|
1777
|
+
* @returns {Promise<void>}
|
|
1778
|
+
*/
|
|
1779
|
+
static loadDatabase() {
|
|
1780
|
+
return __async(this, null, function* () {
|
|
1781
|
+
const settings2 = settings;
|
|
1782
|
+
try {
|
|
1783
|
+
if (!packages.fs.existsSync(settings2.database)) {
|
|
1784
|
+
packages.fs.writeFileSync(settings2.database, "");
|
|
1785
|
+
}
|
|
1786
|
+
cache.db = new packages.sqlite3(settings2.database);
|
|
1787
|
+
const shapfileTable = cache.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='shapefiles'`).get();
|
|
1788
|
+
const stanzaTable = cache.db.prepare(`SELECT name FROM sqlite_master WHERE type='table' AND name='stanzas'`).get();
|
|
1789
|
+
if (!stanzaTable) {
|
|
1790
|
+
cache.db.prepare(`CREATE TABLE stanzas (id INTEGER PRIMARY KEY AUTOINCREMENT, stanza TEXT)`).run();
|
|
1791
|
+
}
|
|
1792
|
+
if (!shapfileTable) {
|
|
1793
|
+
cache.db.prepare(`CREATE TABLE shapefiles (id TEXT PRIMARY KEY, location TEXT, geometry TEXT)`).run();
|
|
1794
|
+
console.log(definitions.messages.shapefile_creation);
|
|
1795
|
+
for (const shape of definitions.shapefiles) {
|
|
1796
|
+
const { id, file } = shape;
|
|
1797
|
+
const filepath = packages.path.join(__dirname, `../../shapefiles`, file);
|
|
1798
|
+
const { features } = yield packages.shapefile.read(filepath, filepath);
|
|
1799
|
+
console.log(`Importing ${features.length} entries from ${file}...`);
|
|
1800
|
+
const insertStmt = cache.db.prepare(`INSERT OR REPLACE INTO shapefiles (id, location, geometry)VALUES (?, ?, ?)`);
|
|
1801
|
+
const insertTransaction = cache.db.transaction((entries) => {
|
|
1802
|
+
for (const feature of entries) {
|
|
1803
|
+
const { properties, geometry } = feature;
|
|
1804
|
+
let final, location;
|
|
1805
|
+
switch (true) {
|
|
1806
|
+
case !!properties.FIPS:
|
|
1807
|
+
final = `${properties.STATE}${id}${properties.FIPS.substring(2)}`;
|
|
1808
|
+
location = `${properties.COUNTYNAME}, ${properties.STATE}`;
|
|
1809
|
+
break;
|
|
1810
|
+
case !!properties.FULLSTAID:
|
|
1811
|
+
final = `${properties.ST}${id}${properties.WFO}`;
|
|
1812
|
+
location = `${properties.CITY}, ${properties.STATE}`;
|
|
1813
|
+
break;
|
|
1814
|
+
case !!properties.STATE:
|
|
1815
|
+
final = `${properties.STATE}${id}${properties.ZONE}`;
|
|
1816
|
+
location = `${properties.NAME}, ${properties.STATE}`;
|
|
1817
|
+
break;
|
|
1818
|
+
default:
|
|
1819
|
+
final = properties.ID;
|
|
1820
|
+
location = properties.NAME;
|
|
1821
|
+
break;
|
|
1822
|
+
}
|
|
1823
|
+
insertStmt.run(final, location, JSON.stringify(geometry));
|
|
1824
|
+
}
|
|
1825
|
+
});
|
|
1826
|
+
yield insertTransaction(features);
|
|
1827
|
+
}
|
|
1828
|
+
console.log(definitions.messages.shapefile_creation_finished);
|
|
1829
|
+
}
|
|
1830
|
+
} catch (error) {
|
|
1831
|
+
cache.events.emit("onError", { code: "error-load-database", message: `Failed to load database: ${error.message}` });
|
|
1832
|
+
}
|
|
1833
|
+
});
|
|
1834
|
+
}
|
|
1835
|
+
};
|
|
1836
|
+
var database_default = Database;
|
|
1837
|
+
|
|
1838
|
+
// src/xmpp.ts
|
|
1839
|
+
var Xmpp = class {
|
|
1840
|
+
/**
|
|
1841
|
+
* isSessionReconnectionEligible checks if the XMPP session is eligible for reconnection based on the last
|
|
1842
|
+
* received stanza time and current interval.
|
|
1843
|
+
*
|
|
1844
|
+
* @public
|
|
1845
|
+
* @static
|
|
1846
|
+
* @async
|
|
1847
|
+
* @param {number} currentInterval
|
|
1848
|
+
* @returns {Promise<void>}
|
|
1849
|
+
*/
|
|
1850
|
+
static isSessionReconnectionEligible(currentInterval) {
|
|
1851
|
+
return __async(this, null, function* () {
|
|
1852
|
+
const settings2 = settings;
|
|
1853
|
+
if ((cache.isConnected || cache.sigHalt) && cache.session) {
|
|
1854
|
+
const lastStanza = Date.now() - cache.lastStanza;
|
|
1855
|
+
if (lastStanza >= currentInterval * 1e3) {
|
|
1856
|
+
if (!cache.attemptingReconnect) {
|
|
1857
|
+
cache.attemptingReconnect = true;
|
|
1858
|
+
cache.isConnected = false;
|
|
1859
|
+
cache.totalReconnects += 1;
|
|
1860
|
+
cache.events.emit(`onReconnect`, { reconnects: cache.totalReconnects, lastStanza, lastName: settings2.NoaaWeatherWireService.clientCredentials.nickname });
|
|
1861
|
+
yield cache.session.stop().catch(() => {
|
|
1862
|
+
});
|
|
1863
|
+
yield cache.session.start().catch(() => {
|
|
1864
|
+
});
|
|
1865
|
+
}
|
|
1866
|
+
}
|
|
1867
|
+
}
|
|
1868
|
+
});
|
|
1869
|
+
}
|
|
1870
|
+
/**
|
|
1871
|
+
* deploySession initializes and starts the XMPP client session, setting up event listeners for
|
|
1872
|
+
* connection management and message handling. This function is specifically tailored for
|
|
1873
|
+
* NoaaWeatherWireService and connects to their XMPP server.
|
|
1874
|
+
*
|
|
1875
|
+
* @public
|
|
1876
|
+
* @static
|
|
1877
|
+
* @async
|
|
1878
|
+
* @returns {Promise<void>}
|
|
1879
|
+
*/
|
|
1880
|
+
static deploySession() {
|
|
1881
|
+
return __async(this, null, function* () {
|
|
1882
|
+
var _a, _b;
|
|
1883
|
+
const settings2 = settings;
|
|
1884
|
+
cache.session = packages.xmpp.client({
|
|
1885
|
+
service: `xmpp://nwws-oi.weather.gov`,
|
|
1886
|
+
domain: `nwws-oi.weather.gov`,
|
|
1887
|
+
username: settings2.NoaaWeatherWireService.clientCredentials.username,
|
|
1888
|
+
password: settings2.NoaaWeatherWireService.clientCredentials.password
|
|
1889
|
+
});
|
|
1890
|
+
(_b = (_a = settings2.NoaaWeatherWireService.clientCredentials).nickname) != null ? _b : _a.nickname = settings2.NoaaWeatherWireService.clientCredentials.username;
|
|
1891
|
+
cache.session.on(`online`, (address) => __async(null, null, function* () {
|
|
1892
|
+
if (cache.lastConnect && Date.now() - cache.lastConnect < 10 * 1e3) {
|
|
1893
|
+
cache.sigHalt = true;
|
|
1894
|
+
utils_default.sleep(2 * 1e3).then(() => __async(null, null, function* () {
|
|
1895
|
+
yield cache.session.stop();
|
|
1896
|
+
}));
|
|
1897
|
+
cache.events.emit(`onError`, { code: `error-reconnecting-too-fast`, message: `The client is attempting to reconnect too fast. Please wait a few seconds before trying again.` });
|
|
1898
|
+
return;
|
|
1899
|
+
}
|
|
1900
|
+
cache.isConnected = true;
|
|
1901
|
+
cache.sigHalt = false;
|
|
1902
|
+
cache.lastConnect = Date.now();
|
|
1903
|
+
cache.session.send(packages.xmpp.xml("presence", { to: `nwws@conference.nwws-oi.weather.gov/${settings2.NoaaWeatherWireService.clientCredentials.nickname}`, xmlns: "http://jabber.org/protocol/muc" }));
|
|
1904
|
+
cache.session.send(packages.xmpp.xml("presence", { to: `nwws@conference.nwws-oi.weather.gov`, type: "available" }));
|
|
1905
|
+
cache.events.emit(`onConnection`, settings2.NoaaWeatherWireService.clientCredentials.nickname);
|
|
1906
|
+
if (cache.attemptingReconnect) {
|
|
1907
|
+
utils_default.sleep(15 * 1e3).then(() => {
|
|
1908
|
+
cache.attemptingReconnect = false;
|
|
1909
|
+
});
|
|
1910
|
+
}
|
|
1911
|
+
}));
|
|
1912
|
+
cache.session.on(`offline`, () => __async(null, null, function* () {
|
|
1913
|
+
cache.isConnected = false;
|
|
1914
|
+
cache.sigHalt = true;
|
|
1915
|
+
cache.events.emit(`onError`, { code: `connection-lost`, message: `XMPP connection went offline` });
|
|
1916
|
+
}));
|
|
1917
|
+
cache.session.on(`error`, (error) => __async(null, null, function* () {
|
|
1918
|
+
cache.isConnected = false;
|
|
1919
|
+
cache.sigHalt = true;
|
|
1920
|
+
cache.events.emit(`onError`, { code: `connection-error`, message: error.message });
|
|
1921
|
+
}));
|
|
1922
|
+
cache.session.on(`stanza`, (stanza) => __async(null, null, function* () {
|
|
1923
|
+
try {
|
|
1924
|
+
cache.lastStanza = Date.now();
|
|
1925
|
+
if (stanza.is(`message`)) {
|
|
1926
|
+
const validate = stanza_default.validate(stanza);
|
|
1927
|
+
if (validate.ignore || validate.isCap && !settings2.NoaaWeatherWireService.alertPreferences.isCapOnly || !validate.isCap && settings2.NoaaWeatherWireService.alertPreferences.isCapOnly || validate.isCap && !validate.isCapDescription) return;
|
|
1928
|
+
events_default.eventHandler(validate);
|
|
1929
|
+
cache.events.emit(`onMessage`, validate);
|
|
1930
|
+
database_default.stanzaCacheImport(JSON.stringify(validate));
|
|
1931
|
+
}
|
|
1932
|
+
if (stanza.is(`presence`) && stanza.attrs.from && stanza.attrs.from.startsWith("nwws@conference.nwws-oi.weather.gov/")) {
|
|
1933
|
+
const occupant = stanza.attrs.from.split("/").slice(1).join("/");
|
|
1934
|
+
cache.events.emit("onOccupant", { occupant, type: stanza.attrs.type === "unavailable" ? "unavailable" : "available" });
|
|
1935
|
+
}
|
|
1936
|
+
} catch (e) {
|
|
1937
|
+
cache.events.emit(`onError`, { code: `error-processing-stanza`, message: e.message });
|
|
1938
|
+
}
|
|
1939
|
+
}));
|
|
1940
|
+
yield cache.session.start();
|
|
1941
|
+
});
|
|
1942
|
+
}
|
|
1943
|
+
};
|
|
1944
|
+
var xmpp_default = Xmpp;
|
|
1945
|
+
|
|
1946
|
+
// src/utils.ts
|
|
1947
|
+
var Utils = class {
|
|
1948
|
+
/**
|
|
1949
|
+
* Zzzzzzz... yeah not much to explain here. Simple sleep function that returns a promise after the specified milliseconds.
|
|
1950
|
+
*
|
|
1951
|
+
* @public
|
|
1952
|
+
* @static
|
|
1953
|
+
* @async
|
|
1954
|
+
* @param {number} ms
|
|
1955
|
+
* @returns {Promise<void>}
|
|
1956
|
+
*/
|
|
1957
|
+
static sleep(ms) {
|
|
1958
|
+
return __async(this, null, function* () {
|
|
1959
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
1960
|
+
});
|
|
1961
|
+
}
|
|
1962
|
+
/**
|
|
1963
|
+
* loadCollectionCache reads cached alert files from the specified cache directory and processes them.
|
|
1964
|
+
*
|
|
1965
|
+
* @public
|
|
1966
|
+
* @static
|
|
1967
|
+
* @async
|
|
1968
|
+
* @returns {Promise<void>}
|
|
1969
|
+
*/
|
|
1970
|
+
static loadCollectionCache() {
|
|
1971
|
+
return __async(this, null, function* () {
|
|
1972
|
+
try {
|
|
1973
|
+
const settings2 = settings;
|
|
1974
|
+
if (settings2.NoaaWeatherWireService.cache.read && settings2.NoaaWeatherWireService.cache.directory) {
|
|
1975
|
+
if (!packages.fs.existsSync(settings2.NoaaWeatherWireService.cache.directory)) return;
|
|
1976
|
+
const cacheDir = settings2.NoaaWeatherWireService.cache.directory;
|
|
1977
|
+
const getAllFiles = packages.fs.readdirSync(cacheDir).filter((file) => file.endsWith(".bin") && file.startsWith("cache-"));
|
|
1978
|
+
for (const file of getAllFiles) {
|
|
1979
|
+
const start = Date.now();
|
|
1980
|
+
const filepath = packages.path.join(cacheDir, file);
|
|
1981
|
+
const readFile = packages.fs.readFileSync(filepath, { encoding: "utf-8" });
|
|
1982
|
+
const isCap = readFile.includes(`<?xml`);
|
|
1983
|
+
if (isCap && !settings2.NoaaWeatherWireService.alertPreferences.isCapOnly) continue;
|
|
1984
|
+
if (!isCap && settings2.NoaaWeatherWireService.alertPreferences.isCapOnly) continue;
|
|
1985
|
+
const validate = stanza_default.validate(readFile, { awipsid: file, isCap, raw: true, issue: void 0 });
|
|
1986
|
+
yield events_default.eventHandler(validate);
|
|
1987
|
+
}
|
|
1988
|
+
}
|
|
1989
|
+
} catch (error) {
|
|
1990
|
+
cache.events.emit("onError", { code: "error-load-cache", message: `Failed to load cache: ${error.message}` });
|
|
1991
|
+
}
|
|
1992
|
+
});
|
|
1993
|
+
}
|
|
1994
|
+
/**
|
|
1995
|
+
* loadGeoJsonData fetches GeoJSON data from the National Weather Service endpoint and processes each alert.
|
|
1996
|
+
*
|
|
1997
|
+
* @public
|
|
1998
|
+
* @static
|
|
1999
|
+
* @async
|
|
2000
|
+
* @returns {Promise<void>}
|
|
2001
|
+
*/
|
|
2002
|
+
static loadGeoJsonData() {
|
|
2003
|
+
return __async(this, null, function* () {
|
|
2004
|
+
try {
|
|
2005
|
+
const settings2 = settings;
|
|
2006
|
+
const response = yield this.createHttpRequest(settings2.NationalWeatherService.endpoint);
|
|
2007
|
+
if (!response.error) {
|
|
2008
|
+
events_default.eventHandler({ message: JSON.stringify(response.message), attributes: {}, isCap: true, isApi: true, isVtec: false, isUGC: false, isCapDescription: false, awipsType: { type: "api", prefix: "AP" }, ignore: false });
|
|
2009
|
+
}
|
|
2010
|
+
} catch (error) {
|
|
2011
|
+
cache.events.emit("onError", { code: "error-fetching-nws-data", message: `Failed to fetch NWS data: ${error.message}` });
|
|
2012
|
+
}
|
|
2013
|
+
});
|
|
2014
|
+
}
|
|
2015
|
+
/**
|
|
2016
|
+
* detectUncaughtExceptions sets up a global handler for uncaught exceptions in the Node.js process,
|
|
2017
|
+
*
|
|
2018
|
+
* @public
|
|
2019
|
+
* @static
|
|
2020
|
+
*/
|
|
2021
|
+
static detectUncaughtExceptions() {
|
|
2022
|
+
if (process.listeners("uncaughtException").some((l) => l.name === "uncaughtExceptionHandler")) return;
|
|
2023
|
+
process.on(`uncaughtException`, (error) => {
|
|
2024
|
+
cache.events.emit(`onError`, { message: `Uncaught Exception: ${error.message}`, code: `error-uncaught-exception`, stack: error.stack });
|
|
2025
|
+
});
|
|
2026
|
+
}
|
|
2027
|
+
/**
|
|
2028
|
+
* createHttpRequest performs an HTTP GET request to the specified URL with optional settings.
|
|
2029
|
+
*
|
|
2030
|
+
* @public
|
|
2031
|
+
* @static
|
|
2032
|
+
* @async
|
|
2033
|
+
* @param {string} url
|
|
2034
|
+
* @param {?types.HTTPSettings} [options]
|
|
2035
|
+
* @returns {unknown}
|
|
2036
|
+
*/
|
|
2037
|
+
static createHttpRequest(url, options) {
|
|
2038
|
+
return __async(this, null, function* () {
|
|
2039
|
+
var _a, _b;
|
|
2040
|
+
const defaultOptions = {
|
|
2041
|
+
timeout: 1e4,
|
|
2042
|
+
headers: {
|
|
2043
|
+
"User-Agent": "AtmosphericX",
|
|
2044
|
+
"Accept": "application/geo+json, text/plain, */*; q=0.9",
|
|
2045
|
+
"Accept-Language": "en-US,en;q=0.9"
|
|
2046
|
+
}
|
|
2047
|
+
};
|
|
2048
|
+
const requestOptions = __spreadProps(__spreadValues(__spreadValues({}, defaultOptions), options), {
|
|
2049
|
+
headers: __spreadValues(__spreadValues({}, defaultOptions.headers), (_a = options == null ? void 0 : options.headers) != null ? _a : {})
|
|
2050
|
+
});
|
|
2051
|
+
try {
|
|
2052
|
+
const resp = yield packages.axios.get(url, {
|
|
2053
|
+
headers: requestOptions.headers,
|
|
2054
|
+
timeout: requestOptions.timeout,
|
|
2055
|
+
maxRedirects: 0,
|
|
2056
|
+
validateStatus: (status) => status === 200 || status === 500
|
|
2057
|
+
});
|
|
2058
|
+
return { error: false, message: resp.data };
|
|
2059
|
+
} catch (err) {
|
|
2060
|
+
return { error: true, message: (_b = err == null ? void 0 : err.message) != null ? _b : String(err) };
|
|
2061
|
+
}
|
|
2062
|
+
});
|
|
2063
|
+
}
|
|
2064
|
+
/**
|
|
2065
|
+
* garbageCollectionCache removes files from the cache directory that exceed the specified maximum file size in megabytes.
|
|
2066
|
+
*
|
|
2067
|
+
* @public
|
|
2068
|
+
* @static
|
|
2069
|
+
* @param {number} maxFileMegabytes
|
|
2070
|
+
*/
|
|
2071
|
+
static garbageCollectionCache(maxFileMegabytes) {
|
|
2072
|
+
try {
|
|
2073
|
+
const settings2 = settings;
|
|
2074
|
+
if (!settings2.NoaaWeatherWireService.cache.directory) return;
|
|
2075
|
+
if (!packages.fs.existsSync(settings2.NoaaWeatherWireService.cache.directory)) return;
|
|
2076
|
+
const maxBytes = maxFileMegabytes * 1024 * 1024;
|
|
2077
|
+
const cacheDirectory = settings2.NoaaWeatherWireService.cache.directory;
|
|
2078
|
+
const stackFiles = [cacheDirectory], files = [];
|
|
2079
|
+
while (stackFiles.length) {
|
|
2080
|
+
const currentDirectory = stackFiles.pop();
|
|
2081
|
+
packages.fs.readdirSync(currentDirectory).forEach((file) => {
|
|
2082
|
+
const fullPath = packages.path.join(currentDirectory, file);
|
|
2083
|
+
const stat = packages.fs.statSync(fullPath);
|
|
2084
|
+
if (stat.isDirectory()) stackFiles.push(fullPath);
|
|
2085
|
+
else files.push({ file: fullPath, size: stat.size });
|
|
2086
|
+
});
|
|
2087
|
+
}
|
|
2088
|
+
if (!files.length) return;
|
|
2089
|
+
files.forEach((f) => {
|
|
2090
|
+
if (f.size > maxBytes) packages.fs.unlinkSync(f.file);
|
|
2091
|
+
});
|
|
2092
|
+
} catch (error) {
|
|
2093
|
+
cache.events.emit("onError", { code: "error-garbage-collection", message: `Failed to perform garbage collection: ${error.message}` });
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
/**
|
|
2097
|
+
* handleCronJob performs periodic tasks based on whether the client is connected to NWWS or fetching data from NWS.
|
|
2098
|
+
*
|
|
2099
|
+
* @public
|
|
2100
|
+
* @static
|
|
2101
|
+
* @param {boolean} isNwws
|
|
2102
|
+
*/
|
|
2103
|
+
static handleCronJob(isNwws) {
|
|
2104
|
+
try {
|
|
2105
|
+
const settings2 = settings;
|
|
2106
|
+
if (isNwws) {
|
|
2107
|
+
if (settings2.NoaaWeatherWireService.cache.read) void this.garbageCollectionCache(settings2.NoaaWeatherWireService.cache.maxSizeMB);
|
|
2108
|
+
if (settings2.NoaaWeatherWireService.clientReconnections.canReconnect) void xmpp_default.isSessionReconnectionEligible(settings2.NoaaWeatherWireService.clientReconnections.currentInterval);
|
|
2109
|
+
} else {
|
|
2110
|
+
void this.loadGeoJsonData();
|
|
2111
|
+
}
|
|
2112
|
+
} catch (error) {
|
|
2113
|
+
cache.events.emit("onError", { code: "error-cron-job", message: `Failed to perform scheduled tasks: ${error.message}` });
|
|
2114
|
+
}
|
|
2115
|
+
}
|
|
2116
|
+
/**
|
|
2117
|
+
* mergeClientSettings merges user-provided settings into the existing client settings, allowing for nested objects to be merged correctly.
|
|
2118
|
+
*
|
|
2119
|
+
* @public
|
|
2120
|
+
* @static
|
|
2121
|
+
* @param {Record<string, any>} target
|
|
2122
|
+
* @param {Record<string, any>} settings
|
|
2123
|
+
*/
|
|
2124
|
+
static mergeClientSettings(target, settings2) {
|
|
2125
|
+
for (const key in settings2) {
|
|
2126
|
+
if (settings2.hasOwnProperty(key)) {
|
|
2127
|
+
if (typeof settings2[key] === "object" && settings2[key] !== null && !Array.isArray(settings2[key])) {
|
|
2128
|
+
if (!target[key] || typeof target[key] !== "object") {
|
|
2129
|
+
target[key] = {};
|
|
2130
|
+
}
|
|
2131
|
+
this.mergeClientSettings(target[key], settings2[key]);
|
|
2132
|
+
} else {
|
|
2133
|
+
target[key] = settings2[key];
|
|
2134
|
+
}
|
|
2135
|
+
}
|
|
2136
|
+
}
|
|
2137
|
+
}
|
|
2138
|
+
};
|
|
2139
|
+
var utils_default = Utils;
|
|
2140
|
+
|
|
2141
|
+
// src/eas.ts
|
|
2142
|
+
var EAS = class {
|
|
2143
|
+
/**
|
|
2144
|
+
* generateEASAudio creates an EAS-compliant audio file in WAV format containing the provided message and VTEC header.
|
|
2145
|
+
*
|
|
2146
|
+
* @public
|
|
2147
|
+
* @static
|
|
2148
|
+
* @param {string} message
|
|
2149
|
+
* @param {string} vtec
|
|
2150
|
+
* @returns {*}
|
|
2151
|
+
*/
|
|
2152
|
+
static generateEASAudio(message, vtec) {
|
|
2153
|
+
return new Promise((resolve) => __async(this, null, function* () {
|
|
2154
|
+
const settings2 = settings;
|
|
2155
|
+
for (const { regex, replacement } of definitions.messageSignatures) {
|
|
2156
|
+
message = message.replace(regex, replacement);
|
|
2157
|
+
}
|
|
2158
|
+
const assetsDir = settings2.global.easSettings.easDirectory;
|
|
2159
|
+
if (!assetsDir) {
|
|
2160
|
+
console.warn(definitions.messages.eas_no_directory);
|
|
2161
|
+
return resolve(null);
|
|
2162
|
+
}
|
|
2163
|
+
const rngFile = `${vtec.replace(/[^a-zA-Z0-9]/g, `_`)}`.substring(0, 32).replace(/^_+|_+$/g, "");
|
|
2164
|
+
if (!packages.fs.existsSync(assetsDir)) {
|
|
2165
|
+
packages.fs.mkdirSync(assetsDir);
|
|
2166
|
+
}
|
|
2167
|
+
const tmpTTS = packages.path.join(assetsDir, `/tmp/${rngFile}.wav`);
|
|
2168
|
+
const outTTS = packages.path.join(assetsDir, `/output/${rngFile}.wav`);
|
|
2169
|
+
const voice = process.platform === "win32" ? "Microsoft David Desktop" : "en-US-GuyNeural";
|
|
2170
|
+
if (!packages.fs.existsSync(packages.path.join(assetsDir, `/tmp`))) {
|
|
2171
|
+
packages.fs.mkdirSync(packages.path.join(assetsDir, `/tmp`), { recursive: true });
|
|
2172
|
+
}
|
|
2173
|
+
if (!packages.fs.existsSync(packages.path.join(assetsDir, `/output`))) {
|
|
2174
|
+
packages.fs.mkdirSync(packages.path.join(assetsDir, `/output`), { recursive: true });
|
|
2175
|
+
}
|
|
2176
|
+
packages.say.export(message, voice, 1, tmpTTS);
|
|
2177
|
+
yield utils_default.sleep(2500);
|
|
2178
|
+
let ttsBuffer = null;
|
|
2179
|
+
while (!packages.fs.existsSync(tmpTTS) || (ttsBuffer = packages.fs.readFileSync(tmpTTS)).length === 0) {
|
|
2180
|
+
yield utils_default.sleep(500);
|
|
2181
|
+
}
|
|
2182
|
+
const ttsWav = this.parseWavPCM16(ttsBuffer);
|
|
2183
|
+
const ttsSamples = this.resamplePCM16(ttsWav.samples, ttsWav.sampleRate, 8e3);
|
|
2184
|
+
const ttsRadio = this.applyNWREffect(ttsSamples, 8e3);
|
|
2185
|
+
let toneRadio = null;
|
|
2186
|
+
if (packages.fs.existsSync(settings2.global.easSettings.easIntroWav)) {
|
|
2187
|
+
const toneBuffer = packages.fs.readFileSync(settings2.global.easSettings.easIntroWav);
|
|
2188
|
+
const toneWav = this.parseWavPCM16(toneBuffer);
|
|
2189
|
+
const toneSamples = toneWav.sampleRate !== 8e3 ? this.resamplePCM16(toneWav.samples, toneWav.sampleRate, 8e3) : toneWav.samples;
|
|
2190
|
+
toneRadio = this.applyNWREffect(toneSamples, 8e3);
|
|
2191
|
+
}
|
|
2192
|
+
let build = toneRadio != null ? [toneRadio, this.generateSilence(0.5, 8e3)] : [];
|
|
2193
|
+
build.push(this.generateSAMEHeader(vtec, 3, 8e3, { preMarkSec: 1.1, gapSec: 0.5 }), this.generateSilence(0.5, 8e3), this.generateAttentionTone(8, 8e3), this.generateSilence(0.5, 8e3), ttsRadio);
|
|
2194
|
+
for (let i = 0; i < 3; i++) {
|
|
2195
|
+
build.push(this.generateSAMEHeader(vtec, 1, 8e3, { preMarkSec: 0.5, gapSec: 0.1 }));
|
|
2196
|
+
build.push(this.generateSilence(0.5, 8e3));
|
|
2197
|
+
}
|
|
2198
|
+
const allSamples = this.concatPCM16(build);
|
|
2199
|
+
const finalSamples = this.addNoise(allSamples, 2e-3);
|
|
2200
|
+
const outBuffer = this.encodeWavPCM16(Array.from(finalSamples).map((v) => ({ value: v })), 8e3);
|
|
2201
|
+
packages.fs.writeFileSync(outTTS, outBuffer);
|
|
2202
|
+
try {
|
|
2203
|
+
packages.fs.unlinkSync(tmpTTS);
|
|
2204
|
+
} catch (error) {
|
|
2205
|
+
if (error.code !== "EBUSY") {
|
|
2206
|
+
throw error;
|
|
2207
|
+
}
|
|
2208
|
+
}
|
|
2209
|
+
return Promise.resolve(outTTS);
|
|
2210
|
+
}));
|
|
2211
|
+
}
|
|
2212
|
+
/**
|
|
2213
|
+
* encodeWavPCM16 encodes an array of samples into a WAV PCM 16-bit Buffer.
|
|
2214
|
+
*
|
|
2215
|
+
* @private
|
|
2216
|
+
* @static
|
|
2217
|
+
* @param {Record<string, number>[]} samples
|
|
2218
|
+
* @param {number} [sampleRate=8000]
|
|
2219
|
+
* @returns {Buffer}
|
|
2220
|
+
*/
|
|
2221
|
+
static encodeWavPCM16(samples, sampleRate = 8e3) {
|
|
2222
|
+
const bytesPerSample = 2;
|
|
2223
|
+
const blockAlign = 1 * bytesPerSample;
|
|
2224
|
+
const byteRate = sampleRate * blockAlign;
|
|
2225
|
+
const subchunk2Size = samples.length * bytesPerSample;
|
|
2226
|
+
const chunkSize = 36 + subchunk2Size;
|
|
2227
|
+
const buffer = Buffer.alloc(44 + subchunk2Size);
|
|
2228
|
+
let o = 0;
|
|
2229
|
+
buffer.write("RIFF", o);
|
|
2230
|
+
o += 4;
|
|
2231
|
+
buffer.writeUInt32LE(chunkSize, o);
|
|
2232
|
+
o += 4;
|
|
2233
|
+
buffer.write("WAVE", o);
|
|
2234
|
+
o += 4;
|
|
2235
|
+
buffer.write("fmt ", o);
|
|
2236
|
+
o += 4;
|
|
2237
|
+
buffer.writeUInt32LE(16, o);
|
|
2238
|
+
o += 4;
|
|
2239
|
+
buffer.writeUInt16LE(1, o);
|
|
2240
|
+
o += 2;
|
|
2241
|
+
buffer.writeUInt16LE(1, o);
|
|
2242
|
+
o += 2;
|
|
2243
|
+
buffer.writeUInt32LE(sampleRate, o);
|
|
2244
|
+
o += 4;
|
|
2245
|
+
buffer.writeUInt32LE(byteRate, o);
|
|
2246
|
+
o += 4;
|
|
2247
|
+
buffer.writeUInt16LE(blockAlign, o);
|
|
2248
|
+
o += 2;
|
|
2249
|
+
buffer.writeUInt16LE(16, o);
|
|
2250
|
+
o += 2;
|
|
2251
|
+
buffer.write("data", o);
|
|
2252
|
+
o += 4;
|
|
2253
|
+
buffer.writeUInt32LE(subchunk2Size, o);
|
|
2254
|
+
o += 4;
|
|
2255
|
+
for (let i = 0; i < samples.length; i++, o += 2) {
|
|
2256
|
+
buffer.writeInt16LE(samples[i].value, o);
|
|
2257
|
+
}
|
|
2258
|
+
return buffer;
|
|
2259
|
+
}
|
|
2260
|
+
/**
|
|
2261
|
+
* parseWavPCM16 decodes a WAV PCM 16-bit Buffer into its sample data and format information.
|
|
2262
|
+
*
|
|
2263
|
+
* @private
|
|
2264
|
+
* @static
|
|
2265
|
+
* @param {Buffer} buffer
|
|
2266
|
+
* @returns {{ samples: any; sampleRate: any; channels: any; bitsPerSample: any; }}
|
|
2267
|
+
*/
|
|
2268
|
+
static parseWavPCM16(buffer) {
|
|
2269
|
+
if (buffer.toString("ascii", 0, 4) !== "RIFF" || buffer.toString("ascii", 8, 12) !== "WAVE") {
|
|
2270
|
+
return null;
|
|
2271
|
+
}
|
|
2272
|
+
let fmt = null;
|
|
2273
|
+
let data = null;
|
|
2274
|
+
let i = 12;
|
|
2275
|
+
while (i + 8 <= buffer.length) {
|
|
2276
|
+
const id = buffer.toString("ascii", i, i + 4);
|
|
2277
|
+
const size = buffer.readUInt32LE(i + 4);
|
|
2278
|
+
const start = i + 8;
|
|
2279
|
+
const end = start + size;
|
|
2280
|
+
if (id === "fmt ") fmt = buffer.slice(start, end);
|
|
2281
|
+
if (id === "data") data = buffer.slice(start, end);
|
|
2282
|
+
i = end + size % 2;
|
|
2283
|
+
}
|
|
2284
|
+
if (!fmt || !data) return null;
|
|
2285
|
+
const audioFormat = fmt.readUInt16LE(0);
|
|
2286
|
+
const channels = fmt.readUInt16LE(2);
|
|
2287
|
+
const sampleRate = fmt.readUInt32LE(4);
|
|
2288
|
+
const bitsPerSample = fmt.readUInt16LE(14);
|
|
2289
|
+
if (audioFormat !== 1 || bitsPerSample !== 16 || channels !== 1) {
|
|
2290
|
+
return null;
|
|
2291
|
+
}
|
|
2292
|
+
const samples = new Int16Array(data.buffer, data.byteOffset, data.length / 2);
|
|
2293
|
+
return { samples: new Int16Array(samples), sampleRate, channels, bitsPerSample };
|
|
2294
|
+
}
|
|
2295
|
+
/**
|
|
2296
|
+
* concatPCM16 concatenates multiple Int16Array buffers into a single Int16Array buffer.
|
|
2297
|
+
*
|
|
2298
|
+
* @private
|
|
2299
|
+
* @static
|
|
2300
|
+
* @param {Int16Array[]} arrays
|
|
2301
|
+
* @returns {*}
|
|
2302
|
+
*/
|
|
2303
|
+
static concatPCM16(arrays) {
|
|
2304
|
+
let total = 0;
|
|
2305
|
+
for (const a of arrays) total += a.length;
|
|
2306
|
+
const out = new Int16Array(total);
|
|
2307
|
+
let o = 0;
|
|
2308
|
+
for (const a of arrays) {
|
|
2309
|
+
out.set(a, o);
|
|
2310
|
+
o += a.length;
|
|
2311
|
+
}
|
|
2312
|
+
return out;
|
|
2313
|
+
}
|
|
2314
|
+
/**
|
|
2315
|
+
* pcm16toFloat converts an Int16Array of PCM 16-bit samples to a Float32Array of normalized float samples.
|
|
2316
|
+
*
|
|
2317
|
+
* @private
|
|
2318
|
+
* @static
|
|
2319
|
+
* @param {Int16Array} int16
|
|
2320
|
+
* @returns {*}
|
|
2321
|
+
*/
|
|
2322
|
+
static pcm16toFloat(int16) {
|
|
2323
|
+
const out = new Float32Array(int16.length);
|
|
2324
|
+
for (let i = 0; i < int16.length; i++) out[i] = int16[i] / 32768;
|
|
2325
|
+
return out;
|
|
2326
|
+
}
|
|
2327
|
+
/**
|
|
2328
|
+
* floatToPcm16 converts a Float32Array of normalized float samples to an Int16Array of PCM 16-bit samples.
|
|
2329
|
+
*
|
|
2330
|
+
* @private
|
|
2331
|
+
* @static
|
|
2332
|
+
* @param {Float32Array} float32
|
|
2333
|
+
* @returns {*}
|
|
2334
|
+
*/
|
|
2335
|
+
static floatToPcm16(float32) {
|
|
2336
|
+
const out = new Int16Array(float32.length);
|
|
2337
|
+
for (let i = 0; i < float32.length; i++) {
|
|
2338
|
+
let v = Math.max(-1, Math.min(1, float32[i]));
|
|
2339
|
+
out[i] = Math.round(v * 32767);
|
|
2340
|
+
}
|
|
2341
|
+
return out;
|
|
2342
|
+
}
|
|
2343
|
+
/**
|
|
2344
|
+
* resamplePCM16 resamples an Int16Array of PCM 16-bit samples from the original sample rate to the target sample rate using linear interpolation.
|
|
2345
|
+
*
|
|
2346
|
+
* @private
|
|
2347
|
+
* @static
|
|
2348
|
+
* @param {Int16Array} int16
|
|
2349
|
+
* @param {number} originalRate
|
|
2350
|
+
* @param {number} targetRate
|
|
2351
|
+
* @returns {*}
|
|
2352
|
+
*/
|
|
2353
|
+
static resamplePCM16(int16, originalRate, targetRate) {
|
|
2354
|
+
if (originalRate === targetRate) return int16;
|
|
2355
|
+
const ratio = targetRate / originalRate;
|
|
2356
|
+
const outLen = Math.max(1, Math.round(int16.length * ratio));
|
|
2357
|
+
const out = new Int16Array(outLen);
|
|
2358
|
+
for (let i = 0; i < outLen; i++) {
|
|
2359
|
+
const pos = i / ratio;
|
|
2360
|
+
const i0 = Math.floor(pos);
|
|
2361
|
+
const i1 = Math.min(i0 + 1, int16.length - 1);
|
|
2362
|
+
const frac = pos - i0;
|
|
2363
|
+
const v = int16[i0] * (1 - frac) + int16[i1] * frac;
|
|
2364
|
+
out[i] = Math.round(v);
|
|
2365
|
+
}
|
|
2366
|
+
return out;
|
|
2367
|
+
}
|
|
2368
|
+
/**
|
|
2369
|
+
* generateSilence creates an Int16Array of PCM 16-bit samples representing silence for the specified duration in milliseconds.
|
|
2370
|
+
*
|
|
2371
|
+
* @private
|
|
2372
|
+
* @static
|
|
2373
|
+
* @param {number} ms
|
|
2374
|
+
* @param {number} [sampleRate=8000]
|
|
2375
|
+
* @returns {*}
|
|
2376
|
+
*/
|
|
2377
|
+
static generateSilence(ms, sampleRate = 8e3) {
|
|
2378
|
+
return new Int16Array(Math.floor(ms * sampleRate));
|
|
2379
|
+
}
|
|
2380
|
+
/**
|
|
2381
|
+
* generateAttentionTone creates an Int16Array of PCM 16-bit samples representing the EAS attention tone for the specified duration in milliseconds.
|
|
2382
|
+
*
|
|
2383
|
+
* @private
|
|
2384
|
+
* @static
|
|
2385
|
+
* @param {*} ms
|
|
2386
|
+
* @param {number} [sampleRate=8000]
|
|
2387
|
+
* @returns {*}
|
|
2388
|
+
*/
|
|
2389
|
+
static generateAttentionTone(ms, sampleRate = 8e3) {
|
|
2390
|
+
const len = Math.floor(ms * sampleRate);
|
|
2391
|
+
const out = new Int16Array(len);
|
|
2392
|
+
const f1 = 853;
|
|
2393
|
+
const f2 = 960;
|
|
2394
|
+
const twoPi = Math.PI * 2;
|
|
2395
|
+
const amp = 0.1;
|
|
2396
|
+
const fadeLen = Math.floor(sampleRate * 0);
|
|
2397
|
+
for (let i = 0; i < len; i++) {
|
|
2398
|
+
const t = i / sampleRate;
|
|
2399
|
+
const s = Math.sin(twoPi * f1 * t) + Math.sin(twoPi * f2 * t);
|
|
2400
|
+
let gain = 1;
|
|
2401
|
+
if (i < fadeLen) gain = i / fadeLen;
|
|
2402
|
+
else if (i > len - fadeLen) gain = (len - i) / fadeLen;
|
|
2403
|
+
const v = Math.max(-1, Math.min(1, s / 2 * amp * gain));
|
|
2404
|
+
out[i] = Math.round(v * 32767);
|
|
2405
|
+
}
|
|
2406
|
+
return out;
|
|
2407
|
+
}
|
|
2408
|
+
/**
|
|
2409
|
+
* applyNWREffect applies a series of audio processing effects to simulate the sound characteristics of NOAA Weather Radio broadcasts.
|
|
2410
|
+
*
|
|
2411
|
+
* @private
|
|
2412
|
+
* @static
|
|
2413
|
+
* @param {Int16Array} int16
|
|
2414
|
+
* @param {number} [sampleRate=8000]
|
|
2415
|
+
* @returns {*}
|
|
2416
|
+
*/
|
|
2417
|
+
static applyNWREffect(int16, sampleRate = 8e3) {
|
|
2418
|
+
const hpCut = 3555;
|
|
2419
|
+
const lpCut = 1600;
|
|
2420
|
+
const noiseLevel = 0;
|
|
2421
|
+
const crushBits = 8;
|
|
2422
|
+
const x = this.pcm16toFloat(int16);
|
|
2423
|
+
const dt = 1 / sampleRate;
|
|
2424
|
+
const rcHP = 1 / (2 * Math.PI * hpCut);
|
|
2425
|
+
const aHP = rcHP / (rcHP + dt);
|
|
2426
|
+
let yHP = 0, xPrev = 0;
|
|
2427
|
+
for (let i = 0; i < x.length; i++) {
|
|
2428
|
+
const xi = x[i];
|
|
2429
|
+
yHP = aHP * (yHP + xi - xPrev);
|
|
2430
|
+
xPrev = xi;
|
|
2431
|
+
x[i] = yHP;
|
|
2432
|
+
}
|
|
2433
|
+
const rcLP = 1 / (2 * Math.PI * lpCut);
|
|
2434
|
+
const aLP = dt / (rcLP + dt);
|
|
2435
|
+
let yLP = 0;
|
|
2436
|
+
for (let i = 0; i < x.length; i++) {
|
|
2437
|
+
yLP = yLP + aLP * (x[i] - yLP);
|
|
2438
|
+
x[i] = yLP;
|
|
2439
|
+
}
|
|
2440
|
+
const compGain = 2;
|
|
2441
|
+
const norm = Math.tanh(compGain);
|
|
2442
|
+
for (let i = 0; i < x.length; i++) x[i] = Math.tanh(x[i] * compGain) / norm;
|
|
2443
|
+
const levels = Math.pow(2, crushBits) - 1;
|
|
2444
|
+
return this.floatToPcm16(x);
|
|
2445
|
+
}
|
|
2446
|
+
/**
|
|
2447
|
+
* addNoise adds low-level white noise to an Int16Array of PCM 16-bit samples to simulate analog broadcast imperfections.
|
|
2448
|
+
*
|
|
2449
|
+
* @private
|
|
2450
|
+
* @static
|
|
2451
|
+
* @param {Int16Array} int16
|
|
2452
|
+
* @param {number} [noiseLevel=0.02]
|
|
2453
|
+
* @returns {*}
|
|
2454
|
+
*/
|
|
2455
|
+
static addNoise(int16, noiseLevel = 0.02) {
|
|
2456
|
+
const x = this.pcm16toFloat(int16);
|
|
2457
|
+
for (let i = 0; i < x.length; i++) x[i] += (Math.random() * 2 - 1) * noiseLevel;
|
|
2458
|
+
let peak = 0;
|
|
2459
|
+
for (let i = 0; i < x.length; i++) peak = Math.max(peak, Math.abs(x[i]));
|
|
2460
|
+
if (peak > 1) for (let i = 0; i < x.length; i++) x[i] *= 0.98 / peak;
|
|
2461
|
+
return this.floatToPcm16(x);
|
|
2462
|
+
}
|
|
2463
|
+
/**
|
|
2464
|
+
* asciiTo8N1Bits converts an ASCII string to a sequence of bits using 8-N-1 encoding (8 data bits, no parity, 1 stop bit).
|
|
2465
|
+
*
|
|
2466
|
+
* @private
|
|
2467
|
+
* @static
|
|
2468
|
+
* @param {string} str
|
|
2469
|
+
* @returns {{}}
|
|
2470
|
+
*/
|
|
2471
|
+
static asciiTo8N1Bits(str) {
|
|
2472
|
+
const bits = [];
|
|
2473
|
+
for (let i = 0; i < str.length; i++) {
|
|
2474
|
+
const c = str.charCodeAt(i) & 255;
|
|
2475
|
+
bits.push(0);
|
|
2476
|
+
for (let b = 0; b < 8; b++) bits.push(c >> b & 1);
|
|
2477
|
+
bits.push(1, 1);
|
|
2478
|
+
}
|
|
2479
|
+
return bits;
|
|
2480
|
+
}
|
|
2481
|
+
/**
|
|
2482
|
+
* generateAFSK generates an Int16Array of PCM 16-bit samples representing AFSK modulation of the provided bit sequence.
|
|
2483
|
+
*
|
|
2484
|
+
* @private
|
|
2485
|
+
* @static
|
|
2486
|
+
* @param {number[]} bits
|
|
2487
|
+
* @param {number} [sampleRate=8000]
|
|
2488
|
+
* @returns {*}
|
|
2489
|
+
*/
|
|
2490
|
+
static generateAFSK(bits, sampleRate = 8e3) {
|
|
2491
|
+
const baud = 520.83;
|
|
2492
|
+
const markFreq = 2083.3;
|
|
2493
|
+
const spaceFreq = 1562.5;
|
|
2494
|
+
const amplitude = 0.6;
|
|
2495
|
+
const twoPi = Math.PI * 2;
|
|
2496
|
+
const result = [];
|
|
2497
|
+
let phase = 0;
|
|
2498
|
+
let frac = 0;
|
|
2499
|
+
for (let b = 0; b < bits.length; b++) {
|
|
2500
|
+
const bit = bits[b];
|
|
2501
|
+
const freq = bit ? markFreq : spaceFreq;
|
|
2502
|
+
const samplesPerBit = sampleRate / baud + frac;
|
|
2503
|
+
const n = Math.round(samplesPerBit);
|
|
2504
|
+
frac = samplesPerBit - n;
|
|
2505
|
+
const inc = twoPi * freq / sampleRate;
|
|
2506
|
+
for (let i = 0; i < n; i++) {
|
|
2507
|
+
result.push(Math.round(Math.sin(phase) * amplitude * 32767));
|
|
2508
|
+
phase += inc;
|
|
2509
|
+
if (phase > twoPi) phase -= twoPi;
|
|
2510
|
+
}
|
|
2511
|
+
}
|
|
2512
|
+
const fadeSamples = Math.floor(sampleRate * 2e-3);
|
|
2513
|
+
for (let i = 0; i < fadeSamples; i++) {
|
|
2514
|
+
const gain = i / fadeSamples;
|
|
2515
|
+
result[i] = Math.round(result[i] * gain);
|
|
2516
|
+
result[result.length - 1 - i] = Math.round(result[result.length - 1 - i] * gain);
|
|
2517
|
+
}
|
|
2518
|
+
return Int16Array.from(result);
|
|
2519
|
+
}
|
|
2520
|
+
/**
|
|
2521
|
+
* generateSAMEHeader generates an Int16Array of PCM 16-bit samples representing the SAME header repeated the specified number of times.
|
|
2522
|
+
*
|
|
2523
|
+
* @private
|
|
2524
|
+
* @static
|
|
2525
|
+
* @param {string} vtec
|
|
2526
|
+
* @param {number} repeats
|
|
2527
|
+
* @param {number} [sampleRate=8000]
|
|
2528
|
+
* @param {{preMarkSec?: number, gapSec?: number}} [options={}]
|
|
2529
|
+
* @returns {*}
|
|
2530
|
+
*/
|
|
2531
|
+
static generateSAMEHeader(vtec, repeats, sampleRate = 8e3, options = {}) {
|
|
2532
|
+
var _a, _b;
|
|
2533
|
+
const preMarkSec = (_a = options.preMarkSec) != null ? _a : 0.3;
|
|
2534
|
+
const gapSec = (_b = options.gapSec) != null ? _b : 0.1;
|
|
2535
|
+
const bursts = [];
|
|
2536
|
+
const gap = this.generateSilence(gapSec, sampleRate);
|
|
2537
|
+
for (let i = 0; i < repeats; i++) {
|
|
2538
|
+
const bodyBits = this.asciiTo8N1Bits(vtec);
|
|
2539
|
+
const body = this.generateAFSK(bodyBits, sampleRate);
|
|
2540
|
+
const extendedBodyDuration = Math.round(preMarkSec * sampleRate);
|
|
2541
|
+
const extendedBody = new Int16Array(extendedBodyDuration + gap.length);
|
|
2542
|
+
for (let j = 0; j < extendedBodyDuration; j++) {
|
|
2543
|
+
extendedBody[j] = Math.round(body[j % body.length] * 0.2);
|
|
2544
|
+
}
|
|
2545
|
+
extendedBody.set(gap, extendedBodyDuration);
|
|
2546
|
+
bursts.push(extendedBody);
|
|
2547
|
+
if (i !== repeats - 1) bursts.push(gap);
|
|
2548
|
+
}
|
|
2549
|
+
return this.concatPCM16(bursts);
|
|
2550
|
+
}
|
|
2551
|
+
};
|
|
2552
|
+
var eas_default = EAS;
|
|
2553
|
+
|
|
2554
|
+
// src/parsers/events.ts
|
|
2555
|
+
var EventParser = class {
|
|
2556
|
+
/**
|
|
2557
|
+
* getBaseProperties extracts and compiles the base properties of an alert message, including location, timing, description, sender information, and various parameters.
|
|
2558
|
+
*
|
|
2559
|
+
* @public
|
|
2560
|
+
* @static
|
|
2561
|
+
* @async
|
|
2562
|
+
* @param {string} message
|
|
2563
|
+
* @param {types.TypeCompiled} validated
|
|
2564
|
+
* @param {types.UGCParsed} [ugc=null]
|
|
2565
|
+
* @param {types.VTECParsed} [vtec=null]
|
|
2566
|
+
* @returns {Promise<types.BaseProperties>}
|
|
2567
|
+
*/
|
|
2568
|
+
static getBaseProperties(message, validated, ugc = null, vtec = null) {
|
|
2569
|
+
return __async(this, null, function* () {
|
|
2570
|
+
var _a, _b, _c, _d, _e, _f, _g, _h;
|
|
2571
|
+
const settings2 = settings;
|
|
2572
|
+
const definitions2 = {
|
|
2573
|
+
tornado: text_default.textProductToString(message, `TORNADO...`) || text_default.textProductToString(message, `WATERSPOUT...`) || `N/A`,
|
|
2574
|
+
hail: text_default.textProductToString(message, `MAX HAIL SIZE...`, [`IN`]) || text_default.textProductToString(message, `HAIL...`, [`IN`]) || `N/A`,
|
|
2575
|
+
gusts: text_default.textProductToString(message, `MAX WIND GUST...`) || text_default.textProductToString(message, `WIND...`) || `N/A`,
|
|
2576
|
+
flood: text_default.textProductToString(message, `FLASH FLOOD...`) || `N/A`,
|
|
2577
|
+
damage: text_default.textProductToString(message, `DAMAGE THREAT...`) || `N/A`,
|
|
2578
|
+
source: text_default.textProductToString(message, `SOURCE...`, [`.`]) || `N/A`,
|
|
2579
|
+
attributes: text_default.textProductToString(message, `STANZA ATTRIBUTES...`) ? JSON.parse(text_default.textProductToString(message, `STANZA ATTRIBUTES...`)) : null,
|
|
2580
|
+
polygon: text_default.textProductToPolygon(message),
|
|
2581
|
+
description: text_default.textProductToDescription(message, (_a = vtec == null ? void 0 : vtec.raw) != null ? _a : null),
|
|
2582
|
+
wmo: message.match(new RegExp(definitions.expressions.wmo, "imu")),
|
|
2583
|
+
mdTorIntensity: text_default.textProductToString(message, `MOST PROBABLE PEAK TORNADO INTENSITY...`) || `N/A`,
|
|
2584
|
+
mdWindGusts: text_default.textProductToString(message, `MOST PROBABLE PEAK WIND GUST...`) || `N/A`,
|
|
2585
|
+
mdHailSize: text_default.textProductToString(message, `MOST PROBABLE PEAK HAIL SIZE...`) || `N/A`
|
|
2586
|
+
};
|
|
2587
|
+
const getOffice = this.getICAO(vtec, (_c = (_b = validated.attributes) != null ? _b : definitions2.attributes) != null ? _c : {}, definitions2.wmo);
|
|
2588
|
+
const getCorrectIssued = this.getCorrectIssuedDate((_e = (_d = definitions2.attributes) != null ? _d : validated.attributes) != null ? _e : {});
|
|
2589
|
+
const getCorrectExpiry = this.getCorrectExpiryDate(vtec, ugc);
|
|
2590
|
+
const getAwip = text_default.awipTextToEvent((_g = (_f = definitions2.attributes) == null ? void 0 : _f.awipsid) != null ? _g : validated.awipsType.prefix);
|
|
2591
|
+
const base = {
|
|
2592
|
+
locations: (ugc == null ? void 0 : ugc.locations.join(`; `)) || `No Location Specified (UGC Missing)`,
|
|
2593
|
+
issued: getCorrectIssued,
|
|
2594
|
+
expires: getCorrectExpiry,
|
|
2595
|
+
geocode: { UGC: (ugc == null ? void 0 : ugc.zones) || [`XX000`] },
|
|
2596
|
+
description: definitions2.description,
|
|
2597
|
+
sender_name: getOffice.name,
|
|
2598
|
+
sender_icao: getOffice.icao,
|
|
2599
|
+
attributes: __spreadProps(__spreadValues(__spreadValues({}, validated.attributes), definitions2.attributes), {
|
|
2600
|
+
getAwip
|
|
2601
|
+
}),
|
|
2602
|
+
parameters: {
|
|
2603
|
+
wmo: Array.isArray(definitions2.wmo) ? definitions2.wmo[0] : (_h = definitions2.wmo) != null ? _h : `N/A`,
|
|
2604
|
+
source: definitions2.source,
|
|
2605
|
+
max_hail_size: definitions2.hail,
|
|
2606
|
+
max_wind_gust: definitions2.gusts,
|
|
2607
|
+
damage_threat: definitions2.damage,
|
|
2608
|
+
tornado_detection: definitions2.tornado,
|
|
2609
|
+
flood_detection: definitions2.flood,
|
|
2610
|
+
discussion_tornado_intensity: definitions2.mdTorIntensity,
|
|
2611
|
+
discussion_wind_intensity: definitions2.mdWindGusts,
|
|
2612
|
+
discussion_hail_intensity: definitions2.mdHailSize
|
|
2613
|
+
},
|
|
2614
|
+
geometry: definitions2.polygon.length > 0 ? { type: "Polygon", coordinates: definitions2.polygon } : null
|
|
2615
|
+
};
|
|
2616
|
+
if (settings2.NoaaWeatherWireService.alertPreferences.isShapefileUGC && base.geometry == null && ugc != null) {
|
|
2617
|
+
const coordinates = yield ugc_default.getCoordinates(ugc.zones);
|
|
2618
|
+
base.geometry = { type: "Polygon", coordinates };
|
|
2619
|
+
}
|
|
2620
|
+
return base;
|
|
2621
|
+
});
|
|
2622
|
+
}
|
|
2623
|
+
/**
|
|
2624
|
+
* enhanceEvent refines the event name based on specific conditions and tags found in the event's description and parameters.
|
|
2625
|
+
*
|
|
2626
|
+
* @public
|
|
2627
|
+
* @static
|
|
2628
|
+
* @param {types.TypeAlert} event
|
|
2629
|
+
* @returns {{ eventName: any; tags: any; }}
|
|
2630
|
+
*/
|
|
2631
|
+
static enhanceEvent(event) {
|
|
2632
|
+
var _a, _b, _c, _d, _e;
|
|
2633
|
+
let eventName = (_b = (_a = event == null ? void 0 : event.properties) == null ? void 0 : _a.event) != null ? _b : `Unknown Event`;
|
|
2634
|
+
const defEventTable = definitions.enhancedEvents;
|
|
2635
|
+
const defEventTags = definitions.tags;
|
|
2636
|
+
const properties = event == null ? void 0 : event.properties;
|
|
2637
|
+
const parameters = properties == null ? void 0 : properties.parameters;
|
|
2638
|
+
const description = (_c = properties == null ? void 0 : properties.description) != null ? _c : `Unknown Description`;
|
|
2639
|
+
const damageThreatTag = (_d = parameters == null ? void 0 : parameters.damage_threat) != null ? _d : `N/A`;
|
|
2640
|
+
const tornadoThreatTag = (_e = parameters == null ? void 0 : parameters.tornado_detection) != null ? _e : `N/A`;
|
|
2641
|
+
for (const eventGroup of defEventTable) {
|
|
2642
|
+
const [baseEvent, conditions] = Object.entries(eventGroup)[0];
|
|
2643
|
+
if (eventName === baseEvent) {
|
|
2644
|
+
for (const [specificEvent, condition] of Object.entries(conditions)) {
|
|
2645
|
+
const conditionMet = condition.description && description.includes(condition.description.toLowerCase()) || condition.condition && condition.condition(damageThreatTag || tornadoThreatTag);
|
|
2646
|
+
if (conditionMet) {
|
|
2647
|
+
eventName = specificEvent;
|
|
2648
|
+
break;
|
|
2649
|
+
}
|
|
2650
|
+
}
|
|
2651
|
+
if (baseEvent === "Severe Thunderstorm Warning" && damageThreatTag === "POSSIBLE" && !eventName.includes("(TPROB)")) eventName += " (TPROB)";
|
|
2652
|
+
break;
|
|
2653
|
+
}
|
|
2654
|
+
}
|
|
2655
|
+
const tags = Object.entries(defEventTags).filter(([key]) => description.includes(key.toLowerCase())).map(([, value]) => value);
|
|
2656
|
+
return { eventName, tags: tags.length > 0 ? tags : [`N/A`] };
|
|
2657
|
+
}
|
|
2658
|
+
/**
|
|
2659
|
+
* getCorrectExpiryDate determines the correct expiration date for an alert based on VTEC information or UGC zones.
|
|
2660
|
+
*
|
|
2661
|
+
* @public
|
|
2662
|
+
* @static
|
|
2663
|
+
* @param {unknown[]} events
|
|
2664
|
+
*/
|
|
2665
|
+
static validateEvents(events2) {
|
|
2666
|
+
var _a, _b, _c, _d, _e;
|
|
2667
|
+
if (events2.length == 0) return;
|
|
2668
|
+
const settings2 = settings;
|
|
2669
|
+
const filteringSettings = (_b = (_a = settings) == null ? void 0 : _a.global) == null ? void 0 : _b.alertFiltering;
|
|
2670
|
+
const easSettings = (_d = (_c = settings) == null ? void 0 : _c.global) == null ? void 0 : _d.easSettings;
|
|
2671
|
+
const globalSettings = (_e = settings) == null ? void 0 : _e.global;
|
|
2672
|
+
const sets = {};
|
|
2673
|
+
const bools = {};
|
|
2674
|
+
const megered = __spreadValues(__spreadValues(__spreadValues({}, filteringSettings), easSettings), globalSettings);
|
|
2675
|
+
for (const key in megered) {
|
|
2676
|
+
const setting = megered[key];
|
|
2677
|
+
if (Array.isArray(setting)) {
|
|
2678
|
+
sets[key] = new Set(setting.map((item) => item.toLowerCase()));
|
|
2679
|
+
}
|
|
2680
|
+
if (typeof setting === "boolean") {
|
|
2681
|
+
bools[key] = setting;
|
|
2682
|
+
}
|
|
2683
|
+
}
|
|
2684
|
+
const filtered = events2.filter((alert) => {
|
|
2685
|
+
var _a2, _b2, _c2, _d2;
|
|
2686
|
+
const originalEvent = alert;
|
|
2687
|
+
const props = originalEvent == null ? void 0 : originalEvent.properties;
|
|
2688
|
+
const ugcs = (_b2 = (_a2 = props == null ? void 0 : props.geocode) == null ? void 0 : _a2.UGC) != null ? _b2 : [];
|
|
2689
|
+
if (bools == null ? void 0 : bools.betterEventParsing) {
|
|
2690
|
+
const { eventName, tags } = this.enhanceEvent(originalEvent);
|
|
2691
|
+
originalEvent.properties.event = eventName;
|
|
2692
|
+
originalEvent.properties.tags = tags;
|
|
2693
|
+
}
|
|
2694
|
+
const eventCheck = (bools == null ? void 0 : bools.useParentEvents) ? (_c2 = props.parent) == null ? void 0 : _c2.toLowerCase() : (_d2 = props.event) == null ? void 0 : _d2.toLowerCase();
|
|
2695
|
+
const statusCorrelation = definitions.correlations.find((c) => c.type === originalEvent.properties.action_type);
|
|
2696
|
+
for (const key in sets) {
|
|
2697
|
+
const setting = sets[key];
|
|
2698
|
+
if (key === "filteredEvents" && setting.size > 0 && eventCheck != null && !setting.has(eventCheck)) return false;
|
|
2699
|
+
if (key === "ignoredEvents" && setting.size > 0 && eventCheck != null && setting.has(eventCheck)) return false;
|
|
2700
|
+
if (key === "filteredICOAs" && setting.size > 0 && props.sender_icao != null && !setting.has(props.sender_icao.toLowerCase())) return false;
|
|
2701
|
+
if (key === "ignoredICOAs" && setting.size > 0 && props.sender_icao != null && setting.has(props.sender_icao.toLowerCase())) return false;
|
|
2702
|
+
if (key === "ugcFilter" && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc) => setting.has(ugc.toLowerCase()))) return false;
|
|
2703
|
+
if (key === "stateFilter" && setting.size > 0 && ugcs.length > 0 && !ugcs.some((ugc) => setting.has(ugc.substring(0, 2).toLowerCase()))) return false;
|
|
2704
|
+
if (key === "easAlerts" && setting.size > 0 && eventCheck != null && setting.has(eventCheck) && settings2.isNWWS) {
|
|
2705
|
+
eas_default.generateEASAudio(props.description, alert.header);
|
|
2706
|
+
}
|
|
2707
|
+
}
|
|
2708
|
+
for (const key in bools) {
|
|
2709
|
+
const setting = bools[key];
|
|
2710
|
+
if (key === "checkExpired" && setting && new Date(props == null ? void 0 : props.expires).getTime() < (/* @__PURE__ */ new Date()).getTime()) return false;
|
|
2711
|
+
}
|
|
2712
|
+
originalEvent.properties.action_type = statusCorrelation ? statusCorrelation.forward : originalEvent.properties.action_type;
|
|
2713
|
+
originalEvent.properties.is_cancelled = statusCorrelation ? statusCorrelation.cancel == true && bools.checkExpired : false;
|
|
2714
|
+
if (props.description) {
|
|
2715
|
+
const detectedPhrase = definitions.cancelSignatures.find((sig) => props.description.toLowerCase().includes(sig.toLowerCase()));
|
|
2716
|
+
if (detectedPhrase && bools.checkExpired) {
|
|
2717
|
+
originalEvent.properties.action_type = "Cancel";
|
|
2718
|
+
originalEvent.properties.is_cancelled = true;
|
|
2719
|
+
return false;
|
|
2720
|
+
}
|
|
2721
|
+
}
|
|
2722
|
+
if (originalEvent.vtec) {
|
|
2723
|
+
const getType = originalEvent.vtec.split(`.`)[0];
|
|
2724
|
+
const isTestProduct = definitions.productTypes[getType] == `Test Product`;
|
|
2725
|
+
if (isTestProduct) {
|
|
2726
|
+
return false;
|
|
2727
|
+
}
|
|
2728
|
+
}
|
|
2729
|
+
cache.events.emit(`on${originalEvent.properties.parent.replace(/\s+/g, "")}`);
|
|
2730
|
+
return true;
|
|
2731
|
+
});
|
|
2732
|
+
if (filtered.length > 0) {
|
|
2733
|
+
cache.events.emit(`onAlerts`, filtered);
|
|
2734
|
+
}
|
|
2735
|
+
}
|
|
2736
|
+
/**
|
|
2737
|
+
* getHeader constructs a standardized alert header string based on provided attributes, properties, and VTEC information.
|
|
2738
|
+
*
|
|
2739
|
+
* @public
|
|
2740
|
+
* @static
|
|
2741
|
+
* @param {types.TypeAttributes} attributes
|
|
2742
|
+
* @param {?types.BaseProperties} [properties]
|
|
2743
|
+
* @param {?types.VTECParsed} [vtec]
|
|
2744
|
+
* @returns {string}
|
|
2745
|
+
*/
|
|
2746
|
+
static getHeader(attributes, properties, vtec) {
|
|
2747
|
+
var _a, _b, _c, _d, _e, _f, _g;
|
|
2748
|
+
const parent = `ATSX`;
|
|
2749
|
+
const alertType = (_d = (_c = (_a = attributes == null ? void 0 : attributes.awipsType) == null ? void 0 : _a.type) != null ? _c : (_b = attributes == null ? void 0 : attributes.getAwip) == null ? void 0 : _b.prefix) != null ? _d : `XX`;
|
|
2750
|
+
const ugc = ((_e = properties == null ? void 0 : properties.geocode) == null ? void 0 : _e.UGC) != null ? (_f = properties == null ? void 0 : properties.geocode) == null ? void 0 : _f.UGC.join(`-`) : `000000`;
|
|
2751
|
+
const status = (vtec == null ? void 0 : vtec.status) || "Issued";
|
|
2752
|
+
const issued = (properties == null ? void 0 : properties.issued) != null ? (_g = new Date(properties == null ? void 0 : properties.issued)) == null ? void 0 : _g.toISOString().replace(/[-:]/g, "").split(".")[0] : (/* @__PURE__ */ new Date()).toISOString().replace(/[-:]/g, "").split(".")[0];
|
|
2753
|
+
const sender = (properties == null ? void 0 : properties.sender_icao) || `XXXX`;
|
|
2754
|
+
const header = `ZCZC-${parent}-${alertType}-${ugc}-${status}-${issued}-${sender}-`;
|
|
2755
|
+
return header;
|
|
2756
|
+
}
|
|
2757
|
+
/**
|
|
2758
|
+
* eventHandler routes the validated alert message to the appropriate event parser based on its type (API, CAP, VTEC, UGC, or plain text).
|
|
2759
|
+
*
|
|
2760
|
+
* @public
|
|
2761
|
+
* @static
|
|
2762
|
+
* @param {types.TypeCompiled} validated
|
|
2763
|
+
* @returns {*}
|
|
2764
|
+
*/
|
|
2765
|
+
static eventHandler(validated) {
|
|
2766
|
+
if (validated.isApi) return api_default.event(validated);
|
|
2767
|
+
if (validated.isCap) return cap_default.event(validated);
|
|
2768
|
+
if (!validated.isCap && validated.isVtec && validated.isUGC) return vtec_default2.event(validated);
|
|
2769
|
+
if (!validated.isCap && !validated.isVtec && validated.isUGC) return ugc_default2.event(validated);
|
|
2770
|
+
if (!validated.isCap && !validated.isVtec && !validated.isUGC) return text_default2.event(validated);
|
|
2771
|
+
}
|
|
2772
|
+
/**
|
|
2773
|
+
* getICAO retrieves the ICAO code and corresponding office name based on VTEC tracking information, message attributes, or WMO code.
|
|
2774
|
+
*
|
|
2775
|
+
* @private
|
|
2776
|
+
* @static
|
|
2777
|
+
* @param {types.VTECParsed} vtec
|
|
2778
|
+
* @param {Record<string, string>} attributes
|
|
2779
|
+
* @param {(RegExpMatchArray | string | null)} WMO
|
|
2780
|
+
* @returns {{ icao: any; name: any; }}
|
|
2781
|
+
*/
|
|
2782
|
+
static getICAO(vtec, attributes, WMO) {
|
|
2783
|
+
var _a, _b, _c;
|
|
2784
|
+
const icao = vtec != null ? vtec == null ? void 0 : vtec.tracking.split(`-`)[0] : (_a = attributes == null ? void 0 : attributes.cccc) != null ? _a : WMO != null ? Array.isArray(WMO) ? WMO[0] : WMO : `N/A`;
|
|
2785
|
+
const name = (_c = (_b = definitions.ICAO) == null ? void 0 : _b[icao]) != null ? _c : `N/A`;
|
|
2786
|
+
return { icao, name };
|
|
2787
|
+
}
|
|
2788
|
+
/**
|
|
2789
|
+
* getCorrectIssuedDate ensures the issued date is valid and falls back to the current date if not.
|
|
2790
|
+
*
|
|
2791
|
+
* @private
|
|
2792
|
+
* @static
|
|
2793
|
+
* @param {Record<string, string>} attributes
|
|
2794
|
+
* @returns {*}
|
|
2795
|
+
*/
|
|
2796
|
+
static getCorrectIssuedDate(attributes) {
|
|
2797
|
+
const time = attributes.issue != null ? new Date(attributes.issue).toLocaleString() : (attributes == null ? void 0 : attributes.issue) != null ? new Date(attributes.issue).toLocaleString() : (/* @__PURE__ */ new Date()).toLocaleString();
|
|
2798
|
+
if (time == `Invalid Date`) return (/* @__PURE__ */ new Date()).toLocaleString();
|
|
2799
|
+
return time;
|
|
2800
|
+
}
|
|
2801
|
+
/**
|
|
2802
|
+
* getCorrectExpiryDate determines the correct expiration date for an alert based on VTEC information or UGC zones.
|
|
2803
|
+
*
|
|
2804
|
+
* @private
|
|
2805
|
+
* @static
|
|
2806
|
+
* @param {types.VTECParsed} vtec
|
|
2807
|
+
* @param {types.UGCParsed} ugc
|
|
2808
|
+
* @returns {*}
|
|
2809
|
+
*/
|
|
2810
|
+
static getCorrectExpiryDate(vtec, ugc) {
|
|
2811
|
+
const time = (vtec == null ? void 0 : vtec.expires) && !isNaN(new Date(vtec.expires).getTime()) ? new Date(vtec.expires).toLocaleString() : (ugc == null ? void 0 : ugc.expiry) != null ? new Date(ugc.expiry).toLocaleString() : new Date((/* @__PURE__ */ new Date()).getTime() + 1 * 60 * 60 * 1e3).toLocaleString();
|
|
2812
|
+
if (time == `Invalid Date`) return new Date((/* @__PURE__ */ new Date()).getTime() + 1 * 60 * 60 * 1e3).toLocaleString();
|
|
2813
|
+
return time;
|
|
2814
|
+
}
|
|
2815
|
+
};
|
|
2816
|
+
var events_default = EventParser;
|
|
2817
|
+
|
|
2818
|
+
// src/parsers/types/text.ts
|
|
2819
|
+
var UGCAlerts2 = class {
|
|
2820
|
+
static getTracking(baseProperties) {
|
|
2821
|
+
return `${baseProperties.sender_icao}`;
|
|
2822
|
+
}
|
|
2823
|
+
static getEvent(message, attributes) {
|
|
2824
|
+
const offshoreEvent = Object.keys(definitions.offshore).find((event) => message.toLowerCase().includes(event.toLowerCase()));
|
|
2825
|
+
if (offshoreEvent) return Object.keys(definitions.offshore).find((event) => message.toLowerCase().includes(event.toLowerCase()));
|
|
2826
|
+
return attributes.type.split(`-`).map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `);
|
|
2827
|
+
}
|
|
2828
|
+
static event(validated) {
|
|
2829
|
+
return __async(this, null, function* () {
|
|
2830
|
+
var _a;
|
|
2831
|
+
let processed = [];
|
|
2832
|
+
const messages = (_a = validated.message.split(/(?=\$\$|ISSUED TIME...|=================================================)/g)) == null ? void 0 : _a.map((msg) => msg.trim());
|
|
2833
|
+
if (!messages || messages.length == 0) return;
|
|
2834
|
+
for (let i = 0; i < messages.length; i++) {
|
|
2835
|
+
const tick = performance.now();
|
|
2836
|
+
const message = messages[i];
|
|
2837
|
+
const getBaseProperties = yield events_default.getBaseProperties(message, validated);
|
|
2838
|
+
const getHeader = events_default.getHeader(__spreadValues(__spreadValues({}, validated.attributes), getBaseProperties.attributes), getBaseProperties);
|
|
2839
|
+
const getEvent = this.getEvent(message, getBaseProperties.attributes.getAwip);
|
|
2840
|
+
processed.push({
|
|
2841
|
+
preformance: performance.now() - tick,
|
|
2842
|
+
tracking: this.getTracking(getBaseProperties),
|
|
2843
|
+
header: getHeader,
|
|
2844
|
+
vtec: `N/A`,
|
|
2845
|
+
history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: `Issued` }],
|
|
2846
|
+
properties: __spreadValues({ event: getEvent, parent: getEvent, action_type: `Issued` }, getBaseProperties)
|
|
2847
|
+
});
|
|
2848
|
+
}
|
|
2849
|
+
events_default.validateEvents(processed);
|
|
2850
|
+
});
|
|
2851
|
+
}
|
|
2852
|
+
};
|
|
2853
|
+
var text_default2 = UGCAlerts2;
|
|
2854
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
2855
|
+
0 && (module.exports = {
|
|
2856
|
+
UGCAlerts
|
|
2857
|
+
});
|