@atmosx/event-product-parser 3.0.622 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (199) hide show
  1. package/.github/CHANGELOGS.md +52 -0
  2. package/.github/CODE_OF_CONDUCT.md +24 -1
  3. package/.github/CONTRIBUTING.md +22 -3
  4. package/.github/FUNDING.yaml +3 -0
  5. package/.github/LICENSE +24 -1
  6. package/.github/SECURITY.md +25 -6
  7. package/.github/docs/chapters/general-configurations.md +109 -0
  8. package/.github/docs/chapters/sources-and-attributes.md +219 -0
  9. package/README.md +56 -148
  10. package/dist/{cjs/index.cjs → index.cjs} +4486 -1471
  11. package/package.json +13 -8
  12. package/pnpm-workspace.yaml +1 -0
  13. package/src/bootstrap.ts +66 -51
  14. package/src/components/audio/GenerateAudioMessage.ts +149 -0
  15. package/src/{@modules/@eas → components/audio/components}/GetCleanDescription.ts +2 -2
  16. package/src/{@modules/@eas → components/audio/components}/GetFloatPCM16.ts +2 -2
  17. package/src/{@modules/@eas → components/audio/components}/GetMergedPCM16.ts +2 -2
  18. package/src/{@modules/@eas → components/audio/components}/GetPCM16.ts +13 -8
  19. package/src/{@modules/@eas → components/audio/components}/GetPCMToFloat.ts +2 -2
  20. package/src/{@modules/@eas → components/audio/components}/GetSampledPCM16.ts +14 -8
  21. package/src/{@modules/@eas → components/audio/components}/GetTTS.ts +15 -8
  22. package/src/{@modules/@eas → components/audio/components}/GetWavPCM16.ts +7 -7
  23. package/src/{@modules/@eas → components/audio/components}/SetAFSK.ts +13 -8
  24. package/src/{@modules/@eas → components/audio/components}/SetAsciiToBits.ts +2 -2
  25. package/src/{@modules/@eas → components/audio/components}/SetAttentionTone.ts +11 -6
  26. package/src/{@modules/@eas → components/audio/components}/SetNoise.ts +12 -7
  27. package/src/{@modules/@eas → components/audio/components}/SetRadioEffect.ts +12 -7
  28. package/src/{@modules/@eas → components/audio/components}/SetSameHeader.ts +22 -14
  29. package/src/components/database/CreateQuery.ts +39 -0
  30. package/src/components/database/InitializeDatabase.ts +71 -0
  31. package/src/components/database/imports/Boundaires.ts +168 -0
  32. package/src/{@modules/@database/ImportBroadcastify.ts → components/database/imports/Broadcastify.ts} +15 -25
  33. package/src/components/database/imports/Census.ts +87 -0
  34. package/src/{@modules/@database/ImportShapefiles.ts → components/database/imports/Shapefiles.ts} +18 -21
  35. package/src/{@modules/@database/ImportStanza.ts → components/database/imports/Stanza.ts} +15 -19
  36. package/src/components/events/CreateEvents.ts +88 -0
  37. package/src/components/events/FilterEvents.ts +149 -0
  38. package/src/{@building/CreateEvent.ts → components/events/ProcessStanza.ts} +22 -22
  39. package/src/components/events/RemoveEvents.ts +74 -0
  40. package/src/{@building → components/events/components}/GetCleanedEvent.ts +2 -2
  41. package/src/{@building → components/events/components}/GetEventAttachments.ts +14 -12
  42. package/src/components/events/components/GetEventDirection.ts +28 -0
  43. package/src/{@building → components/events/components}/GetEventEnhancedName.ts +5 -6
  44. package/src/{@building → components/events/components}/GetEventGeometry.ts +19 -13
  45. package/src/{@building → components/events/components}/GetEventHeader.ts +13 -13
  46. package/src/components/events/components/GetEventMerged.ts +51 -0
  47. package/src/{@building → components/events/components}/GetEventNodes.ts +20 -18
  48. package/src/{@building → components/events/components}/GetEventOffice.ts +13 -13
  49. package/src/components/events/components/GetEventPopulation.ts +58 -0
  50. package/src/components/events/components/GetEventProperties.ts +106 -0
  51. package/src/{@building → components/events/components}/GetEventSignature.ts +9 -9
  52. package/src/{@building → components/events/components}/GetEventTags.ts +3 -3
  53. package/src/{@core/CreateListener.ts → components/events/components/GetEventTheme.ts} +8 -6
  54. package/src/components/events/components/GetEventTracking.ts +64 -0
  55. package/src/{@modules/@database → components/events/utilities}/GetCachedEvents.ts +18 -14
  56. package/src/{@manager → components/events/utilities}/SetHash.ts +18 -13
  57. package/src/{@manager → components/events/utilities}/UpdateEvents.ts +12 -12
  58. package/src/{@manager → components/events/utilities}/UpdateNode.ts +14 -14
  59. package/src/{@parsers/@text → components/formatting}/GetEmbededText.ts +12 -6
  60. package/src/{@parsers/@text → components/formatting}/GetStringText.ts +13 -9
  61. package/src/{@modules/@utilities → components/geometry}/GetShapeNearestPoint.ts +24 -19
  62. package/src/components/geometry/GetUnionPolygon.ts +36 -0
  63. package/src/components/images/GenerateGraphic.ts +242 -0
  64. package/src/components/images/components/CreateRectangle.ts +22 -0
  65. package/src/components/images/components/CreateText.ts +22 -0
  66. package/src/components/images/modules/CreateSVG.ts +37 -0
  67. package/src/components/images/modules/GetGeographicalBoundaries.ts +72 -0
  68. package/src/components/images/modules/GetGeographicalCities.ts +58 -0
  69. package/src/components/images/modules/GetGeographicalEvents.ts +48 -0
  70. package/src/components/images/modules/GetGeometryBounds.ts +82 -0
  71. package/src/components/images/modules/GetParsedBoundary.ts +58 -0
  72. package/src/components/images/modules/GetSVGPath.ts +53 -0
  73. package/src/components/images/modules/NormalizePolygon.ts +57 -0
  74. package/src/{@modules/@stanza → components/stanza}/GetAwipsType.ts +16 -17
  75. package/src/{@modules/@stanza → components/stanza}/ValidateStanza.ts +27 -15
  76. package/src/{@modules/@utilities → components/utilities}/CreateHttp.ts +26 -26
  77. package/src/components/utilities/EmitThread.ts +97 -0
  78. package/src/{@modules/@utilities → components/utilities}/GetLatestIssuance.ts +2 -2
  79. package/src/{@modules/@utilities → components/utilities}/GetMatched.ts +10 -5
  80. package/src/{@modules/@utilities → components/utilities}/QueueManager.ts +7 -8
  81. package/src/{@modules/@utilities → components/utilities}/SetDebug.ts +13 -13
  82. package/src/components/utilities/SetEventEmit.ts +42 -0
  83. package/src/{@modules/@utilities → components/utilities}/SetSettings.ts +7 -7
  84. package/src/{@modules/@utilities → components/utilities}/SetSleep.ts +5 -5
  85. package/src/components/utilities/SetTimeoutAction.ts +64 -0
  86. package/src/components/utilities/SetWarning.ts +47 -0
  87. package/src/{@modules/@xmpp → components/xmpp}/DeployXMPP.ts +20 -20
  88. package/src/{@modules/@xmpp → components/xmpp}/ErrorXMPP.ts +6 -6
  89. package/src/{@modules/@xmpp → components/xmpp}/OfflineXMPP.ts +13 -13
  90. package/src/{@modules/@xmpp → components/xmpp}/OnlineXMPP.ts +18 -18
  91. package/src/components/xmpp/ReconnectXMPP.ts +64 -0
  92. package/src/{@modules/@xmpp → components/xmpp}/StanzaXMPP.ts +26 -26
  93. package/src/{@enums → enums}/AWIPS.ts +2 -2
  94. package/src/{@enums → enums}/Actions.ts +2 -2
  95. package/src/{@enums → enums}/Cancellation.ts +2 -2
  96. package/src/{@enums → enums}/Causes.ts +2 -2
  97. package/src/{@enums → enums}/Correlations.ts +2 -2
  98. package/src/{@enums → enums}/Enhanced.ts +4 -3
  99. package/src/{@enums → enums}/Events.ts +2 -2
  100. package/src/{@enums → enums}/Expressions.ts +3 -3
  101. package/src/{@enums → enums}/GlobalFilter.ts +2 -2
  102. package/src/{@enums → enums}/Hail.ts +2 -2
  103. package/src/{@enums → enums}/ICAO.ts +2 -2
  104. package/src/{@enums → enums}/Matches.ts +8 -3
  105. package/src/{@enums → enums}/Products.ts +2 -2
  106. package/src/{@enums → enums}/Records.ts +2 -2
  107. package/src/{@enums → enums}/Severity.ts +2 -2
  108. package/src/{@enums → enums}/Shapefiles.ts +2 -2
  109. package/src/{@enums → enums}/States.ts +68 -4
  110. package/src/{@enums → enums}/Status.ts +2 -2
  111. package/src/{@enums → enums}/Strings.ts +2 -2
  112. package/src/{@enums → enums}/Tags.ts +8 -3
  113. package/src/{@enums → enums}/Testing.ts +2 -2
  114. package/src/enums/Themes.ts +70 -0
  115. package/src/events/ParseAPI.ts +133 -0
  116. package/src/{@events → events}/ParseText.ts +24 -22
  117. package/src/{@events → events}/ParseUGC.ts +27 -26
  118. package/src/{@events → events}/ParseVTEC.ts +37 -36
  119. package/src/{@core → exports}/ClearEvents.ts +9 -9
  120. package/src/{@core → exports}/GetEvents.ts +4 -4
  121. package/src/exports/GetNearestCity.ts +45 -0
  122. package/src/{@core → exports}/GetNodes.ts +4 -4
  123. package/src/{@core → exports}/GetRandomEvent.ts +4 -4
  124. package/src/{@core → exports}/GetVersion.ts +4 -4
  125. package/src/{@core → exports}/ManualEvent.ts +24 -24
  126. package/src/{@core → exports}/QueryStanza.ts +12 -11
  127. package/src/exports/SetNode.ts +100 -0
  128. package/src/{@core → exports}/StartService.ts +22 -21
  129. package/src/{@core → exports}/StopService.ts +10 -10
  130. package/src/index.ts +55 -37
  131. package/src/{@parsers/@hvtec → parsers/hvtec}/HVExtract.ts +7 -7
  132. package/src/{@parsers/@text → parsers/text}/GetDescriptionFromProduct.ts +20 -21
  133. package/src/parsers/text/GetPolygonFromProduct.ts +71 -0
  134. package/src/{@parsers/@text → parsers/text}/GetTextFromProduct.ts +12 -12
  135. package/src/parsers/ugc/GetCache.ts +24 -0
  136. package/src/{@parsers/@ugc → parsers/ugc}/GetExpiry.ts +2 -2
  137. package/src/{@parsers/@ugc/GetUGCHeader.ts → parsers/ugc/GetHeader.ts} +4 -4
  138. package/src/{@parsers/@ugc → parsers/ugc}/GetLocations.ts +10 -10
  139. package/src/parsers/ugc/GetZonePolygon.ts +73 -0
  140. package/src/{@parsers/@ugc → parsers/ugc}/GetZones.ts +3 -3
  141. package/src/{@parsers/@ugc → parsers/ugc}/SetCache.ts +13 -8
  142. package/src/{@parsers/@ugc → parsers/ugc}/UGCExtract.ts +14 -14
  143. package/src/{@parsers/@pvtec → parsers/vtec}/GetExpiry.ts +2 -2
  144. package/src/{@parsers/@pvtec → parsers/vtec}/VTECExtract.ts +17 -17
  145. package/src/tasks/CreateTasks.ts +116 -0
  146. package/src/{@tasks → tasks}/TaskGenerateJSON.ts +10 -11
  147. package/src/{@tasks → tasks}/TaskGenerateText.ts +11 -12
  148. package/src/tasks/TaskSendNTFY.ts +96 -0
  149. package/src/tasks/TaskSendWebhook.ts +107 -0
  150. package/src/{@types → types/event}/Attributes.ts +2 -3
  151. package/src/{@types → types/event}/Event.ts +8 -7
  152. package/src/{@types → types/event}/HVTEC.ts +2 -2
  153. package/src/{@types → types/event}/Properties.ts +17 -4
  154. package/src/{@types → types/event}/Stanza.ts +2 -2
  155. package/src/{@types → types/event}/VTEC.ts +8 -8
  156. package/src/{@types → types/standard}/Actions.ts +3 -3
  157. package/src/{@types → types/standard}/Hash.ts +5 -5
  158. package/src/{@modules/@utilities/GetSettings.ts → types/standard/Nodes.ts} +15 -7
  159. package/src/{@types → types/standard}/Settings.ts +19 -12
  160. package/src/{@types → types/standard}/StanzaCompiled.ts +15 -15
  161. package/src/{@types → types/standard}/UGC.ts +5 -5
  162. package/tools/build.js +2 -15
  163. package/tsconfig.json +29 -17
  164. package/.github/SUPPORTED_EVENTS.md +0 -370
  165. package/.github/docs/docs-functions.md +0 -30
  166. package/.github/docs/docs-general-configurations.md +0 -209
  167. package/.github/docs/docs-integrations.md +0 -37
  168. package/.github/docs/docs-listeners.md +0 -28
  169. package/.github/docs/docs-source-configuration.md +0 -218
  170. package/.github/docs/docs-technical.md +0 -50
  171. package/.github/logo.png +0 -0
  172. package/.github/samples/SAMPLE_EAS_GENERATION.wav +0 -0
  173. package/.github/samples/SAMPLE_GEOJSON.json +0 -204
  174. package/.github/samples/SAMPLE_TEXT_PRODUCT.txt +0 -119
  175. package/dist/esm/index.mjs +0 -22298
  176. package/src/@building/GetEventProperties.ts +0 -101
  177. package/src/@building/GetEventTracking.ts +0 -68
  178. package/src/@core/SetNode.ts +0 -87
  179. package/src/@events/ParseAPI.ts +0 -122
  180. package/src/@manager/MakeEvents.ts +0 -109
  181. package/src/@manager/RemoveEvent.ts +0 -70
  182. package/src/@manager/ValidateEvents.ts +0 -141
  183. package/src/@modules/@database/InitializeDatabase.ts +0 -59
  184. package/src/@modules/@eas/GenerateEASMessage.ts +0 -152
  185. package/src/@modules/@utilities/GetFormattedTime.ts +0 -43
  186. package/src/@modules/@utilities/SetCronSchedule.ts +0 -94
  187. package/src/@modules/@utilities/SetEventEmit.ts +0 -41
  188. package/src/@modules/@utilities/SetListener.ts +0 -30
  189. package/src/@modules/@utilities/SetTimeoutAction.ts +0 -64
  190. package/src/@modules/@utilities/SetWarning.ts +0 -34
  191. package/src/@modules/@xmpp/ReconnectXMPP.ts +0 -64
  192. package/src/@parsers/@text/GetPolygonFromProduct.ts +0 -53
  193. package/src/@parsers/@text/GetXML.ts +0 -61
  194. package/src/@parsers/@ugc/GetCache.ts +0 -24
  195. package/src/@parsers/@ugc/GetZonePolygon.ts +0 -111
  196. package/src/@tasks/CreateTasks.ts +0 -110
  197. package/src/@tasks/TaskGenerateAudio.ts +0 -35
  198. package/src/@tasks/TaskSendNTFY.ts +0 -93
  199. package/src/@tasks/TaskSendWebhook.ts +0 -101
@@ -11,26 +11,31 @@
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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "types/Event"
21
- import { TypeHash } from "types/Hash"
22
- import { bootstrap } from "@bootstrap"
20
+ import { TypeEvent } from "TypesEvent/Event"
21
+ import { TypeHash } from "TypesStandard/Hash"
22
+ import { Bootstrap } from "@Bootstrap"
23
23
 
24
- export const SetHash = (event: TypeEvent, entry: TypeHash): void => {
25
- if (!entry) {
26
- bootstrap.cache.hashes.push({
27
- tracking: event.properties.metadata.tracking,
28
- hashes: [event.properties.metadata.hash],
29
- expires: event.properties.expires
24
+ interface SetHashOptions {
25
+ Event: TypeEvent
26
+ Entry: TypeHash
27
+ }
28
+
29
+ export const SetHash = ({ Event, Entry }: SetHashOptions): void => {
30
+ if (!Entry) {
31
+ Bootstrap.Cache.Hashes.push({
32
+ Tracking: Event.properties.metadata.tracking,
33
+ Hashes: [Event.properties.metadata.hash],
34
+ Expires: Event.properties.expires
30
35
  });
31
36
  return;
32
37
  }
33
38
 
34
- entry.hashes.push(event.properties.metadata.hash);
35
- entry.expires = event.properties.expires;
39
+ Entry.Hashes.push(Event.properties.metadata.hash);
40
+ Entry.Expires = Event.properties.expires;
36
41
  }
@@ -11,25 +11,25 @@
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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "types/Event"
21
- import { bootstrap } from "@bootstrap"
22
- import { SetEventEmit } from "@utilities/SetEventEmit"
23
- import { RemoveEvent } from "@manager/RemoveEvent"
20
+ import { TypeEvent } from "TypesEvent/Event"
21
+ import { Bootstrap } from "@Bootstrap"
22
+ import { SetEventEmit } from "@Utilities/SetEventEmit"
23
+ import { RemoveEvents } from "@Builder/RemoveEvents"
24
24
 
25
- export const UpdateEvents = async (selectedEvent?: TypeEvent): Promise<void> => {
26
- const events = bootstrap.cache.events.features;
25
+ export const UpdateEvents = async (selected?: TypeEvent): Promise<void> => {
26
+ const events = Bootstrap.Cache.Events.features;
27
27
  async function update(event: TypeEvent) {
28
28
  if (new Date(event.properties.expires) < new Date()) {
29
- SetEventEmit({ event: `onExpiredProduct`, metadata: event })
30
- await RemoveEvent(event, true)
29
+ SetEventEmit({ Event: `onExpiredProduct`, Metadata: event })
30
+ await RemoveEvents({ Event: event, IsTimeBasedExpiration: true })
31
31
  }
32
32
  }
33
- if (!selectedEvent) { await Promise.all(events.map(async (event) => { await update(event) })) }
34
- if (selectedEvent) { await update(selectedEvent) }
33
+ if (!selected) { await Promise.all(events.map(async (event) => { await update(event) })) }
34
+ if (selected) { await update(selected) }
35
35
  }
@@ -11,23 +11,23 @@
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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "types/Event"
21
- import { bootstrap } from "@bootstrap"
22
- import { GetEventNodes } from "@building/GetEventNodes"
23
- import { SetEventEmit } from "@utilities/SetEventEmit"
20
+ import { TypeEvent } from "TypesEvent/Event"
21
+ import { Bootstrap } from "@Bootstrap"
22
+ import { GetEventNodes } from "@BuilderComponents/GetEventNodes"
23
+ import { SetEventEmit } from "@Utilities/SetEventEmit"
24
24
 
25
- export const UpdateNode = async (selectedEvent?: TypeEvent): Promise<void> => {
26
- const events = bootstrap.cache.events.features;
27
- const ttl = bootstrap.settings.GlobalSettings.NodeTTL * 1e3;
25
+ export const UpdateNode = async (selected?: TypeEvent): Promise<void> => {
26
+ const events = Bootstrap.Cache.Events.features;
27
+ const ttl = Bootstrap.Settings.GlobalSettings.NodePolygonTTL * 1e3;
28
28
  let total = 0;
29
29
 
30
- const TTLEvents = selectedEvent ? [selectedEvent] : events.filter((evt) => {
30
+ const TTLEvents = selected ? [selected] : events.filter((evt) => {
31
31
  const lastUpdate = evt?.properties?.metadata?.updated ?? null;
32
32
  return lastUpdate == null || (Date.now() - lastUpdate) >= ttl;
33
33
  });
@@ -52,10 +52,10 @@ export const UpdateNode = async (selectedEvent?: TypeEvent): Promise<void> => {
52
52
 
53
53
  if (total > 0) {
54
54
  SetEventEmit({
55
- event: `onNodeUpdate`,
56
- metadata: {
57
- type: `global-update`,
58
- updated: total
55
+ Event: `onNodeUpdate`,
56
+ Metadata: {
57
+ Type: `global-update`,
58
+ Updated: total
59
59
  },
60
60
  })
61
61
  }
@@ -11,27 +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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "types/Event"
20
+ import { TypeEvent } from "TypesEvent/Event"
21
21
 
22
- export const GetEmebedText = (event: TypeEvent): string => {
22
+ export const GetEmbededText = (event: TypeEvent): string => {
23
23
  const line = (label: string, value: unknown, condition = true) => condition && value ? `${label} ${value}` : null;
24
24
  const isStatement = event.properties.status_metadata.is_statement;
25
25
  const isExpired = event.properties.status_metadata.is_expired;
26
+ const isIssued = event.properties.status_metadata.is_issued;
27
+ const isUpdated = event.properties.status_metadata.is_updated;
26
28
  return [
27
29
  line(`**Locations:**`, event?.properties?.locations?.slice(0, 100)),
28
- line(`**Issued:**`, `<t:${Math.floor(new Date(event.properties.issued).getTime() / 1000)}:R>`, !isExpired),
29
- line(`**Expires:**`, `<t:${Math.floor(new Date(event.properties.expires).getTime() / 1000)}:R>`, !isStatement),
30
+ line(`**Issued:**`, `<t:${Math.floor(new Date(event.properties.issued).getTime() / 1000)}:R>`, isIssued),
31
+ line(`**Updated:**`, `<t:${Math.floor(new Date(event.properties.issued).getTime() / 1000)}:R>`, isUpdated && !isExpired),
32
+ line(`**Expires:**`, `<t:${Math.floor(new Date(event.properties.expires).getTime() / 1000)}:R>`, !isStatement && !isExpired),
33
+ line(`**Expired:**`, `<t:${Math.floor(new Date(event.properties.expires).getTime() / 1000)}:R>`, isExpired),
30
34
  line(`**Damage Threat:**`, event?.properties?.parameters?.damage_threat, !isExpired),
31
35
  line(`**Flood Threat:**`, event?.properties?.parameters?.flood_threat, !isExpired),
32
36
  line(`**Tornado Threat:**`, event?.properties?.parameters?.tornado_threat, !isExpired),
33
37
  line(`**Wind Gusts:**`, `${event?.properties?.parameters?.estimated_wind_gusts} ${event?.properties?.parameters?.wind_threat ? ` (${event?.properties?.parameters?.wind_threat})` : ''}`, !isExpired && event?.properties?.parameters?.estimated_wind_gusts != null),
34
38
  line(`**Hail Size:**`, `${event?.properties?.parameters?.estimated_hail_size} ${event?.properties?.parameters?.hail_threat ? ` (${event?.properties?.parameters?.hail_threat})` : ''}`, !isExpired && event?.properties?.parameters?.estimated_hail_size != null),
39
+ line(`**Direction:**`, event?.properties?.parameters?.direction, !isExpired && event?.properties?.parameters?.direction != null),
35
40
  line(`**Discussion:**`, event?.properties?.discussion_parameters?.discussion_number, !isExpired),
36
41
  line(`**Concern:**`, event?.properties?.discussion_parameters?.discussion_concerning, !isExpired),
37
42
  line(`**SPC Max Tornado Threat:**`, event?.properties?.discussion_parameters?.discussion_max_tornado, !isExpired),
@@ -50,6 +55,7 @@ export const GetEmebedText = (event: TypeEvent): string => {
50
55
  line(`**Max Tops (x100 feet):**`, event?.properties?.watch_parameters?.max_tops_x100feet, !isExpired),
51
56
  line(`**Tags:**`, event?.properties?.parameters?.tags?.length > 0 ? event?.properties?.parameters?.tags.join(', ') : null, !isExpired),
52
57
  line(`**Sender:**`, event?.properties?.geocode?.office?.name ? `${event?.properties?.geocode?.office?.name} (${event?.properties?.geocode?.office?.office})` : event?.properties?.geocode?.office?.office),
58
+ line(`**Population:**`, event?.properties?.geocode?.census?.population, !isExpired),
53
59
  line(`**Tracking:**`, event?.properties?.metadata?.tracking),
54
60
  line(`**Logs:**`, event?.properties?.metadata?.history?.length > 0 ? event?.properties?.metadata?.history.length : null),
55
61
  ].filter(Boolean).join('\n');
@@ -11,31 +11,34 @@
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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
- import { TypeEvent } from "types/Event"
21
- import { TypeSettings } from "types/Settings"
22
- import { bootstrap } from "@bootstrap"
20
+ import { TypeEvent } from "TypesEvent/Event"
21
+ import { Bootstrap } from "@Bootstrap"
23
22
 
24
23
  export const GetStringText = (event: TypeEvent): string => {
25
- const settings = bootstrap.settings as TypeSettings;
26
- const timezone = settings.NotifyServer.Timezone ?? `UTC`;
24
+ const timezone = Bootstrap.Settings.Timezone ?? `UTC`;
27
25
  const line = (label: string, value: unknown, condition = true) => condition && value ? `${label} ${value}` : null;
28
26
  const isStatement = event.properties.status_metadata.is_statement;
29
27
  const isExpired = event.properties.status_metadata.is_expired;
28
+ const isIssued = event.properties.status_metadata.is_issued;
29
+ const isUpdated = event.properties.status_metadata.is_updated;
30
30
  return [
31
31
  line(`Locations:`, event?.properties?.locations?.slice(0, 100)),
32
- line(`Issued:`, `${new Date(event.properties.issued).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, !isExpired),
33
- line(`Expires:`, `${new Date(event.properties.expires).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, !isStatement),
32
+ line(`Issued:`, `${new Date(event.properties.issued).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, isIssued),
33
+ line(`Updated:`, `${new Date(event.properties.issued).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, isUpdated && !isExpired),
34
+ line(`Expires:`, `${new Date(event.properties.expires).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, !isStatement && !isExpired),
35
+ line(`Expired:`, `${new Date(event.properties.expires).toLocaleString([], { timeZone: timezone })} (${(timezone.replace(`America/`, ``))})`, isExpired),
34
36
  line(`Damage Threat:`, event?.properties?.parameters?.damage_threat, !isExpired),
35
37
  line(`Flood Threat:`, event?.properties?.parameters?.flood_threat, !isExpired),
36
38
  line(`Tornado Threat:`, event?.properties?.parameters?.tornado_threat, !isExpired),
37
39
  line(`Wind Gusts:`, `${event?.properties?.parameters?.estimated_wind_gusts} ${event?.properties?.parameters?.wind_threat ? ` (${event?.properties?.parameters?.wind_threat})` : ''}`, !isExpired && event?.properties?.parameters?.estimated_wind_gusts != null),
38
40
  line(`Hail Size:`, `${event?.properties?.parameters?.estimated_hail_size} ${event?.properties?.parameters?.hail_threat ? ` (${event?.properties?.parameters?.hail_threat})` : ''}`, !isExpired && event?.properties?.parameters?.estimated_hail_size != null),
41
+ line(`Direction:`, event?.properties?.parameters?.direction, !isExpired && event?.properties?.parameters?.direction != null),
39
42
  line(`Discussion:`, event?.properties?.discussion_parameters?.discussion_number, !isExpired),
40
43
  line(`Concern:`, event?.properties?.discussion_parameters?.discussion_concerning, !isExpired),
41
44
  line(`SPC Max Tornado Threat:`, event?.properties?.discussion_parameters?.discussion_max_tornado, !isExpired),
@@ -53,6 +56,7 @@ export const GetStringText = (event: TypeEvent): string => {
53
56
  line(`Max Surface Wind:`, event?.properties?.watch_parameters?.max_wind_surface, !isExpired),
54
57
  line(`Max Tops (x100 feet):`, event?.properties?.watch_parameters?.max_tops_x100feet, !isExpired),
55
58
  line(`Sender:`, event?.properties?.geocode?.office?.name ? `${event?.properties?.geocode?.office?.name} (${event?.properties?.geocode?.office?.office})` : event?.properties?.geocode?.office?.office),
59
+ line(`Population:`, event?.properties?.geocode?.census?.population, !isExpired),
56
60
  line(`Tracking:`, event?.properties?.metadata?.tracking),
57
61
  ].filter(Boolean).join('\n');
58
62
  }
@@ -11,23 +11,28 @@
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: http://localhost/documentation | https://atmosphericx.scriptkitty.cafe/documentation
14
+ Documentation: https://atmosphericx.scriptkitty.cafe/documentation
15
15
 
16
- Internal Package: @atmosx/event-product-parser
16
+ Independent Package: @atmosx/event-product-parser
17
17
 
18
18
  */
19
19
 
20
+ interface GetShapeNearestPointOptions {
21
+ Coordinates: number[][]
22
+ Point: [number, number]
23
+ }
24
+
20
25
  interface GetShapeNearestPointResponse {
21
- proximity: boolean
22
- point: [number, number]
23
- distance: number | null
24
- distanceKm?: number | null
25
- distanceMeters?: number | null
26
+ Proximity: boolean
27
+ Point: [number, number]
28
+ Distance: number | null
29
+ DistanceKm?: number | null
30
+ DistanceMeters?: number | null
26
31
  }
27
32
 
28
- export const GetShapeNearestPoint = (coordinates: number[][], point: [number, number]): GetShapeNearestPointResponse => {
29
- if (!coordinates || !point) {
30
- return { proximity: false, point: [0, 0], distance: null }
33
+ export const GetShapeNearestPoint = ({ Coordinates, Point }: GetShapeNearestPointOptions): GetShapeNearestPointResponse => {
34
+ if (!Coordinates || !Point) {
35
+ return { Proximity: false, Point: [0, 0], Distance: null }
31
36
  }
32
37
  const normalize = (coords: any): any[] => {
33
38
  if (!Array.isArray(coords)) return [];
@@ -43,10 +48,10 @@ export const GetShapeNearestPoint = (coordinates: number[][], point: [number, nu
43
48
  }
44
49
  return [];
45
50
  }
46
- const polys = normalize(coordinates);
47
- if (polys.length === 0) return { proximity: false, point: [0, 0], distance: null };
48
- const lon = point[0];
49
- const lat = point[1];
51
+ const polys = normalize(Coordinates);
52
+ if (polys.length === 0) return { Proximity: false, Point: [0, 0], Distance: null };
53
+ const lon = Point[0];
54
+ const lat = Point[1];
50
55
  const pointInRing = (pt: [number, number], ring: number[][]): boolean => {
51
56
  let x = pt[0], y = pt[1];
52
57
  let inside = false;
@@ -76,13 +81,13 @@ export const GetShapeNearestPoint = (coordinates: number[][], point: [number, nu
76
81
  for (const poly of polys) {
77
82
  const outer = poly[0];
78
83
  const holes = poly.slice(1);
79
- if (pointInRing(point, outer)) {
84
+ if (pointInRing(Point, outer)) {
80
85
  let inHole = false;
81
86
  for (const hole of holes) {
82
- if (pointInRing(point, hole)) { inHole = true; break; }
87
+ if (pointInRing(Point, hole)) { inHole = true; break; }
83
88
  }
84
89
  if (!inHole) {
85
- return { proximity: true, point, distance: 0 };
90
+ return { Proximity: true, Point, Distance: 0 };
86
91
  }
87
92
  }
88
93
  for (const ring of poly) {
@@ -105,10 +110,10 @@ export const GetShapeNearestPoint = (coordinates: number[][], point: [number, nu
105
110
  }
106
111
  }
107
112
  if (!isFinite(minDistance) || closestPoint == null) {
108
- return { proximity: false, point: [0,0], distance: null };
113
+ return { Proximity: false, Point: [0,0], Distance: null };
109
114
  }
110
115
  const distanceMiles = minDistance;
111
116
  const distanceKm = Number((distanceMiles * 1.609344).toFixed(3));
112
117
  const distanceMeters = Math.round(distanceKm * 1000);
113
- return { proximity: distanceMiles === 0, point: closestPoint, distance: distanceMiles, distanceKm, distanceMeters };
118
+ return { Proximity: distanceMiles === 0, Point: closestPoint, Distance: distanceMiles, DistanceKm: distanceKm, DistanceMeters: distanceMeters };
114
119
  }
@@ -0,0 +1,36 @@
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 { union } from "polygon-clipping";
21
+
22
+ interface GetUnionPolygonOptions {
23
+ Polygons: number[][][][] | null;
24
+ }
25
+
26
+ export const GetUnionPolygon = ({ Polygons }: GetUnionPolygonOptions): GeoJSON.MultiPolygon | null => {
27
+ if (!Polygons || Polygons.length === 0) {
28
+ return null;
29
+ }
30
+ const unionFn = union as unknown as (...polygons: number[][][][]) => number[][][][];
31
+ const unioned = unionFn(...Polygons)
32
+ return {
33
+ type: `MultiPolygon`,
34
+ coordinates: unioned
35
+ }
36
+ };
@@ -0,0 +1,242 @@
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 "TypesEvent/Event"
22
+ import { Bootstrap } from "@Bootstrap"
23
+ import { GetStringText } from "@Formatting/GetStringText"
24
+ import { GetEventGeometry } from "@BuilderComponents/GetEventGeometry"
25
+ import { GetGeographicalEvents } from "@ImageModules/GetGeographicalEvents"
26
+ import { GetUnionPolygon } from "@Geometry/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 { geoBounds, geoMercator, geoPath } from "d3-geo"
34
+ import { mkdir, readFile } from "fs/promises"
35
+ import { join } from "path";
36
+ import sharp from "sharp"
37
+
38
+ interface GenerateGraphicOptions {
39
+ Regions?: string[]
40
+ IgnoredRegions?: string[]
41
+ Event?: TypeEvent
42
+ File?: {
43
+ Directory?: string
44
+ Name?: string
45
+ }
46
+ MaxMiles?: number
47
+ Width?: number
48
+ Height?: number
49
+ }
50
+
51
+ export const GenerateGraphic = async ({ File, Regions, Event, MaxMiles = 350, Width = 1200, Height = 675 }: GenerateGraphicOptions): Promise<string> => {
52
+ let polygons: GeoJSON.Polygon | GeoJSON.MultiPolygon | null
53
+ let icon: string | null;
54
+ let renders: any = {
55
+ cities: null,
56
+ states: null,
57
+ counties: null,
58
+ };
59
+ let pathing: any = {
60
+ cities: null,
61
+ events: null,
62
+ counties: null,
63
+ states: null,
64
+ }
65
+
66
+ const A = [EnumStates, EnumZones].reduce((acc, obj) => ({ ...acc, ...obj }), {});
67
+ const B = [`HI`, `AK`];
68
+ const { coordinates } = Event?.geometry ?? {};
69
+ const { properties } = Event ?? {};
70
+ const { regions, theme, event } = properties ?? {};
71
+ const inConus = Event ? regions?.every(state => A[state] && !B.includes(state)) : true;
72
+ const R = (Regions ?? null)?.length
73
+ ? [...new Set(Regions ?? null)]
74
+ : null;
75
+ const E = GetGeographicalEvents({ Regions: R, Event: Event });
76
+ if (inConus || !Event) {
77
+ const boundaries = GetGeographicalBoundaries({ Regions: R });
78
+ const cities = GetGeographicalCities({ Regions: R });
79
+ renders.cities = cities.cities.filter(city => city.population >= 5e3).sort((a, b) => b.population - a.population).filter((city, index, cities) => {
80
+ return !cities.slice(0, index).some(other => {
81
+ const dist = Math.sqrt(
82
+ Math.pow(city.lat - other.lat, 2) +
83
+ Math.pow(city.lon - other.lon, 2)
84
+ );
85
+ return dist < (Event || Regions ? 0.4 : 3);
86
+ });
87
+ });
88
+ renders.states = GetParsedBoundary(boundaries.states);
89
+ renders.counties = GetParsedBoundary(boundaries.counties);
90
+ }
91
+
92
+ if (Event) {
93
+ polygons = (coordinates?.length > 0) ? Event?.geometry : await GetEventGeometry({ Event });
94
+ if (polygons.coordinates.length == 0) { return null; }
95
+ if (inConus) {
96
+ const eBounds = GetGeometryBounds({ Geometry: polygons, Padding: MaxMiles });
97
+ renders.counties = renders?.counties?.filter((county: GeoJSON.Feature<GeoJSON.Polygon>) => {
98
+ const cBounds = GetGeometryBounds({ Geometry: county.geometry, Padding: MaxMiles });
99
+ 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 );
100
+ });
101
+ renders.states = renders?.states?.filter((state: GeoJSON.Feature<GeoJSON.Polygon>) => {
102
+ const sBounds = GetGeometryBounds({ Geometry: state.geometry, Padding: MaxMiles });
103
+ 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 );
104
+ });
105
+ renders.cities = renders?.cities?.filter((city: { lat: number; lon: number }) => {
106
+ return ( city.lat >= eBounds.Search.minLat && city.lat <= eBounds.Search.maxLat && city.lon >= eBounds.Search.minLon && city.lon <= eBounds.Search.maxLon );
107
+ });
108
+ }
109
+ }
110
+
111
+ const iMode = geoMercator();
112
+ const iPath = geoPath().projection(iMode);
113
+ const jFeatures: GeoJSON.Feature[] = [...renders?.states ?? [], ...renders?.counties ?? []];
114
+ const jCollection: GeoJSON.FeatureCollection = { type: `FeatureCollection`, features: jFeatures };
115
+ const events = (await Promise.all(
116
+ E.map(async (event: TypeEvent) => {
117
+ const zones = event.properties?.geocode?.ugc ?? [];
118
+ if (zones?.length === 0) return null;
119
+ return {event, polygon: await GetUnionPolygon({
120
+ Polygons: polygons ? [polygons.coordinates] : [await GetEventGeometry({ Event: event }).coordinates],
121
+ })};
122
+ })
123
+ )).filter(Boolean);
124
+
125
+ if (polygons) {
126
+ let geometry = polygons;
127
+ const geo = {
128
+ type: "FeatureCollection",
129
+ features: [{ type: "Feature", geometry, properties: {} }]
130
+ };
131
+ const bounds = geoBounds(geo as any);
132
+ if (bounds[0][0] === -180 && bounds[0][1] === -90 && bounds[1][0] === 180 && bounds[1][1] === 90) {
133
+ if (geometry.type === "Polygon") {
134
+ geometry = { ...geometry, coordinates: geometry.coordinates.map((ring) => [...ring].reverse())};
135
+ } else if (geometry.type === "MultiPolygon") {
136
+ geometry = {...geometry, coordinates: geometry.coordinates.map((polygon) => polygon.map((ring) => [...ring].reverse()))};
137
+ }
138
+ geo.features[0].geometry = geometry;
139
+ }
140
+ iMode.fitExtent([[150, 150], [Width - 150, Height - 150]], geo as any);
141
+ } else {
142
+ iMode.fitExtent( [[60, 60], [Width - 60, Height - 60]], jCollection);
143
+ }
144
+
145
+ pathing.events = events?.map(({ event, polygon: polys }) => {
146
+ const color = event?.properties?.theme ?? theme ?? `rgb(56, 72, 88)`;
147
+ return GetSVGPath({
148
+ Polygons: polys,
149
+ IPath: iPath,
150
+ Map: false,
151
+ Settings: { BorderColor: `${color}`, BorderWidth: 2, FillColor: `${color}`, FillOpacity: 0.1 }
152
+ });
153
+ }).filter(Boolean).join(``);
154
+
155
+ pathing.counties = renders?.counties?.map((polygon: GeoJSON.Feature) => GetSVGPath({
156
+ Polygons: polygon.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon,
157
+ IPath: iPath,
158
+ Map: true,
159
+ Settings: { BorderColor: `darkgray`, BorderWidth: 0.5, FillColor: `black`, FillOpacity: 1 }
160
+ })).filter(Boolean).join(``);
161
+
162
+ pathing.states = renders?.states?.map((polygon: GeoJSON.Feature) => GetSVGPath({
163
+ Polygons: polygon.geometry as GeoJSON.Polygon | GeoJSON.MultiPolygon,
164
+ IPath: iPath,
165
+ Map: false,
166
+ Settings: { BorderColor: `white`, BorderWidth: 1, FillColor: `#ffffff`, FillOpacity: 0 }
167
+ })).filter(Boolean).join(``);
168
+
169
+ pathing.cities = renders?.cities?.map((city: { lat: number; lon: number; name: string; population: number }) => {
170
+ const point = iMode([city.lon, city.lat]);
171
+ const [x, y] = point;
172
+ const radius = 2 * Math.max(0.75, Math.min(1.5, Width / 1000));
173
+ return `
174
+ <circle cx="${x}" cy="${y}" r="${radius}" fill="#ffffff" />
175
+ <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>
176
+ `;
177
+ }).join(``);
178
+
179
+ const title = Event
180
+ ? (event ?? `Event`)
181
+ : (R?.length > 0 ? `Region: ${R.map(state => EnumStates[state] ?? state).join(`, `)}` : `Contiguous United States`);
182
+ const subtitleLines = Event
183
+ ? GetStringText(Event).split(`\n`).filter(line => line.trim().length > 0).slice(0, 10)
184
+ : [`Last Updated: ${new Date().toLocaleString()}`];
185
+ const scale = Math.max(0.75, Math.min(1.0, Width / 1000));
186
+ const titleSize = Math.round(17 * scale);
187
+ const lineSize = Math.round(13 * scale);
188
+ const lineHeight = Math.round(15 * scale);
189
+ const paddingX = Math.round(16 * scale);
190
+ const paddingTop = Math.round(16 * scale);
191
+ const paddingBottom = Math.round(16 * scale);
192
+ const accentWidth = Math.max(4, Math.round(5 * scale));
193
+ const iconSize = Math.round(Math.min(122, Math.max(48, Width * 0.07)) * scale);
194
+ const hasIcon = Boolean(Bootstrap?.Settings?.GlobalSettings?.ArchiveSettings?.Logo);
195
+
196
+ if (hasIcon) {
197
+ const buffer = await readFile(Bootstrap.Settings.GlobalSettings.ArchiveSettings.Logo);
198
+ icon = `data:image/png;base64,${Buffer.from(buffer).toString(`base64`)}`;
199
+ }
200
+
201
+ const boxWidth = Math.min(Math.round(Width * 0.4), Width - Math.round(28 * scale));
202
+ const textStartX = paddingX + accentWidth + Math.round(6 * scale);
203
+ const textEndPadding = hasIcon ? iconSize + Math.round(20 * scale) : Math.round(16 * scale);
204
+ const availableTextWidth = boxWidth - textStartX - textEndPadding;
205
+ const maxTitleChars = Math.max(20, Math.floor(availableTextWidth / (titleSize * 0.52)));
206
+ const maxLineChars = Math.max(24, Math.floor(availableTextWidth / (lineSize * 0.52)));
207
+
208
+ const titleY = paddingTop + titleSize;
209
+ const firstLineY = titleY + Math.round(22 * scale);
210
+ const contentHeight = firstLineY + (subtitleLines.length * lineHeight) + paddingBottom;
211
+ const boxHeight = Math.max(contentHeight, hasIcon ? paddingTop + iconSize + paddingBottom : contentHeight);
212
+
213
+ const iconX = boxWidth - iconSize - Math.round(5 * scale);
214
+ const iconY = paddingTop;
215
+
216
+ const SVG = CreateSVG({
217
+ Width, Height,
218
+ MapFeatures: !inConus ? [pathing.events] : [pathing.counties, pathing.states, pathing.events, pathing.cities],
219
+ Features: [
220
+ `<rect x="${Math.round(9 * scale)}" y="${Math.round(10 * scale)}" width="${boxWidth}" height="${boxHeight}" fill="rgba(0, 0, 0, 0.57)" />`,
221
+ `<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)}" />`,
222
+ `<rect x="${Math.round(6 * scale)}" y="${Math.round(7 * scale)}" width="${accentWidth}" height="${boxHeight}" fill="${theme ?? `rgb(56, 72, 88)`}" />`,
223
+ hasIcon && icon
224
+ ? `<image href="${icon}" x="${iconX}" y="${iconY}" width="${iconSize}" height="${iconSize}" preserveAspectRatio="xMidYMid meet" opacity="0.95" />`
225
+ : ``,
226
+ `<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>`,
227
+ ...subtitleLines.map((line, i) =>
228
+ `<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.replace(/</g, "&lt;").replace(/>/g, "&gt;")}</text>`
229
+ ),
230
+ `<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>`
231
+ ]
232
+ });
233
+
234
+ const { Directory, Name } = File ?? {};
235
+ const dir = Directory ?? Bootstrap.Settings?.GlobalSettings?.ArchiveSettings?.ImageDirectory;
236
+ const name = Name ?? event ?? `img`;
237
+ await mkdir(dir, { recursive: true });
238
+ await sharp(Buffer.from(SVG))
239
+ .png()
240
+ .toFile(join(dir, `${name}${name.includes(`.png`) ? `` : `.png`}`));
241
+ return dir + `/${name}${name.includes(`.png`) ? `` : `.png`}`;
242
+ }
@@ -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 => {}