@sjvair/monitor-map 4.2.0 → 5.0.0-beta.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 (265) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +61 -126
  3. package/dist/lib/MonitorDetailPanel.svelte +23 -20
  4. package/dist/lib/MonitorMapLayout.svelte +147 -77
  5. package/dist/lib/abort.d.ts +16 -0
  6. package/dist/lib/abort.js +51 -0
  7. package/dist/lib/components/DataBox.svelte +4 -1
  8. package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  9. package/dist/lib/config.svelte.d.ts +7 -0
  10. package/dist/lib/config.svelte.js +10 -0
  11. package/dist/lib/core.d.ts +2 -2
  12. package/dist/lib/core.js +2 -1
  13. package/dist/lib/data-chart/DataChart.svelte +54 -24
  14. package/dist/lib/data-chart/DateRange.d.ts +13 -4
  15. package/dist/lib/data-chart/DateRange.js +50 -14
  16. package/dist/lib/data-chart/chart-utils.js +5 -7
  17. package/dist/lib/data-chart/level-colors.d.ts +3 -4
  18. package/dist/lib/data-chart/level-colors.js +7 -4
  19. package/dist/lib/data-chart/pacific-time.d.ts +9 -0
  20. package/dist/lib/data-chart/pacific-time.js +25 -0
  21. package/dist/lib/data-chart/requests.d.ts +2 -1
  22. package/dist/lib/data-chart/requests.js +5 -2
  23. package/dist/lib/data-chart/tooltip.js +2 -2
  24. package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
  25. package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
  26. package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
  27. package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
  28. package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
  29. package/dist/lib/ev-stations/ev-station-icons.js +1 -13
  30. package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
  31. package/dist/lib/ev-stations/ev-stations.js +53 -0
  32. package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
  33. package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
  34. package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
  35. package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
  36. package/dist/lib/ev-stations/types.d.ts +0 -29
  37. package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
  38. package/dist/lib/ev-stations/zip-codes.js +28 -16
  39. package/dist/lib/index.d.ts +8 -56
  40. package/dist/lib/index.js +7 -46
  41. package/dist/lib/legend/Legend.svelte +18 -19
  42. package/dist/lib/legend/Legend.svelte.d.ts +2 -2
  43. package/dist/lib/levels.d.ts +7 -0
  44. package/dist/lib/levels.js +19 -0
  45. package/dist/lib/map/MapShell.svelte +17 -24
  46. package/dist/lib/map/MapShell.svelte.d.ts +4 -4
  47. package/dist/lib/map/basemaps.d.ts +25 -0
  48. package/dist/lib/map/basemaps.js +66 -0
  49. package/dist/lib/map/live-map.svelte.d.ts +3 -0
  50. package/dist/lib/map/live-map.svelte.js +22 -0
  51. package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
  52. package/dist/lib/map/sync-plugins.svelte.js +52 -0
  53. package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
  54. package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
  55. package/dist/lib/monitors/filtered-source.svelte.js +28 -0
  56. package/dist/lib/monitors/monitor-details.svelte.js +11 -3
  57. package/dist/lib/monitors/monitors.svelte.d.ts +29 -26
  58. package/dist/lib/monitors/monitors.svelte.js +35 -126
  59. package/dist/lib/monitors/retry-load.d.ts +8 -0
  60. package/dist/lib/monitors/retry-load.js +11 -0
  61. package/dist/lib/monitors/subscriptions.d.ts +3 -0
  62. package/dist/lib/monitors/subscriptions.js +9 -0
  63. package/dist/lib/monitors/types.d.ts +1 -41
  64. package/dist/lib/options/map-option-defaults.d.ts +25 -36
  65. package/dist/lib/options/map-option-defaults.js +12 -30
  66. package/dist/lib/options/map-options.svelte.d.ts +13 -0
  67. package/dist/lib/options/map-options.svelte.js +26 -0
  68. package/dist/lib/options/menus/LayersMenu.svelte +8 -20
  69. package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
  70. package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
  71. package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
  72. package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
  73. package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
  74. package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
  75. package/dist/lib/options/plugin-order.d.ts +8 -0
  76. package/dist/lib/options/plugin-order.js +36 -0
  77. package/dist/lib/options/pollutant-param.d.ts +7 -5
  78. package/dist/lib/options/pollutant-param.js +9 -5
  79. package/dist/lib/options/row-icons.d.ts +8 -0
  80. package/dist/lib/options/row-icons.js +15 -0
  81. package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
  82. package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
  83. package/dist/lib/search/Search.svelte +20 -33
  84. package/dist/lib/search/Search.svelte.d.ts +5 -1
  85. package/dist/lib/search/search-pin.svelte.d.ts +11 -0
  86. package/dist/lib/search/search-pin.svelte.js +28 -0
  87. package/dist/lib/search/service.d.ts +7 -2
  88. package/dist/lib/search/service.js +17 -10
  89. package/dist/lib/stores.svelte.d.ts +8 -0
  90. package/dist/lib/stores.svelte.js +9 -0
  91. package/dist/lib/wind/wind-plugin.d.ts +11 -0
  92. package/dist/lib/wind/wind-plugin.js +25 -0
  93. package/package.json +12 -10
  94. package/src/lib/MonitorDetailPanel.svelte +23 -20
  95. package/src/lib/MonitorMapLayout.svelte +147 -77
  96. package/src/lib/abort.ts +59 -0
  97. package/src/lib/components/DataBox.svelte +4 -1
  98. package/src/lib/config.svelte.ts +16 -0
  99. package/src/lib/core.ts +2 -2
  100. package/src/lib/data-chart/DataChart.svelte +54 -24
  101. package/src/lib/data-chart/DateRange.ts +58 -15
  102. package/src/lib/data-chart/chart-utils.ts +6 -7
  103. package/src/lib/data-chart/level-colors.ts +8 -9
  104. package/src/lib/data-chart/pacific-time.ts +31 -0
  105. package/src/lib/data-chart/requests.ts +14 -7
  106. package/src/lib/data-chart/tooltip.ts +2 -2
  107. package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
  108. package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
  109. package/src/lib/ev-stations/ev-station-icons.ts +1 -17
  110. package/src/lib/ev-stations/ev-stations.ts +72 -0
  111. package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
  112. package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
  113. package/src/lib/ev-stations/types.ts +0 -34
  114. package/src/lib/ev-stations/zip-codes.ts +29 -16
  115. package/src/lib/index.ts +8 -99
  116. package/src/lib/legend/Legend.svelte +18 -19
  117. package/src/lib/levels.ts +24 -0
  118. package/src/lib/map/MapShell.svelte +17 -24
  119. package/src/lib/map/basemaps.ts +76 -0
  120. package/src/lib/map/live-map.svelte.ts +24 -0
  121. package/src/lib/map/sync-plugins.svelte.ts +69 -0
  122. package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
  123. package/src/lib/monitors/filtered-source.svelte.ts +38 -0
  124. package/src/lib/monitors/monitor-details.svelte.ts +12 -3
  125. package/src/lib/monitors/monitors.svelte.ts +62 -153
  126. package/src/lib/monitors/retry-load.ts +14 -0
  127. package/src/lib/monitors/subscriptions.ts +17 -0
  128. package/src/lib/monitors/types.ts +1 -47
  129. package/src/lib/options/map-option-defaults.ts +30 -53
  130. package/src/lib/options/map-options.svelte.ts +30 -0
  131. package/src/lib/options/menus/LayersMenu.svelte +8 -20
  132. package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
  133. package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
  134. package/src/lib/options/menus/PollutantMenu.svelte +4 -9
  135. package/src/lib/options/menus/SettingsMenu.svelte +32 -25
  136. package/src/lib/options/plugin-order.ts +38 -0
  137. package/src/lib/options/pollutant-param.ts +14 -9
  138. package/src/lib/options/row-icons.ts +17 -0
  139. package/src/lib/options/rows/MarkerIcon.svelte +5 -5
  140. package/src/lib/search/Search.svelte +20 -33
  141. package/src/lib/search/search-pin.svelte.ts +35 -0
  142. package/src/lib/search/service.ts +34 -15
  143. package/src/lib/stores.svelte.ts +15 -0
  144. package/src/lib/wind/wind-plugin.ts +33 -0
  145. package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  146. package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
  147. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
  148. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
  149. package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
  150. package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
  151. package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -9
  152. package/dist/lib/collocation-sites/collocations.svelte.js +0 -17
  153. package/dist/lib/collocation-sites/types.d.ts +0 -8
  154. package/dist/lib/collocation-sites/types.js +0 -1
  155. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  156. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
  157. package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
  158. package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
  159. package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
  160. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
  161. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
  162. package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -9
  163. package/dist/lib/ev-stations/ev-stations.svelte.js +0 -55
  164. package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
  165. package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
  166. package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
  167. package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
  168. package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
  169. package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
  170. package/dist/lib/hms/hms.svelte.d.ts +0 -19
  171. package/dist/lib/hms/hms.svelte.js +0 -227
  172. package/dist/lib/legend/LegendBar.svelte +0 -45
  173. package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
  174. package/dist/lib/legend/legend-data.d.ts +0 -40
  175. package/dist/lib/legend/legend-data.js +0 -65
  176. package/dist/lib/map/Map.svelte +0 -13
  177. package/dist/lib/map/Map.svelte.d.ts +0 -8
  178. package/dist/lib/map/basemap.d.ts +0 -14
  179. package/dist/lib/map/basemap.js +0 -10
  180. package/dist/lib/map/icons.d.ts +0 -7
  181. package/dist/lib/map/icons.js +0 -33
  182. package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
  183. package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
  184. package/dist/lib/map/integrations/click-manager.d.ts +0 -14
  185. package/dist/lib/map/integrations/click-manager.js +0 -52
  186. package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
  187. package/dist/lib/map/integrations/integrations-manager.js +0 -20
  188. package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
  189. package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
  190. package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
  191. package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
  192. package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
  193. package/dist/lib/map/integrations/map-icon-manager.js +0 -52
  194. package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
  195. package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
  196. package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
  197. package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
  198. package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
  199. package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
  200. package/dist/lib/map/integrations/types.d.ts +0 -14
  201. package/dist/lib/map/integrations/types.js +0 -1
  202. package/dist/lib/map/map-manager.svelte.d.ts +0 -20
  203. package/dist/lib/map/map-manager.svelte.js +0 -63
  204. package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
  205. package/dist/lib/map/map-style-state.svelte.js +0 -86
  206. package/dist/lib/map/utils.d.ts +0 -7
  207. package/dist/lib/map/utils.js +0 -36
  208. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  209. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
  210. package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
  211. package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
  212. package/dist/lib/monitors/monitor-utils.d.ts +0 -4
  213. package/dist/lib/monitors/monitor-utils.js +0 -41
  214. package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
  215. package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
  216. package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
  217. package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
  218. package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
  219. package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
  220. package/dist/lib/options/reset-map-options.d.ts +0 -2
  221. package/dist/lib/options/reset-map-options.js +0 -24
  222. package/dist/lib/reactivity.svelte.d.ts +0 -0
  223. package/dist/lib/reactivity.svelte.js +0 -83
  224. package/dist/lib/wind/wind.svelte.d.ts +0 -10
  225. package/dist/lib/wind/wind.svelte.js +0 -13
  226. package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  227. package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
  228. package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
  229. package/src/lib/collocation-sites/collocations.svelte.ts +0 -24
  230. package/src/lib/collocation-sites/types.ts +0 -10
  231. package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  232. package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
  233. package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
  234. package/src/lib/ev-stations/ev-stations.svelte.ts +0 -79
  235. package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
  236. package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
  237. package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
  238. package/src/lib/hms/hms.svelte.ts +0 -264
  239. package/src/lib/legend/LegendBar.svelte +0 -45
  240. package/src/lib/legend/legend-data.ts +0 -101
  241. package/src/lib/map/Map.svelte +0 -13
  242. package/src/lib/map/basemap.ts +0 -24
  243. package/src/lib/map/icons.ts +0 -42
  244. package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
  245. package/src/lib/map/integrations/click-manager.ts +0 -64
  246. package/src/lib/map/integrations/integrations-manager.ts +0 -25
  247. package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
  248. package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
  249. package/src/lib/map/integrations/map-icon-manager.ts +0 -67
  250. package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
  251. package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
  252. package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
  253. package/src/lib/map/integrations/types.ts +0 -24
  254. package/src/lib/map/map-manager.svelte.ts +0 -83
  255. package/src/lib/map/map-style-state.svelte.ts +0 -94
  256. package/src/lib/map/utils.ts +0 -53
  257. package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  258. package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
  259. package/src/lib/monitors/monitor-utils.ts +0 -57
  260. package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
  261. package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
  262. package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
  263. package/src/lib/options/reset-map-options.ts +0 -28
  264. package/src/lib/reactivity.svelte.ts +0 -82
  265. package/src/lib/wind/wind.svelte.ts +0 -20
@@ -1,48 +0,0 @@
1
- import { asDataURI, fireIcon } from "$lib/map/icons";
2
- import { MapIconManager } from "$lib/map/integrations/map-icon-manager";
3
- import type { LegendCategory } from "$lib/legend/legend-data";
4
-
5
- export type FRPTier = "sm" | "md" | "lg" | "xl" | "xxl";
6
-
7
- export const FRP_TIERS: Record<FRPTier, { color: string; size: number }> = {
8
- sm: { color: "#FFD700", size: 20 },
9
- md: { color: "#FF8C00", size: 24 },
10
- lg: { color: "#FF4500", size: 28 },
11
- xl: { color: "#DC143C", size: 32 },
12
- xxl: { color: "#8B0000", size: 36 }
13
- };
14
-
15
- const FRP_TIER_LABELS: Record<FRPTier, string> = {
16
- sm: "<10",
17
- md: "10-49",
18
- lg: "50-149",
19
- xl: "150-349",
20
- xxl: "≥350"
21
- };
22
-
23
- /** Fire legend blocks (MW), in tier order, using the same colors as the map icons. */
24
- export const FIRE_LEGEND_CATEGORIES: Array<LegendCategory> = (
25
- Object.keys(FRP_TIERS) as Array<FRPTier>
26
- ).map((tier) => ({ color: FRP_TIERS[tier].color, label: FRP_TIER_LABELS[tier] }));
27
-
28
- export function getTierIconId(frp: number): string {
29
- if (frp < 10) return "hms-fire-sm";
30
- if (frp < 50) return "hms-fire-md";
31
- if (frp < 150) return "hms-fire-lg";
32
- if (frp < 350) return "hms-fire-xl";
33
- return "hms-fire-xxl";
34
- }
35
-
36
- export class HMSFireIconManager extends MapIconManager {
37
- constructor() {
38
- super();
39
- for (const [tier, { color, size }] of Object.entries(FRP_TIERS) as [
40
- FRPTier,
41
- { color: string; size: number }
42
- ][]) {
43
- const img = new Image(size, size);
44
- img.src = asDataURI(fireIcon(color, size));
45
- this.register(`hms-fire-${tier}`, img);
46
- }
47
- }
48
- }
@@ -1,109 +0,0 @@
1
- import type { Map as MaptilerMap } from "@maptiler/sdk";
2
- import type { Feature } from "geojson";
3
- import type { Point } from "geojson";
4
- import { mapManager } from "$lib/map/map-manager.svelte.js";
5
- import { MapIconLayerIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
6
- import { hmsManager } from "./hms.svelte";
7
- import { HMSFireIconManager, getTierIconId } from "./hms-fire-icon-manager";
8
-
9
- type FireProperties = Record<string, unknown>;
10
-
11
- const ZOOM_THRESHOLD = 10;
12
-
13
- class HMSFireMapIntegration extends MapIconLayerIntegration<FireProperties> {
14
- referenceId = "hms-fire";
15
- enabled: boolean = $state(true);
16
- icons = new HMSFireIconManager();
17
- private zoomedIn: boolean = $state(false);
18
-
19
- get features(): Array<Feature<Point, FireProperties>> {
20
- return this.zoomedIn ? this.hotspotFeatures : this.groupFeatures;
21
- }
22
-
23
- get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
24
- return {
25
- id: this.referenceId,
26
- type: "symbol",
27
- source: this.referenceId,
28
- layout: {
29
- "icon-image": ["get", "icon"],
30
- "icon-allow-overlap": true,
31
- "icon-ignore-placement": true
32
- },
33
- paint: {}
34
- };
35
- }
36
-
37
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
38
- return {
39
- type: "geojson",
40
- promoteId: "id",
41
- data: { type: "FeatureCollection", features: this.features }
42
- };
43
- }
44
-
45
- private get groupFeatures(): Array<Feature<Point, FireProperties>> {
46
- return (hmsManager.fireGroups ?? []).map((g) => ({
47
- type: "Feature",
48
- properties: {
49
- id: g.id,
50
- icon: getTierIconId(g.avgFrp),
51
- avgFrp: g.avgFrp,
52
- count: g.count
53
- },
54
- geometry: { type: "Point", coordinates: g.coordinates }
55
- }));
56
- }
57
-
58
- private get hotspotFeatures(): Array<Feature<Point, FireProperties>> {
59
- return (hmsManager.fire ?? []).map((d) => ({
60
- type: "Feature",
61
- properties: {
62
- id: d.id,
63
- icon: getTierIconId(d.frp),
64
- frp: d.frp
65
- },
66
- geometry: d.geometry as Point
67
- }));
68
- }
69
-
70
- private onZoom = () => {
71
- if (!mapManager.map) return;
72
- this.zoomedIn = mapManager.map.getZoom() >= ZOOM_THRESHOLD;
73
- };
74
-
75
- constructor() {
76
- super();
77
-
78
- $effect.root(() => {
79
- $effect(() => {
80
- const features = this.features;
81
- if (!mapManager.map || !this.enabled) return;
82
- mapManager.setDataSource(this.referenceId, features);
83
- });
84
- });
85
- }
86
-
87
- apply() {
88
- if (!mapManager.map) return;
89
- this.icons
90
- .loadIcons()
91
- .then(() => {
92
- if (!mapManager.map || !this.enabled || mapManager.styleLoading) return;
93
- this.remove();
94
- this.zoomedIn = mapManager.map.getZoom() >= ZOOM_THRESHOLD;
95
- mapManager.map.addSource(this.referenceId, this.mapSource);
96
- mapManager.map.addLayer(this.mapLayer, this.beforeLayer);
97
- mapManager.map.on("zoom", this.onZoom);
98
- })
99
- .catch(console.error);
100
- }
101
-
102
- remove() {
103
- mapManager.map?.off("zoom", this.onZoom);
104
- super.remove();
105
- }
106
- }
107
-
108
- export const hmsFireMapIntegration = new HMSFireMapIntegration();
109
- export type { HMSFireMapIntegration };
@@ -1,105 +0,0 @@
1
- import type { Map as MaptilerMap } from "@maptiler/sdk";
2
- import type { Feature, MultiPolygon } from "geojson";
3
- import type { HMSSmokeGeoJSON } from "@sjvair/sdk/hms";
4
- import { mapManager } from "$lib/map/map-manager.svelte.js";
5
- import { MapGeoJSONIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
6
- import { hmsManager } from "./hms.svelte";
7
- import type { SmokeDensityStyle } from "$lib/legend/legend-data";
8
-
9
- /** Smoke fill styles, shared by the map layer and the legend so they can't drift apart. */
10
- export const SMOKE_DENSITY_STYLES = {
11
- light: { label: "Light", color: "#bfc8c3", opacity: 0.2 },
12
- medium: { label: "Medium", color: "#757b78", opacity: 0.3 },
13
- heavy: { label: "Heavy", color: "#333634", opacity: 0.4 }
14
- } satisfies Record<string, SmokeDensityStyle>;
15
-
16
- export const SMOKE_LEGEND: Array<SmokeDensityStyle> = Object.values(SMOKE_DENSITY_STYLES);
17
-
18
- type SmokeProperties = {
19
- id: string;
20
- density: HMSSmokeGeoJSON["density"];
21
- };
22
-
23
- class HMSSmokeMapIntegration extends MapGeoJSONIntegration<SmokeProperties> {
24
- referenceId = "hms-smoke";
25
- enabled: boolean = $state(true);
26
-
27
- get features(): Array<Feature<MultiPolygon, SmokeProperties>> {
28
- return (hmsManager.smoke ?? []).map((d) => ({
29
- type: "Feature",
30
- properties: {
31
- id: d.id,
32
- density: d.density
33
- },
34
- geometry: d.geometry as MultiPolygon
35
- }));
36
- }
37
-
38
- get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
39
- return {
40
- id: this.referenceId,
41
- type: "fill",
42
- source: this.referenceId,
43
- layout: {},
44
- paint: {
45
- "fill-color": [
46
- "match",
47
- ["get", "density"],
48
- "light",
49
- SMOKE_DENSITY_STYLES.light.color,
50
- "medium",
51
- SMOKE_DENSITY_STYLES.medium.color,
52
- SMOKE_DENSITY_STYLES.heavy.color
53
- ],
54
- "fill-opacity": [
55
- "match",
56
- ["get", "density"],
57
- "light",
58
- SMOKE_DENSITY_STYLES.light.opacity,
59
- "medium",
60
- SMOKE_DENSITY_STYLES.medium.opacity,
61
- SMOKE_DENSITY_STYLES.heavy.opacity
62
- ],
63
- "fill-outline-color": [
64
- "match",
65
- ["get", "density"],
66
- "light",
67
- SMOKE_DENSITY_STYLES.light.color,
68
- "medium",
69
- SMOKE_DENSITY_STYLES.medium.color,
70
- SMOKE_DENSITY_STYLES.heavy.color
71
- ]
72
- }
73
- };
74
- }
75
-
76
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
77
- return {
78
- type: "geojson",
79
- promoteId: "id",
80
- data: { type: "FeatureCollection", features: this.features }
81
- };
82
- }
83
-
84
- constructor() {
85
- super();
86
-
87
- $effect.root(() => {
88
- $effect(() => {
89
- const features = this.features;
90
- if (!mapManager.map || !this.enabled) return;
91
- mapManager.setDataSource(this.referenceId, features);
92
- });
93
- });
94
- }
95
-
96
- apply() {
97
- if (!mapManager.map) return;
98
- this.remove();
99
- mapManager.map.addSource(this.referenceId, this.mapSource);
100
- super.apply();
101
- }
102
- }
103
-
104
- export const hmsSmokeMapIntegration = new HMSSmokeMapIntegration();
105
- export type { HMSSmokeMapIntegration };
@@ -1,264 +0,0 @@
1
- import {
2
- getHMSFire,
3
- getHMSSmoke,
4
- type HMSFireGeoJSON,
5
- type HMSSmokeGeoJSON
6
- } from "@sjvair/sdk/hms";
7
- import clustersDbscan from "@turf/clusters-dbscan";
8
- import { featureCollection, point } from "@turf/helpers";
9
-
10
- const HMS_FIRE_DBSCAN_EPSILON = 2;
11
- const HMS_FIRE_DBSCAN_MIN_POINTS = 1;
12
-
13
- export interface HMSFireGroup {
14
- id: string;
15
- coordinates: [number, number];
16
- avgFrp: number;
17
- count: number;
18
- }
19
-
20
- class HmsManager {
21
- smoke: Array<HMSSmokeGeoJSON> | undefined = $state();
22
- fire: Array<HMSFireGeoJSON & { frp: number }> | undefined = $state();
23
- fireGroups: Array<HMSFireGroup> | undefined = $state();
24
-
25
- async init() {
26
- await Promise.allSettled([this.loadSmoke(), this.loadFire()]);
27
- }
28
-
29
- async loadSmoke() {
30
- this.smoke = await getHMSSmoke();
31
- }
32
-
33
- async loadFire() {
34
- try {
35
- const raw = await getHMSFire();
36
- const filtered = raw.filter((d): d is HMSFireGeoJSON & { frp: number } => d.frp !== null);
37
- const deduped = deduplicateByCoordinates(filtered);
38
- this.fire = deduped;
39
- this.fireGroups = computeFireGroups(deduped);
40
- } catch (err) {
41
- console.error("HMS fire load failed:", err);
42
- }
43
- }
44
- }
45
-
46
- function deduplicateByCoordinates(
47
- fires: (HMSFireGeoJSON & { frp: number })[]
48
- ): (HMSFireGeoJSON & { frp: number })[] {
49
- const latest: Record<string, HMSFireGeoJSON & { frp: number }> = {};
50
- for (const fire of fires) {
51
- const [lng, lat] = fire.geometry.coordinates as [number, number];
52
- const key = `${lng},${lat}`;
53
- const existing = latest[key];
54
- if (!existing || fire.timestamp > existing.timestamp) {
55
- latest[key] = fire;
56
- }
57
- }
58
- return Object.values(latest);
59
- }
60
-
61
- function computeFireGroups(fires: (HMSFireGeoJSON & { frp: number })[]): HMSFireGroup[] {
62
- if (!fires.length) return [];
63
-
64
- const fc = featureCollection(
65
- fires.map((d) =>
66
- point(d.geometry.coordinates as [number, number], { fireId: d.id, frp: d.frp })
67
- )
68
- );
69
-
70
- const clustered = clustersDbscan(fc, HMS_FIRE_DBSCAN_EPSILON, {
71
- minPoints: HMS_FIRE_DBSCAN_MIN_POINTS,
72
- units: "kilometers"
73
- });
74
-
75
- const groups: Record<string, { frps: number[]; lngs: number[]; lats: number[] }> = {};
76
-
77
- for (const feature of clustered.features) {
78
- const props = feature.properties as {
79
- dbscan: "core" | "edge" | "noise";
80
- cluster?: number;
81
- fireId: string;
82
- frp: number;
83
- };
84
- // With minPoints=1 every point self-qualifies as core, so "noise" never occurs.
85
- // This branch activates correctly if minPoints is raised above 1 in future.
86
- const key = props.dbscan === "noise" ? `noise-${props.fireId}` : `cluster-${props.cluster}`;
87
- const [lng, lat] = feature.geometry.coordinates as [number, number];
88
- const entry = groups[key] ?? { frps: [], lngs: [], lats: [] };
89
- entry.frps.push(props.frp);
90
- entry.lngs.push(lng);
91
- entry.lats.push(lat);
92
- groups[key] = entry;
93
- }
94
-
95
- return Object.entries(groups).map(([key, { frps, lngs, lats }]) => ({
96
- id: `hms-fire-group-${key}`,
97
- coordinates: [
98
- lngs.reduce((s, v) => s + v, 0) / lngs.length,
99
- lats.reduce((s, v) => s + v, 0) / lats.length
100
- ] as [number, number],
101
- avgFrp: frps.reduce((s, v) => s + v, 0) / frps.length,
102
- count: frps.length
103
- }));
104
- }
105
-
106
- //async function fakeFireData(): Promise<Array<HMSFireGeoJSON>> {
107
- // const response: FetchHMSFireResponse["body"] = {
108
- // data: [
109
- // {
110
- // id: "4Kx7mNpQrQ",
111
- // date: "2026-04-13",
112
- // satellite: "GOES-16",
113
- // timestamp: "2026-04-13T18:30:00Z",
114
- // frp: 360,
115
- // ecosystem: 4,
116
- // method: "AF",
117
- // geometry: {
118
- // type: "Point",
119
- // coordinates: [-118.532, 36.591],
120
- // },
121
- // },
122
- // {
123
- // id: "4Kx7mNpQrD",
124
- // date: "2026-04-13",
125
- // satellite: "GOES-16",
126
- // timestamp: "2026-04-13T18:30:00Z",
127
- // frp: 20,
128
- // ecosystem: 4,
129
- // method: "AF",
130
- // geometry: {
131
- // type: "Point",
132
- // coordinates: [-118.632, 36.691],
133
- // },
134
- // },
135
- // {
136
- // id: "4Kx7mNpQrT",
137
- // date: "2026-04-13",
138
- // satellite: "GOES-16",
139
- // timestamp: "2026-04-13T18:30:00Z",
140
- // frp: 152.7,
141
- // ecosystem: 4,
142
- // method: "AF",
143
- // geometry: {
144
- // type: "Point",
145
- // coordinates: [-118.832, 36.491],
146
- // },
147
- // },
148
- // {
149
- // id: "9vBjLsWqYm",
150
- // date: "2026-04-13",
151
- // satellite: "GOES-17",
152
- // timestamp: "2026-04-13T17:45:00Z",
153
- // frp: 58.2,
154
- // ecosystem: 2,
155
- // method: "AF",
156
- // geometry: {
157
- // type: "Point",
158
- // coordinates: [-119.204, 37.112],
159
- // },
160
- // },
161
- // {
162
- // id: "2HcFnRkVxZ",
163
- // date: "2026-04-13",
164
- // satellite: "GOES-16",
165
- // timestamp: "2026-04-13T16:00:00Z",
166
- // frp: null,
167
- // ecosystem: 7,
168
- // method: "FDC",
169
- // geometry: {
170
- // type: "Point",
171
- // coordinates: [-120.571, 35.883],
172
- // },
173
- // },
174
- // ],
175
- // page: 1,
176
- // count: 3,
177
- // pages: 1,
178
- // has_next_page: false,
179
- // has_previous_page: false,
180
- // };
181
- // return Promise.resolve(response.data);
182
- //}
183
-
184
- //async function fakeSmokeData(): Promise<Array<HMSSmokeGeoJSON>> {
185
- // const response: FetchHMSSmokeResponse["body"] = {
186
- // data: [
187
- // {
188
- // id: "sM3kNpQrL1",
189
- // date: "2026-04-13",
190
- // satellite: "GOES-16",
191
- // start: "2026-04-13T12:00:00Z",
192
- // end: "2026-04-13T18:00:00Z",
193
- // density: "light",
194
- // geometry: {
195
- // type: "MultiPolygon",
196
- // coordinates: [
197
- // [
198
- // [
199
- // [-122.5, 39.5],
200
- // [-120.5, 39.5],
201
- // [-120.5, 41.0],
202
- // [-122.5, 41.0],
203
- // [-122.5, 39.5]
204
- // ]
205
- // ]
206
- // ]
207
- // }
208
- // },
209
- // {
210
- // id: "sM3kNpQrL2",
211
- // date: "2026-04-13",
212
- // satellite: "GOES-16",
213
- // start: "2026-04-13T12:00:00Z",
214
- // end: "2026-04-13T18:00:00Z",
215
- // density: "medium",
216
- // geometry: {
217
- // type: "MultiPolygon",
218
- // coordinates: [
219
- // [
220
- // [
221
- // [-121.0, 36.5],
222
- // [-119.0, 36.5],
223
- // [-119.0, 38.0],
224
- // [-121.0, 38.0],
225
- // [-121.0, 36.5]
226
- // ]
227
- // ]
228
- // ]
229
- // }
230
- // },
231
- // {
232
- // id: "sM3kNpQrL3",
233
- // date: "2026-04-13",
234
- // satellite: "GOES-16",
235
- // start: "2026-04-13T12:00:00Z",
236
- // end: "2026-04-13T18:00:00Z",
237
- // density: "heavy",
238
- // geometry: {
239
- // type: "MultiPolygon",
240
- // coordinates: [
241
- // [
242
- // [
243
- // [-119.0, 33.5],
244
- // [-116.5, 33.5],
245
- // [-116.5, 35.0],
246
- // [-119.0, 35.0],
247
- // [-119.0, 33.5]
248
- // ]
249
- // ]
250
- // ]
251
- // }
252
- // }
253
- // ],
254
- // page: 1,
255
- // count: 3,
256
- // pages: 1,
257
- // has_next_page: false,
258
- // has_previous_page: false
259
- // };
260
- // return Promise.resolve(response.data);
261
- //}
262
-
263
- export const hmsManager = new HmsManager();
264
- export type { HmsManager as HMSManager };
@@ -1,45 +0,0 @@
1
- <script lang="ts">
2
- import {
3
- blocksBackground,
4
- gradientBackground,
5
- tickPosition,
6
- type LegendBarData
7
- } from "./legend-data";
8
-
9
- let { bar }: { bar: LegendBarData } = $props();
10
-
11
- const background = $derived(
12
- bar.kind === "gradient"
13
- ? gradientBackground(bar.colors)
14
- : blocksBackground(bar.categories.map((category) => category.color))
15
- );
16
-
17
- // Labels placed on exact stop positions: gradient ticks, or block breakpoints.
18
- const positioned = $derived(bar.kind === "gradient" ? bar.ticks : (bar.breakpoints ?? null));
19
- </script>
20
-
21
- <div class="h-3 rounded" style:background></div>
22
- {#if positioned}
23
- <div class="relative mt-1 h-4 text-xs">
24
- {#each positioned as label, i (i)}
25
- {@const isFirst = i === 0}
26
- {@const isLast = i === positioned.length - 1}
27
- <span
28
- class={[
29
- "absolute top-0 whitespace-nowrap",
30
- isFirst && "left-0",
31
- isLast && "right-0",
32
- !isFirst && !isLast && "-translate-x-1/2"
33
- ]}
34
- style:left={!isFirst && !isLast ? `${tickPosition(i, positioned.length)}%` : undefined}
35
- >{label}</span
36
- >
37
- {/each}
38
- </div>
39
- {:else if bar.kind === "blocks"}
40
- <div class="mt-1 flex text-xs">
41
- {#each bar.categories as category, i (i)}
42
- <span class="min-w-0 flex-1 text-center break-words">{category.label ?? ""}</span>
43
- {/each}
44
- </div>
45
- {/if}
@@ -1,101 +0,0 @@
1
- import type { SJVAirEntryLevel } from "@sjvair/sdk";
2
- import { mix } from "color2k";
3
- import type { MonitorsPollutant } from "$lib/monitors/types";
4
-
5
- export interface LegendCategory {
6
- color: string;
7
- label?: string;
8
- }
9
-
10
- export type LegendBarData =
11
- | { kind: "gradient"; colors: Array<string>; ticks: Array<string> }
12
- | { kind: "blocks"; categories: Array<LegendCategory>; breakpoints?: Array<string> };
13
-
14
- export interface LegendSection {
15
- id: string;
16
- title: string;
17
- unit?: string;
18
- bar: LegendBarData;
19
- }
20
-
21
- export interface SmokeDensityStyle {
22
- label: string;
23
- color: string;
24
- opacity: number;
25
- }
26
-
27
- const POLLUTANT_TITLES: Record<MonitorsPollutant, { title: string; unit: string }> = {
28
- pm25: { title: "PM2.5", unit: "µg/m³" },
29
- o3: { title: "Ozone", unit: "ppb" }
30
- };
31
-
32
- /**
33
- * Pollutant legend built from the monitors meta levels — the same levels the map markers are
34
- * colored by, so the two can never disagree. One tick per level, at that level's color stop.
35
- */
36
- export function pollutantLegendSection(
37
- pollutant: MonitorsPollutant,
38
- levels: ReadonlyArray<Pick<SJVAirEntryLevel, "color" | "range">>
39
- ): LegendSection | null {
40
- if (levels.length === 0) return null;
41
- return {
42
- id: "pollutant",
43
- ...POLLUTANT_TITLES[pollutant],
44
- bar: {
45
- kind: "gradient",
46
- colors: levels.map((level) => level.color),
47
- ticks: levels.map((level) => String(Math.floor(level.range[0])))
48
- }
49
- };
50
- }
51
-
52
- /** Fire & Smoke share one toggle, so they render as two independent bars. */
53
- export function fireSmokeLegendSections(
54
- fireTiers: ReadonlyArray<LegendCategory>,
55
- smoke: ReadonlyArray<SmokeDensityStyle>
56
- ): Array<LegendSection> {
57
- return [
58
- { id: "fire", title: "Fire", unit: "MW", bar: { kind: "blocks", categories: [...fireTiers] } },
59
- {
60
- id: "smoke",
61
- title: "Smoke Density",
62
- bar: {
63
- kind: "blocks",
64
- categories: smoke.map(({ label, color, opacity }) => ({
65
- label,
66
- color: swatchOverWhite(color, opacity)
67
- }))
68
- }
69
- }
70
- ];
71
- }
72
-
73
- /** Approximates how a translucent fill layer looks over a light basemap. */
74
- export function swatchOverWhite(color: string, opacity: number): string {
75
- return mix("#ffffff", color, opacity);
76
- }
77
-
78
- export function gradientBackground(colors: ReadonlyArray<string>): string {
79
- if (colors.length === 0) return "none";
80
- if (colors.length === 1) return colors[0];
81
- return `linear-gradient(to right, ${colors.join(", ")})`;
82
- }
83
-
84
- /** Hard-edged equal-width blocks: each color repeated at both edges of its own share. */
85
- export function blocksBackground(colors: ReadonlyArray<string>): string {
86
- const count = colors.length;
87
- if (count === 0) return "none";
88
- const stops = colors.map(
89
- (color, i) => `${color} ${percent(i / count)}, ${color} ${percent((i + 1) / count)}`
90
- );
91
- return `linear-gradient(to right, ${stops.join(", ")})`;
92
- }
93
-
94
- /** Percent position of the index-th of `count` evenly spaced labels. */
95
- export function tickPosition(index: number, count: number): number {
96
- return count <= 1 ? 0 : (index / (count - 1)) * 100;
97
- }
98
-
99
- function percent(fraction: number): string {
100
- return `${(fraction * 100).toFixed(2)}%`;
101
- }
@@ -1,13 +0,0 @@
1
- <script lang="ts">
2
- import "@maptiler/sdk/dist/maptiler-sdk.css";
3
- import { initializeMap } from "./map-manager.svelte.js";
4
- import { integrationsManager } from "$lib/map/integrations/integrations-manager";
5
- import type { SomeMapIntegration } from "./integrations/types";
6
-
7
- const { integrations }: { integrations: Array<SomeMapIntegration> } = $props();
8
-
9
- // svelte-ignore state_referenced_locally (we only need the initial reference)
10
- integrationsManager.register(...integrations);
11
- </script>
12
-
13
- <div {@attach initializeMap} style="width: 100%; height: 100%;"></div>