@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,98 +0,0 @@
1
- import type { MonitorData, SJVAirEntryLevel } from "@sjvair/sdk";
2
- import { asDataURI, circle, square, triangle } from "$lib/map/icons";
3
- import { MapIconManager } from "$lib/map/integrations/map-icon-manager";
4
- import { mapManager } from "$lib/map/map-manager.svelte.js";
5
- import type { MonitorsDataSource } from "./types";
6
-
7
- const MONITOR_ICONS = { circle, square, triangle };
8
- const MONITOR_ICON_BORDER_WIDTH = 2;
9
- const MONITOR_ICON_DEFAULT_COLOR = "#969696"; // light gray
10
- const MONITOR_ICON_DISPLAY_COLOR = "#48c78e"; // light gray
11
-
12
- export function getIconId<T extends MonitorData>(monitor: T, level: SJVAirEntryLevel): string {
13
- const id = `${monitor.location}-${monitor.is_active ? level.name : "default"}`;
14
-
15
- switch (monitor.type) {
16
- case "airnow":
17
- case "aqview":
18
- case "bam1022":
19
- case "aqlite":
20
- return `${id}-triangle`;
21
-
22
- case "airgradient":
23
- case "purpleair":
24
- case "vozbox":
25
- return `${id}-circle`;
26
-
27
- default:
28
- throw new Error(`Map icon for ${monitor.device} has not been set`);
29
- }
30
- }
31
-
32
- export abstract class MonitorShapeIconManager extends MapIconManager {
33
- protected abstract get idPrefix(): string;
34
- protected abstract transformSvg(svg: string): string;
35
- protected get iconSize(): number {
36
- return 24;
37
- }
38
-
39
- constructor(private dataSource: Pick<MonitorsDataSource, "levels">) {
40
- super();
41
-
42
- $effect.root(() => {
43
- $effect(() => {
44
- if (this.dataSource.levels) {
45
- const iconLevels = [
46
- ...this.dataSource.levels,
47
- { name: "default", color: MONITOR_ICON_DEFAULT_COLOR },
48
- { name: "display", color: MONITOR_ICON_DISPLAY_COLOR }
49
- ];
50
-
51
- for (const location of ["inside", "outside"]) {
52
- for (const [shapeName, shape] of Object.entries(MONITOR_ICONS)) {
53
- for (const level of iconLevels) {
54
- const parts = [this.idPrefix, location, level.name, shapeName].filter(Boolean);
55
- const id = parts.join("-");
56
- const src = asDataURI(
57
- this.transformSvg(
58
- shape(
59
- level.color,
60
- MONITOR_ICON_BORDER_WIDTH,
61
- location === "inside" ? "#000000" : undefined
62
- )
63
- )
64
- );
65
-
66
- if (this.icons.has(id)) {
67
- const icon = this.icons.get(id)!;
68
- if (icon.image.src !== src) {
69
- icon.image.src = src;
70
- if (mapManager.map?.hasImage(id)) {
71
- this.updateImage(icon, mapManager.map);
72
- }
73
- }
74
- continue;
75
- } else {
76
- const icon = new Image();
77
- icon.src = src;
78
- icon.width = this.iconSize;
79
- icon.height = this.iconSize;
80
- this.register(id, icon);
81
- }
82
- }
83
- }
84
- }
85
- }
86
- });
87
- });
88
- }
89
- }
90
-
91
- export class MonitorsIconManager extends MonitorShapeIconManager {
92
- protected get idPrefix(): string {
93
- return "";
94
- }
95
- protected transformSvg(svg: string): string {
96
- return svg;
97
- }
98
- }
@@ -1,318 +0,0 @@
1
- import {
2
- type ExpressionSpecification,
3
- type FilterSpecification,
4
- type Map as MaptilerMap
5
- } from "@maptiler/sdk";
6
- import type { MonitorType, SJVAirEntryLevel } from "@sjvair/sdk";
7
- import { untrack } from "svelte";
8
- import type { Geometry } from "geojson";
9
- import { mapManager } from "$lib/map/map-manager.svelte.js";
10
- import { clickManager, type ClickHandler } from "$lib/map/integrations/click-manager";
11
- import { MapIconLayerIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
12
- import { monitorsManager } from "./monitors.svelte";
13
- import { getIconId, MonitorsIconManager } from "./monitors-icon-manager.svelte";
14
- import { TooltipManager } from "$lib/map/integrations/tooltip.svelte";
15
- import { MapDisplayOption } from "$lib/map/integrations/map-display-option.svelte";
16
- import { getCurrentLevel, getOrder } from "./monitor-utils";
17
- import { MonitorsClusterRenderer, monitorTooltip } from "./monitors-cluster-renderer";
18
- import type { MonitorMapFeature, MonitorMarkerProperties, MonitorsDataSource } from "./types";
19
-
20
- const filters = {
21
- monitor(deviceType: MonitorType): ExpressionSpecification {
22
- return ["==", ["get", "type"], deviceType];
23
- }
24
- };
25
-
26
- class MonitorsMapIntegration extends MapIconLayerIntegration<MonitorMarkerProperties> {
27
- referenceId: string = "monitors";
28
- enabled: boolean = $state(true);
29
- clustered: boolean = $state(true);
30
-
31
- icons: MonitorsIconManager;
32
- tooltipManager: TooltipManager = new TooltipManager();
33
- private renderer: MonitorsClusterRenderer = new MonitorsClusterRenderer(this);
34
-
35
- displayOptions = $derived.by(() => ({
36
- airnow: new MapDisplayOption("AirNow", true, this.icons.get("outside-display-triangle")),
37
- aqlite: new MapDisplayOption("AQLite", true, this.icons.get("outside-display-triangle")),
38
- aqview: new MapDisplayOption("AQview", true, this.icons.get("outside-display-triangle")),
39
- bam1022: new MapDisplayOption("SJVAir", true, this.icons.get("outside-display-triangle")),
40
- airgradient: new MapDisplayOption(
41
- "AirGradient",
42
- true,
43
- this.icons.get("outside-display-circle")
44
- ),
45
- purpleair: new MapDisplayOption("PurpleAir", true, this.icons.get("outside-display-circle")),
46
- vozbox: new MapDisplayOption("VOZbox", true, this.icons.get("outside-display-circle")),
47
- inactive: new MapDisplayOption("Inactive", false, this.icons.get("outside-default-circle")),
48
- inside: new MapDisplayOption("Inside", false, this.icons.get("inside-display-circle"))
49
- }));
50
-
51
- /** Set by the app to handle navigation when a monitor is clicked. Receives the monitor ID. */
52
- onMonitorClick: ((id: string) => void) | null = null;
53
- selectedMonitorId: string | null = $state(null);
54
-
55
- private handleMonitorClick: ClickHandler = (features) => {
56
- if (!this.onMonitorClick) return;
57
- const sorted = [...features].sort(
58
- (a, b) => (b.properties?.order ?? 0) - (a.properties?.order ?? 0)
59
- );
60
- const top = sorted[0];
61
- if (!top?.properties?.id) return;
62
- this.selectedMonitorId = top.properties.id as string;
63
- this.onMonitorClick(this.selectedMonitorId);
64
- if (top.geometry.type !== "Point") return;
65
- const coords = top.geometry.coordinates as [number, number];
66
- mapManager.map?.easeTo({
67
- center: coords,
68
- zoom: Math.max(mapManager.map.getZoom(), 12)
69
- });
70
- };
71
-
72
- features: Array<MonitorMapFeature> = $derived.by(() => {
73
- if (!this.dataSource.meta || !this.dataSource.latest || !this.dataSource.pollutant) {
74
- return [];
75
- }
76
- const levels = this.dataSource.meta.entryType(this.dataSource.pollutant).asIter.levels;
77
-
78
- return Array.from(
79
- this.dataSource.latest.values().map((m) => {
80
- const feature: MonitorMapFeature = {
81
- type: "Feature",
82
- properties: {
83
- icon: "outside-default-square",
84
- id: m.id,
85
- is_active: m.is_active,
86
- is_sjvair: m.is_sjvair,
87
- location: m.location,
88
- name: m.name,
89
- order: getOrder(m),
90
- type: m.type,
91
- value: m.latest.value
92
- },
93
- geometry: m.position! as Geometry
94
- };
95
-
96
- if (levels) {
97
- const level = getCurrentLevel(m.latest.value, levels);
98
- if (level) {
99
- feature.properties.icon = getIconId(m, level);
100
- }
101
- }
102
-
103
- return feature;
104
- })
105
- );
106
- });
107
-
108
- filters: FilterSpecification = $derived.by((): FilterSpecification => {
109
- const monitorFilters: ExpressionSpecification = ["any"];
110
- const locationFilters: ExpressionSpecification = [
111
- "any",
112
- ["==", ["get", "location"], "outside"]
113
- ];
114
- const statusFilters: ExpressionSpecification = ["any", ["==", ["get", "is_active"], true]];
115
-
116
- if (this.displayOptions.airnow.value) monitorFilters.push(filters.monitor("airnow"));
117
- if (this.displayOptions.aqlite.value) monitorFilters.push(filters.monitor("aqlite"));
118
- if (this.displayOptions.aqview.value) monitorFilters.push(filters.monitor("aqview"));
119
- if (this.displayOptions.bam1022.value) monitorFilters.push(filters.monitor("bam1022"));
120
- if (this.displayOptions.airgradient.value) monitorFilters.push(filters.monitor("airgradient"));
121
- if (this.displayOptions.purpleair.value) monitorFilters.push(filters.monitor("purpleair"));
122
- if (this.displayOptions.vozbox.value) monitorFilters.push(filters.monitor("vozbox"));
123
- if (this.displayOptions.inside.value)
124
- locationFilters.push(["==", ["get", "location"], "inside"]);
125
- if (this.displayOptions.inactive.value) statusFilters.push(["==", ["get", "is_active"], false]);
126
-
127
- return ["all", monitorFilters, locationFilters, statusFilters];
128
- });
129
-
130
- // Groups features by monitor type for per-type cluster sources, applying display option
131
- // filters so cluster aggregates only include visible monitors.
132
- featuresByType: Record<string, MonitorMapFeature[]> = $derived.by(() => {
133
- const opts = this.displayOptions;
134
- const byType: Record<string, MonitorMapFeature[]> = {};
135
-
136
- for (const feat of this.features) {
137
- const p = feat.properties;
138
-
139
- if (!p.is_active && !opts.inactive.value) continue;
140
- if (p.location === "inside" && !opts.inside.value) continue;
141
-
142
- const typeVisible: Partial<Record<MonitorType, boolean>> = {
143
- airnow: opts.airnow.value,
144
- aqlite: opts.aqlite.value,
145
- aqview: opts.aqview.value,
146
- bam1022: opts.bam1022.value,
147
- airgradient: opts.airgradient.value,
148
- purpleair: opts.purpleair.value,
149
- vozbox: opts.vozbox.value
150
- };
151
- if (!(typeVisible[p.type] ?? true)) continue;
152
-
153
- const arr = byType[p.type] ?? [];
154
- arr.push(feat);
155
- byType[p.type] = arr;
156
- }
157
- return byType;
158
- });
159
-
160
- clusterIconThresholds: Array<SJVAirEntryLevel> = $derived.by(() => {
161
- if (!this.dataSource.meta || !this.dataSource.pollutant) return [];
162
- return this.dataSource.meta.entryType(this.dataSource.pollutant).asIter.levels ?? [];
163
- });
164
-
165
- get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
166
- return {
167
- id: this.referenceId,
168
- type: "symbol",
169
- source: this.referenceId,
170
- filter: this.filters,
171
- layout: {
172
- "symbol-sort-key": ["coalesce", ["get", "order"], 0],
173
- "icon-allow-overlap": true,
174
- "icon-ignore-placement": true,
175
- "icon-image": ["get", "icon"],
176
- "icon-size": 1
177
- },
178
- paint: {}
179
- };
180
- }
181
-
182
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
183
- return {
184
- type: "geojson",
185
- promoteId: "id",
186
- data: {
187
- type: "FeatureCollection",
188
- features: this.features
189
- }
190
- };
191
- }
192
-
193
- constructor(private dataSource: MonitorsDataSource = monitorsManager) {
194
- super();
195
- this.icons = new MonitorsIconManager(dataSource);
196
-
197
- $effect.root(() => {
198
- // Every effect below early-returns while disabled (Pollutant = None) so a data refresh
199
- // can't re-add the layer. `enabled` is read untracked: the base class's own effect
200
- // already applies/removes on enabled changes, and tracking it here would apply twice.
201
- // The guard comes after each effect's tracked reads so its dependencies are always
202
- // collected; returning before reading anything would leave the effect permanently dead.
203
-
204
- // Push filter changes to the active layer(s) imperatively
205
- $effect(() => {
206
- const filter = this.filters;
207
- const clustered = this.clustered;
208
- if (!mapManager.map) return;
209
- if (!untrack(() => this.enabled)) return;
210
-
211
- if (clustered) {
212
- this.renderer.syncFilter();
213
- } else {
214
- if (mapManager.map.getLayer(this.referenceId)) {
215
- mapManager.map.setFilter(this.referenceId, filter);
216
- }
217
- }
218
- });
219
-
220
- // Sync unclustered source data when features change
221
- $effect(() => {
222
- const features = this.features;
223
- if (!mapManager.map || this.clustered) return;
224
- if (!untrack(() => this.enabled)) return;
225
- mapManager.setDataSource(this.referenceId, features);
226
- });
227
-
228
- // Keep cluster source data in sync when features or display options change
229
- $effect(() => {
230
- void this.featuresByType;
231
- if (!mapManager.map || !this.clustered) return;
232
- if (!untrack(() => this.enabled)) return;
233
- this.renderer.syncFeatures();
234
- });
235
-
236
- // Push updated icon expressions to cluster layers when the pollutant changes
237
- $effect(() => {
238
- const thresholds = this.clusterIconThresholds;
239
- if (!mapManager.map || !this.clustered || !thresholds.length) return;
240
- if (!untrack(() => this.enabled)) return;
241
- this.renderer.syncThresholds();
242
- });
243
-
244
- // Re-apply when clustered mode switches or when data first arrives (cold load).
245
- // featuresByType is tracked so this fires once data is available; untrack on apply()
246
- // prevents reactive reads inside it from leaking into this effect's dependency graph.
247
- $effect(() => {
248
- void this.clustered;
249
- const hasFeatures = Object.keys(this.featuresByType).length > 0;
250
- if (!untrack(() => mapManager.map) || !hasFeatures) return;
251
- if (!untrack(() => this.enabled)) return;
252
- untrack(() => this.apply());
253
- });
254
-
255
- // Sync selected icon scale via feature state
256
- $effect(() => {
257
- void this.selectedMonitorId;
258
- if (!mapManager.map) return;
259
- untrack(() => this.applySelectedState());
260
- });
261
- });
262
- }
263
-
264
- apply() {
265
- if (!mapManager.map) return;
266
-
267
- if (!this.tooltipManager.has(this.referenceId)) {
268
- this.tooltipManager.register(this.referenceId, monitorTooltip);
269
- }
270
-
271
- if (this.clustered) {
272
- if (mapManager.map.getLayer(this.referenceId)) mapManager.map.removeLayer(this.referenceId);
273
- if (mapManager.map.getSource(this.referenceId)) mapManager.map.removeSource(this.referenceId);
274
- clickManager.unregister([this.referenceId]);
275
- this.tooltipManager.disable();
276
- this.renderer.remove();
277
- // Clustering may be toggled again before the icons arrive; only the latest apply runs.
278
- this.supersedePending();
279
- this.whenIconsLoad(() => {
280
- this.renderer.apply(this.handleMonitorClick);
281
- this.tooltipManager.enable();
282
- this.applySelectedState();
283
- });
284
- } else {
285
- this.tooltipManager.disable();
286
- this.renderer.remove();
287
- this.tooltipManager.enable();
288
- clickManager.unregister([this.referenceId]);
289
- super.apply();
290
- this.whenIconsLoad(() => {
291
- clickManager.register([this.referenceId], this.handleMonitorClick);
292
- this.applySelectedState();
293
- });
294
- }
295
- }
296
-
297
- remove() {
298
- this.tooltipManager.disable();
299
- clickManager.unregister([this.referenceId]);
300
- this.renderer.remove();
301
- super.remove();
302
- }
303
-
304
- private applySelectedState(): void {
305
- if (!mapManager.map) return;
306
- const layerIds = this.clustered ? this.renderer.unclusteredLayerIds : [this.referenceId];
307
- const iconSize = this.selectedMonitorId
308
- ? (["match", ["get", "id"], this.selectedMonitorId, 1.3, 1] as ExpressionSpecification)
309
- : 1;
310
- for (const layerId of layerIds) {
311
- if (!mapManager.map.getLayer(layerId)) continue;
312
- mapManager.map.setLayoutProperty(layerId, "icon-size", iconSize);
313
- }
314
- }
315
- }
316
-
317
- export const monitorsMapIntegration = new MonitorsMapIntegration();
318
- export { MonitorsMapIntegration };
@@ -1,28 +0,0 @@
1
- import { monitorsManager } from "$lib/monitors/monitors.svelte";
2
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
3
- import { collocationSitesMapIntegration } from "$lib/collocation-sites/collocations-map-integration.svelte";
4
- import { hmsFireMapIntegration } from "$lib/hms/hms-fire-map-integration.svelte";
5
- import { hmsSmokeMapIntegration } from "$lib/hms/hms-smoke-map-integration.svelte";
6
- import { evStationsMapIntegration } from "$lib/ev-stations/ev-stations-map-integration.svelte";
7
- import { windMapIntegration } from "$lib/wind/wind.svelte";
8
- import { mapStyleState } from "$lib/map/map-style-state.svelte.js";
9
- import { applyMapOptionDefaults } from "./map-option-defaults";
10
-
11
- /** "Reset Map Options": restores every map option to its default. */
12
- export function resetMapOptions(): void {
13
- applyMapOptionDefaults(
14
- {
15
- manager: monitorsManager,
16
- integration: monitorsMapIntegration,
17
- collocationSites: collocationSitesMapIntegration,
18
- hmsFire: hmsFireMapIntegration,
19
- hmsSmoke: hmsSmokeMapIntegration,
20
- evStations: evStationsMapIntegration,
21
- wind: windMapIntegration,
22
- // The basemap reset may start loading a new style; integrations skip the style while
23
- // mapManager.styleLoading is set and the refresh on "style.load" re-applies them.
24
- mapStyle: mapStyleState
25
- },
26
- monitorsManager.meta?.default_pollutant ?? null
27
- );
28
- }
@@ -1,82 +0,0 @@
1
- //class ReactiveState<T> {
2
- // private internalValue?: T = $state();
3
- //
4
- // constructor(initialValue?: T) {
5
- // this.internalValue = initialValue;
6
- // }
7
- // get value(): T | undefined {
8
- // return this.internalValue;
9
- // }
10
- //
11
- // set value(newValue: T) {
12
- // // Perform any validation or side effects here
13
- // this.internalValue = newValue; // Update the reactive state
14
- // }
15
- //}
16
- //
17
- //class ShallowReactiveState<T> {
18
- // private internalValue?: T = $state.raw();
19
- //
20
- // constructor(initialValue?: T) {
21
- // this.internalValue = initialValue;
22
- // }
23
- // get value(): T | undefined {
24
- // return this.internalValue;
25
- // }
26
- //
27
- // set value(newValue: T) {
28
- // // Perform any validation or side effects here
29
- // this.internalValue = newValue; // Update the reactive state
30
- // }
31
- //}
32
- //
33
- //
34
- //class DerivedReactiveState<T> {
35
- // private internalValue?: T;
36
- //
37
- // constructor(expression: () => T) {
38
- // this.internalValue = $derived.by(expression);
39
- // }
40
- // get value(): T | undefined {
41
- // return this.internalValue;
42
- // }
43
- // set value(newValue: T) {
44
- // this.internalValue = newValue;
45
- // }
46
- //}
47
- //
48
- //export function Derived<V>(value: () => V) {
49
- // let ref: DerivedReactiveState<V> = new DerivedReactiveState<V>(value);
50
- // return function <V, T = unknown>(
51
- // _target: ClassAccessorDecoratorTarget<T, V>,
52
- // _ctx: ClassAccessorDecoratorContext,
53
- // ): ClassAccessorDecoratorResult<T, V> {
54
- //
55
- // return {
56
- // get() {
57
- // return ref.value as V;
58
- // },
59
- // };
60
- // }
61
- //}
62
- //export function Reactive(shallow?: boolean) {
63
- // return function <V, T = unknown>(
64
- // _target: ClassAccessorDecoratorTarget<T, V>,
65
- // _ctx: ClassAccessorDecoratorContext,
66
- // ): ClassAccessorDecoratorResult<T, V> {
67
- // let ref = shallow ? new ShallowReactiveState<V>() : new ReactiveState<V>();
68
- //
69
- // return {
70
- // get(): V {
71
- // return ref.value as V;
72
- // },
73
- // set(newValue): void {
74
- // ref.value = newValue;
75
- // },
76
- // init(initialValue: V): V {
77
- // ref.value = initialValue;
78
- // return ref.value as V;
79
- // }
80
- // };
81
- // };
82
- //}
@@ -1,20 +0,0 @@
1
- import { Map as MaptilerMap } from "@maptiler/sdk";
2
- import { WindLayer } from "@maptiler/weather";
3
- import { baseLayerSeperator } from "../map/integrations/base-layer-seperator";
4
- import { MapLayerIntegration } from "$lib/map/integrations/map-layer-integration.svelte";
5
-
6
- class WindMapIntegration extends MapLayerIntegration {
7
- referenceId: string = "MapTiler Wind";
8
-
9
- beforeLayer = baseLayerSeperator.referenceId;
10
-
11
- enabled: boolean = $state(false);
12
-
13
- mapLayer = new WindLayer({
14
- id: this.referenceId,
15
- opacity: 0.5
16
- }) as unknown as Parameters<MaptilerMap["addLayer"]>[0];
17
- }
18
-
19
- export const windMapIntegration = new WindMapIntegration();
20
- export type { WindMapIntegration };