@sjvair/monitor-map 3.6.3 → 4.0.1
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/README.md +2 -2
- package/dist/lib/MonitorMapLayout.svelte +74 -36
- package/dist/lib/assets/icons/ozone/ozone-08-sunrise-haze.svg +1 -0
- package/dist/lib/assets/icons/pm25/pm25-02-particle-field.svg +1 -0
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.d.ts +3 -0
- package/dist/lib/collocation-sites/collocations-map-integration.svelte.js +15 -1
- package/dist/lib/ev-stations/ev-stations-map-integration.svelte.js +27 -6
- package/dist/lib/hms/hms-fire-icon-manager.d.ts +8 -0
- package/dist/lib/hms/hms-fire-icon-manager.js +10 -1
- package/dist/lib/hms/hms-fire-map-integration.svelte.js +1 -1
- package/dist/lib/hms/hms-smoke-map-integration.svelte.d.ts +20 -0
- package/dist/lib/hms/hms-smoke-map-integration.svelte.js +22 -7
- package/dist/lib/index.d.ts +21 -7
- package/dist/lib/index.js +22 -7
- package/dist/lib/legend/Legend.svelte +40 -0
- package/dist/lib/legend/Legend.svelte.d.ts +7 -0
- package/dist/lib/legend/LegendBar.svelte +45 -0
- package/dist/lib/legend/LegendBar.svelte.d.ts +7 -0
- package/dist/lib/legend/legend-data.d.ts +40 -0
- package/dist/lib/legend/legend-data.js +65 -0
- package/dist/lib/map/MapShell.svelte +7 -6
- package/dist/lib/map/MapShell.svelte.d.ts +3 -1
- package/dist/lib/map/basemap.d.ts +14 -0
- package/dist/lib/map/basemap.js +10 -0
- package/dist/lib/map/integrations/map-geojson-integration.svelte.d.ts +6 -0
- package/dist/lib/map/integrations/map-geojson-integration.svelte.js +15 -2
- package/dist/lib/map/integrations/map-layer-integration.svelte.js +7 -3
- package/dist/lib/map/map-style-state.svelte.d.ts +13 -0
- package/dist/lib/map/map-style-state.svelte.js +35 -0
- package/dist/lib/map/map.svelte.d.ts +6 -0
- package/dist/lib/map/map.svelte.js +6 -0
- package/dist/lib/monitors/monitor-groups.d.ts +11 -0
- package/dist/lib/monitors/monitor-groups.js +7 -0
- package/dist/lib/monitors/monitor-utils.js +1 -3
- package/dist/lib/monitors/monitors-icon-manager.svelte.js +2 -5
- package/dist/lib/monitors/monitors-map-integration.svelte.d.ts +4 -4
- package/dist/lib/monitors/monitors-map-integration.svelte.js +45 -32
- package/dist/lib/monitors/monitors.svelte.d.ts +1 -0
- package/dist/lib/monitors/monitors.svelte.js +22 -0
- package/dist/lib/options/OptionsBar.svelte +96 -0
- package/dist/lib/options/OptionsBar.svelte.d.ts +10 -0
- package/dist/lib/options/OptionsGroup.svelte +12 -0
- package/dist/lib/options/OptionsGroup.svelte.d.ts +7 -0
- package/dist/lib/options/OptionsMenu.svelte +71 -0
- package/dist/lib/options/OptionsMenu.svelte.d.ts +15 -0
- package/dist/lib/options/group-state.d.ts +7 -0
- package/dist/lib/options/group-state.js +15 -0
- package/dist/lib/options/layout.d.ts +7 -0
- package/dist/lib/options/layout.js +9 -0
- package/dist/lib/options/map-option-defaults.d.ts +56 -0
- package/dist/lib/options/map-option-defaults.js +47 -0
- package/dist/lib/options/menus/LayersMenu.svelte +47 -0
- package/dist/lib/options/menus/LayersMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/MonitorsMenu.svelte +68 -0
- package/dist/lib/options/menus/MonitorsMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/OverlaysMenu.svelte +18 -0
- package/dist/lib/{MapLegend.svelte.d.ts → options/menus/OverlaysMenu.svelte.d.ts} +3 -3
- package/dist/lib/options/menus/PollutantMenu.svelte +89 -0
- package/dist/lib/options/menus/PollutantMenu.svelte.d.ts +3 -0
- package/dist/lib/options/menus/SettingsMenu.svelte +73 -0
- package/dist/lib/{hms/components/HMSFireLegend.svelte.d.ts → options/menus/SettingsMenu.svelte.d.ts} +3 -3
- package/dist/lib/options/options-state.svelte.d.ts +12 -0
- package/dist/lib/options/options-state.svelte.js +42 -0
- package/dist/lib/options/pollutant-param.d.ts +17 -0
- package/dist/lib/options/pollutant-param.js +14 -0
- package/dist/lib/options/reset-map-options.d.ts +2 -0
- package/dist/lib/options/reset-map-options.js +24 -0
- package/dist/lib/options/rows/CheckboxRow.svelte +51 -0
- package/dist/lib/options/rows/CheckboxRow.svelte.d.ts +13 -0
- package/dist/lib/options/rows/GroupRow.svelte +43 -0
- package/dist/lib/options/rows/GroupRow.svelte.d.ts +17 -0
- package/dist/lib/options/rows/MarkerIcon.svelte +10 -0
- package/dist/lib/options/rows/MarkerIcon.svelte.d.ts +8 -0
- package/dist/lib/options/rows/RadioRow.svelte +33 -0
- package/dist/lib/options/rows/RadioRow.svelte.d.ts +13 -0
- package/dist/lib/options/rows/SubmenuRow.svelte +22 -0
- package/dist/lib/options/rows/SubmenuRow.svelte.d.ts +11 -0
- package/dist/lib/search/Search.svelte +6 -6
- package/package.json +5 -3
- package/src/lib/MonitorMapLayout.svelte +74 -36
- package/src/lib/assets/icons/ozone/ozone-08-sunrise-haze.svg +1 -0
- package/src/lib/assets/icons/pm25/pm25-02-particle-field.svg +1 -0
- package/src/lib/collocation-sites/collocations-map-integration.svelte.ts +17 -1
- package/src/lib/ev-stations/ev-stations-map-integration.svelte.ts +26 -6
- package/src/lib/hms/hms-fire-icon-manager.ts +16 -2
- package/src/lib/hms/hms-fire-map-integration.svelte.ts +1 -1
- package/src/lib/hms/hms-smoke-map-integration.svelte.ts +25 -7
- package/src/lib/index.ts +31 -7
- package/src/lib/legend/Legend.svelte +40 -0
- package/src/lib/legend/LegendBar.svelte +45 -0
- package/src/lib/legend/legend-data.ts +101 -0
- package/src/lib/map/MapShell.svelte +7 -6
- package/src/lib/map/basemap.ts +24 -0
- package/src/lib/map/integrations/map-geojson-integration.svelte.ts +15 -2
- package/src/lib/map/integrations/map-layer-integration.svelte.ts +6 -3
- package/src/lib/map/map-style-state.svelte.ts +42 -0
- package/src/lib/map/map.svelte.ts +6 -0
- package/src/lib/monitors/monitor-groups.ts +20 -0
- package/src/lib/monitors/monitor-utils.ts +1 -5
- package/src/lib/monitors/monitors-icon-manager.svelte.ts +2 -8
- package/src/lib/monitors/monitors-map-integration.svelte.ts +39 -29
- package/src/lib/monitors/monitors.svelte.ts +22 -0
- package/src/lib/options/OptionsBar.svelte +96 -0
- package/src/lib/options/OptionsGroup.svelte +12 -0
- package/src/lib/options/OptionsMenu.svelte +71 -0
- package/src/lib/options/group-state.ts +16 -0
- package/src/lib/options/layout.ts +10 -0
- package/src/lib/options/map-option-defaults.ts +71 -0
- package/src/lib/options/menus/LayersMenu.svelte +47 -0
- package/src/lib/options/menus/MonitorsMenu.svelte +68 -0
- package/src/lib/options/menus/OverlaysMenu.svelte +18 -0
- package/src/lib/options/menus/PollutantMenu.svelte +89 -0
- package/src/lib/options/menus/SettingsMenu.svelte +73 -0
- package/src/lib/options/options-state.svelte.ts +48 -0
- package/src/lib/options/pollutant-param.ts +35 -0
- package/src/lib/options/reset-map-options.ts +28 -0
- package/src/lib/options/rows/CheckboxRow.svelte +51 -0
- package/src/lib/options/rows/GroupRow.svelte +43 -0
- package/src/lib/options/rows/MarkerIcon.svelte +10 -0
- package/src/lib/options/rows/RadioRow.svelte +33 -0
- package/src/lib/options/rows/SubmenuRow.svelte +22 -0
- package/src/lib/search/Search.svelte +6 -6
- package/dist/lib/MapLegend.svelte +0 -15
- package/dist/lib/collocation-sites/collocation-utils.d.ts +0 -3
- package/dist/lib/collocation-sites/collocation-utils.js +0 -30
- package/dist/lib/components/DisplayOption.svelte +0 -7
- package/dist/lib/components/DisplayOption.svelte.d.ts +0 -5
- package/dist/lib/components/MapLayersDisplayOptions.svelte +0 -42
- package/dist/lib/components/MapLayersDisplayOptions.svelte.d.ts +0 -18
- package/dist/lib/components/SegmentedControl.svelte +0 -52
- package/dist/lib/components/SegmentedControl.svelte.d.ts +0 -11
- package/dist/lib/components/ToggleSwitch.svelte +0 -93
- package/dist/lib/components/ToggleSwitch.svelte.d.ts +0 -9
- package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
- package/dist/lib/ev-stations/components/EvStationsDisplayOptions.svelte.d.ts +0 -18
- package/dist/lib/hms/components/HMSFireLegend.svelte +0 -21
- package/dist/lib/map/MapStyleDisplayOptions.svelte +0 -61
- package/dist/lib/map/MapStyleDisplayOptions.svelte.d.ts +0 -3
- package/dist/lib/map/Menu.svelte +0 -65
- package/dist/lib/map/Menu.svelte.d.ts +0 -5
- package/dist/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
- package/dist/lib/monitors/components/MonitorMarkersLegend.svelte.d.ts +0 -3
- package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
- package/dist/lib/monitors/components/MonitorsDisplayOptions.svelte.d.ts +0 -3
- package/src/lib/MapLegend.svelte +0 -15
- package/src/lib/collocation-sites/collocation-utils.ts +0 -41
- package/src/lib/components/DisplayOption.svelte +0 -7
- package/src/lib/components/MapLayersDisplayOptions.svelte +0 -42
- package/src/lib/components/SegmentedControl.svelte +0 -52
- package/src/lib/components/ToggleSwitch.svelte +0 -93
- package/src/lib/ev-stations/components/EvStationsDisplayOptions.svelte +0 -46
- package/src/lib/hms/components/HMSFireLegend.svelte +0 -21
- package/src/lib/map/MapStyleDisplayOptions.svelte +0 -61
- package/src/lib/map/Menu.svelte +0 -65
- package/src/lib/monitors/components/MonitorMarkersLegend.svelte +0 -41
- package/src/lib/monitors/components/MonitorsDisplayOptions.svelte +0 -85
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M6 15a6 6 0 0 1 12 0" stroke="#F2A93B" fill="none"/><path d="M20.37 13.52L21.85 13.26M17.46 8.49L18.43 7.34M12.00 6.50L12.00 5.00M6.54 8.49L5.57 7.34M3.63 13.52L2.15 13.26" stroke="#F2A93B" fill="none"/><path d="M2 15h20M5 18.5h14M8.5 22h7" stroke="#5B6770" fill="none"/></svg>
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="6" cy="6" r="2" fill="#6B5B4E"/><circle cx="16.5" cy="5" r="1.5" fill="#9C8878"/><circle cx="11" cy="12" r="2.5" fill="#6B5B4E"/><circle cx="5" cy="18" r="1.5" fill="#9C8878"/><circle cx="17.5" cy="17.5" r="2" fill="#6B5B4E"/></svg>
|
|
@@ -25,6 +25,11 @@ class CollocationSitesMapIntegration extends MapIconLayerIntegration<Collocation
|
|
|
25
25
|
icons: CollocationIconManager = new CollocationIconManager(monitorsManager);
|
|
26
26
|
tooltipManager: TooltipManager = new TooltipManager();
|
|
27
27
|
|
|
28
|
+
/** The option only applies while monitors are shown with PM2.5. */
|
|
29
|
+
get available(): boolean {
|
|
30
|
+
return monitorsMapIntegration.enabled && monitorsManager.pollutant === "pm25";
|
|
31
|
+
}
|
|
32
|
+
|
|
28
33
|
features: Array<CollocationSiteMapFeature> = $derived.by(() => {
|
|
29
34
|
if (
|
|
30
35
|
!monitorsManager.meta ||
|
|
@@ -93,8 +98,10 @@ class CollocationSitesMapIntegration extends MapIconLayerIntegration<Collocation
|
|
|
93
98
|
mapManager.setDataSource(this.referenceId, features);
|
|
94
99
|
});
|
|
95
100
|
|
|
101
|
+
// Remove the layer while the option is unavailable (Ozone or Pollutant = None); restore
|
|
102
|
+
// it when it becomes available again if the user still has it checked.
|
|
96
103
|
$effect(() => {
|
|
97
|
-
if (
|
|
104
|
+
if (!this.available) {
|
|
98
105
|
untrack(() => this.remove());
|
|
99
106
|
} else if (untrack(() => this.enabled)) {
|
|
100
107
|
untrack(() => this.apply());
|
|
@@ -104,6 +111,10 @@ class CollocationSitesMapIntegration extends MapIconLayerIntegration<Collocation
|
|
|
104
111
|
}
|
|
105
112
|
|
|
106
113
|
apply() {
|
|
114
|
+
if (!this.available) {
|
|
115
|
+
this.remove();
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
107
118
|
if (!mapManager.map) return;
|
|
108
119
|
|
|
109
120
|
if (!this.tooltipManager.has(this.referenceId)) {
|
|
@@ -114,6 +125,11 @@ class CollocationSitesMapIntegration extends MapIconLayerIntegration<Collocation
|
|
|
114
125
|
|
|
115
126
|
super.apply();
|
|
116
127
|
}
|
|
128
|
+
|
|
129
|
+
remove() {
|
|
130
|
+
this.tooltipManager.disable();
|
|
131
|
+
super.remove();
|
|
132
|
+
}
|
|
117
133
|
}
|
|
118
134
|
|
|
119
135
|
export const collocationSitesMapIntegration = new CollocationSitesMapIntegration();
|
|
@@ -26,7 +26,7 @@ class EvStationsMapIntegration
|
|
|
26
26
|
implements EvStationsClusterContext
|
|
27
27
|
{
|
|
28
28
|
referenceId: string = "ev-stations";
|
|
29
|
-
enabled: boolean = $state(
|
|
29
|
+
enabled: boolean = $state(false);
|
|
30
30
|
clustered: boolean = $state(true);
|
|
31
31
|
|
|
32
32
|
icons: EvStationIconManager = new EvStationIconManager();
|
|
@@ -35,8 +35,8 @@ class EvStationsMapIntegration
|
|
|
35
35
|
private currentPopup: Popup | null = null;
|
|
36
36
|
|
|
37
37
|
displayOptions = $derived.by(() => ({
|
|
38
|
-
lvl2: new MapDisplayOption("Level 2",
|
|
39
|
-
lvl3: new MapDisplayOption("Level 3",
|
|
38
|
+
lvl2: new MapDisplayOption("Level 2", true, this.icons.get("ev-station-lvl2")!),
|
|
39
|
+
lvl3: new MapDisplayOption("Level 3", true, this.icons.get("ev-station-lvl3")!)
|
|
40
40
|
}));
|
|
41
41
|
|
|
42
42
|
features: Array<EvStationMapFeature> = $derived.by(() => {
|
|
@@ -115,23 +115,38 @@ class EvStationsMapIntegration
|
|
|
115
115
|
super();
|
|
116
116
|
|
|
117
117
|
$effect.root(() => {
|
|
118
|
-
// Lazy-fetch level data
|
|
118
|
+
// Lazy-fetch level data only once the integration is enabled and the level is on
|
|
119
119
|
$effect(() => {
|
|
120
|
-
if (
|
|
120
|
+
if (
|
|
121
|
+
this.enabled &&
|
|
122
|
+
this.displayOptions.lvl2.value &&
|
|
123
|
+
evStationsManager.lvl2Stations === undefined
|
|
124
|
+
) {
|
|
121
125
|
evStationsManager.loadLvl2Stations();
|
|
122
126
|
}
|
|
123
127
|
});
|
|
124
128
|
|
|
125
129
|
$effect(() => {
|
|
126
|
-
if (
|
|
130
|
+
if (
|
|
131
|
+
this.enabled &&
|
|
132
|
+
this.displayOptions.lvl3.value &&
|
|
133
|
+
evStationsManager.lvl3Stations === undefined
|
|
134
|
+
) {
|
|
127
135
|
evStationsManager.loadLvl3Stations();
|
|
128
136
|
}
|
|
129
137
|
});
|
|
130
138
|
|
|
139
|
+
// Every effect below early-returns while disabled so a data refresh can't re-add the layer.
|
|
140
|
+
// `enabled` is read untracked: the base class's own effect already applies/removes on
|
|
141
|
+
// enabled changes, and tracking it here would apply twice. The guard comes after each
|
|
142
|
+
// effect's tracked reads so its dependencies are always collected; returning before
|
|
143
|
+
// reading anything would leave the effect permanently dead.
|
|
144
|
+
|
|
131
145
|
// Push filter changes to the unclustered layer
|
|
132
146
|
$effect(() => {
|
|
133
147
|
const filter = this.filters;
|
|
134
148
|
if (!mapManager.map || this.clustered) return;
|
|
149
|
+
if (!untrack(() => this.enabled)) return;
|
|
135
150
|
if (mapManager.map.getLayer(this.referenceId)) {
|
|
136
151
|
mapManager.map.setFilter(this.referenceId, filter);
|
|
137
152
|
}
|
|
@@ -141,6 +156,7 @@ class EvStationsMapIntegration
|
|
|
141
156
|
$effect(() => {
|
|
142
157
|
const features = this.features;
|
|
143
158
|
if (!mapManager.map || this.clustered) return;
|
|
159
|
+
if (!untrack(() => this.enabled)) return;
|
|
144
160
|
mapManager.setDataSource(this.referenceId, features);
|
|
145
161
|
});
|
|
146
162
|
|
|
@@ -148,6 +164,7 @@ class EvStationsMapIntegration
|
|
|
148
164
|
$effect(() => {
|
|
149
165
|
void this.featuresByLevel;
|
|
150
166
|
if (!mapManager.map || !this.clustered) return;
|
|
167
|
+
if (!untrack(() => this.enabled)) return;
|
|
151
168
|
this.renderer.syncFeatures();
|
|
152
169
|
});
|
|
153
170
|
|
|
@@ -156,6 +173,7 @@ class EvStationsMapIntegration
|
|
|
156
173
|
void this.clustered;
|
|
157
174
|
const hasFeatures = Object.keys(this.featuresByLevel).length > 0;
|
|
158
175
|
if (!untrack(() => mapManager.map) || !hasFeatures) return;
|
|
176
|
+
if (!untrack(() => this.enabled)) return;
|
|
159
177
|
untrack(() => this.apply());
|
|
160
178
|
});
|
|
161
179
|
});
|
|
@@ -171,6 +189,7 @@ class EvStationsMapIntegration
|
|
|
171
189
|
this.tooltipManager.disable();
|
|
172
190
|
this.renderer.remove();
|
|
173
191
|
this.icons.loadIcons().then(() => {
|
|
192
|
+
if (mapManager.styleLoading) return;
|
|
174
193
|
this.renderer.apply(this.handleStationClick);
|
|
175
194
|
this.tooltipManager.enable();
|
|
176
195
|
});
|
|
@@ -180,6 +199,7 @@ class EvStationsMapIntegration
|
|
|
180
199
|
clickManager.unregister([this.referenceId]);
|
|
181
200
|
super.apply();
|
|
182
201
|
this.icons.loadIcons().then(() => {
|
|
202
|
+
if (mapManager.styleLoading) return;
|
|
183
203
|
clickManager.register([this.referenceId], this.handleStationClick);
|
|
184
204
|
this.tooltipManager.enable();
|
|
185
205
|
});
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import { asDataURI, fireIcon } from "$lib/map/icons";
|
|
2
2
|
import { MapIconManager } from "$lib/map/integrations/map-icon-manager";
|
|
3
|
+
import type { LegendCategory } from "$lib/legend/legend-data";
|
|
3
4
|
|
|
4
|
-
type FRPTier = "sm" | "md" | "lg" | "xl" | "xxl";
|
|
5
|
+
export type FRPTier = "sm" | "md" | "lg" | "xl" | "xxl";
|
|
5
6
|
|
|
6
|
-
const FRP_TIERS: Record<FRPTier, { color: string; size: number }> = {
|
|
7
|
+
export const FRP_TIERS: Record<FRPTier, { color: string; size: number }> = {
|
|
7
8
|
sm: { color: "#FFD700", size: 20 },
|
|
8
9
|
md: { color: "#FF8C00", size: 24 },
|
|
9
10
|
lg: { color: "#FF4500", size: 28 },
|
|
@@ -11,6 +12,19 @@ const FRP_TIERS: Record<FRPTier, { color: string; size: number }> = {
|
|
|
11
12
|
xxl: { color: "#8B0000", size: 36 }
|
|
12
13
|
};
|
|
13
14
|
|
|
15
|
+
const FRP_TIER_LABELS: Record<FRPTier, string> = {
|
|
16
|
+
sm: "<10",
|
|
17
|
+
md: "10-49",
|
|
18
|
+
lg: "50-149",
|
|
19
|
+
xl: "150-349",
|
|
20
|
+
xxl: "≥350"
|
|
21
|
+
};
|
|
22
|
+
|
|
23
|
+
/** Fire legend blocks (MW), in tier order, using the same colors as the map icons. */
|
|
24
|
+
export const FIRE_LEGEND_CATEGORIES: Array<LegendCategory> = (
|
|
25
|
+
Object.keys(FRP_TIERS) as Array<FRPTier>
|
|
26
|
+
).map((tier) => ({ color: FRP_TIERS[tier].color, label: FRP_TIER_LABELS[tier] }));
|
|
27
|
+
|
|
14
28
|
export function getTierIconId(frp: number): string {
|
|
15
29
|
if (frp < 10) return "hms-fire-sm";
|
|
16
30
|
if (frp < 50) return "hms-fire-md";
|
|
@@ -89,7 +89,7 @@ class HMSFireMapIntegration extends MapIconLayerIntegration<FireProperties> {
|
|
|
89
89
|
this.icons
|
|
90
90
|
.loadIcons()
|
|
91
91
|
.then(() => {
|
|
92
|
-
if (!mapManager.map || !this.enabled) return;
|
|
92
|
+
if (!mapManager.map || !this.enabled || mapManager.styleLoading) return;
|
|
93
93
|
this.remove();
|
|
94
94
|
this.zoomedIn = mapManager.map.getZoom() >= ZOOM_THRESHOLD;
|
|
95
95
|
mapManager.map.addSource(this.referenceId, this.mapSource);
|
|
@@ -4,6 +4,16 @@ import type { HMSSmokeGeoJSON } from "@sjvair/sdk/hms";
|
|
|
4
4
|
import { mapManager } from "$lib/map/map.svelte";
|
|
5
5
|
import { MapGeoJSONIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
|
|
6
6
|
import { hmsManager } from "./hms.svelte";
|
|
7
|
+
import type { SmokeDensityStyle } from "$lib/legend/legend-data";
|
|
8
|
+
|
|
9
|
+
/** Smoke fill styles, shared by the map layer and the legend so they can't drift apart. */
|
|
10
|
+
export const SMOKE_DENSITY_STYLES = {
|
|
11
|
+
light: { label: "Light", color: "#bfc8c3", opacity: 0.2 },
|
|
12
|
+
medium: { label: "Medium", color: "#757b78", opacity: 0.3 },
|
|
13
|
+
heavy: { label: "Heavy", color: "#333634", opacity: 0.4 }
|
|
14
|
+
} satisfies Record<string, SmokeDensityStyle>;
|
|
15
|
+
|
|
16
|
+
export const SMOKE_LEGEND: Array<SmokeDensityStyle> = Object.values(SMOKE_DENSITY_STYLES);
|
|
7
17
|
|
|
8
18
|
type SmokeProperties = {
|
|
9
19
|
id: string;
|
|
@@ -36,20 +46,28 @@ class HMSSmokeMapIntegration extends MapGeoJSONIntegration<SmokeProperties> {
|
|
|
36
46
|
"match",
|
|
37
47
|
["get", "density"],
|
|
38
48
|
"light",
|
|
39
|
-
|
|
49
|
+
SMOKE_DENSITY_STYLES.light.color,
|
|
50
|
+
"medium",
|
|
51
|
+
SMOKE_DENSITY_STYLES.medium.color,
|
|
52
|
+
SMOKE_DENSITY_STYLES.heavy.color
|
|
53
|
+
],
|
|
54
|
+
"fill-opacity": [
|
|
55
|
+
"match",
|
|
56
|
+
["get", "density"],
|
|
57
|
+
"light",
|
|
58
|
+
SMOKE_DENSITY_STYLES.light.opacity,
|
|
40
59
|
"medium",
|
|
41
|
-
|
|
42
|
-
|
|
60
|
+
SMOKE_DENSITY_STYLES.medium.opacity,
|
|
61
|
+
SMOKE_DENSITY_STYLES.heavy.opacity
|
|
43
62
|
],
|
|
44
|
-
"fill-opacity": ["match", ["get", "density"], "light", 0.2, "medium", 0.3, 0.4],
|
|
45
63
|
"fill-outline-color": [
|
|
46
64
|
"match",
|
|
47
65
|
["get", "density"],
|
|
48
66
|
"light",
|
|
49
|
-
|
|
67
|
+
SMOKE_DENSITY_STYLES.light.color,
|
|
50
68
|
"medium",
|
|
51
|
-
|
|
52
|
-
|
|
69
|
+
SMOKE_DENSITY_STYLES.medium.color,
|
|
70
|
+
SMOKE_DENSITY_STYLES.heavy.color
|
|
53
71
|
]
|
|
54
72
|
}
|
|
55
73
|
};
|
package/src/lib/index.ts
CHANGED
|
@@ -59,7 +59,6 @@ export { hmsSmokeMapIntegration } from "./hms/hms-smoke-map-integration.svelte";
|
|
|
59
59
|
// Components
|
|
60
60
|
export { default as Map } from "./map/Map.svelte";
|
|
61
61
|
export { default as MapShell, type MapShellProps } from "./map/MapShell.svelte";
|
|
62
|
-
export { default as Menu } from "./map/Menu.svelte";
|
|
63
62
|
export {
|
|
64
63
|
default as LoadScreen,
|
|
65
64
|
loadScreenState,
|
|
@@ -67,12 +66,37 @@ export {
|
|
|
67
66
|
disable as disableLoadScreen
|
|
68
67
|
} from "./LoadScreen.svelte";
|
|
69
68
|
export type { LoadScreenState } from "./LoadScreen.svelte";
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
export { default as
|
|
73
|
-
export { default as
|
|
74
|
-
export { default as
|
|
75
|
-
export { default as
|
|
69
|
+
|
|
70
|
+
// Options toolbar / panel (compose these inside MapShell's `menu` snippet)
|
|
71
|
+
export { default as OptionsBar } from "./options/OptionsBar.svelte";
|
|
72
|
+
export { default as OptionsGroup } from "./options/OptionsGroup.svelte";
|
|
73
|
+
export { default as OptionsMenu } from "./options/OptionsMenu.svelte";
|
|
74
|
+
export { default as CheckboxRow } from "./options/rows/CheckboxRow.svelte";
|
|
75
|
+
export { default as RadioRow } from "./options/rows/RadioRow.svelte";
|
|
76
|
+
export { default as SubmenuRow } from "./options/rows/SubmenuRow.svelte";
|
|
77
|
+
export { default as GroupRow } from "./options/rows/GroupRow.svelte";
|
|
78
|
+
export { default as MarkerIcon } from "./options/rows/MarkerIcon.svelte";
|
|
79
|
+
export { default as PollutantMenu } from "./options/menus/PollutantMenu.svelte";
|
|
80
|
+
export { default as MonitorsMenu } from "./options/menus/MonitorsMenu.svelte";
|
|
81
|
+
export { default as LayersMenu } from "./options/menus/LayersMenu.svelte";
|
|
82
|
+
export { default as OverlaysMenu } from "./options/menus/OverlaysMenu.svelte";
|
|
83
|
+
export { default as SettingsMenu } from "./options/menus/SettingsMenu.svelte";
|
|
84
|
+
export { resetMapOptions } from "./options/reset-map-options";
|
|
85
|
+
export { mapStyleState } from "./map/map-style-state.svelte.js";
|
|
86
|
+
export type { MapStyleState } from "./map/map-style-state.svelte.js";
|
|
87
|
+
|
|
88
|
+
// Legend
|
|
89
|
+
export { default as Legend } from "./legend/Legend.svelte";
|
|
90
|
+
export { default as LegendBar } from "./legend/LegendBar.svelte";
|
|
91
|
+
export {
|
|
92
|
+
pollutantLegendSection,
|
|
93
|
+
fireSmokeLegendSections,
|
|
94
|
+
type LegendSection,
|
|
95
|
+
type LegendBarData,
|
|
96
|
+
type LegendCategory
|
|
97
|
+
} from "./legend/legend-data";
|
|
98
|
+
export { FIRE_LEGEND_CATEGORIES } from "./hms/hms-fire-icon-manager";
|
|
99
|
+
export { SMOKE_LEGEND } from "./hms/hms-smoke-map-integration.svelte";
|
|
76
100
|
|
|
77
101
|
// Integration base classes (extend these to add custom map features)
|
|
78
102
|
export { MapIntegration } from "./map/integrations/map-integration.svelte";
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import { ChevronDownIcon, InfoIcon } from "@lucide/svelte";
|
|
3
|
+
import LegendBar from "./LegendBar.svelte";
|
|
4
|
+
import type { LegendSection } from "./legend-data";
|
|
5
|
+
|
|
6
|
+
let { sections }: { sections: Array<LegendSection> } = $props();
|
|
7
|
+
|
|
8
|
+
// Independent of which sections are active, so toggling a layer doesn't reset it.
|
|
9
|
+
let collapsed = $state(false);
|
|
10
|
+
</script>
|
|
11
|
+
|
|
12
|
+
{#if sections.length}
|
|
13
|
+
<div class="pointer-events-auto w-84 max-w-[calc(100vw-2rem)] rounded-md bg-white shadow">
|
|
14
|
+
<button
|
|
15
|
+
type="button"
|
|
16
|
+
class="flex w-full cursor-pointer items-center gap-2 px-4 py-2 font-semibold"
|
|
17
|
+
aria-expanded={!collapsed}
|
|
18
|
+
onclick={() => (collapsed = !collapsed)}
|
|
19
|
+
>
|
|
20
|
+
<InfoIcon size={18} class="text-gray-500" />
|
|
21
|
+
<span class="flex-1 text-left">Legend</span>
|
|
22
|
+
<ChevronDownIcon size={18} class="transition-transform {collapsed ? '-rotate-90' : ''}" />
|
|
23
|
+
</button>
|
|
24
|
+
{#if !collapsed}
|
|
25
|
+
<div class="flex flex-col gap-3 border-t border-gray-200 px-4 py-3">
|
|
26
|
+
{#each sections as section (section.id)}
|
|
27
|
+
<div>
|
|
28
|
+
<p class="mb-2 text-center text-sm font-semibold">
|
|
29
|
+
{section.title}
|
|
30
|
+
{#if section.unit}
|
|
31
|
+
<span class="text-xs font-normal text-gray-500">({section.unit})</span>
|
|
32
|
+
{/if}
|
|
33
|
+
</p>
|
|
34
|
+
<LegendBar bar={section.bar} />
|
|
35
|
+
</div>
|
|
36
|
+
{/each}
|
|
37
|
+
</div>
|
|
38
|
+
{/if}
|
|
39
|
+
</div>
|
|
40
|
+
{/if}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
<script lang="ts">
|
|
2
|
+
import {
|
|
3
|
+
blocksBackground,
|
|
4
|
+
gradientBackground,
|
|
5
|
+
tickPosition,
|
|
6
|
+
type LegendBarData
|
|
7
|
+
} from "./legend-data";
|
|
8
|
+
|
|
9
|
+
let { bar }: { bar: LegendBarData } = $props();
|
|
10
|
+
|
|
11
|
+
const background = $derived(
|
|
12
|
+
bar.kind === "gradient"
|
|
13
|
+
? gradientBackground(bar.colors)
|
|
14
|
+
: blocksBackground(bar.categories.map((category) => category.color))
|
|
15
|
+
);
|
|
16
|
+
|
|
17
|
+
// Labels placed on exact stop positions: gradient ticks, or block breakpoints.
|
|
18
|
+
const positioned = $derived(bar.kind === "gradient" ? bar.ticks : (bar.breakpoints ?? null));
|
|
19
|
+
</script>
|
|
20
|
+
|
|
21
|
+
<div class="h-3 rounded" style:background></div>
|
|
22
|
+
{#if positioned}
|
|
23
|
+
<div class="relative mt-1 h-4 text-xs">
|
|
24
|
+
{#each positioned as label, i (i)}
|
|
25
|
+
{@const isFirst = i === 0}
|
|
26
|
+
{@const isLast = i === positioned.length - 1}
|
|
27
|
+
<span
|
|
28
|
+
class={[
|
|
29
|
+
"absolute top-0 whitespace-nowrap",
|
|
30
|
+
isFirst && "left-0",
|
|
31
|
+
isLast && "right-0",
|
|
32
|
+
!isFirst && !isLast && "-translate-x-1/2"
|
|
33
|
+
]}
|
|
34
|
+
style:left={!isFirst && !isLast ? `${tickPosition(i, positioned.length)}%` : undefined}
|
|
35
|
+
>{label}</span
|
|
36
|
+
>
|
|
37
|
+
{/each}
|
|
38
|
+
</div>
|
|
39
|
+
{:else if bar.kind === "blocks"}
|
|
40
|
+
<div class="mt-1 flex text-xs">
|
|
41
|
+
{#each bar.categories as category, i (i)}
|
|
42
|
+
<span class="min-w-0 flex-1 text-center break-words">{category.label ?? ""}</span>
|
|
43
|
+
{/each}
|
|
44
|
+
</div>
|
|
45
|
+
{/if}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import type { SJVAirEntryLevel } from "@sjvair/sdk";
|
|
2
|
+
import { mix } from "color2k";
|
|
3
|
+
import type { MonitorsPollutant } from "$lib/monitors/types";
|
|
4
|
+
|
|
5
|
+
export interface LegendCategory {
|
|
6
|
+
color: string;
|
|
7
|
+
label?: string;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export type LegendBarData =
|
|
11
|
+
| { kind: "gradient"; colors: Array<string>; ticks: Array<string> }
|
|
12
|
+
| { kind: "blocks"; categories: Array<LegendCategory>; breakpoints?: Array<string> };
|
|
13
|
+
|
|
14
|
+
export interface LegendSection {
|
|
15
|
+
id: string;
|
|
16
|
+
title: string;
|
|
17
|
+
unit?: string;
|
|
18
|
+
bar: LegendBarData;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export interface SmokeDensityStyle {
|
|
22
|
+
label: string;
|
|
23
|
+
color: string;
|
|
24
|
+
opacity: number;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
const POLLUTANT_TITLES: Record<MonitorsPollutant, { title: string; unit: string }> = {
|
|
28
|
+
pm25: { title: "PM2.5", unit: "µg/m³" },
|
|
29
|
+
o3: { title: "Ozone", unit: "ppb" }
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Pollutant legend built from the monitors meta levels — the same levels the map markers are
|
|
34
|
+
* colored by, so the two can never disagree. One tick per level, at that level's color stop.
|
|
35
|
+
*/
|
|
36
|
+
export function pollutantLegendSection(
|
|
37
|
+
pollutant: MonitorsPollutant,
|
|
38
|
+
levels: ReadonlyArray<Pick<SJVAirEntryLevel, "color" | "range">>
|
|
39
|
+
): LegendSection | null {
|
|
40
|
+
if (levels.length === 0) return null;
|
|
41
|
+
return {
|
|
42
|
+
id: "pollutant",
|
|
43
|
+
...POLLUTANT_TITLES[pollutant],
|
|
44
|
+
bar: {
|
|
45
|
+
kind: "gradient",
|
|
46
|
+
colors: levels.map((level) => level.color),
|
|
47
|
+
ticks: levels.map((level) => String(Math.floor(level.range[0])))
|
|
48
|
+
}
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Fire & Smoke share one toggle, so they render as two independent bars. */
|
|
53
|
+
export function fireSmokeLegendSections(
|
|
54
|
+
fireTiers: ReadonlyArray<LegendCategory>,
|
|
55
|
+
smoke: ReadonlyArray<SmokeDensityStyle>
|
|
56
|
+
): Array<LegendSection> {
|
|
57
|
+
return [
|
|
58
|
+
{ id: "fire", title: "Fire", unit: "MW", bar: { kind: "blocks", categories: [...fireTiers] } },
|
|
59
|
+
{
|
|
60
|
+
id: "smoke",
|
|
61
|
+
title: "Smoke Density",
|
|
62
|
+
bar: {
|
|
63
|
+
kind: "blocks",
|
|
64
|
+
categories: smoke.map(({ label, color, opacity }) => ({
|
|
65
|
+
label,
|
|
66
|
+
color: swatchOverWhite(color, opacity)
|
|
67
|
+
}))
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
];
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/** Approximates how a translucent fill layer looks over a light basemap. */
|
|
74
|
+
export function swatchOverWhite(color: string, opacity: number): string {
|
|
75
|
+
return mix("#ffffff", color, opacity);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function gradientBackground(colors: ReadonlyArray<string>): string {
|
|
79
|
+
if (colors.length === 0) return "none";
|
|
80
|
+
if (colors.length === 1) return colors[0];
|
|
81
|
+
return `linear-gradient(to right, ${colors.join(", ")})`;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/** Hard-edged equal-width blocks: each color repeated at both edges of its own share. */
|
|
85
|
+
export function blocksBackground(colors: ReadonlyArray<string>): string {
|
|
86
|
+
const count = colors.length;
|
|
87
|
+
if (count === 0) return "none";
|
|
88
|
+
const stops = colors.map(
|
|
89
|
+
(color, i) => `${color} ${percent(i / count)}, ${color} ${percent((i + 1) / count)}`
|
|
90
|
+
);
|
|
91
|
+
return `linear-gradient(to right, ${stops.join(", ")})`;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/** Percent position of the index-th of `count` evenly spaced labels. */
|
|
95
|
+
export function tickPosition(index: number, count: number): number {
|
|
96
|
+
return count <= 1 ? 0 : (index / (count - 1)) * 100;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function percent(fraction: number): string {
|
|
100
|
+
return `${(fraction * 100).toFixed(2)}%`;
|
|
101
|
+
}
|
|
@@ -19,8 +19,10 @@
|
|
|
19
19
|
* trigger the escape-hatch full-page navigation below.
|
|
20
20
|
*/
|
|
21
21
|
knownRoutes?: Array<string>;
|
|
22
|
-
/**
|
|
22
|
+
/** Menus (OptionsGroup/OptionsMenu) rendered inside the options toolbar/panel */
|
|
23
23
|
menu?: Snippet;
|
|
24
|
+
/** Rendered last in the toolbar (wide) or beside the menu button (narrow) */
|
|
25
|
+
search?: Snippet;
|
|
24
26
|
/** Freeform absolutely-positioned content over the map (legend, search, etc.) */
|
|
25
27
|
overlays?: Snippet;
|
|
26
28
|
/** The routed detail panel content */
|
|
@@ -50,7 +52,7 @@
|
|
|
50
52
|
<script lang="ts">
|
|
51
53
|
import LoadScreen, { disable as disableLoadScreen } from "$lib/LoadScreen.svelte";
|
|
52
54
|
import Map from "$lib/map/Map.svelte";
|
|
53
|
-
import
|
|
55
|
+
import OptionsBar from "$lib/options/OptionsBar.svelte";
|
|
54
56
|
import { mapManager } from "$lib/map/map.svelte";
|
|
55
57
|
import { useMonitorMapRouter } from "$lib/router-context";
|
|
56
58
|
|
|
@@ -60,6 +62,7 @@
|
|
|
60
62
|
panelOpen,
|
|
61
63
|
knownRoutes = [],
|
|
62
64
|
menu,
|
|
65
|
+
search,
|
|
63
66
|
overlays,
|
|
64
67
|
children,
|
|
65
68
|
basePath: providedBasePath,
|
|
@@ -124,10 +127,8 @@
|
|
|
124
127
|
<div class="relative flex-1 overflow-hidden">
|
|
125
128
|
<Map {integrations} />
|
|
126
129
|
{@render overlays?.()}
|
|
127
|
-
{#if menu}
|
|
128
|
-
<
|
|
129
|
-
<Menu>{@render menu()}</Menu>
|
|
130
|
-
</div>
|
|
130
|
+
{#if menu || search}
|
|
131
|
+
<OptionsBar {search}>{@render menu?.()}</OptionsBar>
|
|
131
132
|
{/if}
|
|
132
133
|
</div>
|
|
133
134
|
<div
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/** The slices of MapTiler's MapStyleVariant / ReferenceMapStyle this module needs. */
|
|
2
|
+
export interface StyleVariantLike {
|
|
3
|
+
getType(): string;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
export interface ReferenceStyleLike<V extends StyleVariantLike> {
|
|
7
|
+
hasVariant(type: string): boolean;
|
|
8
|
+
getVariant(type: string): V;
|
|
9
|
+
getDefaultVariant(): V;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* Variant to show after switching basemaps: the same variant type (e.g. "DARK") when the new
|
|
14
|
+
* basemap offers it, otherwise the new basemap's default.
|
|
15
|
+
*/
|
|
16
|
+
export function variantForStyle<V extends StyleVariantLike>(
|
|
17
|
+
style: ReferenceStyleLike<V>,
|
|
18
|
+
previous: StyleVariantLike | null
|
|
19
|
+
): V {
|
|
20
|
+
if (previous && style.hasVariant(previous.getType())) {
|
|
21
|
+
return style.getVariant(previous.getType());
|
|
22
|
+
}
|
|
23
|
+
return style.getDefaultVariant();
|
|
24
|
+
}
|
|
@@ -11,7 +11,18 @@ export abstract class MapGeoJSONIntegration<
|
|
|
11
11
|
abstract mapSource: Parameters<MaptilerMap["addSource"]>[1];
|
|
12
12
|
|
|
13
13
|
remove() {
|
|
14
|
-
|
|
14
|
+
this.removeLayerAndSource();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Removes only this integration's own layer and source. Non-virtual on purpose: `apply()`
|
|
19
|
+
* paths call it to clear stale map state before re-adding, without triggering a subclass's
|
|
20
|
+
* `remove()` override (which also tears down tooltips/click handlers that apply just set up).
|
|
21
|
+
*/
|
|
22
|
+
protected removeLayerAndSource(): void {
|
|
23
|
+
if (mapManager.map?.getLayer(this.referenceId)) {
|
|
24
|
+
mapManager.map.removeLayer(this.referenceId);
|
|
25
|
+
}
|
|
15
26
|
if (mapManager.map?.getSource(this.referenceId)) {
|
|
16
27
|
mapManager.map.removeSource(this.referenceId);
|
|
17
28
|
}
|
|
@@ -27,7 +38,9 @@ export abstract class MapIconLayerIntegration<
|
|
|
27
38
|
if (!mapManager.map) return;
|
|
28
39
|
|
|
29
40
|
this.icons.loadIcons().then(() => {
|
|
30
|
-
|
|
41
|
+
// A basemap change started while icons loaded; the refresh on "style.load" re-applies.
|
|
42
|
+
if (mapManager.styleLoading) return;
|
|
43
|
+
this.removeLayerAndSource();
|
|
31
44
|
mapManager.map?.addSource(this.referenceId, this.mapSource);
|
|
32
45
|
super.apply();
|
|
33
46
|
});
|
|
@@ -14,12 +14,15 @@ export abstract class MapLayerIntegration extends MapIntegration {
|
|
|
14
14
|
// Each integration manages its own lifecycle. Tracking both mapManager.map and
|
|
15
15
|
// this.enabled means apply/remove fire automatically on map load and on enabled
|
|
16
16
|
// changes. untrack on the call itself prevents reactive reads inside apply/remove
|
|
17
|
-
// from leaking back into this effect's dependency graph.
|
|
17
|
+
// from leaking back into this effect's dependency graph. While a new basemap style is
|
|
18
|
+
// loading nothing is applied or removed: the refresh on "style.load" does both.
|
|
18
19
|
$effect.root(() => {
|
|
19
20
|
$effect(() => {
|
|
20
|
-
|
|
21
|
+
const enabled = this.enabled;
|
|
22
|
+
if (!mapManager.map || mapManager.styleLoading) return;
|
|
23
|
+
if (enabled) {
|
|
21
24
|
untrack(() => this.apply());
|
|
22
|
-
} else
|
|
25
|
+
} else {
|
|
23
26
|
untrack(() => this.remove());
|
|
24
27
|
}
|
|
25
28
|
});
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { MapStyleVariant, ReferenceMapStyle } from "@maptiler/sdk";
|
|
2
|
+
import { DefaultMapStyle, mapManager } from "./map.svelte";
|
|
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
|
+
private applied: MapStyleVariant = DefaultMapStyle;
|
|
11
|
+
|
|
12
|
+
selectReferenceStyle(style: ReferenceMapStyle): void {
|
|
13
|
+
this.referenceStyle = style;
|
|
14
|
+
this.selectVariant(variantForStyle(style, this.variant));
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
selectVariant(variant: MapStyleVariant): void {
|
|
18
|
+
this.variant = variant;
|
|
19
|
+
this.apply();
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
reset(): void {
|
|
23
|
+
this.referenceStyle = DefaultMapStyle.getReferenceStyle();
|
|
24
|
+
this.selectVariant(DefaultMapStyle);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
private apply(): void {
|
|
28
|
+
const map = mapManager.map;
|
|
29
|
+
if (!map || this.variant === this.applied) return;
|
|
30
|
+
this.applied = this.variant;
|
|
31
|
+
// setStyle wipes every custom source/layer; re-add enabled integrations once it loads.
|
|
32
|
+
map.once("style.load", () => {
|
|
33
|
+
mapManager.styleLoading = false;
|
|
34
|
+
integrationsManager.refresh();
|
|
35
|
+
});
|
|
36
|
+
mapManager.styleLoading = true;
|
|
37
|
+
map.setStyle(this.variant);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const mapStyleState = new MapStyleState();
|
|
42
|
+
export type { MapStyleState };
|
|
@@ -15,6 +15,12 @@ const excludeLayers = ["Sport", "Tourism", "Culture", "Shopping", "Food", "Trans
|
|
|
15
15
|
|
|
16
16
|
class MapManager {
|
|
17
17
|
map: MaptilerMap | null = $state(null);
|
|
18
|
+
/**
|
|
19
|
+
* True between `setStyle()` and the new style's "style.load". Plain (non-reactive) on purpose:
|
|
20
|
+
* integrations skip touching the style while it's set, and the `integrationsManager.refresh()`
|
|
21
|
+
* that runs on "style.load" applies/removes every integration afterwards.
|
|
22
|
+
*/
|
|
23
|
+
styleLoading: boolean = false;
|
|
18
24
|
|
|
19
25
|
setDataSource(sourceId: string, features: Array<Feature<Geometry, GeoJsonProperties>>): void {
|
|
20
26
|
const source = mapManager.map?.getSource(sourceId);
|