@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
@@ -4,9 +4,9 @@
4
4
  import CheckboxRow from "../rows/CheckboxRow.svelte";
5
5
  import GroupRow from "../rows/GroupRow.svelte";
6
6
  import MarkerIcon from "../rows/MarkerIcon.svelte";
7
- import { monitorsManager } from "$lib/monitors/monitors.svelte";
8
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
9
- import { collocationSitesMapIntegration } from "$lib/collocation-sites/collocations-map-integration.svelte";
7
+ import { mapOptions } from "../map-options.svelte.js";
8
+ import { ROW_ICONS } from "../row-icons";
9
+ import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
10
10
  import {
11
11
  LOW_COST_TYPES,
12
12
  REFERENCE_GRADE_TYPES,
@@ -14,52 +14,68 @@
14
14
  type MonitorGroupType
15
15
  } from "$lib/monitors/monitor-groups";
16
16
 
17
+ const TYPE_LABELS: Record<MonitorGroupType, string> = {
18
+ airnow: "AirNow",
19
+ aqlite: "AQLite",
20
+ aqview: "AQview",
21
+ bam1022: "SJVAir",
22
+ airgradient: "AirGradient",
23
+ purpleair: "PurpleAir",
24
+ vozbox: "VOZbox"
25
+ };
26
+
27
+ /** A type's filter as the `{ value }` option GroupRow binds to. */
28
+ function typeOption(type: MonitorGroupType) {
29
+ return {
30
+ get value() {
31
+ return mapOptions.monitorTypes[type] ?? false;
32
+ },
33
+ set value(value: boolean) {
34
+ mapOptions.monitorTypes[type] = value;
35
+ }
36
+ };
37
+ }
38
+
17
39
  // Rows for types that don't report the current pollutant are hidden (meta-driven).
18
40
  function visibleRows(types: ReadonlyArray<MonitorGroupType>) {
19
- const displayOptions = monitorsMapIntegration.displayOptions;
20
41
  return types
21
42
  .filter((type) =>
22
43
  typeSupportsPollutant(monitorsManager.meta, type, monitorsManager.pollutant)
23
44
  )
24
- .map((type) => ({
25
- key: type,
26
- label: displayOptions[type].label,
27
- option: displayOptions[type]
28
- }));
45
+ .map((type) => ({ key: type, label: TYPE_LABELS[type], option: typeOption(type) }));
29
46
  }
30
47
 
31
48
  const referenceGrade = $derived(visibleRows(REFERENCE_GRADE_TYPES));
32
49
  const lowCost = $derived(visibleRows(LOW_COST_TYPES));
33
- const icons = monitorsMapIntegration.icons;
34
50
  </script>
35
51
 
36
- <OptionsMenu id="monitors" label="Monitors" disabled={!monitorsMapIntegration.enabled}>
52
+ <OptionsMenu id="monitors" label="Monitors" disabled={!mapOptions.monitorsOn}>
37
53
  {#snippet icon()}
38
54
  <RadioTowerIcon size={20} color="#4682b4" />
39
55
  {/snippet}
40
56
  <GroupRow label="Reference-grade" options={referenceGrade}>
41
57
  {#snippet icon()}
42
- <MarkerIcon icon={icons.get("outside-display-triangle")} />
58
+ <MarkerIcon svg={ROW_ICONS.referenceGrade} />
43
59
  {/snippet}
44
60
  </GroupRow>
45
61
  <GroupRow label="Low-cost sensors" options={lowCost}>
46
62
  {#snippet icon()}
47
- <MarkerIcon icon={icons.get("outside-display-circle")} />
63
+ <MarkerIcon svg={ROW_ICONS.lowCost} />
48
64
  {/snippet}
49
65
  </GroupRow>
50
66
  <hr class="my-1 border-gray-200" />
51
- <CheckboxRow label="Inactive" bind:checked={monitorsMapIntegration.displayOptions.inactive.value}>
67
+ <CheckboxRow label="Inactive" bind:checked={mapOptions.inactive}>
52
68
  {#snippet icon()}
53
- <MarkerIcon icon={monitorsMapIntegration.displayOptions.inactive.icon} />
69
+ <MarkerIcon svg={ROW_ICONS.inactive} />
54
70
  {/snippet}
55
71
  </CheckboxRow>
56
- <CheckboxRow label="Inside" bind:checked={monitorsMapIntegration.displayOptions.inside.value}>
72
+ <CheckboxRow label="Inside" bind:checked={mapOptions.inside}>
57
73
  {#snippet icon()}
58
- <MarkerIcon icon={monitorsMapIntegration.displayOptions.inside.icon} />
74
+ <MarkerIcon svg={ROW_ICONS.inside} />
59
75
  {/snippet}
60
76
  </CheckboxRow>
61
77
  {#if monitorsManager.pollutant === "pm25"}
62
- <CheckboxRow label="Collocation Sites" bind:checked={collocationSitesMapIntegration.enabled}>
78
+ <CheckboxRow label="Collocation Sites" bind:checked={mapOptions.collocationSites}>
63
79
  {#snippet icon()}
64
80
  <CrosshairIcon size={16} color="#4A5FC6" />
65
81
  {/snippet}
@@ -2,7 +2,7 @@
2
2
  import { SquareStackIcon, WindIcon } from "@lucide/svelte";
3
3
  import OptionsMenu from "../OptionsMenu.svelte";
4
4
  import CheckboxRow from "../rows/CheckboxRow.svelte";
5
- import { windMapIntegration } from "$lib/wind/wind.svelte";
5
+ import { mapOptions } from "../map-options.svelte.js";
6
6
  </script>
7
7
 
8
8
  <!-- Independent checkboxes: several overlays may be on at once. -->
@@ -10,7 +10,7 @@
10
10
  {#snippet icon()}
11
11
  <SquareStackIcon size={20} color="#708090" />
12
12
  {/snippet}
13
- <CheckboxRow label="Wind" bind:checked={windMapIntegration.enabled}>
13
+ <CheckboxRow label="Wind" bind:checked={mapOptions.wind}>
14
14
  {#snippet icon()}
15
15
  <WindIcon size={18} color="#1e90ff" />
16
16
  {/snippet}
@@ -9,8 +9,8 @@
9
9
  currentSelection,
10
10
  type PollutantSelection
11
11
  } from "../pollutant-param";
12
- import { monitorsManager } from "$lib/monitors/monitors.svelte";
13
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
12
+ import { mapOptions } from "../map-options.svelte.js";
13
+ import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
14
14
  import pm25Icon from "$lib/assets/icons/pm25/pm25-02-particle-field.svg";
15
15
  import ozoneIcon from "$lib/assets/icons/ozone/ozone-08-sunrise-haze.svg";
16
16
 
@@ -44,16 +44,11 @@
44
44
  ];
45
45
 
46
46
  const menus = useOptionsMenus();
47
- const selection = $derived(
48
- currentSelection(monitorsManager.pollutant, monitorsMapIntegration.enabled)
49
- );
47
+ const selection = $derived(currentSelection(monitorsManager.pollutant, mapOptions.monitorsOn));
50
48
  const selected = $derived(options.find((o) => o.value === selection) ?? options[0]);
51
49
 
52
50
  function select(value: PollutantSelection) {
53
- applyPollutantSelection(value, {
54
- manager: monitorsManager,
55
- integration: monitorsMapIntegration
56
- });
51
+ applyPollutantSelection(value, { manager: monitorsManager, options: mapOptions });
57
52
  // Toolbar dropdown closes on pick; the panel section stays open.
58
53
  if (isWideLayout()) menus.close("pollutant");
59
54
  }
@@ -1,29 +1,36 @@
1
1
  <script lang="ts">
2
+ import type { MapContext } from "@sjvair/map-sdk";
2
3
  import { ContrastIcon, MapIcon, NetworkIcon, RotateCcwIcon, SettingsIcon } from "@lucide/svelte";
3
4
  import OptionsMenu from "../OptionsMenu.svelte";
4
5
  import CheckboxRow from "../rows/CheckboxRow.svelte";
5
6
  import RadioRow from "../rows/RadioRow.svelte";
6
- import { resetMapOptions } from "../reset-map-options";
7
- import { MAP_STYLE_OPTIONS } from "$lib/map/utils";
8
- import { mapStyleState } from "$lib/map/map-style-state.svelte.js";
9
- import { monitorsMapIntegration } from "$lib/monitors/monitors-map-integration.svelte";
10
- import { evStationsMapIntegration } from "$lib/ev-stations/ev-stations-map-integration.svelte";
7
+ import { mapOptions, resetMapOptions } from "../map-options.svelte.js";
8
+ import { asPollutant } from "../pollutant-param";
9
+ import { basemapChoices, choiceForId, defaultBasemapId, switchBasemap } from "$lib/map/basemaps";
10
+ import { monitorsManager } from "$lib/monitors/monitors.svelte.js";
11
+
12
+ interface Props {
13
+ map: MapContext;
14
+ }
15
+
16
+ let { map }: Props = $props();
17
+
18
+ const choices = basemapChoices();
19
+ // Reads map-sdk's selection, so a basemap that fails to load shows as reverted.
20
+ const current = $derived(choiceForId(map.basemap.current));
21
+
22
+ function reset() {
23
+ resetMapOptions(asPollutant(monitorsManager.meta?.default_pollutant));
24
+ map.basemap.select(defaultBasemapId());
25
+ }
11
26
  </script>
12
27
 
13
28
  <OptionsMenu id="settings" label="Settings" iconOnly alignRight>
14
29
  {#snippet icon()}
15
30
  <SettingsIcon size={20} color="#696969" />
16
31
  {/snippet}
17
- <CheckboxRow
18
- label="Marker Clusters"
19
- bind:checked={
20
- () => monitorsMapIntegration.clustered && evStationsMapIntegration.clustered,
21
- (value) => {
22
- monitorsMapIntegration.clustered = value;
23
- evStationsMapIntegration.clustered = value;
24
- }
25
- }
26
- >
32
+ <!-- Monitors' and EV chargers' clustering together. -->
33
+ <CheckboxRow label="Marker Clusters" bind:checked={mapOptions.clustered}>
27
34
  {#snippet icon()}
28
35
  <NetworkIcon size={18} color="#1e90ff" />
29
36
  {/snippet}
@@ -37,12 +44,12 @@
37
44
  <select
38
45
  id="monitor-map-basemap"
39
46
  class="w-full rounded-md border border-gray-300 bg-white p-1.5"
40
- bind:value={
41
- () => mapStyleState.referenceStyle, (style) => mapStyleState.selectReferenceStyle(style)
42
- }
47
+ value={current?.name}
48
+ onchange={(event) =>
49
+ map.basemap.select(switchBasemap(map.basemap.current, event.currentTarget.value))}
43
50
  >
44
- {#each MAP_STYLE_OPTIONS as style (style.getId())}
45
- <option value={style}>{style.getName()}</option>
51
+ {#each choices as choice (choice.name)}
52
+ <option value={choice.name}>{choice.name}</option>
46
53
  {/each}
47
54
  </select>
48
55
  </div>
@@ -51,12 +58,12 @@
51
58
  <ContrastIcon size={18} color="#708090" />
52
59
  Theme
53
60
  </p>
54
- {#each mapStyleState.referenceStyle.getVariants() as variant (variant.getId())}
61
+ {#each current?.variants ?? [] as variant (variant.id)}
55
62
  <RadioRow
56
63
  name="monitor-map-theme"
57
- label={variant.getName()}
58
- checked={variant === mapStyleState.variant}
59
- onselect={() => mapStyleState.selectVariant(variant)}
64
+ label={variant.name}
65
+ checked={variant.id === map.basemap.current}
66
+ onselect={() => map.basemap.select(variant.id)}
60
67
  />
61
68
  {/each}
62
69
  <hr class="my-1 border-gray-200" />
@@ -64,7 +71,7 @@
64
71
  <button
65
72
  type="button"
66
73
  class="flex w-full items-center justify-center gap-2 rounded-md border border-gray-300 px-3 py-1.5 text-sm hover:bg-gray-100"
67
- onclick={resetMapOptions}
74
+ onclick={reset}
68
75
  >
69
76
  <RotateCcwIcon size={16} />
70
77
  Reset Map Options
@@ -0,0 +1,38 @@
1
+ import type { MapOptions } from "./map-option-defaults";
2
+
3
+ /** The map's plugins, bottom to top (4.x's layer order). */
4
+ export const DRAW_ORDER = [
5
+ "wind",
6
+ "hms-smoke",
7
+ "hms-fire",
8
+ "collocation-sites",
9
+ "monitors",
10
+ "ev-l2",
11
+ "ev-l3"
12
+ ] as const;
13
+
14
+ export type PluginKey = (typeof DRAW_ORDER)[number];
15
+
16
+ /** The first of `present` that draws above `key`: the plugin to add `key` before. */
17
+ export function beforeId(key: PluginKey, present: ReadonlySet<string>): string | undefined {
18
+ return DRAW_ORDER.slice(DRAW_ORDER.indexOf(key) + 1).find((above) => present.has(above));
19
+ }
20
+
21
+ /** The plugins `options` put on the map while the monitors show `pollutant`. */
22
+ export function wantedPlugins(options: MapOptions, pollutant: string | null): Set<PluginKey> {
23
+ const wanted = new Set<PluginKey>();
24
+ if (options.wind) wanted.add("wind");
25
+ if (options.fireAndSmoke) {
26
+ wanted.add("hms-smoke");
27
+ wanted.add("hms-fire");
28
+ }
29
+ if (options.monitorsOn) {
30
+ if (options.collocationSites && pollutant === "pm25") wanted.add("collocation-sites");
31
+ wanted.add("monitors");
32
+ }
33
+ if (options.evStations) {
34
+ if (options.evLevels.lvl2) wanted.add("ev-l2");
35
+ if (options.evLevels.lvl3) wanted.add("ev-l3");
36
+ }
37
+ return wanted;
38
+ }
@@ -2,24 +2,29 @@ import type { MonitorsPollutant } from "$lib/monitors/types";
2
2
 
3
3
  /**
4
4
  * What the Pollutant menu shows. "none" is not a monitorsManager.pollutant value — it means
5
- * the monitors integration is disabled while the manager keeps its last real pollutant.
5
+ * the monitors plugin is off while the manager keeps its last real pollutant.
6
6
  */
7
7
  export type PollutantSelection = MonitorsPollutant | "none";
8
8
 
9
9
  export interface PollutantTargets {
10
- manager: { pollutant: MonitorsPollutant | null };
11
- integration: { enabled: boolean };
10
+ manager: { setPollutant(pollutant: MonitorsPollutant): void };
11
+ options: { monitorsOn: boolean };
12
12
  }
13
13
 
14
14
  export function parsePollutantParam(value: unknown): PollutantSelection | null {
15
15
  return value === "pm25" || value === "o3" || value === "none" ? value : null;
16
16
  }
17
17
 
18
+ /** A pollutant the map can show, or null. */
19
+ export function asPollutant(value: unknown): MonitorsPollutant | null {
20
+ return value === "pm25" || value === "o3" ? value : null;
21
+ }
22
+
18
23
  export function currentSelection(
19
- pollutant: MonitorsPollutant | null,
20
- monitorsEnabled: boolean
24
+ pollutant: string | null,
25
+ monitorsOn: boolean
21
26
  ): PollutantSelection | null {
22
- return monitorsEnabled ? pollutant : "none";
27
+ return monitorsOn ? asPollutant(pollutant) : "none";
23
28
  }
24
29
 
25
30
  export function applyPollutantSelection(
@@ -27,9 +32,9 @@ export function applyPollutantSelection(
27
32
  targets: PollutantTargets
28
33
  ): void {
29
34
  if (selection === "none") {
30
- targets.integration.enabled = false;
35
+ targets.options.monitorsOn = false;
31
36
  return;
32
37
  }
33
- targets.manager.pollutant = selection;
34
- targets.integration.enabled = true;
38
+ targets.manager.setPollutant(selection);
39
+ targets.options.monitorsOn = true;
35
40
  }
@@ -0,0 +1,17 @@
1
+ import { circle, triangle } from "@sjvair/map-sdk";
2
+ import { EV_STATION_LEVELS, evStationIcon } from "../ev-stations/ev-station-icons";
3
+
4
+ // The menu rows' marker icons (SVG markup), drawn with the map's own shapes as in 4.x.
5
+ const DISPLAY_COLOR = "#48c78e";
6
+ const INACTIVE_COLOR = "#969696";
7
+ const INSIDE_OUTLINE = "#000000";
8
+ const BORDER_WIDTH = 2;
9
+
10
+ export const ROW_ICONS = {
11
+ referenceGrade: triangle(DISPLAY_COLOR, BORDER_WIDTH),
12
+ lowCost: circle(DISPLAY_COLOR, BORDER_WIDTH),
13
+ inactive: circle(INACTIVE_COLOR, BORDER_WIDTH),
14
+ inside: circle(DISPLAY_COLOR, BORDER_WIDTH, INSIDE_OUTLINE),
15
+ evLevel2: evStationIcon(EV_STATION_LEVELS.lvl2.bg, EV_STATION_LEVELS.lvl2.border),
16
+ evLevel3: evStationIcon(EV_STATION_LEVELS.lvl3.bg, EV_STATION_LEVELS.lvl3.border)
17
+ } as const;
@@ -1,10 +1,10 @@
1
1
  <script lang="ts">
2
- import type { MapImageIcon } from "$lib/map/integrations/types";
2
+ /** A marker image from its SVG markup (e.g. map-sdk's `circle`). */
3
+ let { svg, class: className = "w-4" }: { svg: string | undefined; class?: string } = $props();
3
4
 
4
- let { icon, class: className = "w-4" }: { icon: MapImageIcon | undefined; class?: string } =
5
- $props();
5
+ const src = $derived(svg ? `data:image/svg+xml;utf8,${encodeURIComponent(svg)}` : undefined);
6
6
  </script>
7
7
 
8
- {#if icon}
9
- <img src={icon.image.src} alt="" class={className} />
8
+ {#if src}
9
+ <img {src} alt="" class={className} />
10
10
  {/if}
@@ -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
+ }