@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.
- package/LICENSE +21 -0
- package/README.md +59 -138
- package/dist/lib/MonitorDetailPanel.svelte +23 -20
- package/dist/lib/MonitorMapLayout.svelte +147 -77
- 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 +40 -25
- 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/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 +66 -61
- 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/monitors.svelte.d.ts +25 -35
- package/dist/lib/monitors/monitors.svelte.js +35 -218
- package/dist/lib/monitors/retry-load.d.ts +8 -0
- package/dist/lib/monitors/retry-load.js +11 -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/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 +40 -25
- 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/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 +66 -61
- package/src/lib/monitors/filtered-source.svelte.ts +38 -0
- package/src/lib/monitors/monitors.svelte.ts +58 -261
- package/src/lib/monitors/retry-load.ts +14 -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 -10
- package/dist/lib/collocation-sites/collocations.svelte.js +0 -21
- 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 -10
- package/dist/lib/ev-stations/ev-stations.svelte.js +0 -57
- 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 -20
- package/dist/lib/hms/hms.svelte.js +0 -237
- 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 -27
- 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 -84
- 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 -270
- 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
|
@@ -1,94 +0,0 @@
|
|
|
1
|
-
import type { MapStyleVariant, ReferenceMapStyle } from "@maptiler/sdk";
|
|
2
|
-
import { DefaultMapStyle, mapManager } from "./map-manager.svelte.js";
|
|
3
|
-
import { integrationsManager } from "./integrations/integrations-manager";
|
|
4
|
-
import { variantForStyle } from "./basemap";
|
|
5
|
-
|
|
6
|
-
/** Basemap + theme selection, shared so the Settings menu and Reset Map Options agree. */
|
|
7
|
-
class MapStyleState {
|
|
8
|
-
referenceStyle: ReferenceMapStyle = $state.raw(DefaultMapStyle.getReferenceStyle());
|
|
9
|
-
variant: MapStyleVariant = $state.raw(DefaultMapStyle);
|
|
10
|
-
// The variant last handed to setStyle (loaded or still loading); null once a load has failed
|
|
11
|
-
// with nothing left to fall back to, so that any later selection is tried again.
|
|
12
|
-
private applied: MapStyleVariant | null = DefaultMapStyle;
|
|
13
|
-
// The variant the map last finished loading, which a failed change falls back to.
|
|
14
|
-
private loaded: MapStyleVariant = DefaultMapStyle;
|
|
15
|
-
private stopWaiting: (() => void) | null = null;
|
|
16
|
-
|
|
17
|
-
selectReferenceStyle(style: ReferenceMapStyle): void {
|
|
18
|
-
this.referenceStyle = style;
|
|
19
|
-
this.selectVariant(variantForStyle(style, this.variant));
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
selectVariant(variant: MapStyleVariant): void {
|
|
23
|
-
this.variant = variant;
|
|
24
|
-
this.apply();
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
reset(): void {
|
|
28
|
-
this.referenceStyle = DefaultMapStyle.getReferenceStyle();
|
|
29
|
-
this.selectVariant(DefaultMapStyle);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
private apply(): void {
|
|
33
|
-
if (!mapManager.map || this.variant === this.applied) return;
|
|
34
|
-
this.load(this.variant);
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
private load(variant: MapStyleVariant): void {
|
|
38
|
-
const map = mapManager.map;
|
|
39
|
-
if (!map) return;
|
|
40
|
-
// A newer change supersedes one still in flight.
|
|
41
|
-
this.stopWaiting?.();
|
|
42
|
-
this.applied = variant;
|
|
43
|
-
|
|
44
|
-
// setStyle wipes every custom source/layer; re-add enabled integrations once it loads.
|
|
45
|
-
const onLoad = () => {
|
|
46
|
-
this.stopWaiting?.();
|
|
47
|
-
this.loaded = variant;
|
|
48
|
-
mapManager.styleLoading = false;
|
|
49
|
-
integrationsManager.refresh();
|
|
50
|
-
};
|
|
51
|
-
// "error" also fires for tiles, sprites and glyphs; only the style document failing means
|
|
52
|
-
// the change failed.
|
|
53
|
-
const onError = (event: { error?: unknown }) => {
|
|
54
|
-
if (!isRequestFor(event.error, variant.getExpandedStyleURL())) return;
|
|
55
|
-
this.stopWaiting?.();
|
|
56
|
-
this.recover(variant);
|
|
57
|
-
};
|
|
58
|
-
this.stopWaiting = () => {
|
|
59
|
-
map.off("style.load", onLoad);
|
|
60
|
-
map.off("error", onError);
|
|
61
|
-
this.stopWaiting = null;
|
|
62
|
-
};
|
|
63
|
-
map.on("style.load", onLoad);
|
|
64
|
-
map.on("error", onError);
|
|
65
|
-
mapManager.styleLoading = true;
|
|
66
|
-
map.setStyle(variant);
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
/**
|
|
70
|
-
* A style that fails to load leaves the map with no style at all, so go back to the last one
|
|
71
|
-
* that loaded and make the selection say so. When that one fails too (e.g. offline) there is
|
|
72
|
-
* nothing to draw on: `styleLoading` stays set, since integrations cannot add layers to a
|
|
73
|
-
* missing style, and the next selection starts a fresh attempt.
|
|
74
|
-
*/
|
|
75
|
-
private recover(failed: MapStyleVariant): void {
|
|
76
|
-
if (failed === this.loaded) {
|
|
77
|
-
this.applied = null;
|
|
78
|
-
return;
|
|
79
|
-
}
|
|
80
|
-
this.variant = this.loaded;
|
|
81
|
-
this.referenceStyle = this.loaded.getReferenceStyle();
|
|
82
|
-
this.load(this.loaded);
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
/** Whether a map "error" event's error is a failed request for `url`, ignoring the query. */
|
|
87
|
-
function isRequestFor(error: unknown, url: string): boolean {
|
|
88
|
-
const failed = (error as { url?: unknown } | null | undefined)?.url;
|
|
89
|
-
if (typeof failed !== "string") return false;
|
|
90
|
-
return failed.split("?")[0] === url.split("?")[0];
|
|
91
|
-
}
|
|
92
|
-
|
|
93
|
-
export const mapStyleState = new MapStyleState();
|
|
94
|
-
export type { MapStyleState };
|
package/src/lib/map/utils.ts
DELETED
|
@@ -1,53 +0,0 @@
|
|
|
1
|
-
import { mount, unmount } from "svelte";
|
|
2
|
-
import type { Component } from "svelte";
|
|
3
|
-
import { MapStyle, Popup } from "@maptiler/sdk";
|
|
4
|
-
import type { ReferenceMapStyle, GeoJSONSource, LngLat } from "@maptiler/sdk";
|
|
5
|
-
|
|
6
|
-
export const MAP_STYLE_OPTIONS: ReferenceMapStyle[] = (() => {
|
|
7
|
-
const latest = new Map<string, { version: number; style: ReferenceMapStyle }>();
|
|
8
|
-
for (const style of Object.values(MapStyle) as ReferenceMapStyle[]) {
|
|
9
|
-
const id = style.getId();
|
|
10
|
-
const match = id.match(/^(.+?)_V(\d+)$/i);
|
|
11
|
-
const baseName = match ? match[1] : id;
|
|
12
|
-
const version = match ? parseInt(match[2], 10) : 0;
|
|
13
|
-
const existing = latest.get(baseName);
|
|
14
|
-
if (!existing || version > existing.version) {
|
|
15
|
-
latest.set(baseName, { version, style });
|
|
16
|
-
}
|
|
17
|
-
}
|
|
18
|
-
return Array.from(latest.values())
|
|
19
|
-
.map((e) => e.style)
|
|
20
|
-
.sort((a, b) => a.getName().localeCompare(b.getName()));
|
|
21
|
-
})();
|
|
22
|
-
|
|
23
|
-
export function isGeoJSONSource(source: unknown): source is GeoJSONSource {
|
|
24
|
-
return (
|
|
25
|
-
typeof source === "object" &&
|
|
26
|
-
source !== null &&
|
|
27
|
-
"type" in source &&
|
|
28
|
-
(source as Record<string, unknown>).type === "geojson"
|
|
29
|
-
);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function mountPopup<P extends Record<string, unknown>>(
|
|
33
|
-
component: Component<P>,
|
|
34
|
-
props: P,
|
|
35
|
-
lngLat: LngLat,
|
|
36
|
-
closeButton = false
|
|
37
|
-
): Popup {
|
|
38
|
-
const container = document.createElement("div");
|
|
39
|
-
const instance = mount(component, { target: container, props });
|
|
40
|
-
const popup = new Popup({ closeButton, closeOnClick: false, maxWidth: "none" })
|
|
41
|
-
.setLngLat(lngLat)
|
|
42
|
-
.setDOMContent(container);
|
|
43
|
-
popup.on("close", () => unmount(instance));
|
|
44
|
-
return popup;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export function mountClickPopup<P extends Record<string, unknown>>(
|
|
48
|
-
component: Component<P>,
|
|
49
|
-
props: P,
|
|
50
|
-
lngLat: LngLat
|
|
51
|
-
): Popup {
|
|
52
|
-
return mountPopup(component, props, lngLat, true);
|
|
53
|
-
}
|
|
@@ -1,112 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { Geometry } from "geojson";
|
|
3
|
-
import { GeoJSONSource } from "@maptiler/sdk";
|
|
4
|
-
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
5
|
-
import type { ComponentProps } from "svelte";
|
|
6
|
-
import { area } from "@turf/area";
|
|
7
|
-
import { convertArea } from "@turf/helpers";
|
|
8
|
-
import DataBox from "$lib/components/DataBox.svelte";
|
|
9
|
-
import { mapManager } from "$lib/map/map-manager.svelte.js";
|
|
10
|
-
import type { MonitorClusterMapFeature, MonitorMapFeature } from "../types";
|
|
11
|
-
import { monitorsManager } from "../monitors.svelte";
|
|
12
|
-
import { getCurrentLevel } from "../monitor-utils";
|
|
13
|
-
|
|
14
|
-
interface MonitorClusterTooltipProps {
|
|
15
|
-
feature: MonitorClusterMapFeature;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
interface MonitorClusterTooltipData extends ComponentProps<typeof DataBox> {
|
|
19
|
-
clusterType: string;
|
|
20
|
-
count: number;
|
|
21
|
-
coverage?: string;
|
|
22
|
-
max: number;
|
|
23
|
-
min: number;
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
const { feature }: MonitorClusterTooltipProps = $props();
|
|
27
|
-
|
|
28
|
-
let data: MonitorClusterTooltipData | undefined = $state();
|
|
29
|
-
|
|
30
|
-
$effect(() => {
|
|
31
|
-
const source = mapManager.map!.getSource(feature.source)! as GeoJSONSource;
|
|
32
|
-
const latest = monitorsManager.latest;
|
|
33
|
-
const meta = monitorsManager.meta;
|
|
34
|
-
|
|
35
|
-
source
|
|
36
|
-
.getClusterLeaves(feature.properties.cluster_id, feature.properties.point_count, 0)
|
|
37
|
-
.then((features) => {
|
|
38
|
-
//const monitor = monitorsManager.latest?.get(feature.properties.id);
|
|
39
|
-
|
|
40
|
-
if (!features.length || !meta || !latest) return;
|
|
41
|
-
const monitors = (features as Array<MonitorMapFeature>).map((f) =>
|
|
42
|
-
latest?.get(f.properties.id)
|
|
43
|
-
);
|
|
44
|
-
|
|
45
|
-
const firstMonitor = monitors[0];
|
|
46
|
-
if (!firstMonitor) return;
|
|
47
|
-
|
|
48
|
-
const entryType = meta.entryType(firstMonitor.latest.entry_type);
|
|
49
|
-
const monitorType = meta.monitorType(firstMonitor.type);
|
|
50
|
-
|
|
51
|
-
const coords = monitors.map((m) => m!.position!.coordinates);
|
|
52
|
-
const values = monitors.map((m) => parseFloat(m?.latest.value ?? "0"));
|
|
53
|
-
|
|
54
|
-
const avg = Math.round(values.reduce((sum, value) => sum + value, 0) / monitors.length);
|
|
55
|
-
|
|
56
|
-
const level: SJVAirEntryLevel | undefined = getCurrentLevel(avg, entryType.asIter.levels!);
|
|
57
|
-
|
|
58
|
-
data = {
|
|
59
|
-
color: level?.color || "#d3d3d3",
|
|
60
|
-
count: feature.properties.point_count,
|
|
61
|
-
coverage: calculateArea(coords),
|
|
62
|
-
header: entryType.label,
|
|
63
|
-
clusterType: `${firstMonitor.is_sjvair ? "SJVAir " : ""}${monitorType.label} Monitors`,
|
|
64
|
-
subheading: "(cluster avg)",
|
|
65
|
-
max: values.reduce((prev, current) => (prev > current ? prev : current)),
|
|
66
|
-
min: values.reduce((prev, current) => (prev < current ? prev : current)),
|
|
67
|
-
value: avg.toString()
|
|
68
|
-
};
|
|
69
|
-
});
|
|
70
|
-
|
|
71
|
-
function calculateArea(coords: Array<Array<number>>) {
|
|
72
|
-
// TODO: update to handle lines as well as area (only 2 in cluster)
|
|
73
|
-
if (coords.length === 3) {
|
|
74
|
-
coords.push(coords[0]);
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const coverage = area({
|
|
78
|
-
type: "Feature",
|
|
79
|
-
geometry: {
|
|
80
|
-
type: "Polygon",
|
|
81
|
-
coordinates: [coords]
|
|
82
|
-
}
|
|
83
|
-
} as unknown as Geometry);
|
|
84
|
-
|
|
85
|
-
return coverage > 0 ? convertArea(coverage, "meters", "miles").toFixed(3) : undefined;
|
|
86
|
-
}
|
|
87
|
-
});
|
|
88
|
-
</script>
|
|
89
|
-
|
|
90
|
-
{#if data}
|
|
91
|
-
<div class="flex gap-2">
|
|
92
|
-
<DataBox
|
|
93
|
-
color={data.color}
|
|
94
|
-
header={data.header}
|
|
95
|
-
subheading={data.subheading}
|
|
96
|
-
value={data.value}
|
|
97
|
-
/>
|
|
98
|
-
<div class="flex flex-col">
|
|
99
|
-
<h1 class="text-lg font-bold">{data.clusterType}</h1>
|
|
100
|
-
<p>
|
|
101
|
-
{data.count} monitors
|
|
102
|
-
{#if data.coverage}
|
|
103
|
-
<span>covering {data.coverage}mi²</span>
|
|
104
|
-
{/if}
|
|
105
|
-
</p>
|
|
106
|
-
<div class="mt-auto">
|
|
107
|
-
<p class="text-base">Max: {data.max}µg/m³</p>
|
|
108
|
-
<p class="text-base leading-none">Min: {data.min}µg/m³</p>
|
|
109
|
-
</div>
|
|
110
|
-
</div>
|
|
111
|
-
</div>
|
|
112
|
-
{/if}
|
|
@@ -1,61 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
3
|
-
import { format, formatDistanceToNow } from "date-fns";
|
|
4
|
-
import DataBox from "$lib/components/DataBox.svelte";
|
|
5
|
-
import { monitorsManager } from "../monitors.svelte";
|
|
6
|
-
import { getCurrentLevel } from "../monitor-utils";
|
|
7
|
-
import type { MonitorMapFeature } from "../types";
|
|
8
|
-
|
|
9
|
-
interface MonitorTooltipProps {
|
|
10
|
-
feature: MonitorMapFeature;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
const { feature }: MonitorTooltipProps = $props();
|
|
14
|
-
|
|
15
|
-
const data = $derived.by(() => {
|
|
16
|
-
const monitor = monitorsManager.latest?.get(feature.properties.id);
|
|
17
|
-
|
|
18
|
-
if (!monitor || !monitorsManager.meta) return;
|
|
19
|
-
|
|
20
|
-
const entryType = monitorsManager.meta.entryType(monitor.latest.entry_type);
|
|
21
|
-
const level: SJVAirEntryLevel | undefined = getCurrentLevel(
|
|
22
|
-
monitor.latest.value,
|
|
23
|
-
entryType.asIter.levels!
|
|
24
|
-
);
|
|
25
|
-
|
|
26
|
-
const updateDuration = monitorsManager.meta.monitorType(monitor.type).expected_interval;
|
|
27
|
-
|
|
28
|
-
return {
|
|
29
|
-
color: level?.color ?? "",
|
|
30
|
-
header: entryType.label,
|
|
31
|
-
date: {
|
|
32
|
-
formatted: format(monitor.latest.timestamp, "h:mm:aaa EEEE MMM dd, yyyy"),
|
|
33
|
-
diference: formatDistanceToNow(monitor.latest.timestamp, {
|
|
34
|
-
addSuffix: true,
|
|
35
|
-
includeSeconds: true
|
|
36
|
-
})
|
|
37
|
-
},
|
|
38
|
-
subheading: `(${updateDuration} avg)`,
|
|
39
|
-
value: Math.round(parseInt(monitor.latest.value, 10)).toString()
|
|
40
|
-
};
|
|
41
|
-
});
|
|
42
|
-
</script>
|
|
43
|
-
|
|
44
|
-
{#if data}
|
|
45
|
-
<div class="flex gap-2">
|
|
46
|
-
<DataBox
|
|
47
|
-
color={data.color}
|
|
48
|
-
header={data.header}
|
|
49
|
-
subheading={data.subheading}
|
|
50
|
-
value={data.value}
|
|
51
|
-
/>
|
|
52
|
-
<div class="flex flex-col">
|
|
53
|
-
<p>{data.date.formatted}</p>
|
|
54
|
-
<h1 class="text-lg font-bold underline">{feature.properties.name}</h1>
|
|
55
|
-
<div class="mt-auto">
|
|
56
|
-
<p class="text-base leading-none">Last updated</p>
|
|
57
|
-
<p class="text-base leading-none">{data.date.diference}</p>
|
|
58
|
-
</div>
|
|
59
|
-
</div>
|
|
60
|
-
</div>
|
|
61
|
-
{/if}
|
|
@@ -1,57 +0,0 @@
|
|
|
1
|
-
import type { MonitorData, SJVAirEntryLevel } from "@sjvair/sdk";
|
|
2
|
-
|
|
3
|
-
export function getCurrentLevel(
|
|
4
|
-
value: string | number,
|
|
5
|
-
levels: Array<SJVAirEntryLevel>
|
|
6
|
-
): SJVAirEntryLevel | undefined {
|
|
7
|
-
value = typeof value === "string" ? parseInt(value, 10) : value;
|
|
8
|
-
return levels.find((lvl) => {
|
|
9
|
-
return value >= lvl.range[0] && value <= lvl.range[1];
|
|
10
|
-
});
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function getOrder(monitor: MonitorData): number {
|
|
14
|
-
switch (monitor.type) {
|
|
15
|
-
case "airgradient":
|
|
16
|
-
return 5;
|
|
17
|
-
|
|
18
|
-
case "airnow":
|
|
19
|
-
return 2;
|
|
20
|
-
|
|
21
|
-
case "aqview":
|
|
22
|
-
return 3;
|
|
23
|
-
|
|
24
|
-
case "bam1022":
|
|
25
|
-
return 6;
|
|
26
|
-
|
|
27
|
-
case "purpleair":
|
|
28
|
-
return monitor.is_sjvair ? 4 : 1;
|
|
29
|
-
|
|
30
|
-
case "vozbox":
|
|
31
|
-
return 7;
|
|
32
|
-
|
|
33
|
-
case "aqlite":
|
|
34
|
-
return 8;
|
|
35
|
-
|
|
36
|
-
default:
|
|
37
|
-
throw new Error(`Map ordering for ${monitor.device} has not been set`);
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function getTypeShape(type: string): string {
|
|
42
|
-
switch (type) {
|
|
43
|
-
case "airnow":
|
|
44
|
-
case "aqview":
|
|
45
|
-
case "bam1022":
|
|
46
|
-
case "aqlite":
|
|
47
|
-
return "triangle";
|
|
48
|
-
|
|
49
|
-
case "airgradient":
|
|
50
|
-
case "purpleair":
|
|
51
|
-
case "vozbox":
|
|
52
|
-
return "circle";
|
|
53
|
-
|
|
54
|
-
default:
|
|
55
|
-
return "circle";
|
|
56
|
-
}
|
|
57
|
-
}
|
|
@@ -1,285 +0,0 @@
|
|
|
1
|
-
import type {
|
|
2
|
-
ExpressionSpecification,
|
|
3
|
-
FilterSpecification,
|
|
4
|
-
GeoJSONSource,
|
|
5
|
-
MapLayerEventType,
|
|
6
|
-
Popup
|
|
7
|
-
} from "@maptiler/sdk";
|
|
8
|
-
import type { Point } from "geojson";
|
|
9
|
-
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
10
|
-
import { mapManager } from "$lib/map/map-manager.svelte.js";
|
|
11
|
-
import { mountPopup } from "$lib/map/utils";
|
|
12
|
-
import { clickManager, type ClickHandler } from "$lib/map/integrations/click-manager";
|
|
13
|
-
import { TooltipManager } from "$lib/map/integrations/tooltip.svelte";
|
|
14
|
-
import { getTypeShape } from "./monitor-utils";
|
|
15
|
-
import type { MonitorClusterMapFeature, MonitorMapFeature } from "./types";
|
|
16
|
-
import MonitorTooltip from "./components/MonitorTooltip.svelte";
|
|
17
|
-
import MonitorClusterTooltip from "./components/MonitorClusterTooltip.svelte";
|
|
18
|
-
|
|
19
|
-
const AVG_EXPR: ExpressionSpecification = [
|
|
20
|
-
"/",
|
|
21
|
-
["get", "sumValues"],
|
|
22
|
-
["max", ["get", "point_count"], 1]
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
export function monitorTooltip(evt: MapLayerEventType["mousemove"] & object): Popup | void {
|
|
26
|
-
const features = evt.features as unknown as Array<MonitorMapFeature> | undefined;
|
|
27
|
-
const feature = features?.sort((a, b) => b.properties.order - a.properties.order)[0];
|
|
28
|
-
if (!feature) return;
|
|
29
|
-
return mountPopup(MonitorTooltip, { feature }, evt.lngLat);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function clusterTooltip(evt: MapLayerEventType["mousemove"] & object): Popup | void {
|
|
33
|
-
const feature = evt.features?.[0] as unknown as MonitorClusterMapFeature | undefined;
|
|
34
|
-
if (!feature) return;
|
|
35
|
-
return mountPopup(MonitorClusterTooltip, { feature }, evt.lngLat);
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
export interface MonitorsClusterContext {
|
|
39
|
-
referenceId: string;
|
|
40
|
-
featuresByType: Record<string, MonitorMapFeature[]>;
|
|
41
|
-
filters: FilterSpecification;
|
|
42
|
-
clusterIconThresholds: SJVAirEntryLevel[];
|
|
43
|
-
beforeLayer?: string;
|
|
44
|
-
tooltipManager: TooltipManager;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
export class MonitorsClusterRenderer {
|
|
48
|
-
private _clusterTypes: string[] = [];
|
|
49
|
-
|
|
50
|
-
constructor(private ctx: MonitorsClusterContext) {}
|
|
51
|
-
|
|
52
|
-
apply(monitorClickHandler: ClickHandler): void {
|
|
53
|
-
if (!mapManager.map) return;
|
|
54
|
-
|
|
55
|
-
const sortedEntries = Object.entries(this.ctx.featuresByType).sort(([, a], [, b]) => {
|
|
56
|
-
const maxOrder = (fs: MonitorMapFeature[]) =>
|
|
57
|
-
fs.reduce((m, f) => Math.max(m, f.properties.order), 0);
|
|
58
|
-
return maxOrder(a) - maxOrder(b);
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
for (const [[type, features], index] of sortedEntries.map((e, i) => [e, i] as const)) {
|
|
62
|
-
const sourceId = `${this.ctx.referenceId}-${type}`;
|
|
63
|
-
|
|
64
|
-
mapManager.map.addSource(sourceId, {
|
|
65
|
-
type: "geojson",
|
|
66
|
-
promoteId: "id",
|
|
67
|
-
data: { type: "FeatureCollection", features },
|
|
68
|
-
cluster: true,
|
|
69
|
-
clusterRadius: 40,
|
|
70
|
-
clusterMaxZoom: 9,
|
|
71
|
-
clusterProperties: {
|
|
72
|
-
sumValues: ["+", ["to-number", ["get", "value"]], 0]
|
|
73
|
-
}
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
this.monitorTypeIconsLayer(sourceId, AVG_EXPR, getTypeShape(type));
|
|
77
|
-
this.clusterCountLayer(sourceId, AVG_EXPR);
|
|
78
|
-
this.unclusteredLayer(sourceId);
|
|
79
|
-
|
|
80
|
-
const { icon, unclustered } = this.clusterLayerIds(sourceId);
|
|
81
|
-
|
|
82
|
-
if (!this.ctx.tooltipManager.has(icon)) {
|
|
83
|
-
this.ctx.tooltipManager.register(icon, clusterTooltip, index);
|
|
84
|
-
}
|
|
85
|
-
if (!this.ctx.tooltipManager.has(unclustered)) {
|
|
86
|
-
this.ctx.tooltipManager.register(unclustered, monitorTooltip, index);
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
clickManager.register([unclustered], monitorClickHandler);
|
|
90
|
-
clickManager.register([icon], this.makeClusterClickHandler(sourceId));
|
|
91
|
-
|
|
92
|
-
this._clusterTypes.push(type);
|
|
93
|
-
}
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
remove(): void {
|
|
97
|
-
if (!mapManager.map) return;
|
|
98
|
-
|
|
99
|
-
for (const type of this._clusterTypes) {
|
|
100
|
-
const sourceId = `${this.ctx.referenceId}-${type}`;
|
|
101
|
-
const layers = this.clusterLayerIds(sourceId);
|
|
102
|
-
clickManager.unregister([layers.unclustered]);
|
|
103
|
-
clickManager.unregister([layers.icon]);
|
|
104
|
-
for (const layerId of Object.values(layers)) {
|
|
105
|
-
if (mapManager.map.getLayer(layerId)) {
|
|
106
|
-
mapManager.map.removeLayer(layerId);
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
if (mapManager.map.getSource(sourceId)) {
|
|
110
|
-
mapManager.map.removeSource(sourceId);
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
this._clusterTypes = [];
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
get unclusteredLayerIds(): string[] {
|
|
118
|
-
return this._clusterTypes.map((type) => `${this.ctx.referenceId}-${type}-unclustered`);
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
syncFeatures(): void {
|
|
122
|
-
if (!mapManager.map) return;
|
|
123
|
-
for (const type of this._clusterTypes) {
|
|
124
|
-
const sourceId = `${this.ctx.referenceId}-${type}`;
|
|
125
|
-
mapManager.setDataSource(sourceId, this.ctx.featuresByType[type] ?? []);
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
syncFilter(): void {
|
|
130
|
-
if (!mapManager.map) return;
|
|
131
|
-
|
|
132
|
-
for (const type of this._clusterTypes) {
|
|
133
|
-
const sourceId = `${this.ctx.referenceId}-${type}`;
|
|
134
|
-
const { unclustered } = this.clusterLayerIds(sourceId);
|
|
135
|
-
if (mapManager.map.getLayer(unclustered)) {
|
|
136
|
-
mapManager.map.setFilter(unclustered, [
|
|
137
|
-
"all",
|
|
138
|
-
this.ctx.filters as ExpressionSpecification,
|
|
139
|
-
["!", ["has", "point_count"]]
|
|
140
|
-
]);
|
|
141
|
-
}
|
|
142
|
-
}
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
syncThresholds(): void {
|
|
146
|
-
if (!mapManager.map) return;
|
|
147
|
-
|
|
148
|
-
for (const type of this._clusterTypes) {
|
|
149
|
-
const sourceId = `${this.ctx.referenceId}-${type}`;
|
|
150
|
-
const { icon, average } = this.clusterLayerIds(sourceId);
|
|
151
|
-
|
|
152
|
-
if (mapManager.map.getLayer(icon)) {
|
|
153
|
-
mapManager.map.setLayoutProperty(
|
|
154
|
-
icon,
|
|
155
|
-
"icon-image",
|
|
156
|
-
this.buildClusterIconExpression(AVG_EXPR, getTypeShape(type))
|
|
157
|
-
);
|
|
158
|
-
}
|
|
159
|
-
if (mapManager.map.getLayer(average)) {
|
|
160
|
-
mapManager.map.setPaintProperty(average, "text-color", this.clusterTextColorExpr(AVG_EXPR));
|
|
161
|
-
}
|
|
162
|
-
}
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
private makeClusterClickHandler(sourceId: string): ClickHandler {
|
|
166
|
-
return async (features) => {
|
|
167
|
-
try {
|
|
168
|
-
const feature = features[0];
|
|
169
|
-
if (!feature?.properties?.cluster_id) return;
|
|
170
|
-
const source = mapManager.map?.getSource(sourceId) as GeoJSONSource | undefined;
|
|
171
|
-
if (!source) return;
|
|
172
|
-
const zoom = await source.getClusterExpansionZoom(feature.properties.cluster_id);
|
|
173
|
-
const coords = (feature.geometry as Point).coordinates as [number, number];
|
|
174
|
-
mapManager.map?.easeTo({ center: coords, zoom });
|
|
175
|
-
} catch (err) {
|
|
176
|
-
console.error("ClickManager: cluster expansion zoom failed", err);
|
|
177
|
-
}
|
|
178
|
-
};
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
private clusterLayerIds(sourceId: string): {
|
|
182
|
-
icon: string;
|
|
183
|
-
average: string;
|
|
184
|
-
unclustered: string;
|
|
185
|
-
} {
|
|
186
|
-
return {
|
|
187
|
-
icon: `${sourceId}-cluster-icon`,
|
|
188
|
-
average: `${sourceId}-cluster-average`,
|
|
189
|
-
unclustered: `${sourceId}-unclustered`
|
|
190
|
-
};
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
private get clusterTextColorThreshold(): number {
|
|
194
|
-
// Use the max of the 3rd level (unhealthy_sensitive) so text flips to white at unhealthy+
|
|
195
|
-
return this.ctx.clusterIconThresholds[2]?.range[1] ?? 150.5;
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
private clusterTextColorExpr(avgExpr: ExpressionSpecification): ExpressionSpecification {
|
|
199
|
-
return ["case", ["<=", avgExpr, this.clusterTextColorThreshold], "#000000", "#FFFFFF"];
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
private buildClusterIconExpression(
|
|
203
|
-
avgExpr: ExpressionSpecification,
|
|
204
|
-
shape: string
|
|
205
|
-
): ExpressionSpecification {
|
|
206
|
-
const thresholds = this.ctx.clusterIconThresholds;
|
|
207
|
-
if (!thresholds.length) return `outside-default-${shape}` as unknown as ExpressionSpecification;
|
|
208
|
-
|
|
209
|
-
const expr: unknown[] = ["case"];
|
|
210
|
-
for (const level of thresholds.slice(0, -1)) {
|
|
211
|
-
expr.push(["<=", avgExpr, level.range[1]], `outside-${level.name}-${shape}`);
|
|
212
|
-
}
|
|
213
|
-
expr.push(`outside-${thresholds.at(-1)!.name}-${shape}`);
|
|
214
|
-
return expr as ExpressionSpecification;
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
private monitorTypeIconsLayer(
|
|
218
|
-
sourceId: string,
|
|
219
|
-
avgExpr: ExpressionSpecification,
|
|
220
|
-
shape: string
|
|
221
|
-
): void {
|
|
222
|
-
const { icon } = this.clusterLayerIds(sourceId);
|
|
223
|
-
mapManager.map?.addLayer(
|
|
224
|
-
{
|
|
225
|
-
id: icon,
|
|
226
|
-
type: "symbol",
|
|
227
|
-
source: sourceId,
|
|
228
|
-
filter: ["has", "point_count"],
|
|
229
|
-
layout: {
|
|
230
|
-
"icon-image": this.buildClusterIconExpression(avgExpr, shape),
|
|
231
|
-
"icon-size": ["step", ["get", "point_count"], 1.3, 10, 1.6, 25, 2.0],
|
|
232
|
-
"icon-ignore-placement": true,
|
|
233
|
-
"icon-allow-overlap": true
|
|
234
|
-
},
|
|
235
|
-
paint: {
|
|
236
|
-
"icon-opacity": 0.8
|
|
237
|
-
}
|
|
238
|
-
},
|
|
239
|
-
this.ctx.beforeLayer
|
|
240
|
-
);
|
|
241
|
-
}
|
|
242
|
-
|
|
243
|
-
private clusterCountLayer(sourceId: string, avgExpr: ExpressionSpecification): void {
|
|
244
|
-
const { average } = this.clusterLayerIds(sourceId);
|
|
245
|
-
mapManager.map?.addLayer(
|
|
246
|
-
{
|
|
247
|
-
id: average,
|
|
248
|
-
type: "symbol",
|
|
249
|
-
source: sourceId,
|
|
250
|
-
filter: ["has", "point_count"],
|
|
251
|
-
layout: {
|
|
252
|
-
"text-field": ["to-string", ["round", avgExpr]],
|
|
253
|
-
"text-size": 12,
|
|
254
|
-
"text-ignore-placement": true,
|
|
255
|
-
"text-allow-overlap": true
|
|
256
|
-
},
|
|
257
|
-
paint: {
|
|
258
|
-
"text-color": this.clusterTextColorExpr(avgExpr)
|
|
259
|
-
}
|
|
260
|
-
},
|
|
261
|
-
this.ctx.beforeLayer
|
|
262
|
-
);
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
private unclusteredLayer(sourceId: string): void {
|
|
266
|
-
const { unclustered } = this.clusterLayerIds(sourceId);
|
|
267
|
-
mapManager.map?.addLayer(
|
|
268
|
-
{
|
|
269
|
-
id: unclustered,
|
|
270
|
-
type: "symbol",
|
|
271
|
-
source: sourceId,
|
|
272
|
-
filter: ["all", this.ctx.filters as ExpressionSpecification, ["!", ["has", "point_count"]]],
|
|
273
|
-
layout: {
|
|
274
|
-
"symbol-sort-key": ["coalesce", ["get", "order"], 0],
|
|
275
|
-
"icon-allow-overlap": true,
|
|
276
|
-
"icon-ignore-placement": true,
|
|
277
|
-
"icon-image": ["get", "icon"],
|
|
278
|
-
"icon-size": 1
|
|
279
|
-
},
|
|
280
|
-
paint: {}
|
|
281
|
-
},
|
|
282
|
-
this.ctx.beforeLayer
|
|
283
|
-
);
|
|
284
|
-
}
|
|
285
|
-
}
|