@atmosx/event-product-parser 3.0.4 → 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.
Files changed (212) hide show
  1. package/{CONTRIBUTING.md → .github/CONTRIBUTING.md} +1 -1
  2. package/.github/SUPPORTED_EVENTS.md +370 -0
  3. package/.github/docs/docs-functions.md +30 -0
  4. package/.github/docs/docs-general-configurations.md +199 -0
  5. package/.github/docs/docs-integrations.md +38 -0
  6. package/.github/docs/docs-listeners.md +28 -0
  7. package/.github/docs/docs-source-configuration.md +218 -0
  8. package/.github/docs/docs-technical.md +50 -0
  9. package/.github/logo.png +0 -0
  10. package/.github/samples/SAMPLE_EAS_GENERATION.wav +0 -0
  11. package/.github/samples/SAMPLE_GEOJSON.json +204 -0
  12. package/.github/samples/SAMPLE_TEXT_PRODUCT.txt +119 -0
  13. package/README.md +157 -337
  14. package/dist/cjs/index.cjs +24125 -15815
  15. package/dist/esm/index.mjs +24144 -15839
  16. package/package.json +56 -53
  17. package/pnpm-workspace.yaml +4 -0
  18. package/src/bootstrap.ts +83 -43
  19. package/src/building/CreateEvent.ts +54 -0
  20. package/src/{@building/building.clean.ts → building/GetCleanedEvent.ts} +4 -4
  21. package/src/building/GetEventAttachments.ts +88 -0
  22. package/src/building/GetEventDirection.ts +28 -0
  23. package/src/{@building/building.enhance.ts → building/GetEventEnhancedName.ts} +13 -17
  24. package/src/{@building/building.geometry.ts → building/GetEventGeometry.ts} +19 -13
  25. package/src/{@building/building.headers.ts → building/GetEventHeader.ts} +13 -13
  26. package/src/{@building/building.polygon.ts → building/GetEventNodes.ts} +26 -22
  27. package/src/{@building/building.office.ts → building/GetEventOffice.ts} +14 -14
  28. package/src/building/GetEventProperties.ts +105 -0
  29. package/src/{@building/building.signature.ts → building/GetEventSignature.ts} +16 -17
  30. package/src/{@core/core.createListener.ts → building/GetEventTags.ts} +11 -6
  31. package/src/building/GetEventTheme.ts +26 -0
  32. package/src/building/GetEventTracking.ts +64 -0
  33. package/src/components/audio/GenerateAudioMessage.ts +149 -0
  34. package/src/components/audio/components/GetCleanDescription.ts +91 -0
  35. package/src/{@modules/@eas/eas.getFloatPCM16.ts → components/audio/components/GetFloatPCM16.ts} +3 -3
  36. package/src/{@modules/@eas/eas.getMergedPCM16.ts → components/audio/components/GetMergedPCM16.ts} +3 -3
  37. package/src/{@modules/@eas/eas.getPCM16.ts → components/audio/components/GetPCM16.ts} +13 -8
  38. package/src/{@modules/@eas/eas.getPCMToFloat.ts → components/audio/components/GetPCMToFloat.ts} +3 -3
  39. package/src/{@modules/@eas/eas.getSampledPCM16.ts → components/audio/components/GetSampledPCM16.ts} +14 -8
  40. package/src/components/audio/components/GetTTS.ts +61 -0
  41. package/src/{@modules/@eas/eas.getWavPCM16.ts → components/audio/components/GetWavPCM16.ts} +8 -8
  42. package/src/{@modules/@eas/eas.setAFSK.ts → components/audio/components/SetAFSK.ts} +12 -8
  43. package/src/{@modules/@eas/eas.setAsciiToBits.ts → components/audio/components/SetAsciiToBits.ts} +3 -4
  44. package/src/{@modules/@eas/eas.setAttentionTone.ts → components/audio/components/SetAttentionTone.ts} +11 -6
  45. package/src/{@modules/@eas/eas.setNoise.ts → components/audio/components/SetNoise.ts} +14 -8
  46. package/src/{@modules/@eas/eas.setRadioEffect.ts → components/audio/components/SetRadioEffect.ts} +14 -8
  47. package/src/{@modules/@eas/eas.setSameHeader.ts → components/audio/components/SetSameHeader.ts} +22 -15
  48. package/src/{@modules/@utilities/utilities.setWarning.ts → components/database/CreateQuery.ts} +18 -13
  49. package/src/{@modules/@database/database.cache.ts → components/database/GetCachedEvents.ts} +19 -14
  50. package/src/components/database/ImportBoundaries.ts +168 -0
  51. package/src/components/database/ImportBroadcastify.ts +60 -0
  52. package/src/components/database/ImportCities.ts +87 -0
  53. package/src/{@modules/@database/database.shapefiles.ts → components/database/ImportShapefiles.ts} +31 -37
  54. package/src/components/database/ImportStanza.ts +43 -0
  55. package/src/components/database/InitializeDatabase.ts +71 -0
  56. package/src/components/images/GenerateGraphic.ts +234 -0
  57. package/src/components/images/components/CreateRectangle.ts +22 -0
  58. package/src/components/images/components/CreateText.ts +22 -0
  59. package/src/components/images/modules/CreateSVG.ts +37 -0
  60. package/src/components/images/modules/GetGeographicalBoundaries.ts +72 -0
  61. package/src/components/images/modules/GetGeographicalCities.ts +58 -0
  62. package/src/components/images/modules/GetGeographicalEvents.ts +48 -0
  63. package/src/components/images/modules/GetGeometryBounds.ts +82 -0
  64. package/src/components/images/modules/GetParsedBoundary.ts +58 -0
  65. package/src/components/images/modules/GetSVGPath.ts +53 -0
  66. package/src/components/images/modules/NormalizePolygon.ts +57 -0
  67. package/src/{@modules/@stanza/stanza.getAwipsType.ts → components/stanza/GetAwipsType.ts} +19 -18
  68. package/src/components/stanza/ValidateStanza.ts +62 -0
  69. package/src/components/utilities/CreateHttp.ts +83 -0
  70. package/src/{@modules/@utilities/utilities.getFormattedTime.ts → components/utilities/GetFormattedTime.ts} +3 -3
  71. package/src/components/utilities/GetLatestIssuance.ts +32 -0
  72. package/src/components/utilities/GetMatched.ts +38 -0
  73. package/src/{@modules/@utilities/utilities.getShapeNearestPoint.ts → components/utilities/GetShapeNearestPoint.ts} +24 -19
  74. package/src/components/utilities/GetUnionPolygon.ts +36 -0
  75. package/src/components/utilities/QueueManager.ts +70 -0
  76. package/src/components/utilities/SetCronSchedule.ts +98 -0
  77. package/src/{@core/core.clearEvents.ts → components/utilities/SetDebug.ts} +17 -12
  78. package/src/components/utilities/SetEventEmit.ts +42 -0
  79. package/src/{@modules/@utilities/utilities.setSettings.ts → components/utilities/SetSettings.ts} +7 -7
  80. package/src/{@modules/@utilities/utilities.setSleep.ts → components/utilities/SetSleep.ts} +5 -5
  81. package/src/components/utilities/SetTimeoutAction.ts +64 -0
  82. package/src/components/utilities/SetWarning.ts +47 -0
  83. package/src/{@modules/@xmpp/xmpp.xDeploy.ts → components/xmpp/DeployXMPP.ts} +26 -27
  84. package/src/components/xmpp/ErrorXMPP.ts +27 -0
  85. package/src/{@modules/@xmpp/xmpp.xError.ts → components/xmpp/OfflineXMPP.ts} +17 -9
  86. package/src/{@modules/@xmpp/xmpp.xOnline.ts → components/xmpp/OnlineXMPP.ts} +21 -20
  87. package/src/components/xmpp/ReconnectXMPP.ts +64 -0
  88. package/src/components/xmpp/StanzaXMPP.ts +63 -0
  89. package/src/{@building/building.tags.ts → core/ClearEvents.ts} +12 -5
  90. package/src/{@core/core.getVersion.ts → core/GetEvents.ts} +5 -6
  91. package/src/core/GetNodes.ts +24 -0
  92. package/src/{@core/core.getNodes.ts → core/GetRandomEvent.ts} +5 -6
  93. package/src/core/GetVersion.ts +24 -0
  94. package/src/core/ManualEvent.ts +55 -0
  95. package/src/core/QueryStanza.ts +40 -0
  96. package/src/core/SetNode.ts +87 -0
  97. package/src/core/StartService.ts +62 -0
  98. package/src/core/StopService.ts +32 -0
  99. package/src/enums/AWIPS.ts +366 -0
  100. package/src/{@dictionaries/dictionaries.eventActions.ts → enums/Actions.ts} +3 -3
  101. package/src/{@dictionaries/dictionaries.eventCancelMessages.ts → enums/Cancellation.ts} +4 -3
  102. package/src/{@dictionaries/dictionaries.eventCauses.ts → enums/Causes.ts} +3 -3
  103. package/src/{@dictionaries/dictionaries.statusCorrelationText.ts → enums/Correlations.ts} +3 -3
  104. package/src/enums/Enhanced.ts +70 -0
  105. package/src/{@dictionaries/dictionaries.eventTypes.ts → enums/Events.ts} +3 -3
  106. package/src/{@dictionaries/dictionaries.regExp.ts → enums/Expressions.ts} +5 -4
  107. package/src/{@core/core.getEvents.ts → enums/GlobalFilter.ts} +7 -8
  108. package/src/{@dictionaries/dictionaries.hailStrings.ts → enums/Hail.ts} +9 -4
  109. package/src/{@dictionaries/dictionaries.officeICAOs.ts → enums/ICAO.ts} +5 -3
  110. package/src/enums/Matches.ts +60 -0
  111. package/src/{@dictionaries/dictionaries.eventProducts.ts → enums/Products.ts} +3 -3
  112. package/src/{@dictionaries/dictionaries.eventRecords.ts → enums/Records.ts} +3 -3
  113. package/src/{@dictionaries/dictionaries.eventSeverity.ts → enums/Severity.ts} +3 -3
  114. package/src/{@dictionaries/dictionaries.shapefileLinks.ts → enums/Shapefiles.ts} +3 -3
  115. package/src/enums/States.ts +136 -0
  116. package/src/{@dictionaries/dictionaries.eventStatus.ts → enums/Status.ts} +3 -3
  117. package/src/enums/Strings.ts +22 -0
  118. package/src/enums/Tags.ts +172 -0
  119. package/src/{@dictionaries/dictionaries.testSignatures.ts → enums/Testing.ts} +5 -4
  120. package/src/enums/Themes.ts +70 -0
  121. package/src/events/ParseAPI.ts +129 -0
  122. package/src/{@events/events.text.ts → events/ParseText.ts} +32 -23
  123. package/src/{@events/events.ugc.ts → events/ParseUGC.ts} +36 -26
  124. package/src/events/ParseVTEC.ts +88 -0
  125. package/src/index.ts +60 -38
  126. package/src/manager/MakeEvents.ts +110 -0
  127. package/src/manager/RemoveEvent.ts +74 -0
  128. package/src/{@manager/manager.setHash.ts → manager/SetHash.ts} +19 -15
  129. package/src/manager/UpdateEvents.ts +35 -0
  130. package/src/manager/UpdateNode.ts +62 -0
  131. package/src/manager/ValidateEvents.ts +144 -0
  132. package/src/{@parsers/@hvtec/hvtec.extract.ts → parsers/hvtec/HVExtract.ts} +13 -13
  133. package/src/parsers/text/GetDescriptionFromProduct.ts +59 -0
  134. package/src/parsers/text/GetEmbededText.ts +57 -0
  135. package/src/parsers/text/GetPolygonFromProduct.ts +71 -0
  136. package/src/parsers/text/GetStringText.ts +58 -0
  137. package/src/{@parsers/@text/text.getTextFromProduct.ts → parsers/text/GetTextFromProduct.ts} +12 -12
  138. package/src/parsers/ugc/GetCache.ts +24 -0
  139. package/src/{@parsers/@ugc/ugc.expiry.ts → parsers/ugc/GetExpiry.ts} +3 -3
  140. package/src/{@parsers/@ugc/ugc.header.ts → parsers/ugc/GetHeader.ts} +6 -6
  141. package/src/parsers/ugc/GetLocations.ts +57 -0
  142. package/src/parsers/ugc/GetZonePolygon.ts +73 -0
  143. package/src/{@parsers/@ugc/ugc.zones.ts → parsers/ugc/GetZones.ts} +3 -3
  144. package/src/{@modules/@utilities/utilities.setListener.ts → parsers/ugc/SetCache.ts} +13 -10
  145. package/src/{@parsers/@ugc/ugc.extract.ts → parsers/ugc/UGCExtract.ts} +15 -15
  146. package/src/{@parsers/@pvtec/pvtec.expires.ts → parsers/vtec/GetExpiry.ts} +3 -3
  147. package/src/parsers/vtec/VTECExtract.ts +49 -0
  148. package/src/{@types/types.attributes.ts → static/Attributes.ts} +2 -3
  149. package/src/{@types/type.event.ts → static/Event.ts} +11 -8
  150. package/src/{@types/type.properties.ts → static/Properties.ts} +17 -10
  151. package/src/{@types/types.stanza.ts → static/Stanza.ts} +2 -2
  152. package/src/tasks/CreateTasks.ts +123 -0
  153. package/src/{@core/core.stop.ts → tasks/TaskGenerateJSON.ts} +14 -12
  154. package/src/tasks/TaskGenerateText.ts +34 -0
  155. package/src/tasks/TaskSendNTFY.ts +97 -0
  156. package/src/tasks/TaskSendWebhook.ts +107 -0
  157. package/src/types/Actions.ts +37 -0
  158. package/src/{@types/types.hvtec.ts → types/HVTEC.ts} +6 -6
  159. package/src/{@types/types.hash.ts → types/Hash.ts} +5 -5
  160. package/src/types/Nodes.ts +29 -0
  161. package/src/{@types/types.settings.ts → types/Settings.ts} +42 -10
  162. package/src/{@types/types.compiled.ts → types/StanzaCompiled.ts} +15 -14
  163. package/src/{@types/types.ugc.ts → types/UGC.ts} +5 -5
  164. package/src/{@modules/@utilities/utilities.getSettings.ts → types/VTEC.ts} +12 -7
  165. package/tools/build.js +47 -0
  166. package/tsconfig.json +28 -1
  167. package/dist/index.d.mts +0 -239
  168. package/dist/index.d.ts +0 -239
  169. package/src/@building/building.create.ts +0 -42
  170. package/src/@building/building.properties.ts +0 -89
  171. package/src/@building/building.tracking.ts +0 -68
  172. package/src/@building/building.validate.ts +0 -147
  173. package/src/@core/core.getRandomEvent.ts +0 -25
  174. package/src/@core/core.setNode.ts +0 -81
  175. package/src/@core/core.start.ts +0 -61
  176. package/src/@dictionaries/dictionaries.betterEventNames.ts +0 -99
  177. package/src/@dictionaries/dictionaries.eventAwipAbreviations.ts +0 -364
  178. package/src/@dictionaries/dictionaries.eventTags.ts +0 -93
  179. package/src/@dictionaries/dictionaries.eventsMatchText.ts +0 -36
  180. package/src/@dictionaries/dictionaries.transcribedMessageReplacements.ts +0 -68
  181. package/src/@events/events.api.ts +0 -113
  182. package/src/@events/events.vtec.ts +0 -87
  183. package/src/@manager/manager.mkEvent.ts +0 -96
  184. package/src/@manager/manager.rmEvent.ts +0 -49
  185. package/src/@manager/manager.updateEvents.ts +0 -35
  186. package/src/@manager/manager.updateNodes.ts +0 -55
  187. package/src/@manager/manager.updateWebhooks.ts +0 -50
  188. package/src/@modules/@database/database.init.ts +0 -45
  189. package/src/@modules/@database/database.stanza.ts +0 -48
  190. package/src/@modules/@eas/eas.setEasTone.ts +0 -137
  191. package/src/@modules/@stanza/stanza.validate.ts +0 -50
  192. package/src/@modules/@utilities/utilities.createHttp.ts +0 -85
  193. package/src/@modules/@utilities/utilities.createWebhook.ts +0 -115
  194. package/src/@modules/@utilities/utilities.setCronSchedule.ts +0 -65
  195. package/src/@modules/@utilities/utilities.setEventEmit.ts +0 -41
  196. package/src/@modules/@utilities/utilities.setTimeoutAction.ts +0 -59
  197. package/src/@modules/@xmpp/xmpp.xOffline.ts +0 -38
  198. package/src/@modules/@xmpp/xmpp.xReconnect.ts +0 -64
  199. package/src/@modules/@xmpp/xmpp.xStanza.ts +0 -63
  200. package/src/@parsers/@pvtec/pvtec.extract.ts +0 -49
  201. package/src/@parsers/@text/text.getDescriptionFromProduct.ts +0 -53
  202. package/src/@parsers/@text/text.getPolygonFromProduct.ts +0 -32
  203. package/src/@parsers/@text/text.getXML.ts +0 -61
  204. package/src/@parsers/@ugc/ugc.coordinates.ts +0 -110
  205. package/src/@parsers/@ugc/ugc.locations.ts +0 -29
  206. package/src/@types/types.pvtec.ts +0 -30
  207. package/src/@types/types.webhook.ts +0 -27
  208. package/test.js +0 -91
  209. package/tsup.config.ts +0 -16
  210. /package/{CODE_OF_CONDUCT.md → .github/CODE_OF_CONDUCT.md} +0 -0
  211. /package/{LICENSE → .github/LICENSE} +0 -0
  212. /package/{SECURITY.md → .github/SECURITY.md} +0 -0
@@ -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
+ }
@@ -0,0 +1,72 @@
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 { EnumStateFIPS } from "@Enums/States"
21
+ import { CreateQuery } from "@Database/CreateQuery"
22
+
23
+ interface GetGeographicalBoundariesOptions {
24
+ Regions: string[] | null
25
+ }
26
+
27
+ interface GeographicalBoundariesResponse {
28
+ states: any[]
29
+ counties: any[]
30
+ }
31
+
32
+ export const GetGeographicalBoundaries = ({ Regions }: GetGeographicalBoundariesOptions): GeographicalBoundariesResponse => {
33
+ const states = []
34
+ const counties = []
35
+ if (!Regions) {
36
+ const stateBoundaries = CreateQuery({
37
+ Query: `SELECT * FROM boundaries WHERE type = 'state' AND state NOT IN ('AK', 'HI')`,
38
+ })
39
+ const countyBoundaries = CreateQuery({
40
+ Query: `SELECT * FROM boundaries WHERE type = 'county' AND state NOT IN ('AK', 'HI')`,
41
+ })
42
+ states.push(...stateBoundaries)
43
+ counties.push(...countyBoundaries)
44
+ return { states, counties }
45
+ }
46
+ for (const region of Regions) {
47
+ const match = region.match(/^([A-Z]{2})[CZ](\d{3})$/i) ?? null
48
+ if (!match) {
49
+ const children = CreateQuery({
50
+ Query: `SELECT * FROM boundaries WHERE type = 'county' AND state = ?`,
51
+ Parameters: [region]
52
+ })
53
+ const parent = CreateQuery({
54
+ Query: `SELECT * FROM boundaries WHERE type = 'state' AND state = ?`,
55
+ Parameters: [region]
56
+ })
57
+ for (const child of children) {
58
+ counties.push(child)
59
+ }
60
+ states.push(...parent)
61
+ } else {
62
+ const [, state, county] = match
63
+ const fips = Object.entries(EnumStateFIPS).find(([, value]) => value === state)?.[0]
64
+ const child = CreateQuery({
65
+ Query: `SELECT * FROM boundaries WHERE type = 'county' AND id = ?`,
66
+ Parameters: [fips + county]
67
+ })
68
+ counties.push(...child);
69
+ }
70
+ }
71
+ return { states, counties }
72
+ }
@@ -0,0 +1,58 @@
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
+
22
+ interface GetGeographicalCitiesOptions {
23
+ Regions: string[] | null
24
+ }
25
+
26
+ interface GeographicalCitiesResponse {
27
+ cities: {
28
+ id: string;
29
+ name: string;
30
+ state: string;
31
+ county: string;
32
+ population: number;
33
+ lat: number;
34
+ lon: number
35
+ }[]
36
+ }
37
+
38
+ export const GetGeographicalCities = ({ Regions }: GetGeographicalCitiesOptions): GeographicalCitiesResponse => {
39
+ const cities = []
40
+ if (!Regions) {
41
+ const cityBoundaries = CreateQuery({
42
+ Query: `SELECT * FROM cities WHERE state NOT IN ('AK', 'HI')`,
43
+ })
44
+ cities.push(...cityBoundaries)
45
+ return { cities }
46
+ }
47
+ for (const region of Regions) {
48
+ const match = region.match(/^([A-Z]{2})[CZ](\d{3})$/i) ?? null
49
+ if (!match) {
50
+ const parent = CreateQuery({
51
+ Query: `SELECT * FROM cities WHERE state = ?`,
52
+ Parameters: [region]
53
+ })
54
+ cities.push(...parent)
55
+ }
56
+ }
57
+ return { cities }
58
+ }
@@ -0,0 +1,48 @@
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 { TypeEvent } from "StaticTypes/Event"
21
+ import { Bootstrap } from "@Bootstrap"
22
+
23
+ interface GetGeographicalEventsOptions {
24
+ Regions: string[] | null
25
+ Event?: TypeEvent
26
+ }
27
+
28
+ export const GetGeographicalEvents = ({ Regions, Event }: GetGeographicalEventsOptions): TypeEvent[] => {
29
+ const events = Bootstrap.Cache.Events.features
30
+ if (Event) { return [Event] }
31
+ if (!Regions) { return events }
32
+ return events.filter(event => {
33
+ const ugcs = event.properties?.geocode?.ugc ?? []
34
+ return Regions.some(region => {
35
+ const normalized = region.trim().toUpperCase()
36
+ const ugcMatch = normalized.match(/^([A-Z]{2})[CZ](\d{3})$/)
37
+ if (ugcMatch) {
38
+ return ugcs.some(ugc => ugc.trim().toUpperCase() === normalized)
39
+ }
40
+ const stateMatch = normalized.match(/^[A-Z]{2}$/)
41
+ if (stateMatch) {
42
+ return ugcs.some(ugc => ugc.trim().toUpperCase().startsWith(`${normalized}Z`) || ugc.trim().toUpperCase().startsWith(`${normalized}C`))
43
+ }
44
+ return false
45
+ })
46
+ })
47
+
48
+ }
@@ -0,0 +1,82 @@
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 GetGeometryBoundsOptions {
21
+ Geometry: GeoJSON.Geometry
22
+ Padding: number
23
+ }
24
+
25
+ interface GeometryBounds {
26
+ Bounds: {
27
+ minLon: number
28
+ minLat: number
29
+ maxLon: number
30
+ maxLat: number
31
+ }
32
+ Search: {
33
+ minLon: number
34
+ minLat: number
35
+ maxLon: number
36
+ maxLat: number
37
+ }
38
+ }
39
+
40
+ export const GetGeometryBounds = ({ Geometry, Padding }: GetGeometryBoundsOptions): GeometryBounds => {
41
+ let minLon = Infinity;
42
+ let minLat = Infinity;
43
+ let maxLon = -Infinity;
44
+ let maxLat = -Infinity;
45
+ const ProcessCoordinate = (coordinate: GeoJSON.Position): void => {
46
+ const [lon, lat] = coordinate;
47
+ if (!Number.isFinite(lon) || !Number.isFinite(lat)) {
48
+ return;
49
+ }
50
+ minLon = Math.min(minLon, lon);
51
+ minLat = Math.min(minLat, lat);
52
+ maxLon = Math.max(maxLon, lon);
53
+ maxLat = Math.max(maxLat, lat);
54
+ };
55
+ const ProcessRing = (ring: GeoJSON.Position[]): void => {
56
+ for (const coordinate of ring) {
57
+ ProcessCoordinate(coordinate);
58
+ }
59
+ };
60
+ if (Geometry.type === `Polygon`) {
61
+ for (const ring of Geometry.coordinates) {
62
+ ProcessRing(ring);
63
+ }
64
+ } else if (Geometry.type === `MultiPolygon`) {
65
+ for (const polygon of Geometry.coordinates) {
66
+ for (const ring of polygon) {
67
+ ProcessRing(ring);
68
+ }
69
+ }
70
+ }
71
+ const centerLat = (minLat + maxLat) / 2;
72
+ const latPadding = Padding / 69;
73
+ const lonPadding = Padding / (69 * Math.cos(centerLat * Math.PI / 180));
74
+ const searchMinLon = minLon - lonPadding;
75
+ const searchMaxLon = maxLon + lonPadding;
76
+ const searchMinLat = minLat - latPadding;
77
+ const searchMaxLat = maxLat + latPadding;
78
+ return {
79
+ Search: { minLon: searchMinLon, minLat: searchMinLat, maxLon: searchMaxLon, maxLat: searchMaxLat },
80
+ Bounds: { minLon, minLat, maxLon, maxLat }
81
+ };
82
+ };
@@ -0,0 +1,58 @@
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 BoundaryRow {
21
+ id: string
22
+ type: `state` | `county`
23
+ state: string
24
+ name: string
25
+ geometry: string
26
+ }
27
+
28
+ type BoundaryProperties = {
29
+ state: string
30
+ name: string
31
+ id?: string
32
+ }
33
+
34
+ type BoundaryFeature = GeoJSON.Feature<GeoJSON.Polygon | GeoJSON.MultiPolygon, BoundaryProperties>
35
+
36
+ export const GetParsedBoundary = (rows: BoundaryRow[]): BoundaryFeature[] => {
37
+ const features: BoundaryFeature[] = []
38
+ for (const row of rows) {
39
+ try {
40
+ const geometry = JSON.parse(row.geometry) as GeoJSON.Geometry
41
+ if ( !geometry || typeof geometry !== `object` || (geometry.type !== `Polygon` && geometry.type !== `MultiPolygon`) ) {
42
+ continue
43
+ }
44
+ features.push({
45
+ type: `Feature`,
46
+ properties: {
47
+ state: row.state,
48
+ name: row.name,
49
+ ...(row.type === `county` ? { id: row.id } : {})
50
+ },
51
+ geometry
52
+ })
53
+ } catch {
54
+ continue
55
+ }
56
+ }
57
+ return features
58
+ }