@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,19 +1,24 @@
1
1
  <script lang="ts">
2
- import { onDestroy } from "svelte";
3
- import { Marker } from "@maptiler/sdk";
4
2
  import { MapPinIcon, SearchIcon } from "@lucide/svelte";
3
+ import type { MapContext } from "@sjvair/map-sdk";
5
4
  import { useMonitorMapRouter } from "../router-context";
6
- import { mapManager } from "$lib/map/map-manager.svelte.js";
7
5
  import { debounce } from "$lib/utils";
6
+ import { searchPin } from "./search-pin.svelte.js";
8
7
  import { monitorSearch, geocode, type SearchResult } from "./service";
9
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
10
8
 
9
+ interface SearchProps {
10
+ map: MapContext;
11
+ }
12
+
13
+ const { map }: SearchProps = $props();
11
14
  const { navigate, basePath } = useMonitorMapRouter();
12
15
 
13
16
  let collapsed = $state(true);
14
17
  let searchText = $state("");
15
18
  let results: SearchResult[] = $state([]);
16
- let marker: Marker | null = $state.raw(null);
19
+ // The layout passes one MapContext for the widget's lifetime.
20
+ // svelte-ignore state_referenced_locally
21
+ const pin = searchPin(map);
17
22
  let inputEl: HTMLInputElement | null = $state(null);
18
23
 
19
24
  function clickOutside(node: HTMLElement) {
@@ -42,16 +47,14 @@
42
47
  function clearSearch() {
43
48
  searchText = "";
44
49
  results = [];
45
- marker?.remove();
46
- marker = null;
50
+ pin.clear();
47
51
  }
48
52
 
49
53
  async function handleInput() {
50
54
  const query = searchText;
51
55
  if (!query) {
52
56
  results = [];
53
- marker?.remove();
54
- marker = null;
57
+ pin.clear();
55
58
  return;
56
59
  }
57
60
  const monitorResults = await monitorSearch(query);
@@ -73,40 +76,24 @@
73
76
  collapsed = true;
74
77
  searchText = "";
75
78
  results = [];
76
- marker?.remove();
77
- marker = null;
78
- if (mapManager.map && result.monitor.position) {
79
+ pin.clear();
80
+ if (result.monitor.position) {
79
81
  const { coordinates } = result.monitor.position;
80
- const center: [number, number] = [coordinates[0], coordinates[1]];
81
- mapManager.map.flyTo({
82
- center,
83
- zoom: 12
84
- });
82
+ map.flyTo({ center: [coordinates[0], coordinates[1]], zoom: 12 });
85
83
  }
86
84
  await navigate(`${basePath}/monitor/:id`, { params: { id: result.monitor.id } });
87
- monitorsMapIntegration.selectedMonitorId = result.monitor.id;
88
85
  }
89
86
 
90
87
  function selectGeocode(result: Extract<SearchResult, { type: "geocode" }>) {
91
88
  const { feature } = result;
92
- marker?.remove();
93
- marker = null;
94
- if (mapManager.map) {
95
- const center: [number, number] = [feature.center[0], feature.center[1]];
96
- marker = new Marker().setLngLat(center).addTo(mapManager.map);
97
- const isAddress = feature.place_type?.[0] === "address";
98
- mapManager.map.flyTo({
99
- center,
100
- ...(isAddress && { zoom: 16 })
101
- });
102
- }
89
+ pin.clear();
90
+ const center: [number, number] = [feature.center[0], feature.center[1]];
91
+ pin.place(center);
92
+ const isAddress = feature.place_type?.[0] === "address";
93
+ map.flyTo({ center, ...(isAddress && { zoom: 16 }) });
103
94
  results = [];
104
95
  collapsed = true;
105
96
  }
106
-
107
- onDestroy(() => {
108
- marker?.remove();
109
- });
110
97
  </script>
111
98
 
112
99
  <div
@@ -0,0 +1,35 @@
1
+ import { Marker } from "@maptiler/sdk";
2
+ import { maptilerMap, type MapContext } from "@sjvair/map-sdk";
3
+
4
+ export interface SearchPin {
5
+ place(center: [number, number]): void;
6
+ clear(): void;
7
+ }
8
+
9
+ /**
10
+ * The pin a geocoded place leaves on the map. It follows the map: when the GL map is replaced
11
+ * (a basemap or style rebuild), the pin leaves the old one and is added to the new one. Call it
12
+ * during component init; the pin is removed when the component is destroyed.
13
+ */
14
+ export function searchPin(map: MapContext): SearchPin {
15
+ let center = $state.raw<[number, number] | null>(null);
16
+
17
+ $effect(() => {
18
+ const gl = maptilerMap(map);
19
+ const at = center;
20
+ if (!gl || !at) return;
21
+ const marker = new Marker().setLngLat(at).addTo(gl);
22
+ return () => {
23
+ marker.remove();
24
+ };
25
+ });
26
+
27
+ return {
28
+ place(next) {
29
+ center = next;
30
+ },
31
+ clear() {
32
+ center = null;
33
+ }
34
+ };
35
+ }
@@ -1,6 +1,7 @@
1
1
  import { geocoding, type GeocodingFeature } from "@maptiler/sdk";
2
- import type { MonitorLatestType } from "@sjvair/sdk";
3
- import { monitorsManager } from "$lib/monitors/monitors.svelte";
2
+ import type { MonitorData } from "@sjvair/sdk";
3
+ import { monitorMapConfig } from "$lib/config.svelte.js";
4
+ import { monitorsStore } from "$lib/stores.svelte.js";
4
5
  import AirGradientLogo from "../assets/logos/airgradient-blue.svg";
5
6
  import AirNowLogo from "../assets/logos/airnow-compact.jpg";
6
7
  import CarbLogo from "../assets/logos/carb-compact.jpg";
@@ -9,7 +10,7 @@ import SJVAirLogo from "../assets/logos/sjvair.svg";
9
10
 
10
11
  export type MonitorResult = {
11
12
  type: "monitor";
12
- monitor: MonitorLatestType<"pm25" | "o3">;
13
+ monitor: MonitorData;
13
14
  logo: { url: string; alt: string };
14
15
  };
15
16
 
@@ -22,7 +23,7 @@ export type SearchResult = MonitorResult | GeocodeResult;
22
23
 
23
24
  const SJV_CENTER: [number, number] = [-119.8, 36.76];
24
25
 
25
- function getMonitorLogo(monitor: MonitorLatestType<"pm25" | "o3">): { url: string; alt: string } {
26
+ function getMonitorLogo(monitor: MonitorData): { url: string; alt: string } {
26
27
  switch (monitor.data_source.name) {
27
28
  case "AQview":
28
29
  return { url: CarbLogo, alt: "AQview" };
@@ -66,29 +67,43 @@ function resolveProximity(): Promise<[number, number]> {
66
67
  });
67
68
  }
68
69
 
69
- export async function monitorSearch(query: string): Promise<SearchResult[]> {
70
- if (!monitorsManager.latest) return [];
70
+ /** Monitors whose name matches `query`: exact, then prefix, then substring; at most 4. */
71
+ export function searchMonitors(
72
+ query: string,
73
+ monitors: Iterable<MonitorData>,
74
+ latest: ReadonlyMap<string, { value: number | null }>
75
+ ): MonitorData[] {
71
76
  const normalized = query.toLowerCase();
72
- const exact: MonitorLatestType<"pm25" | "o3">[] = [];
73
- const prefix: MonitorLatestType<"pm25" | "o3">[] = [];
74
- const substr: MonitorLatestType<"pm25" | "o3">[] = [];
77
+ const exact: MonitorData[] = [];
78
+ const prefix: MonitorData[] = [];
79
+ const substr: MonitorData[] = [];
75
80
 
76
- for (const monitor of monitorsManager.latest.values()) {
81
+ // Only monitors with a reading for the current pollutant, as the map shows them.
82
+ for (const monitor of monitors) {
83
+ if (latest.get(monitor.id)?.value == null) continue;
77
84
  const name = monitor.name.toLowerCase();
78
85
  if (name === normalized) exact.push(monitor);
79
86
  else if (name.startsWith(normalized)) prefix.push(monitor);
80
87
  else if (name.includes(normalized)) substr.push(monitor);
81
88
  }
82
89
 
83
- return [...exact, ...prefix, ...substr]
84
- .slice(0, 4)
85
- .map((monitor) => ({ type: "monitor" as const, monitor, logo: getMonitorLogo(monitor) }));
90
+ return [...exact, ...prefix, ...substr].slice(0, 4);
86
91
  }
87
92
 
88
- export async function geocode(query: string): Promise<SearchResult[]> {
89
- const proximity = await getProximity();
93
+ export async function monitorSearch(query: string): Promise<SearchResult[]> {
94
+ return searchMonitors(query, monitorsStore.monitors.values(), monitorsStore.latest).map(
95
+ (monitor) => ({ type: "monitor" as const, monitor, logo: getMonitorLogo(monitor) })
96
+ );
97
+ }
98
+
99
+ export async function geocodeQuery(
100
+ query: string,
101
+ proximity: [number, number],
102
+ key: string
103
+ ): Promise<SearchResult[]> {
90
104
  try {
91
105
  const result = await geocoding.forward(query, {
106
+ apiKey: key,
92
107
  country: ["us"],
93
108
  fuzzyMatch: true,
94
109
  limit: 4,
@@ -99,3 +114,7 @@ export async function geocode(query: string): Promise<SearchResult[]> {
99
114
  return [];
100
115
  }
101
116
  }
117
+
118
+ export async function geocode(query: string): Promise<SearchResult[]> {
119
+ return geocodeQuery(query, await getProximity(), monitorMapConfig().maptilerKey);
120
+ }
@@ -0,0 +1,15 @@
1
+ import {
2
+ createCollocationSitesStore,
3
+ createHmsFireStore,
4
+ createHmsSmokeStore,
5
+ createMonitorsStore
6
+ } from "@sjvair/map-sdk/stores";
7
+
8
+ /**
9
+ * The app-wide data stores, created once. This module imports only `@sjvair/map-sdk/stores`, so
10
+ * the light `/core` entry can reach it without loading the map engine.
11
+ */
12
+ export const monitorsStore = createMonitorsStore();
13
+ export const hmsSmokeStore = createHmsSmokeStore();
14
+ export const hmsFireStore = createHmsFireStore();
15
+ export const collocationSitesStore = createCollocationSitesStore({ monitors: monitorsStore });
@@ -0,0 +1,33 @@
1
+ import { config, type CustomLayerInterface } from "@maptiler/sdk";
2
+ import { WindLayer } from "@maptiler/weather";
3
+ import type { MapPlugin } from "@sjvair/map-sdk";
4
+ import { monitorMapConfig } from "../config.svelte.js";
5
+
6
+ export interface WindPluginOptions {
7
+ /** Builds the layer; runs again on each restyle. Defaults to a `@maptiler/weather` WindLayer. */
8
+ createLayer?: (id: string) => CustomLayerInterface;
9
+ }
10
+
11
+ // The weather layer fetches its data with the SDK's global key, which map-sdk (it passes the key
12
+ // to the map only) never sets.
13
+ const defaultCreateLayer = (id: string) => {
14
+ config.apiKey = monitorMapConfig().maptilerKey;
15
+ return new WindLayer({ id, opacity: 0.5 }) as unknown as CustomLayerInterface;
16
+ };
17
+
18
+ /**
19
+ * Wind particles as a map-sdk custom layer. `@maptiler/weather` can't be a map-sdk dependency, so
20
+ * the host adds it here; only the layout chunk reaches this module.
21
+ */
22
+ export function windPlugin(options: WindPluginOptions = {}): MapPlugin {
23
+ const createLayer = options.createLayer ?? defaultCreateLayer;
24
+ return {
25
+ id: "wind",
26
+ label: "Wind",
27
+ selectable: false,
28
+ setup(ctx) {
29
+ ctx.addCustomLayer("wind", createLayer);
30
+ return () => {};
31
+ }
32
+ };
33
+ }
@@ -1,98 +0,0 @@
1
- <script lang="ts">
2
- import { format, formatDistanceToNow } from "date-fns";
3
- import DataBox from "../components/DataBox.svelte";
4
- import type { CollocationSiteMapFeature } from "./types";
5
- import { monitorsManager } from "../monitors/monitors.svelte";
6
- import { getCurrentLevel } from "../monitors/monitor-utils";
7
- import type { MonitorLatest, SJVAirEntryLevel } from "@sjvair/sdk";
8
-
9
- interface CollocationTooltipProps {
10
- feature: CollocationSiteMapFeature;
11
- }
12
-
13
- const { feature }: CollocationTooltipProps = $props();
14
-
15
- const data = $derived.by(() => {
16
- const collocated = monitorsManager.latest?.get(feature.properties.colocated_id);
17
- const reference = monitorsManager.latest?.get(feature.properties.reference_id);
18
-
19
- return {
20
- collocated: getMonitorData(collocated),
21
- reference: getMonitorData(reference)
22
- };
23
- });
24
-
25
- function getMonitorData(monitor: MonitorLatest | undefined) {
26
- if (!monitor || !("value" in monitor.latest) || !monitorsManager.meta) return;
27
-
28
- const entryType = monitorsManager.meta.entryType(monitor.latest.entry_type);
29
- const level: SJVAirEntryLevel | undefined = getCurrentLevel(
30
- monitor.latest.value,
31
- entryType.asIter.levels!
32
- );
33
-
34
- const updateDuration = monitorsManager.meta.monitorType(monitor.type).expected_interval;
35
-
36
- return {
37
- color: level?.color ?? "",
38
- header: entryType.label,
39
- date: {
40
- formatted: format(monitor.latest.timestamp, "h:mm:aaa EEEE MMM dd, yyyy"),
41
- diference: formatDistanceToNow(monitor.latest.timestamp, {
42
- addSuffix: true,
43
- includeSeconds: true
44
- })
45
- },
46
- monitorName: monitor.name,
47
- subheading: `(${updateDuration} avg)`,
48
- value: Math.round(parseInt(monitor.latest.value, 10)).toString()
49
- };
50
- }
51
- </script>
52
-
53
- {#if data}
54
- <div class="flex flex-col gap-4">
55
- {#if data.reference}
56
- <div class="flex gap-2">
57
- <DataBox
58
- color={data.reference.color}
59
- header={data.reference.header}
60
- subheading={data.reference.subheading}
61
- value={data.reference.value}
62
- />
63
- <div class="flex flex-col">
64
- <p class="leading-none">{data.reference.date.formatted}</p>
65
- <h1 class="text-lg font-bold underline">{data.reference.monitorName}</h1>
66
- <p class="leading-none">Reference monitor</p>
67
- <div class="mt-auto">
68
- <p class="text-base leading-none">Last updated</p>
69
- <p class="text-base leading-none">{data.reference.date.diference}</p>
70
- </div>
71
- </div>
72
- </div>
73
- {:else}
74
- <h1>Reference monitor disabled</h1>
75
- {/if}
76
- {#if data.collocated}
77
- <div class="flex flex-row-reverse gap-2">
78
- <DataBox
79
- color={data.collocated.color}
80
- header={data.collocated.header}
81
- subheading={data.collocated.subheading}
82
- value={data.collocated.value}
83
- />
84
- <div class="flex flex-col">
85
- <p class="leading-none">{data.collocated.date.formatted}</p>
86
- <h1 class="text-lg font-bold underline">{data.collocated.monitorName}</h1>
87
- <p class="leading-none">Collocated monitor</p>
88
- <div class="mt-auto">
89
- <p class="text-base leading-none">Last updated</p>
90
- <p class="text-base leading-none">{data.collocated.date.diference}</p>
91
- </div>
92
- </div>
93
- </div>
94
- {:else}
95
- <h1>Collocated monitor disabled</h1>
96
- {/if}
97
- </div>
98
- {/if}
@@ -1,7 +0,0 @@
1
- import type { CollocationSiteMapFeature } from "./types";
2
- interface CollocationTooltipProps {
3
- feature: CollocationSiteMapFeature;
4
- }
5
- declare const CollocationTooltip: import("svelte").Component<CollocationTooltipProps, {}, "">;
6
- type CollocationTooltip = ReturnType<typeof CollocationTooltip>;
7
- export default CollocationTooltip;
@@ -1,7 +0,0 @@
1
- import { MonitorShapeIconManager } from "../monitors/monitors-icon-manager.svelte";
2
- export declare function getCollocationIconId(baseIconId: string): string;
3
- export declare class CollocationIconManager extends MonitorShapeIconManager {
4
- protected get idPrefix(): string;
5
- protected get iconSize(): number;
6
- protected transformSvg(svg: string): string;
7
- }
@@ -1,18 +0,0 @@
1
- import { CROSSHAIR_ICON_SIZE, withCrosshair } from "../map/icons";
2
- import { MonitorShapeIconManager } from "../monitors/monitors-icon-manager.svelte";
3
- const COLLOCATION_ICON_PREFIX = "collocation";
4
- const CROSSHAIR_COLOR = "#4A5FC6";
5
- export function getCollocationIconId(baseIconId) {
6
- return `${COLLOCATION_ICON_PREFIX}-${baseIconId}`;
7
- }
8
- export class CollocationIconManager extends MonitorShapeIconManager {
9
- get idPrefix() {
10
- return COLLOCATION_ICON_PREFIX;
11
- }
12
- get iconSize() {
13
- return CROSSHAIR_ICON_SIZE;
14
- }
15
- transformSvg(svg) {
16
- return withCrosshair(svg, CROSSHAIR_COLOR);
17
- }
18
- }
@@ -1,21 +0,0 @@
1
- import { type Map as MaptilerMap } from "@maptiler/sdk";
2
- import { MapIconLayerIntegration } from "../map/integrations/map-geojson-integration.svelte";
3
- import { CollocationIconManager } from "./collocations-icon-manager.svelte";
4
- import { TooltipManager } from "../map/integrations/tooltip.svelte";
5
- import type { CollocationSiteMapFeature, CollocationSiteMarkerProperties } from "./types";
6
- declare class CollocationSitesMapIntegration extends MapIconLayerIntegration<CollocationSiteMarkerProperties> {
7
- referenceId: string;
8
- enabled: boolean;
9
- icons: CollocationIconManager;
10
- tooltipManager: TooltipManager;
11
- /** The option only applies while monitors are shown with PM2.5. */
12
- get available(): boolean;
13
- features: Array<CollocationSiteMapFeature>;
14
- mapLayer: Parameters<MaptilerMap["addLayer"]>[0];
15
- get mapSource(): Parameters<MaptilerMap["addSource"]>[1];
16
- constructor();
17
- apply(): void;
18
- remove(): void;
19
- }
20
- export declare const collocationSitesMapIntegration: CollocationSitesMapIntegration;
21
- export type { CollocationSitesMapIntegration as MonitorsMapIntegration };
@@ -1,113 +0,0 @@
1
- import { mapManager } from "../map/map-manager.svelte.js";
2
- import { MapIconLayerIntegration } from "../map/integrations/map-geojson-integration.svelte";
3
- import { CollocationIconManager, getCollocationIconId } from "./collocations-icon-manager.svelte";
4
- import { TooltipManager } from "../map/integrations/tooltip.svelte";
5
- import { collocationSitesManager } from "./collocations.svelte";
6
- import { monitorsManager } from "../monitors/monitors.svelte";
7
- import { monitorsMapIntegration } from "../monitors/monitors-map-integration.svelte";
8
- import { untrack } from "svelte";
9
- import CollocationTooltip from "./CollocationTooltip.svelte";
10
- import { mountPopup } from "../map/utils";
11
- function collocationTooltip(evt) {
12
- const feature = evt.features?.[0];
13
- if (!feature)
14
- return;
15
- return mountPopup(CollocationTooltip, { feature }, evt.lngLat);
16
- }
17
- class CollocationSitesMapIntegration extends MapIconLayerIntegration {
18
- referenceId = "collocation-sites";
19
- enabled = $state(false);
20
- icons = new CollocationIconManager(monitorsManager);
21
- tooltipManager = new TooltipManager();
22
- /** The option only applies while monitors are shown with PM2.5. */
23
- get available() {
24
- return monitorsMapIntegration.enabled && monitorsManager.pollutant === "pm25";
25
- }
26
- features = $derived.by(() => {
27
- if (!monitorsManager.meta ||
28
- !monitorsManager.pollutant ||
29
- !collocationSitesManager.collocationSites ||
30
- monitorsMapIntegration.features.length === 0) {
31
- return [];
32
- }
33
- return collocationSitesManager.collocationSites
34
- .map((c) => {
35
- const referenceMonitorFeature = monitorsMapIntegration.features.find((m) => m.properties.id === c.reference_id);
36
- if (!referenceMonitorFeature)
37
- return;
38
- const feature = {
39
- type: "Feature",
40
- properties: {
41
- icon: getCollocationIconId(referenceMonitorFeature.properties.icon),
42
- colocated_id: c.colocated_id,
43
- reference_id: c.reference_id,
44
- name: c.name
45
- },
46
- geometry: c.position
47
- };
48
- return feature;
49
- })
50
- .filter((f) => f !== undefined);
51
- });
52
- mapLayer = {
53
- id: this.referenceId,
54
- type: "symbol",
55
- source: this.referenceId,
56
- layout: {
57
- "icon-allow-overlap": true,
58
- "icon-ignore-placement": true,
59
- "icon-image": ["get", "icon"],
60
- "icon-size": 1
61
- },
62
- paint: {}
63
- };
64
- get mapSource() {
65
- return {
66
- type: "geojson",
67
- promoteId: "id",
68
- data: {
69
- type: "FeatureCollection",
70
- features: this.features
71
- }
72
- };
73
- }
74
- constructor() {
75
- super();
76
- $effect.root(() => {
77
- $effect(() => {
78
- const features = this.features;
79
- if (!mapManager.map || !this.enabled)
80
- return;
81
- mapManager.setDataSource(this.referenceId, features);
82
- });
83
- // Remove the layer while the option is unavailable (Ozone or Pollutant = None); restore
84
- // it when it becomes available again if the user still has it checked.
85
- $effect(() => {
86
- if (!this.available) {
87
- untrack(() => this.remove());
88
- }
89
- else if (untrack(() => this.enabled)) {
90
- untrack(() => this.apply());
91
- }
92
- });
93
- });
94
- }
95
- apply() {
96
- if (!this.available) {
97
- this.remove();
98
- return;
99
- }
100
- if (!mapManager.map)
101
- return;
102
- if (!this.tooltipManager.has(this.referenceId)) {
103
- this.tooltipManager.register(this.referenceId, collocationTooltip, Number.MAX_SAFE_INTEGER);
104
- }
105
- this.tooltipManager.enable();
106
- super.apply();
107
- }
108
- remove() {
109
- this.tooltipManager.disable();
110
- super.remove();
111
- }
112
- }
113
- export const collocationSitesMapIntegration = new CollocationSitesMapIntegration();
@@ -1,9 +0,0 @@
1
- import { type CollocationSite } from "@sjvair/sdk";
2
- declare class CollocationSitesManager {
3
- initialized: boolean;
4
- collocationSites: Array<CollocationSite> | null;
5
- init(): Promise<void>;
6
- update(): Promise<void>;
7
- }
8
- export declare const collocationSitesManager: CollocationSitesManager;
9
- export type { CollocationSitesManager };
@@ -1,17 +0,0 @@
1
- import { getCollocationSites } from "@sjvair/sdk";
2
- class CollocationSitesManager {
3
- initialized = $state(false);
4
- collocationSites = $state(null);
5
- async init() {
6
- if (this.initialized)
7
- return;
8
- this.collocationSites = await getCollocationSites();
9
- this.initialized = true;
10
- }
11
- async update() {
12
- if (!this.initialized)
13
- return;
14
- this.collocationSites = await getCollocationSites();
15
- }
16
- }
17
- export const collocationSitesManager = new CollocationSitesManager();
@@ -1,8 +0,0 @@
1
- import type { Feature, Geometry } from "geojson";
2
- export type CollocationSiteMapFeature = Feature<Geometry, CollocationSiteMarkerProperties>;
3
- export interface CollocationSiteMarkerProperties {
4
- icon: string;
5
- colocated_id: string;
6
- reference_id: string;
7
- name: string;
8
- }
@@ -1 +0,0 @@
1
- export {};
@@ -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,7 +0,0 @@
1
- import type { EvStationClusterMapFeature } from "../types";
2
- interface Props {
3
- feature: EvStationClusterMapFeature;
4
- }
5
- declare const EvStationClusterTooltip: import("svelte").Component<Props, {}, "">;
6
- type EvStationClusterTooltip = ReturnType<typeof EvStationClusterTooltip>;
7
- export default EvStationClusterTooltip;
@@ -1,7 +0,0 @@
1
- import type { EvStationMapFeature } from "../types";
2
- interface Props {
3
- feature: EvStationMapFeature;
4
- }
5
- declare const EvStationTooltip: import("svelte").Component<Props, {}, "">;
6
- type EvStationTooltip = ReturnType<typeof EvStationTooltip>;
7
- export default EvStationTooltip;
@@ -1,19 +0,0 @@
1
- import { type ClickHandler } from "../map/integrations/click-manager";
2
- import { TooltipManager } from "../map/integrations/tooltip.svelte";
3
- import type { EvStationMapFeature } from "./types";
4
- export interface EvStationsClusterContext {
5
- referenceId: string;
6
- featuresByLevel: Record<string, EvStationMapFeature[]>;
7
- beforeLayer?: string;
8
- tooltipManager: TooltipManager;
9
- }
10
- export declare class EvStationsClusterRenderer {
11
- private ctx;
12
- private _levels;
13
- constructor(ctx: EvStationsClusterContext);
14
- apply(stationClickHandler: ClickHandler): void;
15
- remove(): void;
16
- get unclusteredLayerIds(): string[];
17
- syncFeatures(): void;
18
- private makeClusterClickHandler;
19
- }