@atmosx/event-product-parser 3.0.6 → 3.0.7
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 +2 -2
- package/.github/docs/docs-general-configurations.md +2 -12
- package/.github/docs/docs-integrations.md +4 -3
- package/.github/docs/docs-listeners.md +1 -1
- package/.github/docs/docs-source-configuration.md +1 -1
- package/.github/docs/docs-technical.md +1 -1
- package/{.github/README.md → README.md} +15 -9
- package/dist/cjs/index.cjs +24073 -7018
- package/dist/esm/index.mjs +24035 -6983
- package/package.json +56 -47
- package/pnpm-workspace.yaml +4 -0
- package/src/bootstrap.ts +64 -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 +21 -19
- package/src/{@building → building}/GetEventOffice.ts +13 -13
- package/src/building/GetEventProperties.ts +105 -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 +234 -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/{@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/components/utilities/GetUnionPolygon.ts +36 -0
- package/src/components/utilities/QueueManager.ts +70 -0
- package/src/{@modules/@utilities → components/utilities}/SetCronSchedule.ts +29 -25
- 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 +2 -2
- 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 +129 -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/MakeEvents.ts +110 -0
- 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/{@building → manager}/ValidateEvents.ts +47 -57
- package/src/{@parsers/@hvtec → parsers/hvtec}/HVExtract.ts +11 -11
- package/src/{@parsers/@text → parsers/text}/GetDescriptionFromProduct.ts +20 -21
- package/src/{@parsers/@text → parsers/text}/GetEmbededText.ts +7 -6
- package/src/parsers/text/GetPolygonFromProduct.ts +71 -0
- package/src/{@parsers/@text → parsers/text}/GetStringText.ts +9 -9
- 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 +7 -3
- package/src/{@types → static}/Stanza.ts +2 -2
- package/src/tasks/CreateTasks.ts +123 -0
- package/src/tasks/TaskGenerateJSON.ts +34 -0
- package/src/tasks/TaskGenerateText.ts +34 -0
- package/src/tasks/TaskSendNTFY.ts +97 -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 +16 -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/CreateActions.ts +0 -213
- package/src/@manager/MakeEvent.ts +0 -99
- package/src/@manager/RemoveEvent.ts +0 -70
- package/src/@modules/@database/InitializeDatabase.ts +0 -59
- package/src/@modules/@eas/GenerateEASMessage.ts +0 -144
- 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
|
@@ -0,0 +1,87 @@
|
|
|
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
|
+
import { EnumStates } from "@Enums/States"
|
|
20
|
+
import { Bootstrap } from "@Bootstrap"
|
|
21
|
+
import { CreateHttp } from "@Utilities/CreateHttp"
|
|
22
|
+
import { CreateQuery } from "@Database/CreateQuery"
|
|
23
|
+
import { SetWarning } from "@Utilities/SetWarning"
|
|
24
|
+
|
|
25
|
+
interface CityRow {
|
|
26
|
+
id: string
|
|
27
|
+
name: string
|
|
28
|
+
state: string
|
|
29
|
+
county: string
|
|
30
|
+
population: number | string
|
|
31
|
+
lat: number | string
|
|
32
|
+
lon: number | string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export const ImportCities = async (): Promise<void> => {
|
|
36
|
+
const cities = await CreateHttp({
|
|
37
|
+
URL: Bootstrap.Settings.BoundarySettings.CityDatabase,
|
|
38
|
+
Timeout: 5000
|
|
39
|
+
})
|
|
40
|
+
|
|
41
|
+
if (cities.error || !cities.message) {
|
|
42
|
+
SetWarning({ Message: `Failed to download city database` })
|
|
43
|
+
return
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
let database: unknown
|
|
47
|
+
|
|
48
|
+
try {
|
|
49
|
+
database = JSON.parse(cities.message)
|
|
50
|
+
} catch {
|
|
51
|
+
SetWarning({ Message: `Failed to parse city database` })
|
|
52
|
+
return
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
if (!Array.isArray(database)) {
|
|
56
|
+
SetWarning({ Message: `Failed to parse city database` })
|
|
57
|
+
return
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
const StateCodes: Record<string, string> = Object.fromEntries(
|
|
61
|
+
Object.entries(EnumStates).map(([code, name]) => [name, code])
|
|
62
|
+
);
|
|
63
|
+
const insert = `INSERT INTO cities (id, name, state, county, population, lat, lon) VALUES (?, ?, ?, ?, ?, ?, ?)`
|
|
64
|
+
const cityRows: CityRow[] = []
|
|
65
|
+
for (const city of database) {
|
|
66
|
+
if (city.country != `US`) continue
|
|
67
|
+
cityRows.push({
|
|
68
|
+
id: city.id,
|
|
69
|
+
name: city.name,
|
|
70
|
+
state: StateCodes[city.admin1] ?? null,
|
|
71
|
+
county: city.admin2,
|
|
72
|
+
population: city.pop ?? null,
|
|
73
|
+
lat: city.lat,
|
|
74
|
+
lon: city.lon
|
|
75
|
+
})
|
|
76
|
+
}
|
|
77
|
+
SetWarning({ Message: `Importing ${cityRows.length} cities` })
|
|
78
|
+
const transaction = Bootstrap.Database.transaction((rows: CityRow[]) => {
|
|
79
|
+
for (const row of rows) {
|
|
80
|
+
CreateQuery({
|
|
81
|
+
Query: insert,
|
|
82
|
+
Parameters: [row.id, row.name, row.state, row.county, row.population, row.lat, row.lon]
|
|
83
|
+
})
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
transaction(cityRows)
|
|
87
|
+
}
|
|
@@ -11,35 +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
|
-
import {
|
|
21
|
-
import {
|
|
22
|
-
import {
|
|
23
|
-
import {
|
|
24
|
-
import {
|
|
20
|
+
import { EnumShapefiles } from "@Enums/Shapefiles"
|
|
21
|
+
import { Bootstrap } from "@Bootstrap"
|
|
22
|
+
import { SetSleep } from "@Utilities/SetSleep"
|
|
23
|
+
import { SetWarning } from "@Utilities/SetWarning"
|
|
24
|
+
import { CreateQuery } from "@Database/CreateQuery"
|
|
25
25
|
import { existsSync, mkdirSync, writeFileSync, unlinkSync, rm } from "fs"
|
|
26
|
-
import { resolve, extname} from
|
|
27
|
-
import { loadAsync } from
|
|
28
|
-
import { read } from
|
|
26
|
+
import { resolve, extname} from "path"
|
|
27
|
+
import { loadAsync } from "jszip"
|
|
28
|
+
import { read } from "shapefile"
|
|
29
29
|
|
|
30
30
|
export const ImportShapefiles = async (): Promise<void> => {
|
|
31
|
-
const settings = bootstrap.settings as TypeSettings;
|
|
32
31
|
try {
|
|
33
|
-
const tShapefiles =
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
if (tShapefiles === 0) {
|
|
37
|
-
await SetSleep({timeout: 1e3});
|
|
32
|
+
const tShapefiles = CreateQuery({ Query: `SELECT COUNT(*) AS count FROM shapefiles` })[0] as { count: number };
|
|
33
|
+
if (tShapefiles.count === 0) {
|
|
34
|
+
await SetSleep({Timeout: 1e3});
|
|
38
35
|
for (const shapefile of EnumShapefiles) {
|
|
39
36
|
const response = await fetch(shapefile.link);
|
|
40
37
|
const arrayBuff = await response.arrayBuffer();
|
|
41
38
|
const content = await loadAsync(arrayBuff);
|
|
42
|
-
const directory = resolve(__dirname,
|
|
39
|
+
const directory = resolve(__dirname, `shapefiles`);
|
|
43
40
|
if (!existsSync(directory)) {
|
|
44
41
|
mkdirSync(directory, { recursive: true });
|
|
45
42
|
}
|
|
@@ -50,15 +47,14 @@ export const ImportShapefiles = async (): Promise<void> => {
|
|
|
50
47
|
writeFileSync(output, data)
|
|
51
48
|
}
|
|
52
49
|
}
|
|
53
|
-
const filepath = resolve(__dirname, '
|
|
50
|
+
const filepath = resolve(__dirname, 'shapefiles', shapefile.name + '_' + shapefile.id)
|
|
54
51
|
const { features } = await read(
|
|
55
52
|
filepath,
|
|
56
53
|
filepath,
|
|
57
54
|
);
|
|
58
|
-
SetWarning({
|
|
59
|
-
const insert =
|
|
60
|
-
|
|
61
|
-
const transaction = bootstrap.database.transaction((entries: any[]) => {
|
|
55
|
+
SetWarning({ Message: `Importing ${features.length} features from ${shapefile.name}_${shapefile.id} for Shapefiles` })
|
|
56
|
+
const insert = `INSERT INTO shapefiles (id, location, source, geometry) VALUES (?, ?, ?, ?)`;
|
|
57
|
+
const transaction = Bootstrap.Database.transaction((entries: any[]) => {
|
|
62
58
|
for (const entry of entries) {
|
|
63
59
|
const { properties, geometry } = entry;
|
|
64
60
|
let final: string, location: string;
|
|
@@ -78,16 +74,17 @@ export const ImportShapefiles = async (): Promise<void> => {
|
|
|
78
74
|
final = properties.ID ?? properties.WFO
|
|
79
75
|
location = properties.NAME;
|
|
80
76
|
}
|
|
81
|
-
insert
|
|
77
|
+
CreateQuery({ Query: insert, Parameters: [final, location, `${shapefile.name}_${shapefile.id}`, JSON.stringify(geometry)] })
|
|
82
78
|
}
|
|
83
79
|
})
|
|
84
80
|
unlinkSync(`${filepath}.shp`)
|
|
85
81
|
unlinkSync(`${filepath}.dbf`)
|
|
86
82
|
transaction(features)
|
|
87
83
|
}
|
|
88
|
-
rm(resolve(__dirname, '
|
|
84
|
+
rm(resolve(__dirname, 'shapefiles'), { recursive: true, force: true }, () => {});
|
|
89
85
|
}
|
|
90
|
-
} catch (error) {
|
|
91
|
-
|
|
86
|
+
} catch (error: unknown) {
|
|
87
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
88
|
+
SetWarning( {Message: `An error occurred while compiling shapefiles: ${message}` })
|
|
92
89
|
}
|
|
93
90
|
}
|
|
@@ -11,37 +11,33 @@
|
|
|
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 { TypeSettings } from "
|
|
21
|
-
import { TypeStanzaCompiled } from
|
|
22
|
-
import {
|
|
23
|
-
import { SetWarning } from
|
|
20
|
+
import { TypeSettings } from "Types/Settings"
|
|
21
|
+
import { TypeStanzaCompiled } from "Types/StanzaCompiled"
|
|
22
|
+
import { Bootstrap } from "@Bootstrap"
|
|
23
|
+
import { SetWarning } from "@Utilities/SetWarning"
|
|
24
|
+
import { CreateQuery } from "@Database/CreateQuery"
|
|
24
25
|
|
|
25
|
-
export const ImportStanza = async (
|
|
26
|
-
const settings =
|
|
26
|
+
export const ImportStanza = async (Stanza: TypeStanzaCompiled): Promise<void> => {
|
|
27
|
+
const settings = Bootstrap.Settings as TypeSettings;
|
|
27
28
|
try {
|
|
28
29
|
if (!settings.NOAAWeatherWireServiceSettings.CacheSettings.Enabled) { return }
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
.run(stanza.getType.type, JSON.stringify(stanza), stanza.attributes.issue)
|
|
32
|
-
const count = bootstrap.database
|
|
33
|
-
.prepare(`SELECT COUNT(*) as total FROM stanzas`)
|
|
34
|
-
.get() as { total: number }
|
|
30
|
+
CreateQuery({ Query: `INSERT OR IGNORE INTO stanzas (type, stanza, issued) VALUES (?, ?, ?)`, Parameters: [Stanza.Type.Type, JSON.stringify(Stanza), Stanza.Attributes.issue] })
|
|
31
|
+
const count = CreateQuery({ Query: `SELECT COUNT(*) as total FROM stanzas` }) as { total: number };
|
|
35
32
|
const max = settings.NOAAWeatherWireServiceSettings.CacheSettings.MaxDatabaseHistory;
|
|
36
33
|
if (count.total > max) {
|
|
37
34
|
const toDelete = count.total - max;
|
|
38
35
|
if (toDelete > 0) {
|
|
39
|
-
|
|
40
|
-
.prepare(`DELETE FROM stanzas WHERE id IN (SELECT id FROM stanzas ORDER BY issued ASC LIMIT ?)`)
|
|
41
|
-
.run(toDelete);
|
|
36
|
+
CreateQuery({ Query: `DELETE FROM stanzas WHERE id IN (SELECT id FROM stanzas ORDER BY issued ASC LIMIT ?)`, Parameters: [toDelete] })
|
|
42
37
|
}
|
|
43
38
|
}
|
|
44
|
-
} catch (error) {
|
|
45
|
-
|
|
39
|
+
} catch (error: unknown) {
|
|
40
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
41
|
+
SetWarning({ Message: `An error occurred while importing stanza: ${message}` })
|
|
46
42
|
}
|
|
47
43
|
}
|
|
@@ -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
|
+
import { TypeSettings } from "Types/Settings"
|
|
21
|
+
import { Bootstrap } from "@Bootstrap"
|
|
22
|
+
import { SetWarning } from "@Utilities/SetWarning"
|
|
23
|
+
import { ImportShapefiles } from "@Database/ImportShapefiles"
|
|
24
|
+
import { ImportBroadcastify } from "@Database/ImportBroadcastify"
|
|
25
|
+
import { ImportBoundaries } from "@Database/ImportBoundaries"
|
|
26
|
+
import { ImportCities } from "@Database/ImportCities"
|
|
27
|
+
import { CreateQuery } from "@Database/CreateQuery"
|
|
28
|
+
import { existsSync, writeFileSync } from "fs"
|
|
29
|
+
import sqlite3 from "better-sqlite3"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
export const InitializeDatabase = async (): Promise<void> => {
|
|
33
|
+
const settings = Bootstrap.Settings as TypeSettings;
|
|
34
|
+
try {
|
|
35
|
+
if (!existsSync(settings.Database)) {
|
|
36
|
+
writeFileSync(settings.Database, '')
|
|
37
|
+
SetWarning({ Message: `Creating new database at ${settings.Database}` })
|
|
38
|
+
}
|
|
39
|
+
Bootstrap.Database = new sqlite3(settings.Database);
|
|
40
|
+
CreateQuery({ Query: `CREATE TABLE IF NOT EXISTS stanzas ( id INTEGER PRIMARY KEY AUTOINCREMENT, type TEXT, issued TEXT, stanza TEXT )` })
|
|
41
|
+
const isNeedingShapefiles = CreateQuery({ Query: `SELECT name FROM sqlite_master WHERE type='table' AND name='shapefiles';` }) as { name: string }[];
|
|
42
|
+
const isNeedingBroadcastify = CreateQuery({ Query: `SELECT name FROM sqlite_master WHERE type='table' AND name='broadcastify';` }) as { name: string }[];
|
|
43
|
+
const isNeedingBoundaries = CreateQuery({ Query: `SELECT name FROM sqlite_master WHERE type='table' AND name='boundaries';` }) as { name: string }[];
|
|
44
|
+
const isNeedingCities = CreateQuery({ Query: `SELECT name FROM sqlite_master WHERE type='table' AND name='cities';` }) as { name: string }[];
|
|
45
|
+
|
|
46
|
+
if (isNeedingShapefiles.length === 0 || isNeedingBroadcastify.length === 0 || isNeedingBoundaries.length === 0 || isNeedingCities.length === 0) {
|
|
47
|
+
CreateQuery({ Query: `CREATE TABLE IF NOT EXISTS broadcastify ( state TEXT, county TEXT, feed TEXT, type TEXT, link TEXT)` })
|
|
48
|
+
CreateQuery({ Query: `CREATE TABLE IF NOT EXISTS shapefiles ( id, location TEXT, source TEXT, geometry TEXT )` })
|
|
49
|
+
CreateQuery({ Query: `CREATE INDEX IF NOT EXISTS idx_shapefiles_id ON shapefiles (id)` })
|
|
50
|
+
CreateQuery({ Query: `CREATE TABLE IF NOT EXISTS boundaries (id TEXT PRIMARY KEY, type TEXT NOT NULL, state TEXT, name TEXT, geometry TEXT NOT NULL );` })
|
|
51
|
+
CreateQuery({ Query: `CREATE TABLE IF NOT EXISTS cities ( id TEXT PRIMARY KEY, name TEXT, state TEXT, county TEXT, population TEXT, lat REAL NOT NULL, lon REAL NOT NULL);`});
|
|
52
|
+
SetWarning({Message: `Required database tables are currently building, please ${Bootstrap.Colors.Red}DO NOT${Bootstrap.Colors.Reset} close your terminal. The building will not finish and will remain incomplete. If you do mess up, you will need to delete ${settings.Database} and restart the application.` })
|
|
53
|
+
if (isNeedingCities.length === 0) {
|
|
54
|
+
await ImportCities();
|
|
55
|
+
}
|
|
56
|
+
if (isNeedingBroadcastify.length === 0) {
|
|
57
|
+
await ImportBroadcastify();
|
|
58
|
+
}
|
|
59
|
+
if (isNeedingBoundaries.length === 0) {
|
|
60
|
+
await ImportBoundaries();
|
|
61
|
+
}
|
|
62
|
+
if (isNeedingShapefiles.length === 0) {
|
|
63
|
+
await ImportShapefiles();
|
|
64
|
+
}
|
|
65
|
+
SetWarning({ Message: `Database initialization complete. You may now close your terminal or continue using the application.` })
|
|
66
|
+
}
|
|
67
|
+
} catch (error: unknown) {
|
|
68
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
69
|
+
SetWarning({ Message: `An error occurred while initializing the database: ${message}` })
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,234 @@
|
|
|
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 { EnumStates, EnumZones } from "@Enums/States"
|
|
21
|
+
import { TypeEvent } from "StaticTypes/Event"
|
|
22
|
+
import { Bootstrap } from "@Bootstrap"
|
|
23
|
+
import { GetStringText } from "@ParsingText/GetStringText"
|
|
24
|
+
import { GetEventGeometry } from "@Building/GetEventGeometry"
|
|
25
|
+
import { GetGeographicalEvents } from "@ImageModules/GetGeographicalEvents"
|
|
26
|
+
import { GetUnionPolygon } from "@Utilities/GetUnionPolygon"
|
|
27
|
+
import { GetGeographicalBoundaries } from "@ImageModules/GetGeographicalBoundaries"
|
|
28
|
+
import { GetGeographicalCities } from "@ImageModules/GetGeographicalCities"
|
|
29
|
+
import { GetParsedBoundary } from "@ImageModules/GetParsedBoundary"
|
|
30
|
+
import { GetSVGPath } from "@ImageModules/GetSVGPath"
|
|
31
|
+
import { CreateSVG } from "@ImageModules/CreateSVG"
|
|
32
|
+
import { GetGeometryBounds } from "@ImageModules/GetGeometryBounds"
|
|
33
|
+
import { geoMercator, geoPath } from "d3-geo"
|
|
34
|
+
import { mkdir, readFile } from "fs/promises"
|
|
35
|
+
import { join } from "path";
|
|
36
|
+
import sharp from "sharp"
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
interface GenerateGraphicOptions {
|
|
40
|
+
Regions?: string[]
|
|
41
|
+
IgnoredRegions?: string[]
|
|
42
|
+
Event?: TypeEvent
|
|
43
|
+
File?: {
|
|
44
|
+
Directory?: string
|
|
45
|
+
Name?: string
|
|
46
|
+
}
|
|
47
|
+
MaxMiles?: number
|
|
48
|
+
Width?: number
|
|
49
|
+
Height?: number
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export const GenerateGraphic = async ({ File, Regions, Event, MaxMiles = 350, Width = 1200, Height = 675 }: GenerateGraphicOptions): Promise<string> => {
|
|
53
|
+
let polygons: GeoJSON.Polygon | GeoJSON.MultiPolygon | null
|
|
54
|
+
let icon: string | null;
|
|
55
|
+
let renders: any = {
|
|
56
|
+
cities: null,
|
|
57
|
+
states: null,
|
|
58
|
+
counties: null,
|
|
59
|
+
};
|
|
60
|
+
let pathing: any = {
|
|
61
|
+
cities: null,
|
|
62
|
+
events: null,
|
|
63
|
+
counties: null,
|
|
64
|
+
states: null,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
const A = [EnumStates, EnumZones].reduce((acc, obj) => ({ ...acc, ...obj }), {});
|
|
68
|
+
const B = [`HI`, `AK`];
|
|
69
|
+
const { coordinates } = Event?.geometry ?? {};
|
|
70
|
+
const { properties } = Event ?? {};
|
|
71
|
+
const { regions, theme, event } = properties ?? {};
|
|
72
|
+
const inConus = Event ? regions?.every(state => A[state] && !B.includes(state)) : true;
|
|
73
|
+
const R = (Regions ?? null)?.length
|
|
74
|
+
? [...new Set(Regions ?? null)]
|
|
75
|
+
: null;
|
|
76
|
+
const E = GetGeographicalEvents({ Regions: R, Event: Event });
|
|
77
|
+
if (inConus || !Event) {
|
|
78
|
+
const boundaries = GetGeographicalBoundaries({ Regions: R });
|
|
79
|
+
const cities = GetGeographicalCities({ Regions: R });
|
|
80
|
+
renders.cities = cities.cities.filter(city => city.population >= 5e3).sort((a, b) => b.population - a.population).filter((city, index, cities) => {
|
|
81
|
+
return !cities.slice(0, index).some(other => {
|
|
82
|
+
const dist = Math.sqrt(
|
|
83
|
+
Math.pow(city.lat - other.lat, 2) +
|
|
84
|
+
Math.pow(city.lon - other.lon, 2)
|
|
85
|
+
);
|
|
86
|
+
return dist < (Event || Regions ? 0.4 : 3);
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
renders.states = GetParsedBoundary(boundaries.states);
|
|
90
|
+
renders.counties = GetParsedBoundary(boundaries.counties);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (Event) {
|
|
94
|
+
polygons = (coordinates.length > 0) ? Event.geometry : await GetEventGeometry({ Event });
|
|
95
|
+
if (polygons.coordinates.length == 0) { return null; }
|
|
96
|
+
if (inConus) {
|
|
97
|
+
const eBounds = GetGeometryBounds({ Geometry: polygons, Padding: MaxMiles });
|
|
98
|
+
renders.counties = renders?.counties?.filter((county: GeoJSON.Feature<GeoJSON.Polygon>) => {
|
|
99
|
+
const cBounds = GetGeometryBounds({ Geometry: county.geometry, Padding: MaxMiles });
|
|
100
|
+
return ( cBounds.Bounds.minLon <= eBounds.Search.maxLon && cBounds.Bounds.maxLon >= eBounds.Search.minLon && cBounds.Bounds.minLat <= eBounds.Search.maxLat && cBounds.Bounds.maxLat >= eBounds.Search.minLat );
|
|
101
|
+
});
|
|
102
|
+
renders.states = renders?.states?.filter((state: GeoJSON.Feature<GeoJSON.Polygon>) => {
|
|
103
|
+
const sBounds = GetGeometryBounds({ Geometry: state.geometry, Padding: MaxMiles });
|
|
104
|
+
return ( sBounds.Bounds.minLon <= eBounds.Search.maxLon && sBounds.Bounds.maxLon >= eBounds.Search.minLon && sBounds.Bounds.minLat <= eBounds.Search.maxLat && sBounds.Bounds.maxLat >= eBounds.Search.minLat );
|
|
105
|
+
});
|
|
106
|
+
renders.cities = renders?.cities?.filter((city: { lat: number; lon: number }) => {
|
|
107
|
+
return ( city.lat >= eBounds.Search.minLat && city.lat <= eBounds.Search.maxLat && city.lon >= eBounds.Search.minLon && city.lon <= eBounds.Search.maxLon );
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const iMode = geoMercator();
|
|
113
|
+
const iPath = geoPath().projection(iMode);
|
|
114
|
+
const jFeatures: GeoJSON.Feature[] = [...renders?.states ?? [], ...renders?.counties ?? []];
|
|
115
|
+
const jCollection: GeoJSON.FeatureCollection = { type: `FeatureCollection`, features: jFeatures };
|
|
116
|
+
const events = (await Promise.all(
|
|
117
|
+
E.map(async (event: TypeEvent) => {
|
|
118
|
+
const zones = event.properties?.geocode?.ugc ?? [];
|
|
119
|
+
if (zones?.length === 0) return null;
|
|
120
|
+
return {event, polygon: await GetUnionPolygon({
|
|
121
|
+
Polygons: polygons ? [polygons.coordinates] : [await GetEventGeometry({ Event: event }).coordinates],
|
|
122
|
+
})};
|
|
123
|
+
})
|
|
124
|
+
)).filter(Boolean);
|
|
125
|
+
|
|
126
|
+
if (polygons) {
|
|
127
|
+
const geo = {
|
|
128
|
+
type: "FeatureCollection",
|
|
129
|
+
features: [{ type: "Feature", geometry: polygons, properties: {} }]
|
|
130
|
+
};
|
|
131
|
+
iMode.fitExtent([[150, 150], [Width - 150, Height - 150]], geo as any);
|
|
132
|
+
iMode.scale(iMode.scale());
|
|
133
|
+
} else {
|
|
134
|
+
iMode.fitExtent([[60, 60], [Width - 60, Height - 60]], jCollection);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
pathing.events = events?.map(({ event, polygon: polys }) => {
|
|
138
|
+
const color = event?.properties?.theme ?? theme ?? `rgb(56, 72, 88)`;
|
|
139
|
+
return GetSVGPath({
|
|
140
|
+
Polygons: polys,
|
|
141
|
+
IPath: iPath,
|
|
142
|
+
Map: false,
|
|
143
|
+
Settings: { BorderColor: `${color}`, BorderWidth: 2, FillColor: `${color}`, FillOpacity: 0.1 }
|
|
144
|
+
});
|
|
145
|
+
}).filter(Boolean).join(``);
|
|
146
|
+
|
|
147
|
+
pathing.counties = renders?.counties?.map((polygon: GeoJSON.Feature) => GetSVGPath({
|
|
148
|
+
Polygons: polygon.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon,
|
|
149
|
+
IPath: iPath,
|
|
150
|
+
Map: true,
|
|
151
|
+
Settings: { BorderColor: `darkgray`, BorderWidth: 0.5, FillColor: `black`, FillOpacity: 1 }
|
|
152
|
+
})).filter(Boolean).join(``);
|
|
153
|
+
|
|
154
|
+
pathing.states = renders?.states?.map((polygon: GeoJSON.Feature) => GetSVGPath({
|
|
155
|
+
Polygons: polygon.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon,
|
|
156
|
+
IPath: iPath,
|
|
157
|
+
Map: false,
|
|
158
|
+
Settings: { BorderColor: `white`, BorderWidth: 1, FillColor: `#ffffff`, FillOpacity: 0 }
|
|
159
|
+
})).filter(Boolean).join(``);
|
|
160
|
+
|
|
161
|
+
pathing.cities = renders?.cities?.map((city: { lat: number; lon: number; name: string; population: number }) => {
|
|
162
|
+
const point = iMode([city.lon, city.lat]);
|
|
163
|
+
const [x, y] = point;
|
|
164
|
+
const radius = 2 * Math.max(0.75, Math.min(1.5, Width / 1000));
|
|
165
|
+
return `
|
|
166
|
+
<circle cx="${x}" cy="${y}" r="${radius}" fill="#ffffff" />
|
|
167
|
+
<text x="${x}" y="${y + radius + 9}" fill="#ffffff" font-size="${Math.max(10, Math.min(10, Width / 140))}px" font-family="Arial, sans-serif" font-weight="500" text-anchor="middle" dominant-baseline="middle" stroke="#000000" stroke-width="2.5" stroke-opacity="0.7" paint-order="stroke" >${city.name}</text>
|
|
168
|
+
`;
|
|
169
|
+
}).join(``);
|
|
170
|
+
|
|
171
|
+
const title = Event
|
|
172
|
+
? (event ?? `Event`)
|
|
173
|
+
: (R?.length > 0 ? `Region: ${R.map(state => EnumStates[state] ?? state).join(`, `)}` : `Contiguous United States`);
|
|
174
|
+
const subtitleLines = Event
|
|
175
|
+
? GetStringText(Event).split(`\n`).filter(line => line.trim().length > 0).slice(0, 10)
|
|
176
|
+
: [`Last Updated: ${new Date().toLocaleString()}`];
|
|
177
|
+
const scale = Math.max(0.75, Math.min(1.0, Width / 1000));
|
|
178
|
+
const titleSize = Math.round(17 * scale);
|
|
179
|
+
const lineSize = Math.round(13 * scale);
|
|
180
|
+
const lineHeight = Math.round(15 * scale);
|
|
181
|
+
const paddingX = Math.round(16 * scale);
|
|
182
|
+
const paddingTop = Math.round(16 * scale);
|
|
183
|
+
const paddingBottom = Math.round(16 * scale);
|
|
184
|
+
const accentWidth = Math.max(4, Math.round(5 * scale));
|
|
185
|
+
const iconSize = Math.round(Math.min(122, Math.max(48, Width * 0.07)) * scale);
|
|
186
|
+
const hasIcon = Boolean(Bootstrap?.Settings?.GlobalSettings?.ArchiveSettings?.Logo);
|
|
187
|
+
|
|
188
|
+
if (hasIcon) {
|
|
189
|
+
const buffer = await readFile(Bootstrap.Settings.GlobalSettings.ArchiveSettings.Logo);
|
|
190
|
+
icon = `data:image/png;base64,${Buffer.from(buffer).toString(`base64`)}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
const boxWidth = Math.min(Math.round(Width * 0.4), Width - Math.round(28 * scale));
|
|
194
|
+
const textStartX = paddingX + accentWidth + Math.round(6 * scale);
|
|
195
|
+
const textEndPadding = hasIcon ? iconSize + Math.round(20 * scale) : Math.round(16 * scale);
|
|
196
|
+
const availableTextWidth = boxWidth - textStartX - textEndPadding;
|
|
197
|
+
const maxTitleChars = Math.max(20, Math.floor(availableTextWidth / (titleSize * 0.52)));
|
|
198
|
+
const maxLineChars = Math.max(24, Math.floor(availableTextWidth / (lineSize * 0.52)));
|
|
199
|
+
|
|
200
|
+
const titleY = paddingTop + titleSize;
|
|
201
|
+
const firstLineY = titleY + Math.round(22 * scale);
|
|
202
|
+
const contentHeight = firstLineY + (subtitleLines.length * lineHeight) + paddingBottom;
|
|
203
|
+
const boxHeight = Math.max(contentHeight, hasIcon ? paddingTop + iconSize + paddingBottom : contentHeight);
|
|
204
|
+
|
|
205
|
+
const iconX = boxWidth - iconSize - Math.round(5 * scale);
|
|
206
|
+
const iconY = paddingTop;
|
|
207
|
+
|
|
208
|
+
const SVG = CreateSVG({
|
|
209
|
+
Width, Height,
|
|
210
|
+
MapFeatures: !inConus ? [pathing.events] : [pathing.counties, pathing.states, pathing.events, pathing.cities],
|
|
211
|
+
Features: [
|
|
212
|
+
`<rect x="${Math.round(9 * scale)}" y="${Math.round(10 * scale)}" width="${boxWidth}" height="${boxHeight}" fill="rgba(0, 0, 0, 0.57)" />`,
|
|
213
|
+
`<rect x="${Math.round(6 * scale)}" y="${Math.round(7 * scale)}" width="${boxWidth}" height="${boxHeight}" fill="rgba(16, 18, 24, 0.36)" stroke="rgba(255,255,255,0.12)" stroke-width="${Math.max(1, scale)}" />`,
|
|
214
|
+
`<rect x="${Math.round(6 * scale)}" y="${Math.round(7 * scale)}" width="${accentWidth}" height="${boxHeight}" fill="${theme ?? `rgb(56, 72, 88)`}" />`,
|
|
215
|
+
hasIcon && icon
|
|
216
|
+
? `<image href="${icon}" x="${iconX}" y="${iconY}" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet" opacity="0.95" />`
|
|
217
|
+
: ``,
|
|
218
|
+
`<text x="${textStartX}" y="${titleY}" text-anchor="start" font-family="Arial, sans-serif" font-size="${titleSize}" font-weight="700" fill="white">${title.length > maxTitleChars ? title.slice(0, maxTitleChars - 3) + `...` : title}</text>`,
|
|
219
|
+
...subtitleLines.map((line, i) =>
|
|
220
|
+
`<text x="${textStartX}" y="${firstLineY + (i * lineHeight)}" text-anchor="start" font-family="Arial, sans-serif" font-size="${lineSize}" fill="rgba(255,255,255,0.82)">${line.length > maxLineChars ? line.slice(0, maxLineChars - 3) + `...` : line}</text>`
|
|
221
|
+
),
|
|
222
|
+
`<text x="${textStartX}" y="${boxHeight - Math.round(5 * scale)}" text-anchor="start" font-family="Arial, sans-serif" font-size="${Math.max(8, Math.min(8, Width / 140))}px" fill="rgba(255,255,255,0.82)" >This graphic was created by AtmosphericX and is not an official NOAA graphic.</text>`
|
|
223
|
+
]
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
const { Directory, Name } = File ?? {};
|
|
227
|
+
const dir = Directory ?? Bootstrap.Settings?.GlobalSettings?.ArchiveSettings?.ImageDirectory;
|
|
228
|
+
const name = Name ?? event ?? `img`;
|
|
229
|
+
await mkdir(dir, { recursive: true });
|
|
230
|
+
await sharp(Buffer.from(SVG))
|
|
231
|
+
.png()
|
|
232
|
+
.toFile(join(dir, `${name}${name.includes(`.png`) ? `` : `.png`}`));
|
|
233
|
+
return dir + `/${name}${name.includes(`.png`) ? `` : `.png`}`;
|
|
234
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
interface CreateRectangleOptions {}
|
|
21
|
+
|
|
22
|
+
export const CreateRectangle = ({}: CreateRectangleOptions): void => {}
|
|
@@ -0,0 +1,22 @@
|
|
|
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
|
+
interface CreateTextOptions {}
|
|
21
|
+
|
|
22
|
+
export const CreateText = ({}: CreateTextOptions): void => {}
|
|
@@ -0,0 +1,37 @@
|
|
|
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
|
+
interface CreateSVGOptions {
|
|
21
|
+
MapFeatures: string[];
|
|
22
|
+
Features: string[];
|
|
23
|
+
Width: number;
|
|
24
|
+
Height: number
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export const CreateSVG = ({ MapFeatures, Features, Width, Height }: CreateSVGOptions): string => {
|
|
28
|
+
return `
|
|
29
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="${Width}" height="${Height}" viewBox="0 0 ${Width} ${Height}">
|
|
30
|
+
<rect width="${Width}" height="${Height}" fill="black"/>
|
|
31
|
+
<g fill-rule="evenodd" clip-rule="evenodd">
|
|
32
|
+
${MapFeatures.join(`\n`)}
|
|
33
|
+
${Features.join(`\n`)}
|
|
34
|
+
</g>
|
|
35
|
+
</svg>
|
|
36
|
+
`;
|
|
37
|
+
}
|