@sjvair/monitor-map 4.3.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 (254) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +59 -138
  3. package/dist/lib/MonitorDetailPanel.svelte +23 -20
  4. package/dist/lib/MonitorMapLayout.svelte +147 -77
  5. package/dist/lib/components/DataBox.svelte +4 -1
  6. package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
  7. package/dist/lib/config.svelte.d.ts +7 -0
  8. package/dist/lib/config.svelte.js +10 -0
  9. package/dist/lib/core.d.ts +2 -2
  10. package/dist/lib/core.js +2 -1
  11. package/dist/lib/data-chart/DataChart.svelte +40 -25
  12. package/dist/lib/data-chart/DateRange.d.ts +13 -4
  13. package/dist/lib/data-chart/DateRange.js +50 -14
  14. package/dist/lib/data-chart/chart-utils.js +5 -7
  15. package/dist/lib/data-chart/level-colors.d.ts +3 -4
  16. package/dist/lib/data-chart/level-colors.js +7 -4
  17. package/dist/lib/data-chart/pacific-time.d.ts +9 -0
  18. package/dist/lib/data-chart/pacific-time.js +25 -0
  19. package/dist/lib/data-chart/tooltip.js +2 -2
  20. package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
  21. package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
  22. package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
  23. package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
  24. package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
  25. package/dist/lib/ev-stations/ev-station-icons.js +1 -13
  26. package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
  27. package/dist/lib/ev-stations/ev-stations.js +53 -0
  28. package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
  29. package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
  30. package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
  31. package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
  32. package/dist/lib/ev-stations/types.d.ts +0 -29
  33. package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
  34. package/dist/lib/ev-stations/zip-codes.js +28 -16
  35. package/dist/lib/index.d.ts +8 -56
  36. package/dist/lib/index.js +7 -46
  37. package/dist/lib/legend/Legend.svelte +18 -19
  38. package/dist/lib/legend/Legend.svelte.d.ts +2 -2
  39. package/dist/lib/levels.d.ts +7 -0
  40. package/dist/lib/levels.js +19 -0
  41. package/dist/lib/map/MapShell.svelte +17 -24
  42. package/dist/lib/map/MapShell.svelte.d.ts +4 -4
  43. package/dist/lib/map/basemaps.d.ts +25 -0
  44. package/dist/lib/map/basemaps.js +66 -0
  45. package/dist/lib/map/live-map.svelte.d.ts +3 -0
  46. package/dist/lib/map/live-map.svelte.js +22 -0
  47. package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
  48. package/dist/lib/map/sync-plugins.svelte.js +52 -0
  49. package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
  50. package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
  51. package/dist/lib/monitors/filtered-source.svelte.js +28 -0
  52. package/dist/lib/monitors/monitors.svelte.d.ts +25 -35
  53. package/dist/lib/monitors/monitors.svelte.js +35 -218
  54. package/dist/lib/monitors/retry-load.d.ts +8 -0
  55. package/dist/lib/monitors/retry-load.js +11 -0
  56. package/dist/lib/monitors/types.d.ts +1 -41
  57. package/dist/lib/options/map-option-defaults.d.ts +25 -36
  58. package/dist/lib/options/map-option-defaults.js +12 -30
  59. package/dist/lib/options/map-options.svelte.d.ts +13 -0
  60. package/dist/lib/options/map-options.svelte.js +26 -0
  61. package/dist/lib/options/menus/LayersMenu.svelte +8 -20
  62. package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
  63. package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
  64. package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
  65. package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
  66. package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
  67. package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
  68. package/dist/lib/options/plugin-order.d.ts +8 -0
  69. package/dist/lib/options/plugin-order.js +36 -0
  70. package/dist/lib/options/pollutant-param.d.ts +7 -5
  71. package/dist/lib/options/pollutant-param.js +9 -5
  72. package/dist/lib/options/row-icons.d.ts +8 -0
  73. package/dist/lib/options/row-icons.js +15 -0
  74. package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
  75. package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
  76. package/dist/lib/search/Search.svelte +20 -33
  77. package/dist/lib/search/Search.svelte.d.ts +5 -1
  78. package/dist/lib/search/search-pin.svelte.d.ts +11 -0
  79. package/dist/lib/search/search-pin.svelte.js +28 -0
  80. package/dist/lib/search/service.d.ts +7 -2
  81. package/dist/lib/search/service.js +17 -10
  82. package/dist/lib/stores.svelte.d.ts +8 -0
  83. package/dist/lib/stores.svelte.js +9 -0
  84. package/dist/lib/wind/wind-plugin.d.ts +11 -0
  85. package/dist/lib/wind/wind-plugin.js +25 -0
  86. package/package.json +12 -10
  87. package/src/lib/MonitorDetailPanel.svelte +23 -20
  88. package/src/lib/MonitorMapLayout.svelte +147 -77
  89. package/src/lib/components/DataBox.svelte +4 -1
  90. package/src/lib/config.svelte.ts +16 -0
  91. package/src/lib/core.ts +2 -2
  92. package/src/lib/data-chart/DataChart.svelte +40 -25
  93. package/src/lib/data-chart/DateRange.ts +58 -15
  94. package/src/lib/data-chart/chart-utils.ts +6 -7
  95. package/src/lib/data-chart/level-colors.ts +8 -9
  96. package/src/lib/data-chart/pacific-time.ts +31 -0
  97. package/src/lib/data-chart/tooltip.ts +2 -2
  98. package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
  99. package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
  100. package/src/lib/ev-stations/ev-station-icons.ts +1 -17
  101. package/src/lib/ev-stations/ev-stations.ts +72 -0
  102. package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
  103. package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
  104. package/src/lib/ev-stations/types.ts +0 -34
  105. package/src/lib/ev-stations/zip-codes.ts +29 -16
  106. package/src/lib/index.ts +8 -99
  107. package/src/lib/legend/Legend.svelte +18 -19
  108. package/src/lib/levels.ts +24 -0
  109. package/src/lib/map/MapShell.svelte +17 -24
  110. package/src/lib/map/basemaps.ts +76 -0
  111. package/src/lib/map/live-map.svelte.ts +24 -0
  112. package/src/lib/map/sync-plugins.svelte.ts +69 -0
  113. package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +66 -61
  114. package/src/lib/monitors/filtered-source.svelte.ts +38 -0
  115. package/src/lib/monitors/monitors.svelte.ts +58 -261
  116. package/src/lib/monitors/retry-load.ts +14 -0
  117. package/src/lib/monitors/types.ts +1 -47
  118. package/src/lib/options/map-option-defaults.ts +30 -53
  119. package/src/lib/options/map-options.svelte.ts +30 -0
  120. package/src/lib/options/menus/LayersMenu.svelte +8 -20
  121. package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
  122. package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
  123. package/src/lib/options/menus/PollutantMenu.svelte +4 -9
  124. package/src/lib/options/menus/SettingsMenu.svelte +32 -25
  125. package/src/lib/options/plugin-order.ts +38 -0
  126. package/src/lib/options/pollutant-param.ts +14 -9
  127. package/src/lib/options/row-icons.ts +17 -0
  128. package/src/lib/options/rows/MarkerIcon.svelte +5 -5
  129. package/src/lib/search/Search.svelte +20 -33
  130. package/src/lib/search/search-pin.svelte.ts +35 -0
  131. package/src/lib/search/service.ts +34 -15
  132. package/src/lib/stores.svelte.ts +15 -0
  133. package/src/lib/wind/wind-plugin.ts +33 -0
  134. package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  135. package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
  136. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
  137. package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
  138. package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
  139. package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
  140. package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -10
  141. package/dist/lib/collocation-sites/collocations.svelte.js +0 -21
  142. package/dist/lib/collocation-sites/types.d.ts +0 -8
  143. package/dist/lib/collocation-sites/types.js +0 -1
  144. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  145. package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
  146. package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
  147. package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
  148. package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
  149. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
  150. package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
  151. package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -10
  152. package/dist/lib/ev-stations/ev-stations.svelte.js +0 -57
  153. package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
  154. package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
  155. package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
  156. package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
  157. package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
  158. package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
  159. package/dist/lib/hms/hms.svelte.d.ts +0 -20
  160. package/dist/lib/hms/hms.svelte.js +0 -237
  161. package/dist/lib/legend/LegendBar.svelte +0 -45
  162. package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
  163. package/dist/lib/legend/legend-data.d.ts +0 -40
  164. package/dist/lib/legend/legend-data.js +0 -65
  165. package/dist/lib/map/Map.svelte +0 -13
  166. package/dist/lib/map/Map.svelte.d.ts +0 -8
  167. package/dist/lib/map/basemap.d.ts +0 -14
  168. package/dist/lib/map/basemap.js +0 -10
  169. package/dist/lib/map/icons.d.ts +0 -7
  170. package/dist/lib/map/icons.js +0 -33
  171. package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
  172. package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
  173. package/dist/lib/map/integrations/click-manager.d.ts +0 -14
  174. package/dist/lib/map/integrations/click-manager.js +0 -52
  175. package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
  176. package/dist/lib/map/integrations/integrations-manager.js +0 -20
  177. package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
  178. package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
  179. package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
  180. package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
  181. package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
  182. package/dist/lib/map/integrations/map-icon-manager.js +0 -52
  183. package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
  184. package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
  185. package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
  186. package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
  187. package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
  188. package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
  189. package/dist/lib/map/integrations/types.d.ts +0 -14
  190. package/dist/lib/map/integrations/types.js +0 -1
  191. package/dist/lib/map/map-manager.svelte.d.ts +0 -20
  192. package/dist/lib/map/map-manager.svelte.js +0 -63
  193. package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
  194. package/dist/lib/map/map-style-state.svelte.js +0 -86
  195. package/dist/lib/map/utils.d.ts +0 -7
  196. package/dist/lib/map/utils.js +0 -36
  197. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  198. package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
  199. package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
  200. package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
  201. package/dist/lib/monitors/monitor-utils.d.ts +0 -4
  202. package/dist/lib/monitors/monitor-utils.js +0 -41
  203. package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
  204. package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
  205. package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
  206. package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
  207. package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
  208. package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
  209. package/dist/lib/options/reset-map-options.d.ts +0 -2
  210. package/dist/lib/options/reset-map-options.js +0 -24
  211. package/dist/lib/reactivity.svelte.d.ts +0 -0
  212. package/dist/lib/reactivity.svelte.js +0 -83
  213. package/dist/lib/wind/wind.svelte.d.ts +0 -10
  214. package/dist/lib/wind/wind.svelte.js +0 -13
  215. package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
  216. package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
  217. package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
  218. package/src/lib/collocation-sites/collocations.svelte.ts +0 -27
  219. package/src/lib/collocation-sites/types.ts +0 -10
  220. package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
  221. package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
  222. package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
  223. package/src/lib/ev-stations/ev-stations.svelte.ts +0 -84
  224. package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
  225. package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
  226. package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
  227. package/src/lib/hms/hms.svelte.ts +0 -270
  228. package/src/lib/legend/LegendBar.svelte +0 -45
  229. package/src/lib/legend/legend-data.ts +0 -101
  230. package/src/lib/map/Map.svelte +0 -13
  231. package/src/lib/map/basemap.ts +0 -24
  232. package/src/lib/map/icons.ts +0 -42
  233. package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
  234. package/src/lib/map/integrations/click-manager.ts +0 -64
  235. package/src/lib/map/integrations/integrations-manager.ts +0 -25
  236. package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
  237. package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
  238. package/src/lib/map/integrations/map-icon-manager.ts +0 -67
  239. package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
  240. package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
  241. package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
  242. package/src/lib/map/integrations/types.ts +0 -24
  243. package/src/lib/map/map-manager.svelte.ts +0 -83
  244. package/src/lib/map/map-style-state.svelte.ts +0 -94
  245. package/src/lib/map/utils.ts +0 -53
  246. package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
  247. package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
  248. package/src/lib/monitors/monitor-utils.ts +0 -57
  249. package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
  250. package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
  251. package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
  252. package/src/lib/options/reset-map-options.ts +0 -28
  253. package/src/lib/reactivity.svelte.ts +0 -82
  254. package/src/lib/wind/wind.svelte.ts +0 -20
@@ -1,136 +0,0 @@
1
- import { type Popup, type MapLayerEventType, type Map as MaptilerMap } from "@maptiler/sdk";
2
- import type { Geometry } from "geojson";
3
- import { mapManager } from "$lib/map/map-manager.svelte.js";
4
- import { MapIconLayerIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
5
- import { CollocationIconManager, getCollocationIconId } from "./collocations-icon-manager.svelte";
6
- import { TooltipManager } from "$lib/map/integrations/tooltip.svelte";
7
- import { collocationSitesManager } from "./collocations.svelte";
8
- import { monitorsManager } from "$lib/monitors/monitors.svelte";
9
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
10
- import type { CollocationSiteMapFeature, CollocationSiteMarkerProperties } from "./types";
11
- import { untrack } from "svelte";
12
- import CollocationTooltip from "./CollocationTooltip.svelte";
13
- import { mountPopup } from "$lib/map/utils";
14
-
15
- function collocationTooltip(evt: MapLayerEventType["mousemove"] & object): Popup | void {
16
- const feature = evt.features?.[0] as unknown as CollocationSiteMapFeature | undefined;
17
- if (!feature) return;
18
- return mountPopup(CollocationTooltip, { feature }, evt.lngLat);
19
- }
20
-
21
- class CollocationSitesMapIntegration extends MapIconLayerIntegration<CollocationSiteMarkerProperties> {
22
- referenceId: string = "collocation-sites";
23
- enabled: boolean = $state(false);
24
-
25
- icons: CollocationIconManager = new CollocationIconManager(monitorsManager);
26
- tooltipManager: TooltipManager = new TooltipManager();
27
-
28
- /** The option only applies while monitors are shown with PM2.5. */
29
- get available(): boolean {
30
- return monitorsMapIntegration.enabled && monitorsManager.pollutant === "pm25";
31
- }
32
-
33
- features: Array<CollocationSiteMapFeature> = $derived.by(() => {
34
- if (
35
- !monitorsManager.meta ||
36
- !monitorsManager.pollutant ||
37
- !collocationSitesManager.collocationSites ||
38
- monitorsMapIntegration.features.length === 0
39
- ) {
40
- return [];
41
- }
42
-
43
- return collocationSitesManager.collocationSites
44
- .map((c) => {
45
- const referenceMonitorFeature = monitorsMapIntegration.features.find(
46
- (m) => m.properties.id === c.reference_id
47
- );
48
-
49
- if (!referenceMonitorFeature) return;
50
-
51
- const feature: CollocationSiteMapFeature = {
52
- type: "Feature",
53
- properties: {
54
- icon: getCollocationIconId(referenceMonitorFeature!.properties.icon),
55
- colocated_id: c.colocated_id,
56
- reference_id: c.reference_id,
57
- name: c.name
58
- },
59
- geometry: c.position! as Geometry
60
- };
61
-
62
- return feature;
63
- })
64
- .filter((f) => f !== undefined);
65
- });
66
-
67
- mapLayer: Parameters<MaptilerMap["addLayer"]>[0] = {
68
- id: this.referenceId,
69
- type: "symbol",
70
- source: this.referenceId,
71
- layout: {
72
- "icon-allow-overlap": true,
73
- "icon-ignore-placement": true,
74
- "icon-image": ["get", "icon"],
75
- "icon-size": 1
76
- },
77
- paint: {}
78
- };
79
-
80
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
81
- return {
82
- type: "geojson",
83
- promoteId: "id",
84
- data: {
85
- type: "FeatureCollection",
86
- features: this.features
87
- }
88
- };
89
- }
90
-
91
- constructor() {
92
- super();
93
-
94
- $effect.root(() => {
95
- $effect(() => {
96
- const features = this.features;
97
- if (!mapManager.map || !this.enabled) return;
98
- mapManager.setDataSource(this.referenceId, features);
99
- });
100
-
101
- // Remove the layer while the option is unavailable (Ozone or Pollutant = None); restore
102
- // it when it becomes available again if the user still has it checked.
103
- $effect(() => {
104
- if (!this.available) {
105
- untrack(() => this.remove());
106
- } else if (untrack(() => this.enabled)) {
107
- untrack(() => this.apply());
108
- }
109
- });
110
- });
111
- }
112
-
113
- apply() {
114
- if (!this.available) {
115
- this.remove();
116
- return;
117
- }
118
- if (!mapManager.map) return;
119
-
120
- if (!this.tooltipManager.has(this.referenceId)) {
121
- this.tooltipManager.register(this.referenceId, collocationTooltip, Number.MAX_SAFE_INTEGER);
122
- }
123
-
124
- this.tooltipManager.enable();
125
-
126
- super.apply();
127
- }
128
-
129
- remove() {
130
- this.tooltipManager.disable();
131
- super.remove();
132
- }
133
- }
134
-
135
- export const collocationSitesMapIntegration = new CollocationSitesMapIntegration();
136
- export type { CollocationSitesMapIntegration as MonitorsMapIntegration };
@@ -1,27 +0,0 @@
1
- import { getCollocationSites, type CollocationSite } from "@sjvair/sdk";
2
- import type { SignalOptions } from "$lib/abort";
3
-
4
- class CollocationSitesManager {
5
- initialized: boolean = $state(false);
6
-
7
- collocationSites: Array<CollocationSite> | null = $state(null);
8
-
9
- async init({ signal }: SignalOptions = {}): Promise<void> {
10
- if (this.initialized) return;
11
- if (signal?.aborted) throw signal.reason;
12
-
13
- this.collocationSites = await getCollocationSites({ signal });
14
-
15
- this.initialized = true;
16
- }
17
-
18
- async update({ signal }: SignalOptions = {}): Promise<void> {
19
- if (!this.initialized) return;
20
- if (signal?.aborted) throw signal.reason;
21
-
22
- this.collocationSites = await getCollocationSites({ signal });
23
- }
24
- }
25
-
26
- export const collocationSitesManager = new CollocationSitesManager();
27
- export type { CollocationSitesManager };
@@ -1,10 +0,0 @@
1
- import type { Feature, Geometry } from "geojson";
2
-
3
- export type CollocationSiteMapFeature = Feature<Geometry, CollocationSiteMarkerProperties>;
4
-
5
- export interface CollocationSiteMarkerProperties {
6
- icon: string;
7
- colocated_id: string;
8
- reference_id: string;
9
- name: string;
10
- }
@@ -1,16 +0,0 @@
1
- <script lang="ts">
2
- import type { EvStationClusterMapFeature } from "../types";
3
-
4
- interface Props {
5
- feature: EvStationClusterMapFeature;
6
- }
7
-
8
- const { feature }: Props = $props();
9
-
10
- const levelLabel = $derived(feature.source.includes("lvl2") ? "Level 2" : "Level 3");
11
- </script>
12
-
13
- <div class="flex flex-col gap-1 p-1">
14
- <p class="font-semibold">{levelLabel} Chargers</p>
15
- <p class="text-sm">{feature.properties.point_count} stations</p>
16
- </div>
@@ -1,162 +0,0 @@
1
- import type { GeoJSONSource, MapLayerEventType, Popup } from "@maptiler/sdk";
2
- import type { Point } from "geojson";
3
- import { mapManager } from "$lib/map/map-manager.svelte.js";
4
- import { mountPopup } from "$lib/map/utils";
5
- import { clickManager, type ClickHandler } from "$lib/map/integrations/click-manager";
6
- import { TooltipManager } from "$lib/map/integrations/tooltip.svelte";
7
- import { EV_STATION_LEVELS, type EvStationLevel } from "./ev-station-icons";
8
- import type { EvStationClusterMapFeature, EvStationMapFeature } from "./types";
9
- import EvStationClusterTooltip from "./components/EvStationClusterTooltip.svelte";
10
-
11
- export interface EvStationsClusterContext {
12
- referenceId: string;
13
- featuresByLevel: Record<string, EvStationMapFeature[]>;
14
- beforeLayer?: string;
15
- tooltipManager: TooltipManager;
16
- }
17
-
18
- function clusterTooltip(evt: MapLayerEventType["mousemove"] & object): Popup | void {
19
- const feature = evt.features?.[0] as unknown as EvStationClusterMapFeature | undefined;
20
- if (!feature) return;
21
- return mountPopup(EvStationClusterTooltip, { feature }, evt.lngLat);
22
- }
23
-
24
- export class EvStationsClusterRenderer {
25
- private _levels: EvStationLevel[] = [];
26
-
27
- constructor(private ctx: EvStationsClusterContext) {}
28
-
29
- apply(stationClickHandler: ClickHandler): void {
30
- if (!mapManager.map) return;
31
-
32
- for (const level of Object.keys(EV_STATION_LEVELS) as EvStationLevel[]) {
33
- const sourceId = `${this.ctx.referenceId}-${level}`;
34
- const features = this.ctx.featuresByLevel[level] ?? [];
35
- const { bg } = EV_STATION_LEVELS[level];
36
- const clusterBg = bg.replace("rgb(", "rgba(").replace(")", ", 0.6)");
37
-
38
- mapManager.map.addSource(sourceId, {
39
- type: "geojson",
40
- promoteId: "id",
41
- data: { type: "FeatureCollection", features },
42
- cluster: true,
43
- clusterRadius: 40,
44
- clusterMaxZoom: 9
45
- });
46
-
47
- mapManager.map.addLayer(
48
- {
49
- id: `${sourceId}-cluster-circle`,
50
- type: "circle",
51
- source: sourceId,
52
- filter: ["has", "point_count"],
53
- paint: {
54
- "circle-color": clusterBg,
55
- "circle-radius": ["step", ["get", "point_count"], 15, 10, 20, 25, 25]
56
- }
57
- },
58
- this.ctx.beforeLayer
59
- );
60
-
61
- mapManager.map.addLayer(
62
- {
63
- id: `${sourceId}-cluster-label`,
64
- type: "symbol",
65
- source: sourceId,
66
- filter: ["has", "point_count"],
67
- layout: {
68
- "text-field": ["get", "point_count_abbreviated"],
69
- "text-size": 12,
70
- "text-allow-overlap": true,
71
- "text-ignore-placement": true
72
- },
73
- paint: { "text-color": "#ffffff" }
74
- },
75
- this.ctx.beforeLayer
76
- );
77
-
78
- mapManager.map.addLayer(
79
- {
80
- id: `${sourceId}-unclustered`,
81
- type: "symbol",
82
- source: sourceId,
83
- filter: ["!", ["has", "point_count"]],
84
- layout: {
85
- "icon-image": `ev-station-${level}`,
86
- "icon-allow-overlap": true,
87
- "icon-ignore-placement": true,
88
- "icon-size": 1
89
- },
90
- paint: {}
91
- },
92
- this.ctx.beforeLayer
93
- );
94
-
95
- const circleLayerId = `${sourceId}-cluster-circle`;
96
- const unclusteredLayerId = `${sourceId}-unclustered`;
97
-
98
- if (!this.ctx.tooltipManager.has(circleLayerId)) {
99
- this.ctx.tooltipManager.register(circleLayerId, clusterTooltip);
100
- }
101
-
102
- clickManager.register([circleLayerId], this.makeClusterClickHandler(sourceId));
103
- clickManager.register([unclusteredLayerId], stationClickHandler);
104
-
105
- this._levels.push(level);
106
- }
107
- }
108
-
109
- remove(): void {
110
- if (!mapManager.map) return;
111
-
112
- for (const level of this._levels) {
113
- const sourceId = `${this.ctx.referenceId}-${level}`;
114
- const layerIds = [
115
- `${sourceId}-cluster-circle`,
116
- `${sourceId}-cluster-label`,
117
- `${sourceId}-unclustered`
118
- ];
119
-
120
- for (const layerId of layerIds) {
121
- clickManager.unregister([layerId]);
122
- if (mapManager.map.getLayer(layerId)) {
123
- mapManager.map.removeLayer(layerId);
124
- }
125
- }
126
-
127
- if (mapManager.map.getSource(sourceId)) {
128
- mapManager.map.removeSource(sourceId);
129
- }
130
- }
131
-
132
- this._levels = [];
133
- }
134
-
135
- get unclusteredLayerIds(): string[] {
136
- return this._levels.map((level) => `${this.ctx.referenceId}-${level}-unclustered`);
137
- }
138
-
139
- syncFeatures(): void {
140
- if (!mapManager.map) return;
141
- for (const level of this._levels) {
142
- const sourceId = `${this.ctx.referenceId}-${level}`;
143
- mapManager.setDataSource(sourceId, this.ctx.featuresByLevel[level] ?? []);
144
- }
145
- }
146
-
147
- private makeClusterClickHandler(sourceId: string): ClickHandler {
148
- return async (features) => {
149
- try {
150
- const feature = features[0];
151
- if (!feature?.properties?.cluster_id) return;
152
- const source = mapManager.map?.getSource(sourceId) as GeoJSONSource | undefined;
153
- if (!source) return;
154
- const zoom = await source.getClusterExpansionZoom(feature.properties.cluster_id);
155
- const coords = (feature.geometry as Point).coordinates as [number, number];
156
- mapManager.map?.easeTo({ center: coords, zoom });
157
- } catch (err) {
158
- console.error("EvStations: cluster expansion zoom failed", err);
159
- }
160
- };
161
- }
162
- }
@@ -1,248 +0,0 @@
1
- import type {
2
- ExpressionSpecification,
3
- FilterSpecification,
4
- Map as MaptilerMap,
5
- Popup
6
- } from "@maptiler/sdk";
7
- import type { Geometry } from "geojson";
8
- import { untrack } from "svelte";
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 { MapDisplayOption } from "$lib/map/integrations/map-display-option.svelte";
13
- import { TooltipManager } from "$lib/map/integrations/tooltip.svelte";
14
- import { mountClickPopup } from "$lib/map/utils";
15
- import { evStationsManager } from "./ev-stations.svelte";
16
- import { EvStationIconManager } from "./ev-station-icons";
17
- import {
18
- EvStationsClusterRenderer,
19
- type EvStationsClusterContext
20
- } from "./ev-stations-cluster-renderer";
21
- import type { EvStation, EvStationMapFeature, EvStationMarkerProperties } from "./types";
22
- import EvStationTooltip from "./components/EvStationTooltip.svelte";
23
-
24
- class EvStationsMapIntegration
25
- extends MapIconLayerIntegration<EvStationMarkerProperties>
26
- implements EvStationsClusterContext
27
- {
28
- referenceId: string = "ev-stations";
29
- enabled: boolean = $state(false);
30
- clustered: boolean = $state(true);
31
-
32
- icons: EvStationIconManager = new EvStationIconManager();
33
- tooltipManager: TooltipManager = new TooltipManager();
34
- private renderer: EvStationsClusterRenderer = new EvStationsClusterRenderer(this);
35
- private currentPopup: Popup | null = null;
36
-
37
- displayOptions = $derived.by(() => ({
38
- lvl2: new MapDisplayOption("Level 2", true, this.icons.get("ev-station-lvl2")!),
39
- lvl3: new MapDisplayOption("Level 3", true, this.icons.get("ev-station-lvl3")!)
40
- }));
41
-
42
- features: Array<EvStationMapFeature> = $derived.by(() => {
43
- const result: EvStationMapFeature[] = [];
44
- if (evStationsManager.lvl2Stations) {
45
- for (const station of evStationsManager.lvl2Stations) {
46
- result.push(toFeature(station, "lvl2"));
47
- }
48
- }
49
- if (evStationsManager.lvl3Stations) {
50
- for (const station of evStationsManager.lvl3Stations) {
51
- result.push(toFeature(station, "lvl3"));
52
- }
53
- }
54
- return result;
55
- });
56
-
57
- featuresByLevel: Record<string, EvStationMapFeature[]> = $derived.by(() => {
58
- const byLevel: Record<string, EvStationMapFeature[]> = {};
59
- for (const feature of this.features) {
60
- const level = feature.properties.level;
61
- if (level === "lvl2" && !this.displayOptions.lvl2.value) continue;
62
- if (level === "lvl3" && !this.displayOptions.lvl3.value) continue;
63
- const arr = byLevel[level] ?? [];
64
- arr.push(feature);
65
- byLevel[level] = arr;
66
- }
67
- return byLevel;
68
- });
69
-
70
- filters: FilterSpecification = $derived.by((): FilterSpecification => {
71
- const levels: string[] = [];
72
- if (this.displayOptions.lvl2.value) levels.push("lvl2");
73
- if (this.displayOptions.lvl3.value) levels.push("lvl3");
74
- if (levels.length === 0) return ["==", ["get", "level"], "none"] as ExpressionSpecification;
75
- if (levels.length === 2) return ["boolean", true] as ExpressionSpecification;
76
- return ["==", ["get", "level"], levels[0]] as ExpressionSpecification;
77
- });
78
-
79
- private handleStationClick: ClickHandler = (features, evt) => {
80
- this.currentPopup?.remove();
81
- const feature = features[0] as unknown as EvStationMapFeature | undefined;
82
- if (!feature) return;
83
- this.currentPopup = mountClickPopup(EvStationTooltip, { feature }, evt.lngLat);
84
- this.currentPopup.addTo(mapManager.map!);
85
- };
86
-
87
- get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
88
- return {
89
- id: this.referenceId,
90
- type: "symbol",
91
- source: this.referenceId,
92
- filter: this.filters,
93
- layout: {
94
- "icon-allow-overlap": true,
95
- "icon-ignore-placement": true,
96
- "icon-image": ["get", "icon"],
97
- "icon-size": 1
98
- },
99
- paint: {}
100
- };
101
- }
102
-
103
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
104
- return {
105
- type: "geojson",
106
- promoteId: "id",
107
- data: {
108
- type: "FeatureCollection",
109
- features: this.features
110
- }
111
- };
112
- }
113
-
114
- constructor() {
115
- super();
116
-
117
- $effect.root(() => {
118
- // Lazy-fetch level data only once the integration is enabled and the level is on
119
- $effect(() => {
120
- if (
121
- this.enabled &&
122
- this.displayOptions.lvl2.value &&
123
- evStationsManager.lvl2Stations === undefined
124
- ) {
125
- evStationsManager.loadLvl2Stations();
126
- }
127
- });
128
-
129
- $effect(() => {
130
- if (
131
- this.enabled &&
132
- this.displayOptions.lvl3.value &&
133
- evStationsManager.lvl3Stations === undefined
134
- ) {
135
- evStationsManager.loadLvl3Stations();
136
- }
137
- });
138
-
139
- // Every effect below early-returns while disabled so a data refresh can't re-add the layer.
140
- // `enabled` is read untracked: the base class's own effect already applies/removes on
141
- // enabled changes, and tracking it here would apply twice. The guard comes after each
142
- // effect's tracked reads so its dependencies are always collected; returning before
143
- // reading anything would leave the effect permanently dead.
144
-
145
- // Push filter changes to the unclustered layer
146
- $effect(() => {
147
- const filter = this.filters;
148
- if (!mapManager.map || this.clustered) return;
149
- if (!untrack(() => this.enabled)) return;
150
- if (mapManager.map.getLayer(this.referenceId)) {
151
- mapManager.map.setFilter(this.referenceId, filter);
152
- }
153
- });
154
-
155
- // Sync unclustered source when features change
156
- $effect(() => {
157
- const features = this.features;
158
- if (!mapManager.map || this.clustered) return;
159
- if (!untrack(() => this.enabled)) return;
160
- mapManager.setDataSource(this.referenceId, features);
161
- });
162
-
163
- // Sync clustered sources when featuresByLevel changes
164
- $effect(() => {
165
- void this.featuresByLevel;
166
- if (!mapManager.map || !this.clustered) return;
167
- if (!untrack(() => this.enabled)) return;
168
- this.renderer.syncFeatures();
169
- });
170
-
171
- // Re-apply when clustered mode switches or data first arrives
172
- $effect(() => {
173
- void this.clustered;
174
- const hasFeatures = Object.keys(this.featuresByLevel).length > 0;
175
- if (!untrack(() => mapManager.map) || !hasFeatures) return;
176
- if (!untrack(() => this.enabled)) return;
177
- untrack(() => this.apply());
178
- });
179
- });
180
- }
181
-
182
- apply() {
183
- if (!mapManager.map) return;
184
-
185
- if (this.clustered) {
186
- if (mapManager.map.getLayer(this.referenceId)) mapManager.map.removeLayer(this.referenceId);
187
- if (mapManager.map.getSource(this.referenceId)) mapManager.map.removeSource(this.referenceId);
188
- clickManager.unregister([this.referenceId]);
189
- this.tooltipManager.disable();
190
- this.renderer.remove();
191
- // Clustering may be toggled again before the icons arrive; only the latest apply runs.
192
- this.supersedePending();
193
- this.whenIconsLoad(() => {
194
- this.renderer.apply(this.handleStationClick);
195
- this.tooltipManager.enable();
196
- });
197
- } else {
198
- this.renderer.remove();
199
- this.tooltipManager.disable();
200
- clickManager.unregister([this.referenceId]);
201
- super.apply();
202
- this.whenIconsLoad(() => {
203
- clickManager.register([this.referenceId], this.handleStationClick);
204
- this.tooltipManager.enable();
205
- });
206
- }
207
- }
208
-
209
- remove() {
210
- clickManager.unregister([this.referenceId]);
211
- clickManager.unregister(this.renderer.unclusteredLayerIds);
212
- this.renderer.remove();
213
- this.currentPopup?.remove();
214
- this.currentPopup = null;
215
- super.remove();
216
- }
217
- }
218
-
219
- function toFeature(station: EvStation, level: "lvl2" | "lvl3"): EvStationMapFeature {
220
- return {
221
- type: "Feature",
222
- properties: {
223
- icon: `ev-station-${level}`,
224
- id: station.id,
225
- level,
226
- station_name: station.station_name ?? "",
227
- facility_type: station.facility_type ?? "",
228
- ev_network: station.ev_network ?? "",
229
- station_phone: station.station_phone ?? "",
230
- street_address: station.street_address ?? "",
231
- city: station.city ?? "",
232
- state: station.state ?? "",
233
- zip: station.zip ?? "",
234
- access_days_time: station.access_days_time ?? "",
235
- access_detail_code: station.access_detail_code ?? "",
236
- cards_accepted: station.cards_accepted ?? null,
237
- ev_pricing: station.ev_pricing ?? "",
238
- ev_connector_types: JSON.stringify(station.ev_connector_types ?? [])
239
- },
240
- geometry: {
241
- type: "Point",
242
- coordinates: [station.longitude, station.latitude]
243
- } as Geometry
244
- };
245
- }
246
-
247
- export const evStationsMapIntegration = new EvStationsMapIntegration();
248
- export type { EvStationsMapIntegration };
@@ -1,84 +0,0 @@
1
- import {
2
- accessDetailCodeTypes,
3
- creditCardTypes,
4
- evConnectorTypes,
5
- evNetworkTypes,
6
- facilityTypes
7
- } from "./data-maps";
8
- import type { SignalOptions } from "$lib/abort";
9
- import type { EvStation } from "./types";
10
- import * as ZipCodes from "./zip-codes";
11
-
12
- class EvStationsManager {
13
- lvl2Stations: Array<EvStation> | undefined = $state();
14
- lvl3Stations: Array<EvStation> | undefined = $state();
15
-
16
- async loadLvl3Stations({ signal }: SignalOptions = {}): Promise<void> {
17
- this.lvl3Stations = await fetchEvStations("dc_fast", signal);
18
- }
19
-
20
- async loadLvl2Stations({ signal }: SignalOptions = {}): Promise<void> {
21
- this.lvl2Stations = await fetchEvStations("2", signal);
22
- }
23
- }
24
-
25
- const zipCodes = Object.values(ZipCodes)
26
- .reduce((prev, curr) => prev.concat(curr))
27
- .join(",");
28
-
29
- async function fetchEvStations(
30
- chargingLevel: "2" | "dc_fast",
31
- signal?: AbortSignal
32
- ): Promise<Array<EvStation>> {
33
- if (signal?.aborted) throw signal.reason;
34
- const url = new URL(`https://developer.nlr.gov/api/alt-fuel-stations/v1.json`);
35
- url.searchParams.set("fuel_type", "ELEC");
36
- url.searchParams.set("state", "CA");
37
- url.searchParams.set("status", "E");
38
- url.searchParams.set("access", "public");
39
- url.searchParams.set("ev_charging_level", chargingLevel);
40
- url.searchParams.set("zip", zipCodes);
41
- url.searchParams.set("api_key", import.meta.env.VITE_NREL_KEY);
42
-
43
- return await fetch(url, { signal })
44
- .then(async (res) => await res.json())
45
- .then((data) => {
46
- const fuelStations: Array<EvStation> = data.fuel_stations!;
47
- return (
48
- fuelStations
49
- .map((station) => {
50
- if (station.access_detail_code) {
51
- station.access_detail_code = accessDetailCodeTypes.get(station.access_detail_code);
52
- }
53
-
54
- if (station.cards_accepted) {
55
- station.cards_accepted = station.cards_accepted
56
- .split(" ")
57
- .map((c: string) => creditCardTypes.get(c))
58
- .join(", ");
59
- }
60
-
61
- if (station.ev_connector_types && station.ev_connector_types.length) {
62
- station.ev_connector_types = station.ev_connector_types.map((t: string) =>
63
- evConnectorTypes.get(t)
64
- );
65
- }
66
-
67
- if (station.ev_network) {
68
- station.ev_network = evNetworkTypes.get(station.ev_network);
69
- }
70
-
71
- if (station.facility_type) {
72
- station.facility_type = facilityTypes.get(station.facility_type);
73
- }
74
-
75
- return station;
76
- })
77
- // remove stations with invalid coordinates
78
- .filter((station) => station.id !== 226154)
79
- );
80
- });
81
- }
82
-
83
- export const evStationsManager = new EvStationsManager();
84
- export type { EvStationsManager };