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.
Files changed (84) hide show
  1. package/LICENSE +17 -0
  2. package/README.md +6 -0
  3. package/dist/cjs/bootstrap.cjs +1009 -0
  4. package/dist/cjs/database.cjs +1114 -0
  5. package/dist/cjs/dictionaries/awips.cjs +379 -0
  6. package/dist/cjs/dictionaries/events.cjs +139 -0
  7. package/dist/cjs/dictionaries/icao.cjs +265 -0
  8. package/dist/cjs/dictionaries/offshore.cjs +40 -0
  9. package/dist/cjs/dictionaries/signatures.cjs +132 -0
  10. package/dist/cjs/eas.cjs +2857 -0
  11. package/dist/cjs/helper.cjs +3014 -0
  12. package/dist/cjs/parsers/events.cjs +2857 -0
  13. package/dist/cjs/parsers/stanza.cjs +1108 -0
  14. package/dist/cjs/parsers/text.cjs +1142 -0
  15. package/dist/cjs/parsers/types/api.cjs +2857 -0
  16. package/dist/cjs/parsers/types/cap.cjs +2857 -0
  17. package/dist/cjs/parsers/types/text.cjs +2857 -0
  18. package/dist/cjs/parsers/types/ugc.cjs +2857 -0
  19. package/dist/cjs/parsers/types/vtec.cjs +2857 -0
  20. package/dist/cjs/parsers/ugc.cjs +1139 -0
  21. package/dist/cjs/parsers/vtec.cjs +1060 -0
  22. package/dist/cjs/types.cjs +17 -0
  23. package/dist/cjs/utils.cjs +2857 -0
  24. package/dist/cjs/xmpp.cjs +2857 -0
  25. package/dist/esm/bootstrap.mjs +972 -0
  26. package/dist/esm/database.mjs +1079 -0
  27. package/dist/esm/dictionaries/awips.mjs +355 -0
  28. package/dist/esm/dictionaries/events.mjs +111 -0
  29. package/dist/esm/dictionaries/icao.mjs +241 -0
  30. package/dist/esm/dictionaries/offshore.mjs +16 -0
  31. package/dist/esm/dictionaries/signatures.mjs +106 -0
  32. package/dist/esm/eas.mjs +2824 -0
  33. package/dist/esm/helper.mjs +2974 -0
  34. package/dist/esm/parsers/events.mjs +2824 -0
  35. package/dist/esm/parsers/stanza.mjs +1072 -0
  36. package/dist/esm/parsers/text.mjs +1106 -0
  37. package/dist/esm/parsers/types/api.mjs +2824 -0
  38. package/dist/esm/parsers/types/cap.mjs +2824 -0
  39. package/dist/esm/parsers/types/text.mjs +2824 -0
  40. package/dist/esm/parsers/types/ugc.mjs +2824 -0
  41. package/dist/esm/parsers/types/vtec.mjs +2824 -0
  42. package/dist/esm/parsers/ugc.mjs +1104 -0
  43. package/dist/esm/parsers/vtec.mjs +1025 -0
  44. package/dist/esm/types.mjs +0 -0
  45. package/dist/esm/utils.mjs +2824 -0
  46. package/dist/esm/xmpp.mjs +2824 -0
  47. package/package.json +47 -0
  48. package/shapefiles/FireCounties.dbf +0 -0
  49. package/shapefiles/FireCounties.shp +0 -0
  50. package/shapefiles/FireZones.dbf +0 -0
  51. package/shapefiles/FireZones.shp +0 -0
  52. package/shapefiles/ForecastZones.dbf +0 -0
  53. package/shapefiles/ForecastZones.shp +0 -0
  54. package/shapefiles/Marine.dbf +0 -0
  55. package/shapefiles/Marine.shp +0 -0
  56. package/shapefiles/OffShoreZones.dbf +0 -0
  57. package/shapefiles/OffShoreZones.shp +0 -0
  58. package/shapefiles/USCounties.dbf +0 -0
  59. package/shapefiles/USCounties.shp +0 -0
  60. package/src/bootstrap.ts +171 -0
  61. package/src/database.ts +99 -0
  62. package/src/dictionaries/awips.ts +351 -0
  63. package/src/dictionaries/events.ts +109 -0
  64. package/src/dictionaries/icao.ts +237 -0
  65. package/src/dictionaries/offshore.ts +12 -0
  66. package/src/dictionaries/signatures.ts +103 -0
  67. package/src/eas.ts +428 -0
  68. package/src/helper.ts +167 -0
  69. package/src/parsers/events.ts +289 -0
  70. package/src/parsers/stanza.ts +103 -0
  71. package/src/parsers/text.ts +167 -0
  72. package/src/parsers/types/api.ts +94 -0
  73. package/src/parsers/types/cap.ts +89 -0
  74. package/src/parsers/types/text.ts +54 -0
  75. package/src/parsers/types/ugc.ts +85 -0
  76. package/src/parsers/types/vtec.ts +60 -0
  77. package/src/parsers/ugc.ts +148 -0
  78. package/src/parsers/vtec.ts +66 -0
  79. package/src/types.ts +187 -0
  80. package/src/utils.ts +216 -0
  81. package/src/xmpp.ts +123 -0
  82. package/test.js +1 -0
  83. package/tsconfig.json +14 -0
  84. package/tsup.config.ts +11 -0
@@ -0,0 +1,89 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as types from '../../types';
15
+ import * as loader from '../../bootstrap';
16
+ import EventParser from '../events';
17
+ import TextParser from '../text';
18
+
19
+
20
+ export class CapAlerts {
21
+
22
+ private static getTracking(extracted: Record<string, string>) {
23
+ return extracted.vtec ? (() => {
24
+ const vtecValue = Array.isArray(extracted.vtec) ? extracted.vtec[0] : extracted.vtec;
25
+ const splitVTEC = vtecValue.split('.');
26
+ return `${splitVTEC[2]}-${splitVTEC[3]}-${splitVTEC[4]}-${splitVTEC[5]}`;
27
+ })() : `${extracted.wmoidentifier} (${extracted.ugc})`;
28
+ }
29
+
30
+ public static async event(validated: types.TypeCompiled) {
31
+ let processed = [] as unknown[];
32
+ const messages = validated.message.match(/<\?xml[\s\S]*?<\/alert>/g)?.map(msg => msg.trim());
33
+ if (messages == null || messages.length === 0) return;
34
+ for (let message of messages) {
35
+ const tick = performance.now();
36
+ message = message.substring(message.indexOf(`<?xml version="1.0"`), message.lastIndexOf(`>`) + 1);
37
+ const parser = new loader.packages.xml2js.Parser({ explicitArray: false, mergeAttrs: true, trim: true })
38
+ const parsed = await parser.parseStringPromise(message);
39
+ if (parsed == null || parsed.alert == null) continue;
40
+ const extracted = TextParser.getXmlValues(parsed, [
41
+ `vtec`, `wmoidentifier`, `ugc`, `areadesc`,
42
+ `expires`, `sent`, `msgtype`, `description`,
43
+ `event`, `sendername`, `tornadodetection`, `polygon`,
44
+ `maxHailSize`, `maxWindGust`, `thunderstormdamagethreat`,
45
+ `tornadodamagethreat`, `waterspoutdetection`, `flooddetection`,
46
+ ]);
47
+ const getHeader = EventParser.getHeader({ ...validated.attributes,} as types.TypeAttributes);
48
+ const getSource = TextParser.textProductToString(extracted.description, `SOURCE...`, [`.`]) || `N/A`;
49
+ processed.push({
50
+ preformance: performance.now() - tick,
51
+ tracking: this.getTracking(extracted),
52
+ header: getHeader,
53
+ vtec: extracted.vtec || `N/A`,
54
+ history: [{ description: extracted.description || `N/A`, issued: extracted.sent ? new Date(extracted.sent).toLocaleString() : `N/A`, type: extracted.msgtype || `N/A` }],
55
+ properties: {
56
+ locations: extracted.areadesc || `N/A`,
57
+ event: extracted.event || `N/A`,
58
+ issued: extracted.sent ? new Date(extracted.sent).toLocaleString() : `N/A`,
59
+ expires: extracted.expires ? new Date(extracted.expires).toLocaleString() : `N/A`,
60
+ parent: extracted.event || `N/A`,
61
+ action_type: extracted.msgtype || `N/A`,
62
+ description: extracted.description || `N/A`,
63
+ sender_name: extracted.sendername || `N/A`,
64
+ sender_icao: extracted.wmoidentifier ? extracted.wmoidentifier.substring(extracted.wmoidentifier.length - 4) : `N/A`,
65
+ attributes: validated.attributes,
66
+ geocode: {
67
+ UGC: [extracted.ugc],
68
+ },
69
+ parameters: {
70
+ wmo: extracted.wmoidentifier || `N/A`,
71
+ source: getSource,
72
+ max_hail_size: extracted.maxHailSize || `N/A`,
73
+ max_wind_gust: extracted.maxWindGust || `N/A`,
74
+ damage_threat: extracted.thunderstormdamagethreat || `N/A`,
75
+ tornado_detection: extracted.tornadodetection || extracted.waterspoutdetection || `N/A`,
76
+ flood_detection: extracted.flooddetection || `N/A`,
77
+ discussion_tornado_intensity: `N/A`,
78
+ discussion_wind_intensity: `N/A`,
79
+ discussion_hail_intensity: `N/A`,
80
+ },
81
+ geometry: extracted.polygon ? { type: `Polygon`, coordinates: extracted.polygon.split(` `).map((coord: string) => coord.split(`,`).map((num: string) => parseFloat(num))) } : null,
82
+ }
83
+ })
84
+ }
85
+ EventParser.validateEvents(processed);
86
+ }
87
+ }
88
+
89
+ export default CapAlerts;
@@ -0,0 +1,54 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as types from '../../types';
15
+ import * as loader from '../../bootstrap';
16
+ import EventParser from '../events';
17
+
18
+
19
+ export class UGCAlerts {
20
+
21
+ private static getTracking(baseProperties: types.BaseProperties) {
22
+ return `${baseProperties.sender_icao}`
23
+ }
24
+
25
+ private static getEvent(message: string, attributes: Record<string, any>) {
26
+ const offshoreEvent = Object.keys(loader.definitions.offshore).find(event => message.toLowerCase().includes(event.toLowerCase()));
27
+ if (offshoreEvent) return Object.keys(loader.definitions.offshore).find(event => message.toLowerCase().includes(event.toLowerCase()));
28
+ return attributes.type.split(`-`).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `)
29
+ }
30
+
31
+ public static async event(validated: types.TypeCompiled) {
32
+ let processed = [] as unknown[];
33
+ const messages = validated.message.split(/(?=\$\$|ISSUED TIME...|=================================================)/g)?.map(msg => msg.trim());
34
+ if (!messages || messages.length == 0) return;
35
+ for (let i = 0; i < messages.length; i++) {
36
+ const tick = performance.now();
37
+ const message = messages[i]
38
+ const getBaseProperties = await EventParser.getBaseProperties(message, validated) as types.BaseProperties;
39
+ const getHeader = EventParser.getHeader({ ...validated.attributes, ...getBaseProperties.attributes } as types.TypeAttributes, getBaseProperties);
40
+ const getEvent = this.getEvent(message, getBaseProperties.attributes.getAwip);
41
+ processed.push({
42
+ preformance: performance.now() - tick,
43
+ tracking: this.getTracking(getBaseProperties),
44
+ header: getHeader,
45
+ vtec: `N/A`,
46
+ history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: `Issued` }],
47
+ properties: { event: getEvent, parent: getEvent, action_type: `Issued`, ...getBaseProperties, }
48
+ })
49
+ }
50
+ EventParser.validateEvents(processed);
51
+ }
52
+ }
53
+
54
+ export default UGCAlerts;
@@ -0,0 +1,85 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as types from '../../types';
15
+ import * as loader from '../../bootstrap';
16
+ import UgcParser from '../ugc';
17
+ import EventParser from '../events';
18
+
19
+
20
+ export class UGCAlerts {
21
+
22
+ /**
23
+ * getTracking generates a unique tracking identifier based on the sender's ICAO code and a hash of the UGC zones.
24
+ *
25
+ * @private
26
+ * @static
27
+ * @param {types.BaseProperties} baseProperties
28
+ * @param {string[]} zones
29
+ * @returns {string}
30
+ */
31
+ private static getTracking(baseProperties: types.BaseProperties, zones: string[]) {
32
+ return `${baseProperties.sender_icao} (${loader.packages.crypto.createHash('md5').update(zones.join(``)).digest('hex')}})`
33
+ }
34
+
35
+ /**
36
+ * getEvent determines the event name based on offshore definitions or formats it from the attributes.
37
+ *
38
+ * @private
39
+ * @static
40
+ * @param {string} message
41
+ * @param {Record<string, any>} attributes
42
+ * @returns {*}
43
+ */
44
+ private static getEvent(message: string, attributes: Record<string, any>) {
45
+ const offshoreEvent = Object.keys(loader.definitions.offshore).find(event => message.toLowerCase().includes(event.toLowerCase()));
46
+ if (offshoreEvent != undefined ) return Object.keys(loader.definitions.offshore).find(event => message.toLowerCase().includes(event.toLowerCase()));
47
+ return attributes.type.split(`-`).map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join(` `)
48
+ }
49
+
50
+ /**
51
+ * event processes validated UGC alert messages, extracting relevant information and compiling it into structured event objects.
52
+ *
53
+ * @public
54
+ * @static
55
+ * @async
56
+ * @param {types.TypeCompiled} validated
57
+ * @returns {*}
58
+ */
59
+ public static async event(validated: types.TypeCompiled) {
60
+ let processed = [] as unknown[];
61
+ const messages = validated.message.split(/(?=\$\$|ISSUED TIME...|=================================================)/g)?.map(msg => msg.trim());
62
+ if (!messages || messages.length == 0) return;
63
+ for (let i = 0; i < messages.length; i++) {
64
+ const tick = performance.now();
65
+ const message = messages[i]
66
+ const getUGC = await UgcParser.ugcExtractor(message) as types.UGCParsed;
67
+ if (getUGC != null) {
68
+ const getBaseProperties = await EventParser.getBaseProperties(message, validated, getUGC) as types.BaseProperties;
69
+ const getHeader = EventParser.getHeader({ ...validated.attributes, ...getBaseProperties.attributes } as types.TypeAttributes, getBaseProperties);
70
+ const getEvent = this.getEvent(message, getBaseProperties.attributes.getAwip);
71
+ processed.push({
72
+ preformance: performance.now() - tick,
73
+ tracking: this.getTracking(getBaseProperties, getUGC.zones),
74
+ header: getHeader,
75
+ vtec: `N/A`,
76
+ history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: `Issued` }],
77
+ properties: { event: getEvent, parent: getEvent, action_type: `Issued`, ...getBaseProperties, }
78
+ })
79
+ }
80
+ }
81
+ EventParser.validateEvents(processed);
82
+ }
83
+ }
84
+
85
+ export default UGCAlerts;
@@ -0,0 +1,60 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as types from '../../types';
15
+ import VtecParser from '../vtec';
16
+ import UgcParser from '../ugc';
17
+ import EventParser from '../events';
18
+
19
+
20
+ export class VTECAlerts {
21
+
22
+ /**
23
+ * event processes validated VTEC alert messages, extracting relevant information and compiling it into structured event objects.
24
+ *
25
+ * @public
26
+ * @static
27
+ * @async
28
+ * @param {types.TypeCompiled} validated
29
+ * @returns {*}
30
+ */
31
+ public static async event(validated: types.TypeCompiled) {
32
+ let processed = [] as unknown[];
33
+ const messages = validated.message.split(/(?=\$\$)/g)?.map(msg => msg.trim());
34
+ if (!messages || messages.length == 0) return;
35
+ for (let i = 0; i < messages.length; i++) {
36
+ const tick = performance.now();
37
+ const message = messages[i]
38
+ const getVTEC = await VtecParser.vtecExtractor(message) as types.VTECParsed[];
39
+ const getUGC = await UgcParser.ugcExtractor(message) as types.UGCParsed;
40
+ if (getVTEC != null && getUGC != null) {
41
+ for (let j = 0; j < getVTEC.length; j++) {
42
+ const vtec = getVTEC[j];
43
+ const getBaseProperties = await EventParser.getBaseProperties(message, validated, getUGC, vtec) as types.BaseProperties;
44
+ const getHeader = EventParser.getHeader({ ...validated.attributes, ...getBaseProperties.attributes } as types.TypeAttributes, getBaseProperties, vtec);
45
+ processed.push({
46
+ preformance: performance.now() - tick,
47
+ tracking: vtec.tracking,
48
+ header: getHeader,
49
+ vtec: vtec.raw,
50
+ history: [{ description: getBaseProperties.description, issued: getBaseProperties.issued, type: vtec.status }],
51
+ properties: { event: vtec.event, parent: vtec.event, action_type: vtec.status, ...getBaseProperties, }
52
+ })
53
+ }
54
+ }
55
+ }
56
+ EventParser.validateEvents(processed);
57
+ }
58
+ }
59
+
60
+ export default VTECAlerts;
@@ -0,0 +1,148 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as loader from '../bootstrap';
15
+
16
+
17
+ export class UGCParser {
18
+
19
+ /**
20
+ * ugcExtractor extracts and parses UGC codes from a given message string.
21
+ *
22
+ * @public
23
+ * @static
24
+ * @async
25
+ * @param {string} message
26
+ * @returns {unknown}
27
+ */
28
+ public static async ugcExtractor(message: string) {
29
+ const header = this.getHeader(message);
30
+ const zones = this.getZones(header);
31
+ const expiry = this.getExpiry(message)
32
+ const locations = await this.getLocations(zones)
33
+ const ugc = zones.length > 0 ? { zones, locations, expiry } : null;
34
+ return ugc;
35
+ }
36
+
37
+ /**
38
+ * getHeader extracts the UGC header from a UGC message string.
39
+ *
40
+ * @public
41
+ * @static
42
+ * @param {string} message
43
+ * @returns {*}
44
+ */
45
+ public static getHeader(message: string) {
46
+ const start = message.search(new RegExp(loader.definitions.expressions.ugc1, "gimu"));
47
+ const end = message.substring(start).search(new RegExp(loader.definitions.expressions.ugc2, "gimu"));
48
+ const full = message.substring(start, start + end).replace(/\s+/g, '').slice(0, -1);
49
+ return full;
50
+ }
51
+
52
+ /**
53
+ * getExpiry extracts the expiry date and time from a UGC message and returns it as a Date object.
54
+ *
55
+ * @public
56
+ * @static
57
+ * @param {string} message
58
+ * @returns {*}
59
+ */
60
+ public static getExpiry (message: string) {
61
+ const start = message.match(new RegExp(loader.definitions.expressions.ugc3, "gimu"));
62
+ if (start != null) {
63
+ const day = parseInt(start[0].substring(0, 2), 10);
64
+ const hour = parseInt(start[0].substring(2, 4), 10);
65
+ const minute = parseInt(start[0].substring(4, 6), 10);
66
+ const now = new Date();
67
+ const expires = new Date(now.getUTCFullYear(), now.getUTCMonth(), day, hour, minute, 0);
68
+ return expires;
69
+ }
70
+ return null;
71
+ }
72
+
73
+ /**
74
+ * getLocations retrieves unique location names for the provided UGC zone codes from the database.
75
+ *
76
+ * @public
77
+ * @static
78
+ * @async
79
+ * @param {String[]} zones
80
+ * @returns {unknown}
81
+ */
82
+ public static async getLocations (zones: String[]) {
83
+ const locations: string[] = [];
84
+ for (let i = 0; i < zones.length; i++) {
85
+ const id = zones[i].trim();
86
+ const located = await loader.cache.db.prepare(`SELECT location FROM shapefiles WHERE id = ?`).get(id);
87
+ located != undefined ? locations.push(located.location) : locations.push(id);
88
+ }
89
+ return Array.from(new Set(locations)).sort();
90
+ }
91
+
92
+ /**
93
+ * getCoordinates retrieves geographical coordinates for the provided UGC zone codes from the database.
94
+ *
95
+ * @public
96
+ * @static
97
+ * @param {String[]} zones
98
+ * @returns {{}}
99
+ */
100
+ public static getCoordinates (zones: String[]) {
101
+ let coordinates: [number, number][] = [];
102
+ for (let i = 0; i < zones.length; i++) {
103
+ const id = zones[i].trim();
104
+ let located = loader.cache.db.prepare(`SELECT geometry FROM shapefiles WHERE id = ?`).get(id);
105
+ if (located != undefined) {
106
+ let geometry = JSON.parse(located.geometry);
107
+ if (geometry?.type === 'Polygon') {
108
+ coordinates.push(...geometry.coordinates[0].map((coord: [number, number]) => [coord[0], coord[1]]));
109
+ break;
110
+ }
111
+ }
112
+ }
113
+ return coordinates;
114
+ }
115
+
116
+ /**
117
+ * getZones parses a UGC header string and returns an array of individual UGC zone codes.
118
+ *
119
+ * @public
120
+ * @static
121
+ * @param {string} header
122
+ * @returns {*}
123
+ */
124
+ public static getZones (header: string) {
125
+ const ugcSplit = header.split('-')
126
+ const zones: string[] = [];
127
+ let state = ugcSplit[0].substring(0, 2)
128
+ let format = ugcSplit[0].substring(2, 3);
129
+ for (let i = 0; i < ugcSplit.length; i++) {
130
+ if (/^[A-Z]/.test(ugcSplit[i])) {
131
+ state = ugcSplit[i].substring(0, 2);
132
+ if (ugcSplit[i].includes('>')) {
133
+ let [start, end] = ugcSplit[i].split('>'), startNum = parseInt(start.substring(3), 10), endNum = parseInt(end, 10);
134
+ for (let j = startNum; j <= endNum; j++) zones.push(`${state}${format}${j.toString().padStart(3, '0')}`);
135
+ } else zones.push(ugcSplit[i]);
136
+ continue;
137
+ }
138
+ if (ugcSplit[i].includes('>')) {
139
+ let [start, end] = ugcSplit[i].split('>'), startNum = parseInt(start, 10), endNum = parseInt(end, 10);
140
+ for (let j = startNum; j <= endNum; j++) zones.push(`${state}${format}${j.toString().padStart(3, '0')}`);
141
+ } else zones.push(`${state}${format}${ugcSplit[i]}`);
142
+ }
143
+ return zones.filter(item => item !== '');
144
+ }
145
+
146
+ }
147
+
148
+ export default UGCParser;
@@ -0,0 +1,66 @@
1
+ /*
2
+ _ _ __ __
3
+ /\ | | | | (_) \ \ / /
4
+ / \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
5
+ / /\ \| __| "_ ` _ \ / _ \/ __| "_ \| "_ \ / _ \ "__| |/ __| > <
6
+ / ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
7
+ /_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
8
+ | |
9
+ |_|
10
+
11
+ Written by: KiyoWx (k3yomi)
12
+ */
13
+
14
+ import * as loader from '../bootstrap';
15
+
16
+ export class VtecParser {
17
+
18
+ /**
19
+ * vtecExtractor extracts and parses VTEC codes from a given message string.
20
+ *
21
+ * @public
22
+ * @static
23
+ * @async
24
+ * @param {string} message
25
+ * @returns {unknown}
26
+ */
27
+ public static async vtecExtractor(message: string) {
28
+ const vtecs: unknown[] = [];
29
+ const matches = message.match(new RegExp(loader.definitions.expressions.vtec, 'g'));
30
+ if (!matches) return null;
31
+ for (let i = 0; i < matches.length; i++) {
32
+ const vtec = matches[i];
33
+ const parts = vtec.split(`.`);
34
+ const dates = parts[6].split(`-`);
35
+ vtecs.push({
36
+ raw: vtec,
37
+ type: loader.definitions.productTypes[parts[0]],
38
+ tracking: `${parts[2]}-${parts[3]}-${parts[4]}-${parts[5]}`,
39
+ event: `${loader.definitions.events[parts[3]]} ${loader.definitions.actions[parts[4]]}`,
40
+ status: loader.definitions.status[parts[1]],
41
+ wmo: message.match(new RegExp(loader.definitions.expressions.wmo, 'gimu')),
42
+ expires: this.parseExpiryDate(dates)
43
+ })
44
+ }
45
+ return vtecs;
46
+ }
47
+
48
+ /**
49
+ * parseExpiryDate converts VTEC expiry date format to a standard ISO 8601 format with timezone adjustment.
50
+ *
51
+ * @private
52
+ * @static
53
+ * @param {String[]} args
54
+ * @returns {string}
55
+ */
56
+ private static parseExpiryDate(args: String[]) {
57
+ if (args[1] == `000000T0000Z`) return `Invalid Date Format`;
58
+ const expires = `${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`;
59
+ const local = new Date(new Date(expires).getTime() - 4 * 60 * 60000);
60
+ const pad = (n: number) => n.toString().padStart(2, '0');
61
+ return `${local.getFullYear()}-${pad(local.getMonth() + 1)}-${pad(local.getDate())}T${pad(local.getHours())}:${pad(local.getMinutes())}:00.000-04:00`;
62
+ }
63
+
64
+ }
65
+
66
+ export default VtecParser;