@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.
- package/LICENSE +21 -0
- package/README.md +61 -126
- package/dist/lib/MonitorDetailPanel.svelte +23 -20
- package/dist/lib/MonitorMapLayout.svelte +147 -77
- package/dist/lib/abort.d.ts +16 -0
- package/dist/lib/abort.js +51 -0
- package/dist/lib/components/DataBox.svelte +4 -1
- package/dist/lib/components/ui/range-calendar/range-calendar.svelte.d.ts +1 -1
- package/dist/lib/config.svelte.d.ts +7 -0
- package/dist/lib/config.svelte.js +10 -0
- package/dist/lib/core.d.ts +2 -2
- package/dist/lib/core.js +2 -1
- package/dist/lib/data-chart/DataChart.svelte +54 -24
- package/dist/lib/data-chart/DateRange.d.ts +13 -4
- package/dist/lib/data-chart/DateRange.js +50 -14
- package/dist/lib/data-chart/chart-utils.js +5 -7
- package/dist/lib/data-chart/level-colors.d.ts +3 -4
- package/dist/lib/data-chart/level-colors.js +7 -4
- package/dist/lib/data-chart/pacific-time.d.ts +9 -0
- package/dist/lib/data-chart/pacific-time.js +25 -0
- package/dist/lib/data-chart/requests.d.ts +2 -1
- package/dist/lib/data-chart/requests.js +5 -2
- package/dist/lib/data-chart/tooltip.js +2 -2
- package/{src/lib/ev-stations/components/EvStationTooltip.svelte → dist/lib/ev-stations/EvPopup.svelte} +6 -5
- package/dist/lib/ev-stations/EvPopup.svelte.d.ts +7 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/ev-plugin.svelte.js +71 -0
- package/dist/lib/ev-stations/ev-station-icons.d.ts +1 -4
- package/dist/lib/ev-stations/ev-station-icons.js +1 -13
- package/dist/lib/ev-stations/ev-stations.d.ts +7 -0
- package/dist/lib/ev-stations/ev-stations.js +53 -0
- package/dist/lib/ev-stations/ev-store.svelte.d.ts +21 -0
- package/dist/lib/ev-stations/ev-store.svelte.js +83 -0
- package/dist/lib/ev-stations/station-popup.svelte.d.ts +14 -0
- package/dist/lib/ev-stations/station-popup.svelte.js +54 -0
- package/dist/lib/ev-stations/types.d.ts +0 -29
- package/dist/lib/ev-stations/zip-codes.d.ts +11 -0
- package/dist/lib/ev-stations/zip-codes.js +28 -16
- package/dist/lib/index.d.ts +8 -56
- package/dist/lib/index.js +7 -46
- package/dist/lib/legend/Legend.svelte +18 -19
- package/dist/lib/legend/Legend.svelte.d.ts +2 -2
- package/dist/lib/levels.d.ts +7 -0
- package/dist/lib/levels.js +19 -0
- package/dist/lib/map/MapShell.svelte +17 -24
- package/dist/lib/map/MapShell.svelte.d.ts +4 -4
- package/dist/lib/map/basemaps.d.ts +25 -0
- package/dist/lib/map/basemaps.js +66 -0
- package/dist/lib/map/live-map.svelte.d.ts +3 -0
- package/dist/lib/map/live-map.svelte.js +22 -0
- package/dist/lib/map/sync-plugins.svelte.d.ts +19 -0
- package/dist/lib/map/sync-plugins.svelte.js +52 -0
- package/dist/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
- package/dist/lib/monitors/filtered-source.svelte.d.ts +9 -0
- package/dist/lib/monitors/filtered-source.svelte.js +28 -0
- package/dist/lib/monitors/monitor-details.svelte.js +11 -3
- package/dist/lib/monitors/monitors.svelte.d.ts +29 -26
- package/dist/lib/monitors/monitors.svelte.js +35 -126
- package/dist/lib/monitors/retry-load.d.ts +8 -0
- package/dist/lib/monitors/retry-load.js +11 -0
- package/dist/lib/monitors/subscriptions.d.ts +3 -0
- package/dist/lib/monitors/subscriptions.js +9 -0
- package/dist/lib/monitors/types.d.ts +1 -41
- package/dist/lib/options/map-option-defaults.d.ts +25 -36
- package/dist/lib/options/map-option-defaults.js +12 -30
- package/dist/lib/options/map-options.svelte.d.ts +13 -0
- package/dist/lib/options/map-options.svelte.js +26 -0
- package/dist/lib/options/menus/LayersMenu.svelte +8 -20
- package/dist/lib/options/menus/LayersMenu.svelte.d.ts +17 -2
- package/dist/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/dist/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/dist/lib/options/menus/PollutantMenu.svelte +4 -9
- package/dist/lib/options/menus/SettingsMenu.svelte +32 -25
- package/dist/lib/options/menus/SettingsMenu.svelte.d.ts +5 -16
- package/dist/lib/options/plugin-order.d.ts +8 -0
- package/dist/lib/options/plugin-order.js +36 -0
- package/dist/lib/options/pollutant-param.d.ts +7 -5
- package/dist/lib/options/pollutant-param.js +9 -5
- package/dist/lib/options/row-icons.d.ts +8 -0
- package/dist/lib/options/row-icons.js +15 -0
- package/dist/lib/options/rows/MarkerIcon.svelte +5 -5
- package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +1 -2
- package/dist/lib/search/Search.svelte +20 -33
- package/dist/lib/search/Search.svelte.d.ts +5 -1
- package/dist/lib/search/search-pin.svelte.d.ts +11 -0
- package/dist/lib/search/search-pin.svelte.js +28 -0
- package/dist/lib/search/service.d.ts +7 -2
- package/dist/lib/search/service.js +17 -10
- package/dist/lib/stores.svelte.d.ts +8 -0
- package/dist/lib/stores.svelte.js +9 -0
- package/dist/lib/wind/wind-plugin.d.ts +11 -0
- package/dist/lib/wind/wind-plugin.js +25 -0
- package/package.json +12 -10
- package/src/lib/MonitorDetailPanel.svelte +23 -20
- package/src/lib/MonitorMapLayout.svelte +147 -77
- package/src/lib/abort.ts +59 -0
- package/src/lib/components/DataBox.svelte +4 -1
- package/src/lib/config.svelte.ts +16 -0
- package/src/lib/core.ts +2 -2
- package/src/lib/data-chart/DataChart.svelte +54 -24
- package/src/lib/data-chart/DateRange.ts +58 -15
- package/src/lib/data-chart/chart-utils.ts +6 -7
- package/src/lib/data-chart/level-colors.ts +8 -9
- package/src/lib/data-chart/pacific-time.ts +31 -0
- package/src/lib/data-chart/requests.ts +14 -7
- package/src/lib/data-chart/tooltip.ts +2 -2
- package/{dist/lib/ev-stations/components/EvStationTooltip.svelte → src/lib/ev-stations/EvPopup.svelte} +6 -5
- package/src/lib/ev-stations/ev-plugin.svelte.ts +86 -0
- package/src/lib/ev-stations/ev-station-icons.ts +1 -17
- package/src/lib/ev-stations/ev-stations.ts +72 -0
- package/src/lib/ev-stations/ev-store.svelte.ts +117 -0
- package/src/lib/ev-stations/station-popup.svelte.ts +64 -0
- package/src/lib/ev-stations/types.ts +0 -34
- package/src/lib/ev-stations/zip-codes.ts +29 -16
- package/src/lib/index.ts +8 -99
- package/src/lib/legend/Legend.svelte +18 -19
- package/src/lib/levels.ts +24 -0
- package/src/lib/map/MapShell.svelte +17 -24
- package/src/lib/map/basemaps.ts +76 -0
- package/src/lib/map/live-map.svelte.ts +24 -0
- package/src/lib/map/sync-plugins.svelte.ts +69 -0
- package/src/lib/monitors/components/MonitorSubscriptionManager.svelte +82 -82
- package/src/lib/monitors/filtered-source.svelte.ts +38 -0
- package/src/lib/monitors/monitor-details.svelte.ts +12 -3
- package/src/lib/monitors/monitors.svelte.ts +62 -153
- package/src/lib/monitors/retry-load.ts +14 -0
- package/src/lib/monitors/subscriptions.ts +17 -0
- package/src/lib/monitors/types.ts +1 -47
- package/src/lib/options/map-option-defaults.ts +30 -53
- package/src/lib/options/map-options.svelte.ts +30 -0
- package/src/lib/options/menus/LayersMenu.svelte +8 -20
- package/src/lib/options/menus/MonitorsMenu.svelte +34 -18
- package/src/lib/options/menus/OverlaysMenu.svelte +2 -2
- package/src/lib/options/menus/PollutantMenu.svelte +4 -9
- package/src/lib/options/menus/SettingsMenu.svelte +32 -25
- package/src/lib/options/plugin-order.ts +38 -0
- package/src/lib/options/pollutant-param.ts +14 -9
- package/src/lib/options/row-icons.ts +17 -0
- package/src/lib/options/rows/MarkerIcon.svelte +5 -5
- package/src/lib/search/Search.svelte +20 -33
- package/src/lib/search/search-pin.svelte.ts +35 -0
- package/src/lib/search/service.ts +34 -15
- package/src/lib/stores.svelte.ts +15 -0
- package/src/lib/wind/wind-plugin.ts +33 -0
- package/dist/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/dist/lib/collocation-sites/CollocationTooltip.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.d.ts +0 -7
- package/dist/lib/collocation-sites/collocations-icon-manager.svelte.js +0 -18
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +0 -21
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +0 -113
- package/dist/lib/collocation-sites/collocations.svelte.d.ts +0 -9
- package/dist/lib/collocation-sites/collocations.svelte.js +0 -17
- package/dist/lib/collocation-sites/types.d.ts +0 -8
- package/dist/lib/collocation-sites/types.js +0 -1
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/dist/lib/ev-stations/components/EvStationClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/components/EvStationTooltip.svelte.d.ts +0 -7
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.d.ts +0 -19
- package/dist/lib/ev-stations/ev-stations-cluster-renderer.js +0 -131
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.d.ts +0 -31
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +0 -226
- package/dist/lib/ev-stations/ev-stations.svelte.d.ts +0 -9
- package/dist/lib/ev-stations/ev-stations.svelte.js +0 -55
- package/dist/lib/hms/hms-fire-icon-manager.d.ts +0 -13
- package/dist/lib/hms/hms-fire-icon-manager.js +0 -39
- package/dist/lib/hms/hms-fire-map-integration.svelte.d.ts +0 -23
- package/dist/lib/hms/hms-fire-map-integration.svelte.js +0 -94
- package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +0 -39
- package/dist/lib/hms/hms-smoke-map-integration.svelte.js +0 -87
- package/dist/lib/hms/hms.svelte.d.ts +0 -19
- package/dist/lib/hms/hms.svelte.js +0 -227
- package/dist/lib/legend/LegendBar.svelte +0 -45
- package/dist/lib/legend/LegendBar.svelte.d.ts +0 -7
- package/dist/lib/legend/legend-data.d.ts +0 -40
- package/dist/lib/legend/legend-data.js +0 -65
- package/dist/lib/map/Map.svelte +0 -13
- package/dist/lib/map/Map.svelte.d.ts +0 -8
- package/dist/lib/map/basemap.d.ts +0 -14
- package/dist/lib/map/basemap.js +0 -10
- package/dist/lib/map/icons.d.ts +0 -7
- package/dist/lib/map/icons.js +0 -33
- package/dist/lib/map/integrations/base-layer-seperator.d.ts +0 -9
- package/dist/lib/map/integrations/base-layer-seperator.js +0 -11
- package/dist/lib/map/integrations/click-manager.d.ts +0 -14
- package/dist/lib/map/integrations/click-manager.js +0 -52
- package/dist/lib/map/integrations/integrations-manager.d.ts +0 -8
- package/dist/lib/map/integrations/integrations-manager.js +0 -20
- package/dist/lib/map/integrations/map-display-option.svelte.d.ts +0 -7
- package/dist/lib/map/integrations/map-display-option.svelte.js +0 -10
- package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +0 -32
- package/dist/lib/map/integrations/map-geojson-integration.svelte.js +0 -60
- package/dist/lib/map/integrations/map-icon-manager.d.ts +0 -15
- package/dist/lib/map/integrations/map-icon-manager.js +0 -52
- package/dist/lib/map/integrations/map-integration.svelte.d.ts +0 -6
- package/dist/lib/map/integrations/map-integration.svelte.js +0 -2
- package/dist/lib/map/integrations/map-layer-integration.svelte.d.ts +0 -11
- package/dist/lib/map/integrations/map-layer-integration.svelte.js +0 -40
- package/dist/lib/map/integrations/tooltip.svelte.d.ts +0 -36
- package/dist/lib/map/integrations/tooltip.svelte.js +0 -131
- package/dist/lib/map/integrations/types.d.ts +0 -14
- package/dist/lib/map/integrations/types.js +0 -1
- package/dist/lib/map/map-manager.svelte.d.ts +0 -20
- package/dist/lib/map/map-manager.svelte.js +0 -63
- package/dist/lib/map/map-style-state.svelte.d.ts +0 -23
- package/dist/lib/map/map-style-state.svelte.js +0 -86
- package/dist/lib/map/utils.d.ts +0 -7
- package/dist/lib/map/utils.js +0 -36
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/dist/lib/monitors/components/MonitorClusterTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/dist/lib/monitors/components/MonitorTooltip.svelte.d.ts +0 -7
- package/dist/lib/monitors/monitor-utils.d.ts +0 -4
- package/dist/lib/monitors/monitor-utils.js +0 -41
- package/dist/lib/monitors/monitors-cluster-renderer.d.ts +0 -33
- package/dist/lib/monitors/monitors-cluster-renderer.js +0 -221
- package/dist/lib/monitors/monitors-icon-manager.svelte.d.ts +0 -15
- package/dist/lib/monitors/monitors-icon-manager.svelte.js +0 -78
- package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +0 -43
- package/dist/lib/monitors/monitors-map-integration.svelte.js +0 -301
- package/dist/lib/options/reset-map-options.d.ts +0 -2
- package/dist/lib/options/reset-map-options.js +0 -24
- package/dist/lib/reactivity.svelte.d.ts +0 -0
- package/dist/lib/reactivity.svelte.js +0 -83
- package/dist/lib/wind/wind.svelte.d.ts +0 -10
- package/dist/lib/wind/wind.svelte.js +0 -13
- package/src/lib/collocation-sites/CollocationTooltip.svelte +0 -98
- package/src/lib/collocation-sites/collocations-icon-manager.svelte.ts +0 -21
- package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +0 -136
- package/src/lib/collocation-sites/collocations.svelte.ts +0 -24
- package/src/lib/collocation-sites/types.ts +0 -10
- package/src/lib/ev-stations/components/EvStationClusterTooltip.svelte +0 -16
- package/src/lib/ev-stations/ev-stations-cluster-renderer.ts +0 -162
- package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +0 -248
- package/src/lib/ev-stations/ev-stations.svelte.ts +0 -79
- package/src/lib/hms/hms-fire-icon-manager.ts +0 -48
- package/src/lib/hms/hms-fire-map-integration.svelte.ts +0 -109
- package/src/lib/hms/hms-smoke-map-integration.svelte.ts +0 -105
- package/src/lib/hms/hms.svelte.ts +0 -264
- package/src/lib/legend/LegendBar.svelte +0 -45
- package/src/lib/legend/legend-data.ts +0 -101
- package/src/lib/map/Map.svelte +0 -13
- package/src/lib/map/basemap.ts +0 -24
- package/src/lib/map/icons.ts +0 -42
- package/src/lib/map/integrations/base-layer-seperator.ts +0 -17
- package/src/lib/map/integrations/click-manager.ts +0 -64
- package/src/lib/map/integrations/integrations-manager.ts +0 -25
- package/src/lib/map/integrations/map-display-option.svelte.ts +0 -13
- package/src/lib/map/integrations/map-geojson-integration.svelte.ts +0 -77
- package/src/lib/map/integrations/map-icon-manager.ts +0 -67
- package/src/lib/map/integrations/map-integration.svelte.ts +0 -8
- package/src/lib/map/integrations/map-layer-integration.svelte.ts +0 -49
- package/src/lib/map/integrations/tooltip.svelte.ts +0 -153
- package/src/lib/map/integrations/types.ts +0 -24
- package/src/lib/map/map-manager.svelte.ts +0 -83
- package/src/lib/map/map-style-state.svelte.ts +0 -94
- package/src/lib/map/utils.ts +0 -53
- package/src/lib/monitors/components/MonitorClusterTooltip.svelte +0 -112
- package/src/lib/monitors/components/MonitorTooltip.svelte +0 -61
- package/src/lib/monitors/monitor-utils.ts +0 -57
- package/src/lib/monitors/monitors-cluster-renderer.ts +0 -285
- package/src/lib/monitors/monitors-icon-manager.svelte.ts +0 -98
- package/src/lib/monitors/monitors-map-integration.svelte.ts +0 -318
- package/src/lib/options/reset-map-options.ts +0 -28
- package/src/lib/reactivity.svelte.ts +0 -82
- package/src/lib/wind/wind.svelte.ts +0 -20
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Central California Asthma Collaborative (SJVAir)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,27 +1,43 @@
|
|
|
1
1
|
# @sjvair/monitor-map
|
|
2
2
|
|
|
3
|
-
SJVAir's interactive air quality monitor map, built with Svelte 5, Vite
|
|
4
|
-
|
|
3
|
+
SJVAir's interactive air quality monitor map, built with Svelte 5, Vite and
|
|
4
|
+
`@sjvair/map-sdk`. It ships two ways:
|
|
5
5
|
|
|
6
6
|
- **A code-split widget** (`npm run build`), embedded via a `<div id="SJVAirMonitorMap">`
|
|
7
7
|
in a Django template on sjvair.com.
|
|
8
8
|
- **A component library** (`npm run build:lib`, published to npm as
|
|
9
|
-
`@sjvair/monitor-map`), imported by the `sjvair-mobile` Capacitor app
|
|
10
|
-
projects (e.g. `data-dashboard`) that want to embed the map or reuse its map-display
|
|
11
|
-
logic against their own data.
|
|
9
|
+
`@sjvair/monitor-map`), imported by the `sjvair-mobile` Capacitor app.
|
|
12
10
|
|
|
13
11
|
## Installation
|
|
14
12
|
|
|
15
13
|
```bash
|
|
16
|
-
npm install @sjvair/monitor-map
|
|
14
|
+
npm install @sjvair/monitor-map "@sjvair/sdk@npm:@jsr/sjvair__sdk@^5.0.0-beta.3"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
`@sjvair/sdk` (5.x) is a peer dependency, so the host and the map share one copy and the host's
|
|
18
|
+
`setOrigin` covers both. It is published on JSR, so the host's `.npmrc` needs
|
|
19
|
+
`@jsr:registry=https://npm.jsr.io` (and `allow-remote=root` on npm 12). `@sjvair/map-sdk` and
|
|
20
|
+
`@maptiler/sdk` are ordinary dependencies.
|
|
21
|
+
|
|
22
|
+
The 5.0 betas are on the `next` dist-tag: `npm install @sjvair/monitor-map@next`.
|
|
23
|
+
|
|
24
|
+
## Keys
|
|
25
|
+
|
|
26
|
+
The library reads no keys. Call `configureMonitorMap` once at startup, before the map or the EV
|
|
27
|
+
layer is used:
|
|
28
|
+
|
|
29
|
+
```ts
|
|
30
|
+
import { configureMonitorMap } from "@sjvair/monitor-map/core";
|
|
31
|
+
|
|
32
|
+
configureMonitorMap({ maptilerKey: "...", nrelKey: "..." });
|
|
17
33
|
```
|
|
18
34
|
|
|
19
35
|
## Usage
|
|
20
36
|
|
|
21
37
|
### Full-page map, via `sv-router`
|
|
22
38
|
|
|
23
|
-
The default integration path mounts a complete map (
|
|
24
|
-
|
|
39
|
+
The default integration path mounts a complete map (monitors, collocation sites,
|
|
40
|
+
wind, HMS smoke/fire, EV stations) as a route in a host app that
|
|
25
41
|
uses `sv-router`:
|
|
26
42
|
|
|
27
43
|
```ts
|
|
@@ -106,123 +122,35 @@ parameter: the panel shows the map's pollutant when the monitor reports it and o
|
|
|
106
122
|
one it does report, saying so. An inactive monitor gets a panel with its past data and a
|
|
107
123
|
note that it has no current readings; an unknown id gets "Monitor not found."
|
|
108
124
|
|
|
109
|
-
###
|
|
110
|
-
|
|
111
|
-
`
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
panelOpen={false}
|
|
139
|
-
routerEscapeHatch={false}
|
|
140
|
-
>
|
|
141
|
-
{#snippet menu()}
|
|
142
|
-
<OptionsGroup>
|
|
143
|
-
<PollutantMenu />
|
|
144
|
-
<MonitorsMenu />
|
|
145
|
-
</OptionsGroup>
|
|
146
|
-
{/snippet}
|
|
147
|
-
{#snippet loadError()}
|
|
148
|
-
{#if failed}
|
|
149
|
-
<button onclick={load}>Couldn't load air monitors. Try again</button>
|
|
150
|
-
{/if}
|
|
151
|
-
{/snippet}
|
|
152
|
-
</MapShell>
|
|
153
|
-
```
|
|
154
|
-
|
|
155
|
-
The `menu` snippet renders inside the options toolbar; compose `OptionsGroup` with the
|
|
156
|
-
exported menus (`PollutantMenu`, `MonitorsMenu`, `LayersMenu`, `OverlaysMenu`,
|
|
157
|
-
`SettingsMenu`), or `OptionsMenu` with the row components for a custom data source. The
|
|
158
|
-
optional `loadError` snippet renders on the load screen, under the logo, which stays up
|
|
159
|
-
until `ready` is true; use it to show a failed initial load with a retry.
|
|
160
|
-
|
|
161
|
-
See `src/lib/MonitorMapLayout.svelte` for the reference example of wiring a manager, an
|
|
162
|
-
integration list, and menu/overlay content into `MapShell`.
|
|
163
|
-
|
|
164
|
-
**`routerEscapeHatch`**: `MapShell` defaults to `routerEscapeHatch={true}`, which
|
|
165
|
-
intercepts clicks and forces full-page navigation for links outside the map's own
|
|
166
|
-
routes — correct for a full-page map, wrong for a map embedded alongside a host app's
|
|
167
|
-
own navigation. A host with its own routing (like `data-dashboard`) **must** pass
|
|
168
|
-
`routerEscapeHatch={false}`, which also skips `useMonitorMapRouter()` entirely, so no
|
|
169
|
-
`provideMonitorMapRouter(...)` ancestor is required. Pass `basePath` if you need the
|
|
170
|
-
escape-hatch's "known route" prefix without going through `provideMonitorMapRouter`.
|
|
171
|
-
|
|
172
|
-
Mount at most one `MapShell` per app instance — its underlying map/load-screen/
|
|
173
|
-
integration-registration state is module-level singleton state shared across the whole
|
|
174
|
-
page.
|
|
175
|
-
|
|
176
|
-
## Extending
|
|
177
|
-
|
|
178
|
-
### Adding a new map feature
|
|
179
|
-
|
|
180
|
-
Map features are a plugin system built on a few abstract base classes, exported from the
|
|
181
|
-
package root:
|
|
182
|
-
|
|
183
|
-
- `MapIntegration` — base apply/remove lifecycle
|
|
184
|
-
- `MapLayerIntegration` — adds MapTiler layer management
|
|
185
|
-
- `MapGeoJSONIntegration` — adds GeoJSON data-source management, plus a
|
|
186
|
-
`MapIconManager` for sprite icons (this is `src/lib/map/integrations/
|
|
187
|
-
map-geojson-integration.svelte.ts`'s `MapIconLayerIntegration`, exported under this
|
|
188
|
-
name)
|
|
189
|
-
|
|
190
|
-
Extend one of these rather than manipulating the MapTiler map instance directly. Look
|
|
191
|
-
at `src/lib/monitors/monitors-map-integration.svelte.ts` or `src/lib/wind/wind.svelte.ts`
|
|
192
|
-
as reference implementations.
|
|
193
|
-
|
|
194
|
-
### Reusing the monitors map display with different data (`MonitorsDataSource`)
|
|
195
|
-
|
|
196
|
-
`MonitorsMapIntegration` (and the icon-generation base class behind
|
|
197
|
-
`MonitorsIconManager`/`CollocationIconManager`) don't hardcode the live `monitorsManager`
|
|
198
|
-
singleton — they accept a `MonitorsDataSource` via their constructor:
|
|
199
|
-
|
|
200
|
-
```ts
|
|
201
|
-
export interface MonitorsDataSource {
|
|
202
|
-
readonly meta: MonitorsMeta | null;
|
|
203
|
-
readonly pollutant: "pm25" | "o3" | null;
|
|
204
|
-
readonly latest: XMap<string, MonitorLatestType<"pm25" | "o3">> | null;
|
|
205
|
-
readonly levels: Array<SJVAirEntryLevel> | null;
|
|
206
|
-
}
|
|
207
|
-
```
|
|
208
|
-
|
|
209
|
-
`monitorsManager` (auto-polling, always-current readings) is the default when no data
|
|
210
|
-
source is given, so existing consumers are unaffected. A host app that wants the same
|
|
211
|
-
clustering/icon-rendering/filtering/tooltip logic driven by different data — for
|
|
212
|
-
example, `data-dashboard`'s Monitors tab, which colors monitors by the **average of
|
|
213
|
-
historical summary data over a selected date range** rather than a live reading —
|
|
214
|
-
implements its own `MonitorsDataSource` (no polling, populated from
|
|
215
|
-
`getMonitorSummariesDaily`/etc.) and constructs its own instance:
|
|
216
|
-
|
|
217
|
-
```ts
|
|
218
|
-
import { MonitorsMapIntegration, type MonitorsDataSource } from "@sjvair/monitor-map";
|
|
219
|
-
|
|
220
|
-
const myDataSource: MonitorsDataSource = /* ... */;
|
|
221
|
-
const monitorsMapIntegration = new MonitorsMapIntegration(myDataSource);
|
|
222
|
-
```
|
|
223
|
-
|
|
224
|
-
This reuses all of the existing map-rendering work instead of forking it — see
|
|
225
|
-
`CLAUDE.md`'s "Monitor Data Flow" section for more detail.
|
|
125
|
+
### Cancelling requests
|
|
126
|
+
|
|
127
|
+
`monitorsManager.init()` takes `{ signal }` (an `AbortSignal`). Aborting rejects that call with
|
|
128
|
+
the signal's reason. The run is shared: aborting your signal stops only your wait, and the run
|
|
129
|
+
itself is cancelled only when every caller waiting on it has aborted. The panel, chart and
|
|
130
|
+
subscription status cancel their own superseded requests; that work resolves quietly.
|
|
131
|
+
|
|
132
|
+
## Migrating from 4.x
|
|
133
|
+
|
|
134
|
+
- **Keys:** pass the MapTiler and NREL keys with `configureMonitorMap({ maptilerKey, nrelKey })`
|
|
135
|
+
(from `@sjvair/monitor-map` or `/core`). The library no longer reads them from the build
|
|
136
|
+
environment.
|
|
137
|
+
- **`@sjvair/sdk` is a peer dependency** (`^5.0.0-beta.3` for the betas): install it in the host.
|
|
138
|
+
`@sjvair/map-sdk` and `@maptiler/sdk` are ordinary dependencies; add neither.
|
|
139
|
+
- **Svelte `^5.57.0`:** the `svelte` peer range was `^5.0.0`.
|
|
140
|
+
- **`meta` is SDK 5.0's:** `meta.levels` is gone; read `meta.scale`.
|
|
141
|
+
- **`/routes`** is unchanged: `monitorMapRoutes` with the map at `/` and a monitor at
|
|
142
|
+
`/monitor/:id`, lazy as before.
|
|
143
|
+
- **`/core`** keeps `provideMonitorMapRouter`, `useMonitorMapRouter`, `ROUTER_KEY`,
|
|
144
|
+
`MonitorMapRouterApi` and `monitorsManager` (`init(pollutant?, { autoUpdate?, signal? })`,
|
|
145
|
+
`initialized`, `meta`, `list`), and adds `configureMonitorMap` and `monitorMapConfig`.
|
|
146
|
+
- **Tailwind:** add `@source "../node_modules/@sjvair/map-sdk/dist";` and
|
|
147
|
+
`@import "@sjvair/map-sdk/theme.css";` beside the existing monitor-map `@source`. The host
|
|
148
|
+
defines the theme tokens map-sdk needs (`--background` to `--radius`, plus `--popover` and
|
|
149
|
+
`--accent`).
|
|
150
|
+
- **Removed exports:** everything from `.` except `monitorMapRoutes`, the router context,
|
|
151
|
+
`monitorsManager`, `configureMonitorMap` and `monitorMapConfig` (and their types). That includes
|
|
152
|
+
`MapShell`, the options menus, the integration classes, `MonitorsDataSource` and the other
|
|
153
|
+
managers.
|
|
226
154
|
|
|
227
155
|
## Development
|
|
228
156
|
|
|
@@ -237,11 +165,18 @@ npm run lint # Prettier check + ESLint
|
|
|
237
165
|
npm run format # Auto-format with Prettier
|
|
238
166
|
npm test # Vitest (specs in tests/**)
|
|
239
167
|
npm run test:bundle # build:lib, then check the light entries keep maplibre off the eager path
|
|
168
|
+
npm run map-sdk:pack # Optional: pack ../map-sdk into vendor/ to try an unreleased map-sdk
|
|
240
169
|
```
|
|
241
170
|
|
|
171
|
+
`@sjvair/map-sdk` is the published npm version, pinned exactly in `package.json`. To try an
|
|
172
|
+
unreleased map-sdk, run `npm run map-sdk:pack`, then `npm install` the tarball path it prints;
|
|
173
|
+
do not commit that change.
|
|
174
|
+
|
|
242
175
|
## Publishing
|
|
243
176
|
|
|
244
177
|
Publishing to npm (`@sjvair/monitor-map`) happens via GitHub Actions
|
|
245
178
|
(`.github/workflows/release-package.yml`) whenever a GitHub Release is published (or the
|
|
246
179
|
workflow is triggered manually). The workflow runs `npm test` and `npm run test:bundle`
|
|
247
|
-
before publishing. Bump `version` in `package.json` before cutting the release.
|
|
180
|
+
before publishing. Bump `version` in `package.json` before cutting the release. A version with a
|
|
181
|
+
`-` (such as `5.0.0-beta.0`) is published to the `next` dist-tag, not `latest`; mark its GitHub
|
|
182
|
+
Release as a pre-release.
|
|
@@ -1,18 +1,17 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import { untrack, type ComponentProps } from "svelte";
|
|
3
|
-
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
4
3
|
import DataBox from "./components/DataBox.svelte";
|
|
5
4
|
import LoadError from "./LoadError.svelte";
|
|
6
5
|
import MonitorWidgetModal from "./monitors/components/MonitorWidgetModal.svelte";
|
|
7
6
|
import MonitorSubscriptionManager from "./monitors/components/MonitorSubscriptionManager.svelte";
|
|
8
7
|
import DataChart from "./data-chart/DataChart.svelte";
|
|
9
|
-
import { monitorsManager } from "./";
|
|
8
|
+
import { monitorsManager } from "./monitors/monitors.svelte.js";
|
|
10
9
|
import { MonitorDetailsState } from "./monitors/monitor-details.svelte";
|
|
11
|
-
import { choosePanelPollutant } from "./monitors/panel-pollutant";
|
|
10
|
+
import { choosePanelPollutant, PANEL_POLLUTANTS } from "./monitors/panel-pollutant";
|
|
12
11
|
import { useMonitorMapRouter } from "./router-context";
|
|
13
12
|
import { fetchTempByCoords } from "./weather";
|
|
14
13
|
import { Colors, valueToColor } from "./colors";
|
|
15
|
-
import {
|
|
14
|
+
import { NEUTRAL_LEVEL_COLOR, readingDisplay, readingLevel } from "./levels";
|
|
16
15
|
|
|
17
16
|
type TempDataBox = Pick<ComponentProps<typeof DataBox>, "color" | "value">;
|
|
18
17
|
|
|
@@ -28,6 +27,10 @@
|
|
|
28
27
|
// current reading for the map's pollutant.
|
|
29
28
|
const details = new MonitorDetailsState();
|
|
30
29
|
const meta = $derived(monitorsManager.meta);
|
|
30
|
+
// The map's pollutant, when it is one the panel can show.
|
|
31
|
+
const mapPollutant = $derived(
|
|
32
|
+
PANEL_POLLUTANTS.find((p) => p === monitorsManager.pollutant) ?? null
|
|
33
|
+
);
|
|
31
34
|
// Shown only once the metadata is in too; labels and levels come from it.
|
|
32
35
|
const monitor = $derived(details.status === "ready" && meta ? details.monitor : null);
|
|
33
36
|
|
|
@@ -42,14 +45,15 @@
|
|
|
42
45
|
// Bumped whenever the map's readings are replaced, i.e. its 2-minute refresh ran. The data
|
|
43
46
|
// box follows it; the chart decides for itself whether its date range can have new entries.
|
|
44
47
|
let refreshTick = $state(0);
|
|
45
|
-
let seenLatest = untrack(() => monitorsManager.latest);
|
|
48
|
+
let seenLatest = untrack(() => monitorsManager.store.latest);
|
|
46
49
|
|
|
47
50
|
$effect(() => {
|
|
48
|
-
const latest = monitorsManager.latest;
|
|
49
|
-
//
|
|
50
|
-
|
|
51
|
+
const latest = monitorsManager.store.latest;
|
|
52
|
+
// Empty before the first load and while a pollutant change is loading; wait for the
|
|
53
|
+
// readings that follow.
|
|
54
|
+
if (latest.size === 0 || latest === seenLatest) return;
|
|
51
55
|
// The first readings of a cold load arrive alongside this panel's own request.
|
|
52
|
-
const first = seenLatest ===
|
|
56
|
+
const first = seenLatest.size === 0;
|
|
53
57
|
seenLatest = latest;
|
|
54
58
|
if (first) return;
|
|
55
59
|
untrack(() => {
|
|
@@ -59,14 +63,13 @@
|
|
|
59
63
|
});
|
|
60
64
|
|
|
61
65
|
const panel = $derived(
|
|
62
|
-
monitor && meta ? choosePanelPollutant(
|
|
66
|
+
monitor && meta ? choosePanelPollutant(mapPollutant, monitor, meta) : null
|
|
63
67
|
);
|
|
64
68
|
const reading = $derived(
|
|
65
69
|
monitor && panel?.pollutant && panel.hasReading ? monitor.latest[panel.pollutant] : undefined
|
|
66
70
|
);
|
|
67
71
|
// Set when the panel shows a different pollutant than the map is coloring by.
|
|
68
72
|
const pollutantNote = $derived.by(() => {
|
|
69
|
-
const mapPollutant = monitorsManager.pollutant;
|
|
70
73
|
if (!meta || !panel?.pollutant || !panel.hasReading || !mapPollutant) return;
|
|
71
74
|
if (panel.pollutant === mapPollutant) return;
|
|
72
75
|
const shown = meta.entryType(panel.pollutant).label;
|
|
@@ -81,18 +84,15 @@
|
|
|
81
84
|
if (!monitor || !meta || !panel?.pollutant || !reading) return;
|
|
82
85
|
|
|
83
86
|
const entryType = meta.entryType(panel.pollutant);
|
|
84
|
-
const level
|
|
85
|
-
reading.value,
|
|
86
|
-
entryType.asIter.levels!
|
|
87
|
-
);
|
|
87
|
+
const level = readingLevel(reading.value, entryType.scale);
|
|
88
88
|
|
|
89
89
|
const updateDuration = meta.monitorType(monitor.type).expected_interval;
|
|
90
90
|
|
|
91
91
|
return {
|
|
92
|
-
color: level?.color ??
|
|
92
|
+
color: level?.color ?? NEUTRAL_LEVEL_COLOR,
|
|
93
93
|
header: entryType.label,
|
|
94
94
|
subheading: `(${updateDuration} avg)`,
|
|
95
|
-
value:
|
|
95
|
+
value: readingDisplay(reading.value)
|
|
96
96
|
};
|
|
97
97
|
});
|
|
98
98
|
|
|
@@ -201,9 +201,12 @@
|
|
|
201
201
|
</div>
|
|
202
202
|
{/if}
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
204
|
+
<!-- The widget shows current readings, and errors for an inactive monitor. -->
|
|
205
|
+
{#if panel.hasReading}
|
|
206
|
+
<div class="tall:block hidden">
|
|
207
|
+
<MonitorWidgetModal monitorId={monitor.id} />
|
|
208
|
+
</div>
|
|
209
|
+
{/if}
|
|
207
210
|
<!-- eslint-disable-next-line @typescript-eslint/no-explicit-any -->
|
|
208
211
|
{#if (globalThis as any).USER?.is_authenticated}
|
|
209
212
|
<MonitorSubscriptionManager monitorId={monitor.id} />
|
|
@@ -1,38 +1,44 @@
|
|
|
1
1
|
<script lang="ts">
|
|
2
2
|
import type { Snippet } from "svelte";
|
|
3
|
-
import { onDestroy, untrack } from "svelte";
|
|
3
|
+
import { onDestroy, onMount, untrack } from "svelte";
|
|
4
4
|
import { searchParams } from "sv-router";
|
|
5
|
+
import {
|
|
6
|
+
collocationSitesPlugin,
|
|
7
|
+
hmsFirePlugin,
|
|
8
|
+
hmsSmokePlugin,
|
|
9
|
+
monitorsPlugin
|
|
10
|
+
} from "@sjvair/map-sdk";
|
|
11
|
+
import { windPlugin } from "./wind/wind-plugin.js";
|
|
12
|
+
import { evPlugin } from "./ev-stations/ev-plugin.svelte.js";
|
|
13
|
+
import { createEvStore } from "./ev-stations/ev-store.svelte.js";
|
|
14
|
+
import { stationPopups } from "./ev-stations/station-popup.svelte.js";
|
|
5
15
|
import MapShell from "./map/MapShell.svelte";
|
|
16
|
+
import { createLiveMap } from "./map/live-map.svelte.js";
|
|
17
|
+
import { syncPlugins } from "./map/sync-plugins.svelte.js";
|
|
18
|
+
import LoadError from "./LoadError.svelte";
|
|
19
|
+
import { retryMonitorsLoad } from "./monitors/retry-load.js";
|
|
20
|
+
import { monitorsManager } from "./monitors/monitors.svelte.js";
|
|
21
|
+
import { filteredMonitors } from "./monitors/filtered-source.svelte.js";
|
|
22
|
+
import Legend from "./legend/Legend.svelte";
|
|
23
|
+
import {
|
|
24
|
+
collocationSitesStore,
|
|
25
|
+
hmsFireStore,
|
|
26
|
+
hmsSmokeStore,
|
|
27
|
+
monitorsStore
|
|
28
|
+
} from "./stores.svelte.js";
|
|
6
29
|
import OptionsGroup from "./options/OptionsGroup.svelte";
|
|
7
30
|
import PollutantMenu from "./options/menus/PollutantMenu.svelte";
|
|
8
31
|
import MonitorsMenu from "./options/menus/MonitorsMenu.svelte";
|
|
9
32
|
import LayersMenu from "./options/menus/LayersMenu.svelte";
|
|
10
33
|
import OverlaysMenu from "./options/menus/OverlaysMenu.svelte";
|
|
11
34
|
import SettingsMenu from "./options/menus/SettingsMenu.svelte";
|
|
35
|
+
import { mapOptions } from "./options/map-options.svelte.js";
|
|
36
|
+
import { wantedPlugins } from "./options/plugin-order";
|
|
12
37
|
import {
|
|
13
38
|
applyPollutantSelection,
|
|
14
39
|
currentSelection,
|
|
15
40
|
parsePollutantParam
|
|
16
41
|
} from "./options/pollutant-param";
|
|
17
|
-
import LoadError from "./LoadError.svelte";
|
|
18
|
-
import Legend from "./legend/Legend.svelte";
|
|
19
|
-
import {
|
|
20
|
-
fireSmokeLegendSections,
|
|
21
|
-
pollutantLegendSection,
|
|
22
|
-
type LegendSection
|
|
23
|
-
} from "./legend/legend-data";
|
|
24
|
-
import { monitorsManager } from "./monitors/monitors.svelte";
|
|
25
|
-
import { monitorsMapIntegration } from "./monitors/monitors-map-integration.svelte";
|
|
26
|
-
import { windMapIntegration } from "./wind/wind.svelte";
|
|
27
|
-
import { baseLayerSeperator } from "./map/integrations/base-layer-seperator";
|
|
28
|
-
import type { SomeMapIntegration } from "./map/integrations/types";
|
|
29
|
-
import { collocationSitesManager } from "./collocation-sites/collocations.svelte";
|
|
30
|
-
import { collocationSitesMapIntegration } from "./collocation-sites/collocations-map-integration.svelte";
|
|
31
|
-
import { evStationsMapIntegration } from "./ev-stations/ev-stations-map-integration.svelte";
|
|
32
|
-
import { hmsManager } from "./hms/hms.svelte";
|
|
33
|
-
import { hmsFireMapIntegration } from "./hms/hms-fire-map-integration.svelte";
|
|
34
|
-
import { FIRE_LEGEND_CATEGORIES } from "./hms/hms-fire-icon-manager";
|
|
35
|
-
import { hmsSmokeMapIntegration, SMOKE_LEGEND } from "./hms/hms-smoke-map-integration.svelte";
|
|
36
42
|
import Search from "./search/Search.svelte";
|
|
37
43
|
import { useMonitorMapRouter } from "./router-context";
|
|
38
44
|
|
|
@@ -44,51 +50,105 @@
|
|
|
44
50
|
|
|
45
51
|
const { route, navigate, basePath } = useMonitorMapRouter();
|
|
46
52
|
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
collocationSitesMapIntegration,
|
|
50
|
-
windMapIntegration,
|
|
51
|
-
hmsSmokeMapIntegration,
|
|
52
|
-
hmsFireMapIntegration,
|
|
53
|
-
monitorsMapIntegration,
|
|
54
|
-
evStationsMapIntegration
|
|
55
|
-
];
|
|
53
|
+
// The map's state lives as long as this layout; MapView attaches the GL map when it mounts.
|
|
54
|
+
const map = createLiveMap();
|
|
56
55
|
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// "?pollutant=none" must disable monitors before the effects below first run, so the
|
|
60
|
-
// state->URL effect never sees enabled monitors and overwrites the param.
|
|
56
|
+
// "?pollutant=none" must turn monitors off before the effects below first run, so the
|
|
57
|
+
// state->URL effect never sees monitors on and overwrites the param.
|
|
61
58
|
if (parsePollutantParam(route.search.pollutant) === "none") {
|
|
62
|
-
|
|
59
|
+
mapOptions.monitorsOn = false;
|
|
63
60
|
}
|
|
64
61
|
|
|
65
62
|
// The load screen stays up until the monitors load, so a failure has to be shown on it.
|
|
66
|
-
|
|
63
|
+
const loadFailed = $derived(monitorsStore.dataSource.status.state === "error");
|
|
67
64
|
|
|
68
65
|
function loadMonitors() {
|
|
69
|
-
loadFailed = false;
|
|
70
66
|
// A rejected init() can be retried: a later call starts a fresh run.
|
|
71
|
-
monitorsManager.init(route.search.pollutant).catch((error) =>
|
|
72
|
-
console.error(error);
|
|
73
|
-
loadFailed = true;
|
|
74
|
-
});
|
|
67
|
+
monitorsManager.init(route.search.pollutant).catch((error) => console.error(error));
|
|
75
68
|
}
|
|
76
69
|
|
|
77
70
|
loadMonitors();
|
|
78
|
-
// A previous instance of this layout stopped polling in onDestroy; init() is a no-op by now.
|
|
79
|
-
monitorsManager.resumeAutoUpdate();
|
|
80
|
-
collocationSitesManager.init();
|
|
81
|
-
hmsManager.init();
|
|
82
|
-
monitorsMapIntegration.onMonitorClick = (id: string) => {
|
|
83
|
-
navigate(`${basePath}/monitor/:id`, { params: { id } }).catch(console.error);
|
|
84
|
-
};
|
|
85
71
|
|
|
86
|
-
|
|
72
|
+
const panelOpen = $derived(route.pathname.startsWith(`${basePath}/monitor/`));
|
|
73
|
+
const selectedId = $derived(panelOpen ? route.params.id : undefined);
|
|
74
|
+
|
|
75
|
+
// The monitors the Monitors menu's filters let through; clusters are built from these.
|
|
76
|
+
const monitorsSource = filteredMonitors(monitorsStore.dataSource, mapOptions);
|
|
77
|
+
|
|
78
|
+
// EV chargers: each level is fetched the first time it is wanted. A failure shows in the legend
|
|
79
|
+
// with a retry.
|
|
80
|
+
const evStore = createEvStore();
|
|
81
|
+
const evPopups = stationPopups(map);
|
|
82
|
+
$effect(() => {
|
|
83
|
+
const wanted = wantedPlugins(mapOptions, monitorsManager.pollutant);
|
|
84
|
+
for (const [key, level] of [
|
|
85
|
+
["ev-l2", "lvl2"],
|
|
86
|
+
["ev-l3", "lvl3"]
|
|
87
|
+
] as const) {
|
|
88
|
+
if (wanted.has(key)) untrack(() => evStore.load(level)).catch(console.error);
|
|
89
|
+
}
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
// The options decide which plugins are on the map, in DRAW_ORDER. `clustered` is fixed when a
|
|
93
|
+
// plugin is built, so a change rebuilds it.
|
|
94
|
+
syncPlugins(
|
|
95
|
+
map,
|
|
96
|
+
() => wantedPlugins(mapOptions, monitorsManager.pollutant),
|
|
97
|
+
{
|
|
98
|
+
monitors: () => ({
|
|
99
|
+
plugin: monitorsPlugin({
|
|
100
|
+
id: "monitors",
|
|
101
|
+
source: monitorsSource,
|
|
102
|
+
layerLabel: "Monitors",
|
|
103
|
+
selectable: true,
|
|
104
|
+
selectedStyle: "enlarge",
|
|
105
|
+
clustered: mapOptions.clustered
|
|
106
|
+
}),
|
|
107
|
+
// A removed plugin forgets its mode and selection, so each add passes them again.
|
|
108
|
+
options: {
|
|
109
|
+
selectionMode: "single",
|
|
110
|
+
selection: selectedId ? { kind: "monitors", ids: [selectedId] } : null
|
|
111
|
+
}
|
|
112
|
+
}),
|
|
113
|
+
"ev-l2": () =>
|
|
114
|
+
evPlugin("lvl2", evStore.dataSource("lvl2"), { clustered: mapOptions.clustered }),
|
|
115
|
+
"ev-l3": () =>
|
|
116
|
+
evPlugin("lvl3", evStore.dataSource("lvl3"), { clustered: mapOptions.clustered })
|
|
117
|
+
},
|
|
118
|
+
() => mapOptions.clustered
|
|
119
|
+
);
|
|
120
|
+
|
|
121
|
+
// Wind, Fire & Smoke and collocation sites take no build-time option, so they sit in their own call
|
|
122
|
+
// without `deps`: a Marker Clusters change rebuilds only the monitors above. DRAW_ORDER still
|
|
123
|
+
// holds across the two calls because `syncPlugins` reads `map.plugins` (every plugin on the
|
|
124
|
+
// map, whichever call added it) to find the plugin to add before.
|
|
125
|
+
syncPlugins(map, () => wantedPlugins(mapOptions, monitorsManager.pollutant), {
|
|
126
|
+
wind: () => windPlugin(),
|
|
127
|
+
"hms-smoke": () =>
|
|
128
|
+
hmsSmokePlugin({
|
|
129
|
+
id: "hms-smoke",
|
|
130
|
+
source: hmsSmokeStore.dataSource,
|
|
131
|
+
layerLabel: "Smoke"
|
|
132
|
+
}),
|
|
133
|
+
"hms-fire": () =>
|
|
134
|
+
hmsFirePlugin({
|
|
135
|
+
id: "hms-fire",
|
|
136
|
+
source: hmsFireStore.groups,
|
|
137
|
+
hotspots: hmsFireStore.hotspots,
|
|
138
|
+
layerLabel: "Fires"
|
|
139
|
+
}),
|
|
140
|
+
"collocation-sites": () =>
|
|
141
|
+
collocationSitesPlugin({
|
|
142
|
+
id: "collocation-sites",
|
|
143
|
+
source: collocationSitesStore.dataSource,
|
|
144
|
+
layerLabel: "Collocation sites"
|
|
145
|
+
})
|
|
146
|
+
});
|
|
87
147
|
|
|
88
148
|
// Keep the Pollutant menu's selection (pm25 / o3 / none) and the "pollutant" URL param in
|
|
89
|
-
// sync, both directions. init() only seeds the pollutant on the
|
|
149
|
+
// sync, both directions. init() only seeds the pollutant on the store's first-ever
|
|
90
150
|
// initialization; this effect applies a later "?pollutant=" change (e.g. navigating in from
|
|
91
|
-
// elsewhere) to an already-running map.
|
|
151
|
+
// elsewhere, or back/forward) to an already-running map.
|
|
92
152
|
$effect(() => {
|
|
93
153
|
const selection = parsePollutantParam(route.search.pollutant);
|
|
94
154
|
if (!monitorsManager.initialized || !selection) return;
|
|
@@ -96,47 +156,54 @@
|
|
|
96
156
|
// state here would make a UI-driven change re-trigger this effect before the effect below
|
|
97
157
|
// syncs the URL, so it would see the stale URL and revert the user's change.
|
|
98
158
|
untrack(() => {
|
|
99
|
-
if (
|
|
100
|
-
|
|
101
|
-
) {
|
|
102
|
-
applyPollutantSelection(selection, pollutantTargets);
|
|
159
|
+
if (currentSelection(monitorsManager.pollutant, mapOptions.monitorsOn) !== selection) {
|
|
160
|
+
applyPollutantSelection(selection, { manager: monitorsManager, options: mapOptions });
|
|
103
161
|
}
|
|
104
162
|
});
|
|
105
163
|
});
|
|
106
164
|
|
|
107
165
|
// ...and the reverse: reflect UI-driven changes back to the URL.
|
|
108
166
|
$effect(() => {
|
|
109
|
-
const selection = currentSelection(monitorsManager.pollutant,
|
|
167
|
+
const selection = currentSelection(monitorsManager.pollutant, mapOptions.monitorsOn);
|
|
110
168
|
if (selection && searchParams.get("pollutant") !== selection) {
|
|
111
169
|
searchParams.set("pollutant", selection);
|
|
112
170
|
}
|
|
113
171
|
});
|
|
114
172
|
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
173
|
+
onMount(() => {
|
|
174
|
+
// A failed load leaves its layer empty (map-sdk's legend shows the status); log it once.
|
|
175
|
+
for (const store of [hmsSmokeStore, hmsFireStore, collocationSitesStore]) {
|
|
176
|
+
store.init().catch(console.error);
|
|
177
|
+
}
|
|
178
|
+
// A previous instance of this layout paused polling in onDestroy; init() is a no-op by now.
|
|
179
|
+
monitorsManager.resumeAutoUpdate();
|
|
180
|
+
return map.on("featureclick", ({ pluginId, ref, lngLat }) => {
|
|
181
|
+
// A station clicked on the map or picked from the list: its popup.
|
|
182
|
+
if ((pluginId === "ev-l2" || pluginId === "ev-l3") && ref.kind === "feature") {
|
|
183
|
+
const station = evStore.station(ref.id);
|
|
184
|
+
if (station) evPopups.open(pluginId, station, lngLat);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
if (pluginId !== "monitors" || ref.kind !== "monitor") return;
|
|
188
|
+
navigate(`${basePath}/monitor/:id`, { params: { id: ref.id } }).catch(console.error);
|
|
189
|
+
map.easeTo({ center: lngLat, minZoom: 12 });
|
|
190
|
+
});
|
|
119
191
|
});
|
|
120
192
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
if (pollutant) sections.push(pollutant);
|
|
126
|
-
}
|
|
127
|
-
if (hmsFireMapIntegration.enabled && hmsSmokeMapIntegration.enabled) {
|
|
128
|
-
sections.push(...fireSmokeLegendSections(FIRE_LEGEND_CATEGORIES, SMOKE_LEGEND));
|
|
129
|
-
}
|
|
130
|
-
return sections;
|
|
193
|
+
// The selected monitor follows the route: the open panel's monitor, none once it closes.
|
|
194
|
+
$effect(() => {
|
|
195
|
+
const id = selectedId;
|
|
196
|
+
map.setSelection("monitors", id ? { kind: "monitors", ids: [id] } : null);
|
|
131
197
|
});
|
|
132
198
|
|
|
133
199
|
onDestroy(() => {
|
|
134
|
-
monitorsManager.
|
|
200
|
+
monitorsManager.pauseAutoUpdate();
|
|
201
|
+
map.destroy();
|
|
135
202
|
});
|
|
136
203
|
</script>
|
|
137
204
|
|
|
138
205
|
<MapShell
|
|
139
|
-
{
|
|
206
|
+
{map}
|
|
140
207
|
ready={monitorsManager.initialized}
|
|
141
208
|
{panelOpen}
|
|
142
209
|
knownRoutes={[`${basePath}/monitor/`]}
|
|
@@ -144,7 +211,10 @@
|
|
|
144
211
|
>
|
|
145
212
|
{#snippet loadError()}
|
|
146
213
|
{#if loadFailed}
|
|
147
|
-
<LoadError
|
|
214
|
+
<LoadError
|
|
215
|
+
message="Couldn't load air monitors."
|
|
216
|
+
onretry={() => retryMonitorsLoad(monitorsStore, loadMonitors)}
|
|
217
|
+
/>
|
|
148
218
|
{/if}
|
|
149
219
|
{/snippet}
|
|
150
220
|
{#snippet menu()}
|
|
@@ -155,16 +225,16 @@
|
|
|
155
225
|
<OptionsGroup>
|
|
156
226
|
<LayersMenu />
|
|
157
227
|
<OverlaysMenu />
|
|
158
|
-
<SettingsMenu />
|
|
228
|
+
<SettingsMenu {map} />
|
|
159
229
|
</OptionsGroup>
|
|
160
230
|
{/snippet}
|
|
161
|
-
{#snippet search()}
|
|
162
|
-
<Search />
|
|
163
|
-
{/snippet}
|
|
164
231
|
{#snippet overlays()}
|
|
165
232
|
<div class="pointer-events-none absolute bottom-4 left-4 z-10">
|
|
166
|
-
<Legend
|
|
233
|
+
<Legend {map} />
|
|
167
234
|
</div>
|
|
168
235
|
{/snippet}
|
|
236
|
+
{#snippet search()}
|
|
237
|
+
<Search {map} />
|
|
238
|
+
{/snippet}
|
|
169
239
|
{@render children()}
|
|
170
240
|
</MapShell>
|