@atmosx/event-product-parser 3.0.62 → 3.0.75
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/.github/docs/docs-functions.md +1 -1
- package/.github/docs/docs-general-configurations.md +1 -11
- package/.github/docs/docs-integrations.md +3 -2
- package/README.md +10 -8
- package/dist/cjs/index.cjs +24482 -7207
- package/dist/esm/index.mjs +24275 -7003
- package/package.json +56 -47
- package/pnpm-workspace.yaml +4 -0
- package/src/bootstrap.ts +65 -48
- package/src/{@building → building}/CreateEvent.ts +22 -24
- package/src/{@building → building}/GetCleanedEvent.ts +2 -2
- package/src/{@building → building}/GetEventAttachments.ts +14 -12
- package/src/building/GetEventDirection.ts +28 -0
- package/src/{@building → building}/GetEventEnhancedName.ts +5 -8
- package/src/{@building → building}/GetEventGeometry.ts +19 -13
- package/src/{@building → building}/GetEventHeader.ts +13 -13
- package/src/{@building → building}/GetEventNodes.ts +22 -20
- package/src/{@building → building}/GetEventOffice.ts +13 -13
- package/src/building/GetEventPopulation.ts +58 -0
- package/src/building/GetEventProperties.ts +106 -0
- package/src/{@building → building}/GetEventSignature.ts +9 -9
- package/src/{@building → building}/GetEventTags.ts +3 -3
- package/src/building/GetEventTheme.ts +26 -0
- package/src/building/GetEventTracking.ts +64 -0
- package/src/components/audio/GenerateAudioMessage.ts +149 -0
- package/src/{@modules/@eas → components/audio/components}/GetCleanDescription.ts +2 -2
- package/src/{@modules/@eas → components/audio/components}/GetFloatPCM16.ts +2 -2
- package/src/{@modules/@eas → components/audio/components}/GetMergedPCM16.ts +2 -2
- package/src/{@modules/@eas → components/audio/components}/GetPCM16.ts +13 -8
- package/src/{@modules/@eas → components/audio/components}/GetPCMToFloat.ts +2 -2
- package/src/{@modules/@eas → components/audio/components}/GetSampledPCM16.ts +14 -8
- package/src/{@modules/@eas → components/audio/components}/GetTTS.ts +17 -10
- package/src/{@modules/@eas → components/audio/components}/GetWavPCM16.ts +7 -7
- package/src/{@modules/@eas → components/audio/components}/SetAFSK.ts +13 -8
- package/src/{@modules/@eas → components/audio/components}/SetAsciiToBits.ts +2 -2
- package/src/{@modules/@eas → components/audio/components}/SetAttentionTone.ts +11 -6
- package/src/{@modules/@eas → components/audio/components}/SetNoise.ts +12 -7
- package/src/{@modules/@eas → components/audio/components}/SetRadioEffect.ts +12 -7
- package/src/{@modules/@eas → components/audio/components}/SetSameHeader.ts +22 -14
- package/src/{@modules/@utilities/SetWarning.ts → components/database/CreateQuery.ts} +18 -13
- package/src/{@modules/@database → components/database}/GetCachedEvents.ts +18 -15
- package/src/components/database/ImportBoundaries.ts +168 -0
- package/src/{@modules/@database → components/database}/ImportBroadcastify.ts +14 -24
- package/src/components/database/ImportCities.ts +87 -0
- package/src/{@modules/@database → components/database}/ImportShapefiles.ts +23 -26
- package/src/{@modules/@database → components/database}/ImportStanza.ts +15 -19
- package/src/components/database/InitializeDatabase.ts +71 -0
- package/src/components/images/GenerateGraphic.ts +242 -0
- package/src/components/images/components/CreateRectangle.ts +22 -0
- package/src/components/images/components/CreateText.ts +22 -0
- package/src/components/images/modules/CreateSVG.ts +37 -0
- package/src/components/images/modules/GetGeographicalBoundaries.ts +72 -0
- package/src/components/images/modules/GetGeographicalCities.ts +58 -0
- package/src/components/images/modules/GetGeographicalEvents.ts +48 -0
- package/src/components/images/modules/GetGeometryBounds.ts +82 -0
- package/src/components/images/modules/GetParsedBoundary.ts +58 -0
- package/src/components/images/modules/GetSVGPath.ts +53 -0
- package/src/components/images/modules/NormalizePolygon.ts +57 -0
- package/src/{@modules/@stanza → components/stanza}/GetAwipsType.ts +16 -17
- package/src/{@modules/@stanza → components/stanza}/ValidateStanza.ts +27 -15
- package/src/{@modules/@utilities → components/utilities}/CreateHttp.ts +26 -26
- package/src/{@parsers/@text → components/utilities}/GetEmbededText.ts +8 -6
- package/src/{@modules/@utilities → components/utilities}/GetFormattedTime.ts +2 -2
- package/src/{@modules/@utilities → components/utilities}/GetLatestIssuance.ts +2 -2
- package/src/{@modules/@utilities → components/utilities}/GetMatched.ts +10 -5
- package/src/{@modules/@utilities → components/utilities}/GetShapeNearestPoint.ts +24 -19
- package/src/{@parsers/@text → components/utilities}/GetStringText.ts +9 -9
- package/src/components/utilities/GetUnionPolygon.ts +36 -0
- package/src/components/utilities/QueueManager.ts +70 -0
- package/src/components/utilities/SetCronSchedule.ts +106 -0
- package/src/{@modules/@utilities → components/utilities}/SetDebug.ts +12 -12
- package/src/components/utilities/SetEventEmit.ts +42 -0
- package/src/{@modules/@utilities → components/utilities}/SetSettings.ts +7 -7
- package/src/{@modules/@utilities → components/utilities}/SetSleep.ts +5 -5
- package/src/components/utilities/SetTimeoutAction.ts +64 -0
- package/src/components/utilities/SetWarning.ts +47 -0
- package/src/{@modules/@xmpp → components/xmpp}/DeployXMPP.ts +20 -20
- package/src/{@core/CreateListener.ts → components/xmpp/ErrorXMPP.ts} +8 -5
- package/src/{@modules/@xmpp → components/xmpp}/OfflineXMPP.ts +13 -13
- package/src/{@modules/@xmpp → components/xmpp}/OnlineXMPP.ts +18 -18
- package/src/components/xmpp/ReconnectXMPP.ts +64 -0
- package/src/{@modules/@xmpp → components/xmpp}/StanzaXMPP.ts +25 -25
- package/src/{@core → core}/ClearEvents.ts +9 -9
- package/src/{@core → core}/GetEvents.ts +4 -4
- package/src/{@core → core}/GetNodes.ts +4 -4
- package/src/{@core → core}/GetRandomEvent.ts +4 -4
- package/src/{@core → core}/GetVersion.ts +4 -4
- package/src/{@core → core}/ManualEvent.ts +23 -24
- package/src/{@core → core}/QueryStanza.ts +12 -11
- package/src/core/SetNode.ts +87 -0
- package/src/{@core → core}/StartService.ts +21 -20
- package/src/{@core → core}/StopService.ts +10 -10
- package/src/{@enums → enums}/AWIPS.ts +2 -2
- package/src/{@enums → enums}/Actions.ts +2 -2
- package/src/{@enums → enums}/Cancellation.ts +2 -2
- package/src/{@enums → enums}/Causes.ts +2 -2
- package/src/{@enums → enums}/Correlations.ts +2 -2
- package/src/{@enums → enums}/Enhanced.ts +4 -3
- package/src/{@enums → enums}/Events.ts +2 -2
- package/src/{@enums → enums}/Expressions.ts +3 -3
- package/src/{@enums → enums}/GlobalFilter.ts +2 -2
- package/src/{@enums → enums}/Hail.ts +2 -2
- package/src/{@enums → enums}/ICAO.ts +2 -2
- package/src/{@enums → enums}/Matches.ts +8 -3
- package/src/{@enums → enums}/Products.ts +2 -2
- package/src/{@enums → enums}/Records.ts +2 -2
- package/src/{@enums → enums}/Severity.ts +2 -2
- package/src/{@enums → enums}/Shapefiles.ts +2 -2
- package/src/{@enums → enums}/States.ts +68 -4
- package/src/{@enums → enums}/Status.ts +2 -2
- package/src/{@enums → enums}/Strings.ts +2 -2
- package/src/{@enums → enums}/Tags.ts +9 -4
- package/src/{@enums → enums}/Testing.ts +2 -2
- package/src/enums/Themes.ts +70 -0
- package/src/events/ParseAPI.ts +133 -0
- package/src/{@events → events}/ParseText.ts +24 -22
- package/src/{@events → events}/ParseUGC.ts +27 -26
- package/src/{@events → events}/ParseVTEC.ts +38 -37
- package/src/index.ts +57 -37
- package/src/{@manager → manager}/MakeEvents.ts +38 -47
- package/src/manager/RemoveEvent.ts +74 -0
- package/src/{@manager → manager}/SetHash.ts +18 -13
- package/src/{@manager → manager}/UpdateEvents.ts +12 -12
- package/src/{@manager → manager}/UpdateNode.ts +14 -14
- package/src/{@manager → manager}/ValidateEvents.ts +55 -45
- package/src/{@parsers/@hvtec → parsers/hvtec}/HVExtract.ts +11 -11
- package/src/{@parsers/@text → parsers/text}/GetDescriptionFromProduct.ts +20 -21
- package/src/parsers/text/GetPolygonFromProduct.ts +71 -0
- package/src/{@parsers/@text → parsers/text}/GetTextFromProduct.ts +12 -12
- package/src/parsers/ugc/GetCache.ts +24 -0
- package/src/{@parsers/@ugc → parsers/ugc}/GetExpiry.ts +2 -2
- package/src/{@parsers/@ugc/GetUGCHeader.ts → parsers/ugc/GetHeader.ts} +4 -4
- package/src/{@parsers/@ugc → parsers/ugc}/GetLocations.ts +10 -10
- package/src/parsers/ugc/GetZonePolygon.ts +73 -0
- package/src/{@parsers/@ugc → parsers/ugc}/GetZones.ts +3 -3
- package/src/{@parsers/@ugc → parsers/ugc}/SetCache.ts +13 -8
- package/src/{@parsers/@ugc → parsers/ugc}/UGCExtract.ts +14 -14
- package/src/{@parsers/@pvtec → parsers/vtec}/GetExpiry.ts +2 -2
- package/src/parsers/vtec/VTECExtract.ts +49 -0
- package/src/{@types → static}/Attributes.ts +2 -3
- package/src/{@types → static}/Event.ts +8 -7
- package/src/{@types → static}/Properties.ts +17 -3
- package/src/{@types → static}/Stanza.ts +2 -2
- package/src/tasks/CreateTasks.ts +116 -0
- package/src/tasks/TaskGenerateJSON.ts +34 -0
- package/src/tasks/TaskGenerateText.ts +34 -0
- package/src/tasks/TaskSendNTFY.ts +95 -0
- package/src/tasks/TaskSendWebhook.ts +107 -0
- package/src/{@types → types}/Actions.ts +3 -3
- package/src/{@types → types}/HVTEC.ts +6 -6
- package/src/{@types → types}/Hash.ts +5 -5
- package/src/types/Nodes.ts +29 -0
- package/src/{@types → types}/Settings.ts +17 -8
- package/src/{@types → types}/StanzaCompiled.ts +15 -15
- package/src/{@types → types}/UGC.ts +5 -5
- package/src/{@modules/@utilities/GetSettings.ts → types/VTEC.ts} +12 -7
- package/tools/build.js +47 -0
- package/tsconfig.json +28 -1
- package/src/@building/GetEventProperties.ts +0 -96
- package/src/@building/GetEventTracking.ts +0 -68
- package/src/@core/SetNode.ts +0 -81
- package/src/@events/ParseAPI.ts +0 -117
- package/src/@manager/RemoveEvent.ts +0 -70
- package/src/@manager/Tasks.ts +0 -246
- package/src/@modules/@database/InitializeDatabase.ts +0 -59
- package/src/@modules/@eas/GenerateEASMessage.ts +0 -144
- package/src/@modules/@utilities/SetCronSchedule.ts +0 -94
- package/src/@modules/@utilities/SetEventEmit.ts +0 -41
- package/src/@modules/@utilities/SetListener.ts +0 -30
- package/src/@modules/@utilities/SetTimeoutAction.ts +0 -64
- package/src/@modules/@xmpp/ErrorXMPP.ts +0 -27
- package/src/@modules/@xmpp/ReconnectXMPP.ts +0 -64
- package/src/@parsers/@pvtec/VTECExtract.ts +0 -49
- package/src/@parsers/@text/GetPolygonFromProduct.ts +0 -53
- package/src/@parsers/@text/GetXML.ts +0 -61
- package/src/@parsers/@ugc/GetCache.ts +0 -24
- package/src/@parsers/@ugc/GetZonePolygon.ts +0 -111
- package/src/@types/VTEC.ts +0 -30
- package/tsup.config.ts +0 -16
|
@@ -11,58 +11,62 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { TypeEvent } from "
|
|
21
|
-
import { TypeSettings } from "
|
|
22
|
-
import {
|
|
23
|
-
import { GetEventEnhancedName } from "
|
|
24
|
-
import { GetEventSignature } from "
|
|
25
|
-
import { MakeEvents } from "
|
|
26
|
-
import { RemoveEvent } from "
|
|
27
|
-
import { GetEventAttachments } from "
|
|
28
|
-
import { SetEventEmit } from "
|
|
29
|
-
import { SetDebug } from "
|
|
30
|
-
import { GetMatched } from "
|
|
31
|
-
import {
|
|
20
|
+
import { TypeEvent } from "StaticTypes/Event"
|
|
21
|
+
import { TypeSettings } from "Types/Settings"
|
|
22
|
+
import { Bootstrap } from "@Bootstrap"
|
|
23
|
+
import { GetEventEnhancedName } from "@Building/GetEventEnhancedName"
|
|
24
|
+
import { GetEventSignature } from "@Building/GetEventSignature"
|
|
25
|
+
import { MakeEvents } from "@Manager/MakeEvents"
|
|
26
|
+
import { RemoveEvent } from "@Manager/RemoveEvent"
|
|
27
|
+
import { GetEventAttachments } from "@Building/GetEventAttachments"
|
|
28
|
+
import { SetEventEmit } from "@Utilities/SetEventEmit"
|
|
29
|
+
import { SetDebug } from "@Utilities/SetDebug"
|
|
30
|
+
import { GetMatched } from "@Utilities/GetMatched"
|
|
31
|
+
import { GetEventPopulation } from "@Building/GetEventPopulation"
|
|
32
|
+
import { GetEventGeometry } from "@Building/GetEventGeometry"
|
|
32
33
|
import { createHash } from "crypto"
|
|
33
34
|
|
|
34
35
|
export const ValidateEvents = async (events: TypeEvent[]): Promise<void> => {
|
|
35
36
|
const tick = performance.now();
|
|
36
37
|
if (events.length === 0) return
|
|
37
|
-
const configurations =
|
|
38
|
+
const configurations = Bootstrap.Settings as TypeSettings
|
|
38
39
|
const sets = {} as Record<string, Set<string>>;
|
|
39
40
|
const bools = {} as Record<string, boolean>;
|
|
40
41
|
const megered = {...configurations.GlobalSettings, ...configurations.GlobalSettings.EventFiltering}
|
|
41
42
|
for (const key in megered) {
|
|
42
|
-
const setting = megered[key]
|
|
43
|
-
if (Array.isArray(setting)) {
|
|
44
|
-
|
|
43
|
+
const setting = megered[key as keyof typeof megered]
|
|
44
|
+
if (Array.isArray(setting)) {
|
|
45
|
+
(sets as Record<string, any>)[key] = new Set(setting.map(item => String(item).toLowerCase()));
|
|
46
|
+
}
|
|
47
|
+
if (typeof setting === 'boolean') {
|
|
48
|
+
(bools as Record<string, any>)[key] = setting;
|
|
49
|
+
}
|
|
45
50
|
}
|
|
46
|
-
|
|
47
51
|
const isFiltered = (define: TypeEvent): boolean => {
|
|
48
52
|
const properties = define.properties;
|
|
49
53
|
const zones = properties.geocode.ugc;
|
|
50
54
|
const icao = properties.geocode.office.office
|
|
51
55
|
|
|
52
56
|
if (properties.status_metadata.is_test) {
|
|
53
|
-
SetEventEmit({
|
|
57
|
+
SetEventEmit({ Event: `onTestProduct`, Metadata: define })
|
|
54
58
|
if (bools?.IgnoreTestProducts) return true;
|
|
55
59
|
}
|
|
56
60
|
|
|
57
61
|
if (properties.status_metadata.is_expired) {
|
|
58
|
-
SetEventEmit({
|
|
59
|
-
RemoveEvent(define, false)
|
|
62
|
+
SetEventEmit({ Event: `onExpiredProduct`, Metadata: define })
|
|
63
|
+
RemoveEvent({ Event: define, IsTimeBasedExpiration: false })
|
|
60
64
|
return true;
|
|
61
65
|
}
|
|
62
66
|
|
|
63
|
-
if (properties.metadata?.vtec?.
|
|
64
|
-
const isSPC = properties.metadata?.vtec?.
|
|
65
|
-
SetEventEmit({
|
|
67
|
+
if (properties.metadata?.vtec?.Watch && properties.metadata.source != `events.api`) {
|
|
68
|
+
const isSPC = properties.metadata?.vtec?.PredictionCenter;
|
|
69
|
+
SetEventEmit({ Event: isSPC ? `onStormPredictionWatch` : `onNonStormPredictionWatch`, Metadata: define })
|
|
66
70
|
if (bools?.SPCWatchesOnly && !isSPC) {
|
|
67
71
|
return true;
|
|
68
72
|
}
|
|
@@ -74,45 +78,45 @@ export const ValidateEvents = async (events: TypeEvent[]): Promise<void> => {
|
|
|
74
78
|
for (const key in sets) {
|
|
75
79
|
const setting = sets[key]
|
|
76
80
|
const values = [...setting];
|
|
77
|
-
if (key === 'ListeningEvents' && setting.size > 0 && !GetMatched(values, define.properties.event)) {
|
|
81
|
+
if (key === 'ListeningEvents' && setting.size > 0 && !GetMatched({ Strings: values, String: define.properties.event })) {
|
|
78
82
|
SetEventEmit({
|
|
79
|
-
|
|
80
|
-
|
|
83
|
+
Event: `onFilteredEvent`,
|
|
84
|
+
Metadata: define
|
|
81
85
|
});
|
|
82
86
|
return true
|
|
83
87
|
}
|
|
84
|
-
if (key === 'IgnoredEvents' && setting.size > 0 && GetMatched(values, define.properties.event)) {
|
|
88
|
+
if (key === 'IgnoredEvents' && setting.size > 0 && GetMatched({ Strings: values, String: define.properties.event })) {
|
|
85
89
|
SetEventEmit({
|
|
86
|
-
|
|
87
|
-
|
|
90
|
+
Event: `onIgnoredEvent`,
|
|
91
|
+
Metadata: define
|
|
88
92
|
});
|
|
89
93
|
return true
|
|
90
94
|
}
|
|
91
95
|
if (key === 'ListeningICAO' && setting.size > 0 && icao != null && !setting.has(icao.toLowerCase())) {
|
|
92
96
|
SetEventEmit({
|
|
93
|
-
|
|
94
|
-
|
|
97
|
+
Event: `onFilteredICAO`,
|
|
98
|
+
Metadata: define
|
|
95
99
|
});
|
|
96
100
|
return true
|
|
97
101
|
}
|
|
98
102
|
if (key === 'IgnoredICAO' && setting.size > 0 && icao != null && setting.has(icao.toLowerCase())) {
|
|
99
103
|
SetEventEmit({
|
|
100
|
-
|
|
101
|
-
|
|
104
|
+
Event: `onIgnoredICAO`,
|
|
105
|
+
Metadata: define
|
|
102
106
|
});
|
|
103
107
|
return true
|
|
104
108
|
}
|
|
105
109
|
if (key === 'ListeningUGC' && setting.size > 0 && zones.length > 0 && !zones.some((ugc: string) => setting.has(ugc.toLowerCase()))) {
|
|
106
110
|
SetEventEmit({
|
|
107
|
-
|
|
108
|
-
|
|
111
|
+
Event: `onFilteredUGC`,
|
|
112
|
+
Metadata: define
|
|
109
113
|
});
|
|
110
114
|
return true
|
|
111
115
|
}
|
|
112
116
|
if (key === 'ListeningStates' && setting.size > 0 && zones.length > 0 && !zones.some((ugc: string) => setting.has(ugc.substring(0, 2).toLowerCase()))) {
|
|
113
117
|
SetEventEmit({
|
|
114
|
-
|
|
115
|
-
|
|
118
|
+
Event: `onFilteredState`,
|
|
119
|
+
Metadata: define
|
|
116
120
|
});
|
|
117
121
|
return true
|
|
118
122
|
}
|
|
@@ -121,22 +125,28 @@ export const ValidateEvents = async (events: TypeEvent[]): Promise<void> => {
|
|
|
121
125
|
}
|
|
122
126
|
|
|
123
127
|
const filtering = events.filter((event: TypeEvent) => {
|
|
124
|
-
|
|
128
|
+
Bootstrap.Cache.Parsed = Bootstrap.Cache.Parsed.filter((e) => e !== event);
|
|
125
129
|
const define = GetEventSignature(event) as TypeEvent;
|
|
126
130
|
const pre = {...define,properties: {...define.properties, metadata: {...define.properties.metadata }}};
|
|
127
131
|
const properties = define.properties; delete pre.properties.metadata.ms; delete pre.properties.metadata.header;
|
|
128
132
|
const enhanced = properties.event = GetEventEnhancedName(event)
|
|
129
133
|
const filtered = isFiltered(define)
|
|
130
134
|
if (!filtered) {
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
const tick2 = performance.now();
|
|
136
|
+
const geometry = !bools?.DisableGeometryParsing ? GetEventGeometry({ Event: event }) : null;
|
|
137
|
+
const population = GetEventPopulation(geometry)
|
|
138
|
+
const attachments = GetEventAttachments(event)
|
|
139
|
+
event.geometry = geometry;
|
|
140
|
+
event.properties.geocode.census.population = population.population;
|
|
141
|
+
event.properties.geocode.census.cities = population.cities;
|
|
142
|
+
properties.metadata.attachments = attachments;
|
|
143
|
+
SetDebug({ Title: `Filtered`, Message: `${Math.round(performance.now() - tick2)}ms` })
|
|
133
144
|
}
|
|
134
145
|
properties.metadata.hash = createHash("sha256").update(JSON.stringify(pre)).digest("hex")
|
|
135
|
-
SetEventEmit({
|
|
146
|
+
SetEventEmit({ Event: `onProductType${enhanced.replace(/\s+/g, '')}`, Metadata: define });
|
|
136
147
|
return !filtered
|
|
137
148
|
})
|
|
138
149
|
|
|
139
|
-
SetDebug({
|
|
150
|
+
SetDebug({ Title: `ValidateEvents (${filtering.length}/${events.length})`, Message: `${Math.round(performance.now() - tick)}ms` })
|
|
140
151
|
await MakeEvents(filtering)
|
|
141
|
-
SetDebug({ title: `MakeEvents (${filtering.length}/${events.length})`, message: `${Math.round(performance.now() - tick)}ms` })
|
|
142
152
|
}
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { TypeHVTEC } from "
|
|
21
|
-
import { EnumCauses } from "
|
|
22
|
-
import { EnumRecords } from "
|
|
23
|
-
import { EnumExpressions } from "
|
|
24
|
-
import { EnumSeverity } from "
|
|
20
|
+
import { TypeHVTEC } from "Types/HVTEC"
|
|
21
|
+
import { EnumCauses } from "@Enums/Causes"
|
|
22
|
+
import { EnumRecords } from "@Enums/Records"
|
|
23
|
+
import { EnumExpressions } from "@Enums/Expressions"
|
|
24
|
+
import { EnumSeverity } from "@Enums/Severity"
|
|
25
25
|
|
|
26
26
|
export const HVExtract = (message: string): TypeHVTEC[] | null => {
|
|
27
27
|
const getHVTECs = message.match(EnumExpressions.hvtec) ?? [];
|
|
@@ -30,10 +30,10 @@ export const HVExtract = (message: string): TypeHVTEC[] | null => {
|
|
|
30
30
|
const sub = vtec.split(`.`);
|
|
31
31
|
if (sub.length < 7) continue;
|
|
32
32
|
vtecs.push({
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
HVTEC: vtec,
|
|
34
|
+
Severity: EnumSeverity[sub[1]],
|
|
35
|
+
Cause: EnumCauses[sub[2]],
|
|
36
|
+
Record: EnumRecords[sub[6]]
|
|
37
37
|
})
|
|
38
38
|
}
|
|
39
39
|
return vtecs.length > 0 ? vtecs : null;
|
|
@@ -11,21 +11,20 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { EnumExpressions } from "
|
|
20
|
+
import { EnumExpressions } from "@Enums/Expressions"
|
|
21
21
|
|
|
22
22
|
interface GetDescriptionFromProductOptions {
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
Message: string
|
|
24
|
+
Handle?: string
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export const GetDescriptionFromProduct = (
|
|
28
|
-
let message = options.message;
|
|
27
|
+
export const GetDescriptionFromProduct = ({ Message, Handle }: GetDescriptionFromProductOptions): string => {
|
|
29
28
|
const predefinedEndMarkers = ['&&', 'LAT...'];
|
|
30
29
|
const getEndIndex = (text: string, fromIndex = 0): number => {
|
|
31
30
|
const indices = predefinedEndMarkers
|
|
@@ -33,28 +32,28 @@ export const GetDescriptionFromProduct = (options: GetDescriptionFromProductOpti
|
|
|
33
32
|
.filter((idx) => idx !== -1);
|
|
34
33
|
return indices.length ? Math.min(...indices) : -1;
|
|
35
34
|
};
|
|
36
|
-
const dates = Array.from(
|
|
35
|
+
const dates = Array.from(Message.matchAll(EnumExpressions.dateline));
|
|
37
36
|
if (dates.length) {
|
|
38
37
|
const lastMatch = dates[dates.length - 1][0];
|
|
39
|
-
const sIndx =
|
|
38
|
+
const sIndx = Message.lastIndexOf(lastMatch);
|
|
40
39
|
if (sIndx !== -1) {
|
|
41
|
-
const endIndx = getEndIndex(
|
|
42
|
-
|
|
43
|
-
if (
|
|
44
|
-
if (
|
|
45
|
-
const handleIdx =
|
|
46
|
-
|
|
47
|
-
if (
|
|
40
|
+
const endIndx = getEndIndex(Message, sIndx);
|
|
41
|
+
Message = Message.substring(sIndx + lastMatch.length, endIndx !== -1 ? endIndx : undefined).trimStart();
|
|
42
|
+
if (Message.startsWith('/')) Message = Message.slice(1).trimStart();
|
|
43
|
+
if (Handle && Message.includes(Handle)) {
|
|
44
|
+
const handleIdx = Message.indexOf(Handle);
|
|
45
|
+
Message = Message.substring(handleIdx + Handle.length).trimStart();
|
|
46
|
+
if (Message.startsWith('/')) Message = Message.slice(1).trimStart();
|
|
48
47
|
}
|
|
49
48
|
}
|
|
50
|
-
} else if (
|
|
51
|
-
const handleIndx =
|
|
49
|
+
} else if (Handle) {
|
|
50
|
+
const handleIndx = Message.indexOf(Handle);
|
|
52
51
|
if (handleIndx !== -1) {
|
|
53
|
-
let afterHandle =
|
|
52
|
+
let afterHandle = Message.substring(handleIndx + Handle.length).trimStart();
|
|
54
53
|
if (afterHandle.startsWith('/')) afterHandle = afterHandle.slice(1).trimStart();
|
|
55
54
|
const latEnd = getEndIndex(afterHandle);
|
|
56
|
-
|
|
55
|
+
Message = latEnd !== -1 ? afterHandle.substring(0, latEnd).trim() : afterHandle.trim();
|
|
57
56
|
}
|
|
58
57
|
}
|
|
59
|
-
return
|
|
58
|
+
return Message.trim();
|
|
60
59
|
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/*
|
|
2
|
+
_ _ _ __ __
|
|
3
|
+
/\ | | | | (_) \ \ / /
|
|
4
|
+
/ \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
|
|
5
|
+
/ /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
|
|
6
|
+
/ ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
|
|
7
|
+
/_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
|
|
8
|
+
| |
|
|
9
|
+
|_|
|
|
10
|
+
|
|
11
|
+
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
|
+
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
|
+
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
|
+
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
export const GetPolygonFromProduct = (message: string): number[][] => {
|
|
21
|
+
const coordinates: number[][] = [];
|
|
22
|
+
const match = message.match(
|
|
23
|
+
/LAT\.\.\.LON\s+([\s\S]*?)(?=\n\s*(?:TIME\.\.\.MOT\.\.\.LOC|\$\$|[A-Za-z]|$))/i
|
|
24
|
+
);
|
|
25
|
+
if (!match) return coordinates;
|
|
26
|
+
const text = match[1]
|
|
27
|
+
.split(/\r?\n/).map(l => l.trim())
|
|
28
|
+
.filter(l => l && !/[A-Za-z]/.test(l))
|
|
29
|
+
.join(' ').trim();
|
|
30
|
+
if (!text) return coordinates;
|
|
31
|
+
const values = text.match(/\d{4,8}/g);
|
|
32
|
+
if (!values || values.length === 0) return coordinates;
|
|
33
|
+
const points: { lat: number; lon: number }[] = [];
|
|
34
|
+
if (values.every(v => v.length === 8)) {
|
|
35
|
+
for (const value of values) {
|
|
36
|
+
const lat = parseInt(value.slice(0, 4), 10) / 100;
|
|
37
|
+
let lon = parseInt(value.slice(4, 8), 10) / 100;
|
|
38
|
+
if (lon < 20) lon += 100;
|
|
39
|
+
points.push({ lat, lon });
|
|
40
|
+
}
|
|
41
|
+
} else if (values.every(v => v.length === 4)) {
|
|
42
|
+
for (let i = 0; i + 1 < values.length; i += 2) {
|
|
43
|
+
const lat = parseInt(values[i], 10) / 100;
|
|
44
|
+
const lon = parseInt(values[i + 1], 10) / 100;
|
|
45
|
+
points.push({ lat, lon });
|
|
46
|
+
}
|
|
47
|
+
} else {
|
|
48
|
+
for (let i = 0; i + 1 < values.length; i += 2) {
|
|
49
|
+
const latStr = values[i];
|
|
50
|
+
const lonStr = values[i + 1];
|
|
51
|
+
if (latStr.length !== 4) continue;
|
|
52
|
+
if (lonStr.length < 4 || lonStr.length > 5) continue;
|
|
53
|
+
const lat = parseInt(latStr, 10) / 100;
|
|
54
|
+
let lon = parseInt(lonStr, 10) / 100;
|
|
55
|
+
points.push({ lat, lon });
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
if (points.length === 0) return coordinates;
|
|
59
|
+
const isEast = points.some(p => p.lon > 130 && p.lon < 180);
|
|
60
|
+
for (const p of points) {
|
|
61
|
+
const lon = isEast ? p.lon : -p.lon;
|
|
62
|
+
if (Number.isFinite(p.lat) && Number.isFinite(lon) && p.lat >= -90 && p.lat <= 90 && lon >= -180 && lon <= 180) {
|
|
63
|
+
coordinates.push([lon, p.lat]);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (coordinates.length > 2 && (coordinates[0][0] !== coordinates[coordinates.length - 1][0] || coordinates[0][1] !== coordinates[coordinates.length - 1][1])) {
|
|
67
|
+
coordinates.push([...coordinates[0]]);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return coordinates;
|
|
71
|
+
};
|
|
@@ -11,32 +11,32 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
20
|
interface GetTextFromProductOptions {
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
21
|
+
Message: string
|
|
22
|
+
Find: string[]
|
|
23
|
+
Append?: string
|
|
24
|
+
Removal?: string[]
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
export const GetTextFromProduct = (
|
|
28
|
-
const lines =
|
|
27
|
+
export const GetTextFromProduct = ({ Message, Find, Append, Removal }: GetTextFromProductOptions): string => {
|
|
28
|
+
const lines = Message.split(`\n`);
|
|
29
29
|
for (const line of lines) {
|
|
30
|
-
const matchedFind =
|
|
30
|
+
const matchedFind = Find.find((find) => line.includes(find));
|
|
31
31
|
if (matchedFind) {
|
|
32
32
|
let result = line.slice(line.indexOf(matchedFind) + matchedFind.length).trim();
|
|
33
|
-
if (
|
|
34
|
-
for (const str of
|
|
33
|
+
if (Removal) {
|
|
34
|
+
for (const str of Removal) {
|
|
35
35
|
result = result.toLowerCase().split(str.toLowerCase()).join('');
|
|
36
36
|
}
|
|
37
37
|
result = result.replace(matchedFind, '').replace('<', '').trim();
|
|
38
38
|
}
|
|
39
|
-
return result.toUpperCase() + (
|
|
39
|
+
return result.toUpperCase() + (Append ?? ``);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
return null;
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
_ _ _ __ __
|
|
3
|
+
/\ | | | | (_) \ \ / /
|
|
4
|
+
/ \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
|
|
5
|
+
/ /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
|
|
6
|
+
/ ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
|
|
7
|
+
/_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
|
|
8
|
+
| |
|
|
9
|
+
|_|
|
|
10
|
+
|
|
11
|
+
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
|
+
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
|
+
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
|
+
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { Bootstrap } from "@Bootstrap"
|
|
21
|
+
|
|
22
|
+
export const GetCache = (key: string) => {
|
|
23
|
+
return Bootstrap.Cache.UGC.get(key);
|
|
24
|
+
};
|
|
@@ -11,9 +11,9 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
@@ -11,15 +11,15 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import { EnumExpressions } from "
|
|
20
|
+
import { EnumExpressions } from "@Enums/Expressions"
|
|
21
21
|
|
|
22
|
-
export const
|
|
22
|
+
export const GetHeader = (message: string): string | null => {
|
|
23
23
|
const start = message.search(EnumExpressions.ugc1)
|
|
24
24
|
const sub = message.substring(start)
|
|
25
25
|
const end = sub.search(EnumExpressions.ugc2)
|
|
@@ -11,17 +11,17 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
20
|
+
import { Bootstrap } from "@Bootstrap"
|
|
21
|
+
import { GetCache } from "@ParsingUGC/GetCache"
|
|
22
|
+
import { SetCache } from "@ParsingUGC/SetCache"
|
|
23
23
|
|
|
24
|
-
export const
|
|
24
|
+
export const GetLocations = async (zones: string[]): Promise<string[]> => {
|
|
25
25
|
const uniqueZones = Array.from(new Set(zones));
|
|
26
26
|
|
|
27
27
|
const results: string[] = [];
|
|
@@ -29,7 +29,7 @@ export const getLocations = async (zones: string[]): Promise<string[]> => {
|
|
|
29
29
|
|
|
30
30
|
for (let i = 0; i < uniqueZones.length; i++) {
|
|
31
31
|
const zone = uniqueZones[i];
|
|
32
|
-
const cached =
|
|
32
|
+
const cached = GetCache(zone);
|
|
33
33
|
|
|
34
34
|
if (cached) {
|
|
35
35
|
for (let j = 0; j < cached.length; j++) {
|
|
@@ -41,7 +41,7 @@ export const getLocations = async (zones: string[]): Promise<string[]> => {
|
|
|
41
41
|
}
|
|
42
42
|
|
|
43
43
|
if (missing.length > 0) {
|
|
44
|
-
const rows = await
|
|
44
|
+
const rows = await Bootstrap.Database
|
|
45
45
|
.prepare(
|
|
46
46
|
`SELECT id, location FROM shapefiles WHERE id IN (${missing.map(() => '?').join(',')})`
|
|
47
47
|
)
|
|
@@ -49,9 +49,9 @@ export const getLocations = async (zones: string[]): Promise<string[]> => {
|
|
|
49
49
|
|
|
50
50
|
for (let i = 0; i < rows.length; i++) {
|
|
51
51
|
const r = rows[i] as any;
|
|
52
|
-
|
|
52
|
+
SetCache({ Key: r.id, Value: [r.location] });
|
|
53
53
|
results.push(r.location);
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
|
-
return results;
|
|
56
|
+
return Array.from(new Set(results));
|
|
57
57
|
};
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
/*
|
|
2
|
+
_ _ _ __ __
|
|
3
|
+
/\ | | | | (_) \ \ / /
|
|
4
|
+
/ \ | |_ _ __ ___ ___ ___ _ __ | |__ ___ _ __ _ ___ \ V /
|
|
5
|
+
/ /\ \| __| '_ ` _ \ / _ \/ __| '_ \| '_ \ / _ \ '__| |/ __| > <
|
|
6
|
+
/ ____ \ |_| | | | | | (_) \__ \ |_) | | | | __/ | | | (__ / . \
|
|
7
|
+
/_/ \_\__|_| |_| |_|\___/|___/ .__/|_| |_|\___|_| |_|\___/_/ \_\
|
|
8
|
+
| |
|
|
9
|
+
|_|
|
|
10
|
+
|
|
11
|
+
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
|
+
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
|
+
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
|
+
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
|
+
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
import { CreateQuery } from "@Database/CreateQuery"
|
|
21
|
+
import { GetUnionPolygon } from "@Utilities/GetUnionPolygon"
|
|
22
|
+
import { createHash } from "crypto";
|
|
23
|
+
|
|
24
|
+
interface CoordinatesOptions {
|
|
25
|
+
Zones: string[]
|
|
26
|
+
Union: boolean
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
interface CoordinatesResponse {
|
|
30
|
+
type: `Polygon` | `MultiPolygon`
|
|
31
|
+
coordinates: any[]
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export const GetZonePolygon = ({ Zones, Union }: CoordinatesOptions): CoordinatesResponse | null => {
|
|
35
|
+
const list = [...new Set(Zones.map(z => z.trim()))].filter(z => z === 'XX000' ? false : true);
|
|
36
|
+
if (list.length === 0) return null;
|
|
37
|
+
|
|
38
|
+
const placeholders = list.map(() => "?").join(",");
|
|
39
|
+
const rows = CreateQuery({
|
|
40
|
+
Query: `SELECT geometry FROM shapefiles WHERE id IN (${placeholders})`,
|
|
41
|
+
Parameters: list
|
|
42
|
+
});
|
|
43
|
+
const polygons: any[] = [];
|
|
44
|
+
const geometryHashes = new Set<string>();
|
|
45
|
+
for (const row of rows) {
|
|
46
|
+
if (!row?.geometry) continue;
|
|
47
|
+
const geometryHash = createHash("sha256").update(row.geometry).digest("hex");
|
|
48
|
+
if (geometryHashes.has(geometryHash)) continue;
|
|
49
|
+
geometryHashes.add(geometryHash);
|
|
50
|
+
const geom = JSON.parse(row.geometry);
|
|
51
|
+
if (geom?.type === "Polygon") {
|
|
52
|
+
polygons.push(geom.coordinates);
|
|
53
|
+
}
|
|
54
|
+
if (geom?.type === "MultiPolygon") {
|
|
55
|
+
for (const poly of geom.coordinates) {
|
|
56
|
+
polygons.push(poly);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
if (polygons.length === 0) return null;
|
|
61
|
+
if (Union) {
|
|
62
|
+
return GetUnionPolygon({ Polygons: polygons });
|
|
63
|
+
} else {
|
|
64
|
+
const multi: any[] = [];
|
|
65
|
+
for (const polyCoords of polygons) {
|
|
66
|
+
if (Array.isArray(polyCoords) && Array.isArray(polyCoords[0])) {
|
|
67
|
+
multi.push(polyCoords);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (multi.length === 0) return null;
|
|
71
|
+
return {type: "MultiPolygon", coordinates: multi};
|
|
72
|
+
}
|
|
73
|
+
}
|
|
@@ -11,13 +11,13 @@
|
|
|
11
11
|
Created with ♥ by the AtmosphericX Team (KiyoWx, StarflightWx, & CJ Ziegler)
|
|
12
12
|
Discord: https://atmosphericx-discord.scriptkitty.cafe
|
|
13
13
|
Ko-Fi: https://ko-fi.com/k3yomi
|
|
14
|
-
Documentation:
|
|
14
|
+
Documentation: https://atmosphericx.scriptkitty.cafe/documentation
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
Independent Package: @atmosx/event-product-parser
|
|
17
17
|
|
|
18
18
|
*/
|
|
19
19
|
|
|
20
|
-
export const
|
|
20
|
+
export const GetZones = (header: string): string[] => {
|
|
21
21
|
const splits = header.split('-');
|
|
22
22
|
const zones: string[] = [];
|
|
23
23
|
let state = splits[0].substring(0, 2);
|