@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
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
import { asDataURI, fireIcon } from "$lib/map/icons";
|
|
2
|
-
import { MapIconManager } from "$lib/map/integrations/map-icon-manager";
|
|
3
|
-
import type { LegendCategory } from "$lib/legend/legend-data";
|
|
4
|
-
|
|
5
|
-
export type FRPTier = "sm" | "md" | "lg" | "xl" | "xxl";
|
|
6
|
-
|
|
7
|
-
export const FRP_TIERS: Record<FRPTier, { color: string; size: number }> = {
|
|
8
|
-
sm: { color: "#FFD700", size: 20 },
|
|
9
|
-
md: { color: "#FF8C00", size: 24 },
|
|
10
|
-
lg: { color: "#FF4500", size: 28 },
|
|
11
|
-
xl: { color: "#DC143C", size: 32 },
|
|
12
|
-
xxl: { color: "#8B0000", size: 36 }
|
|
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
|
-
|
|
28
|
-
export function getTierIconId(frp: number): string {
|
|
29
|
-
if (frp < 10) return "hms-fire-sm";
|
|
30
|
-
if (frp < 50) return "hms-fire-md";
|
|
31
|
-
if (frp < 150) return "hms-fire-lg";
|
|
32
|
-
if (frp < 350) return "hms-fire-xl";
|
|
33
|
-
return "hms-fire-xxl";
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export class HMSFireIconManager extends MapIconManager {
|
|
37
|
-
constructor() {
|
|
38
|
-
super();
|
|
39
|
-
for (const [tier, { color, size }] of Object.entries(FRP_TIERS) as [
|
|
40
|
-
FRPTier,
|
|
41
|
-
{ color: string; size: number }
|
|
42
|
-
][]) {
|
|
43
|
-
const img = new Image(size, size);
|
|
44
|
-
img.src = asDataURI(fireIcon(color, size));
|
|
45
|
-
this.register(`hms-fire-${tier}`, img);
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
import type { Map as MaptilerMap } from "@maptiler/sdk";
|
|
2
|
-
import type { Feature } from "geojson";
|
|
3
|
-
import type { Point } from "geojson";
|
|
4
|
-
import { mapManager } from "$lib/map/map-manager.svelte.js";
|
|
5
|
-
import { MapIconLayerIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
|
|
6
|
-
import { hmsManager } from "./hms.svelte";
|
|
7
|
-
import { HMSFireIconManager, getTierIconId } from "./hms-fire-icon-manager";
|
|
8
|
-
|
|
9
|
-
type FireProperties = Record<string, unknown>;
|
|
10
|
-
|
|
11
|
-
const ZOOM_THRESHOLD = 10;
|
|
12
|
-
|
|
13
|
-
class HMSFireMapIntegration extends MapIconLayerIntegration<FireProperties> {
|
|
14
|
-
referenceId = "hms-fire";
|
|
15
|
-
enabled: boolean = $state(true);
|
|
16
|
-
icons = new HMSFireIconManager();
|
|
17
|
-
private zoomedIn: boolean = $state(false);
|
|
18
|
-
|
|
19
|
-
get features(): Array<Feature<Point, FireProperties>> {
|
|
20
|
-
return this.zoomedIn ? this.hotspotFeatures : this.groupFeatures;
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
|
|
24
|
-
return {
|
|
25
|
-
id: this.referenceId,
|
|
26
|
-
type: "symbol",
|
|
27
|
-
source: this.referenceId,
|
|
28
|
-
layout: {
|
|
29
|
-
"icon-image": ["get", "icon"],
|
|
30
|
-
"icon-allow-overlap": true,
|
|
31
|
-
"icon-ignore-placement": true
|
|
32
|
-
},
|
|
33
|
-
paint: {}
|
|
34
|
-
};
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
|
|
38
|
-
return {
|
|
39
|
-
type: "geojson",
|
|
40
|
-
promoteId: "id",
|
|
41
|
-
data: { type: "FeatureCollection", features: this.features }
|
|
42
|
-
};
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
private get groupFeatures(): Array<Feature<Point, FireProperties>> {
|
|
46
|
-
return (hmsManager.fireGroups ?? []).map((g) => ({
|
|
47
|
-
type: "Feature",
|
|
48
|
-
properties: {
|
|
49
|
-
id: g.id,
|
|
50
|
-
icon: getTierIconId(g.avgFrp),
|
|
51
|
-
avgFrp: g.avgFrp,
|
|
52
|
-
count: g.count
|
|
53
|
-
},
|
|
54
|
-
geometry: { type: "Point", coordinates: g.coordinates }
|
|
55
|
-
}));
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
private get hotspotFeatures(): Array<Feature<Point, FireProperties>> {
|
|
59
|
-
return (hmsManager.fire ?? []).map((d) => ({
|
|
60
|
-
type: "Feature",
|
|
61
|
-
properties: {
|
|
62
|
-
id: d.id,
|
|
63
|
-
icon: getTierIconId(d.frp),
|
|
64
|
-
frp: d.frp
|
|
65
|
-
},
|
|
66
|
-
geometry: d.geometry as Point
|
|
67
|
-
}));
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
private onZoom = () => {
|
|
71
|
-
if (!mapManager.map) return;
|
|
72
|
-
this.zoomedIn = mapManager.map.getZoom() >= ZOOM_THRESHOLD;
|
|
73
|
-
};
|
|
74
|
-
|
|
75
|
-
constructor() {
|
|
76
|
-
super();
|
|
77
|
-
|
|
78
|
-
$effect.root(() => {
|
|
79
|
-
$effect(() => {
|
|
80
|
-
const features = this.features;
|
|
81
|
-
if (!mapManager.map || !this.enabled) return;
|
|
82
|
-
mapManager.setDataSource(this.referenceId, features);
|
|
83
|
-
});
|
|
84
|
-
});
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
apply() {
|
|
88
|
-
if (!mapManager.map) return;
|
|
89
|
-
this.icons
|
|
90
|
-
.loadIcons()
|
|
91
|
-
.then(() => {
|
|
92
|
-
if (!mapManager.map || !this.enabled || mapManager.styleLoading) return;
|
|
93
|
-
this.remove();
|
|
94
|
-
this.zoomedIn = mapManager.map.getZoom() >= ZOOM_THRESHOLD;
|
|
95
|
-
mapManager.map.addSource(this.referenceId, this.mapSource);
|
|
96
|
-
mapManager.map.addLayer(this.mapLayer, this.beforeLayer);
|
|
97
|
-
mapManager.map.on("zoom", this.onZoom);
|
|
98
|
-
})
|
|
99
|
-
.catch(console.error);
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
remove() {
|
|
103
|
-
mapManager.map?.off("zoom", this.onZoom);
|
|
104
|
-
super.remove();
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
export const hmsFireMapIntegration = new HMSFireMapIntegration();
|
|
109
|
-
export type { HMSFireMapIntegration };
|
|
@@ -1,105 +0,0 @@
|
|
|
1
|
-
import type { Map as MaptilerMap } from "@maptiler/sdk";
|
|
2
|
-
import type { Feature, MultiPolygon } from "geojson";
|
|
3
|
-
import type { HMSSmokeGeoJSON } from "@sjvair/sdk/hms";
|
|
4
|
-
import { mapManager } from "$lib/map/map-manager.svelte.js";
|
|
5
|
-
import { MapGeoJSONIntegration } from "$lib/map/integrations/map-geojson-integration.svelte";
|
|
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);
|
|
17
|
-
|
|
18
|
-
type SmokeProperties = {
|
|
19
|
-
id: string;
|
|
20
|
-
density: HMSSmokeGeoJSON["density"];
|
|
21
|
-
};
|
|
22
|
-
|
|
23
|
-
class HMSSmokeMapIntegration extends MapGeoJSONIntegration<SmokeProperties> {
|
|
24
|
-
referenceId = "hms-smoke";
|
|
25
|
-
enabled: boolean = $state(true);
|
|
26
|
-
|
|
27
|
-
get features(): Array<Feature<MultiPolygon, SmokeProperties>> {
|
|
28
|
-
return (hmsManager.smoke ?? []).map((d) => ({
|
|
29
|
-
type: "Feature",
|
|
30
|
-
properties: {
|
|
31
|
-
id: d.id,
|
|
32
|
-
density: d.density
|
|
33
|
-
},
|
|
34
|
-
geometry: d.geometry as MultiPolygon
|
|
35
|
-
}));
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
get mapLayer(): Parameters<MaptilerMap["addLayer"]>[0] {
|
|
39
|
-
return {
|
|
40
|
-
id: this.referenceId,
|
|
41
|
-
type: "fill",
|
|
42
|
-
source: this.referenceId,
|
|
43
|
-
layout: {},
|
|
44
|
-
paint: {
|
|
45
|
-
"fill-color": [
|
|
46
|
-
"match",
|
|
47
|
-
["get", "density"],
|
|
48
|
-
"light",
|
|
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,
|
|
59
|
-
"medium",
|
|
60
|
-
SMOKE_DENSITY_STYLES.medium.opacity,
|
|
61
|
-
SMOKE_DENSITY_STYLES.heavy.opacity
|
|
62
|
-
],
|
|
63
|
-
"fill-outline-color": [
|
|
64
|
-
"match",
|
|
65
|
-
["get", "density"],
|
|
66
|
-
"light",
|
|
67
|
-
SMOKE_DENSITY_STYLES.light.color,
|
|
68
|
-
"medium",
|
|
69
|
-
SMOKE_DENSITY_STYLES.medium.color,
|
|
70
|
-
SMOKE_DENSITY_STYLES.heavy.color
|
|
71
|
-
]
|
|
72
|
-
}
|
|
73
|
-
};
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
get mapSource(): Parameters<MaptilerMap["addSource"]>[1] {
|
|
77
|
-
return {
|
|
78
|
-
type: "geojson",
|
|
79
|
-
promoteId: "id",
|
|
80
|
-
data: { type: "FeatureCollection", features: this.features }
|
|
81
|
-
};
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
constructor() {
|
|
85
|
-
super();
|
|
86
|
-
|
|
87
|
-
$effect.root(() => {
|
|
88
|
-
$effect(() => {
|
|
89
|
-
const features = this.features;
|
|
90
|
-
if (!mapManager.map || !this.enabled) return;
|
|
91
|
-
mapManager.setDataSource(this.referenceId, features);
|
|
92
|
-
});
|
|
93
|
-
});
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
apply() {
|
|
97
|
-
if (!mapManager.map) return;
|
|
98
|
-
this.remove();
|
|
99
|
-
mapManager.map.addSource(this.referenceId, this.mapSource);
|
|
100
|
-
super.apply();
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
export const hmsSmokeMapIntegration = new HMSSmokeMapIntegration();
|
|
105
|
-
export type { HMSSmokeMapIntegration };
|
|
@@ -1,264 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
getHMSFire,
|
|
3
|
-
getHMSSmoke,
|
|
4
|
-
type HMSFireGeoJSON,
|
|
5
|
-
type HMSSmokeGeoJSON
|
|
6
|
-
} from "@sjvair/sdk/hms";
|
|
7
|
-
import clustersDbscan from "@turf/clusters-dbscan";
|
|
8
|
-
import { featureCollection, point } from "@turf/helpers";
|
|
9
|
-
|
|
10
|
-
const HMS_FIRE_DBSCAN_EPSILON = 2;
|
|
11
|
-
const HMS_FIRE_DBSCAN_MIN_POINTS = 1;
|
|
12
|
-
|
|
13
|
-
export interface HMSFireGroup {
|
|
14
|
-
id: string;
|
|
15
|
-
coordinates: [number, number];
|
|
16
|
-
avgFrp: number;
|
|
17
|
-
count: number;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
class HmsManager {
|
|
21
|
-
smoke: Array<HMSSmokeGeoJSON> | undefined = $state();
|
|
22
|
-
fire: Array<HMSFireGeoJSON & { frp: number }> | undefined = $state();
|
|
23
|
-
fireGroups: Array<HMSFireGroup> | undefined = $state();
|
|
24
|
-
|
|
25
|
-
async init() {
|
|
26
|
-
await Promise.allSettled([this.loadSmoke(), this.loadFire()]);
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
async loadSmoke() {
|
|
30
|
-
this.smoke = await getHMSSmoke();
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
async loadFire() {
|
|
34
|
-
try {
|
|
35
|
-
const raw = await getHMSFire();
|
|
36
|
-
const filtered = raw.filter((d): d is HMSFireGeoJSON & { frp: number } => d.frp !== null);
|
|
37
|
-
const deduped = deduplicateByCoordinates(filtered);
|
|
38
|
-
this.fire = deduped;
|
|
39
|
-
this.fireGroups = computeFireGroups(deduped);
|
|
40
|
-
} catch (err) {
|
|
41
|
-
console.error("HMS fire load failed:", err);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function deduplicateByCoordinates(
|
|
47
|
-
fires: (HMSFireGeoJSON & { frp: number })[]
|
|
48
|
-
): (HMSFireGeoJSON & { frp: number })[] {
|
|
49
|
-
const latest: Record<string, HMSFireGeoJSON & { frp: number }> = {};
|
|
50
|
-
for (const fire of fires) {
|
|
51
|
-
const [lng, lat] = fire.geometry.coordinates as [number, number];
|
|
52
|
-
const key = `${lng},${lat}`;
|
|
53
|
-
const existing = latest[key];
|
|
54
|
-
if (!existing || fire.timestamp > existing.timestamp) {
|
|
55
|
-
latest[key] = fire;
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
return Object.values(latest);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
function computeFireGroups(fires: (HMSFireGeoJSON & { frp: number })[]): HMSFireGroup[] {
|
|
62
|
-
if (!fires.length) return [];
|
|
63
|
-
|
|
64
|
-
const fc = featureCollection(
|
|
65
|
-
fires.map((d) =>
|
|
66
|
-
point(d.geometry.coordinates as [number, number], { fireId: d.id, frp: d.frp })
|
|
67
|
-
)
|
|
68
|
-
);
|
|
69
|
-
|
|
70
|
-
const clustered = clustersDbscan(fc, HMS_FIRE_DBSCAN_EPSILON, {
|
|
71
|
-
minPoints: HMS_FIRE_DBSCAN_MIN_POINTS,
|
|
72
|
-
units: "kilometers"
|
|
73
|
-
});
|
|
74
|
-
|
|
75
|
-
const groups: Record<string, { frps: number[]; lngs: number[]; lats: number[] }> = {};
|
|
76
|
-
|
|
77
|
-
for (const feature of clustered.features) {
|
|
78
|
-
const props = feature.properties as {
|
|
79
|
-
dbscan: "core" | "edge" | "noise";
|
|
80
|
-
cluster?: number;
|
|
81
|
-
fireId: string;
|
|
82
|
-
frp: number;
|
|
83
|
-
};
|
|
84
|
-
// With minPoints=1 every point self-qualifies as core, so "noise" never occurs.
|
|
85
|
-
// This branch activates correctly if minPoints is raised above 1 in future.
|
|
86
|
-
const key = props.dbscan === "noise" ? `noise-${props.fireId}` : `cluster-${props.cluster}`;
|
|
87
|
-
const [lng, lat] = feature.geometry.coordinates as [number, number];
|
|
88
|
-
const entry = groups[key] ?? { frps: [], lngs: [], lats: [] };
|
|
89
|
-
entry.frps.push(props.frp);
|
|
90
|
-
entry.lngs.push(lng);
|
|
91
|
-
entry.lats.push(lat);
|
|
92
|
-
groups[key] = entry;
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
return Object.entries(groups).map(([key, { frps, lngs, lats }]) => ({
|
|
96
|
-
id: `hms-fire-group-${key}`,
|
|
97
|
-
coordinates: [
|
|
98
|
-
lngs.reduce((s, v) => s + v, 0) / lngs.length,
|
|
99
|
-
lats.reduce((s, v) => s + v, 0) / lats.length
|
|
100
|
-
] as [number, number],
|
|
101
|
-
avgFrp: frps.reduce((s, v) => s + v, 0) / frps.length,
|
|
102
|
-
count: frps.length
|
|
103
|
-
}));
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
//async function fakeFireData(): Promise<Array<HMSFireGeoJSON>> {
|
|
107
|
-
// const response: FetchHMSFireResponse["body"] = {
|
|
108
|
-
// data: [
|
|
109
|
-
// {
|
|
110
|
-
// id: "4Kx7mNpQrQ",
|
|
111
|
-
// date: "2026-04-13",
|
|
112
|
-
// satellite: "GOES-16",
|
|
113
|
-
// timestamp: "2026-04-13T18:30:00Z",
|
|
114
|
-
// frp: 360,
|
|
115
|
-
// ecosystem: 4,
|
|
116
|
-
// method: "AF",
|
|
117
|
-
// geometry: {
|
|
118
|
-
// type: "Point",
|
|
119
|
-
// coordinates: [-118.532, 36.591],
|
|
120
|
-
// },
|
|
121
|
-
// },
|
|
122
|
-
// {
|
|
123
|
-
// id: "4Kx7mNpQrD",
|
|
124
|
-
// date: "2026-04-13",
|
|
125
|
-
// satellite: "GOES-16",
|
|
126
|
-
// timestamp: "2026-04-13T18:30:00Z",
|
|
127
|
-
// frp: 20,
|
|
128
|
-
// ecosystem: 4,
|
|
129
|
-
// method: "AF",
|
|
130
|
-
// geometry: {
|
|
131
|
-
// type: "Point",
|
|
132
|
-
// coordinates: [-118.632, 36.691],
|
|
133
|
-
// },
|
|
134
|
-
// },
|
|
135
|
-
// {
|
|
136
|
-
// id: "4Kx7mNpQrT",
|
|
137
|
-
// date: "2026-04-13",
|
|
138
|
-
// satellite: "GOES-16",
|
|
139
|
-
// timestamp: "2026-04-13T18:30:00Z",
|
|
140
|
-
// frp: 152.7,
|
|
141
|
-
// ecosystem: 4,
|
|
142
|
-
// method: "AF",
|
|
143
|
-
// geometry: {
|
|
144
|
-
// type: "Point",
|
|
145
|
-
// coordinates: [-118.832, 36.491],
|
|
146
|
-
// },
|
|
147
|
-
// },
|
|
148
|
-
// {
|
|
149
|
-
// id: "9vBjLsWqYm",
|
|
150
|
-
// date: "2026-04-13",
|
|
151
|
-
// satellite: "GOES-17",
|
|
152
|
-
// timestamp: "2026-04-13T17:45:00Z",
|
|
153
|
-
// frp: 58.2,
|
|
154
|
-
// ecosystem: 2,
|
|
155
|
-
// method: "AF",
|
|
156
|
-
// geometry: {
|
|
157
|
-
// type: "Point",
|
|
158
|
-
// coordinates: [-119.204, 37.112],
|
|
159
|
-
// },
|
|
160
|
-
// },
|
|
161
|
-
// {
|
|
162
|
-
// id: "2HcFnRkVxZ",
|
|
163
|
-
// date: "2026-04-13",
|
|
164
|
-
// satellite: "GOES-16",
|
|
165
|
-
// timestamp: "2026-04-13T16:00:00Z",
|
|
166
|
-
// frp: null,
|
|
167
|
-
// ecosystem: 7,
|
|
168
|
-
// method: "FDC",
|
|
169
|
-
// geometry: {
|
|
170
|
-
// type: "Point",
|
|
171
|
-
// coordinates: [-120.571, 35.883],
|
|
172
|
-
// },
|
|
173
|
-
// },
|
|
174
|
-
// ],
|
|
175
|
-
// page: 1,
|
|
176
|
-
// count: 3,
|
|
177
|
-
// pages: 1,
|
|
178
|
-
// has_next_page: false,
|
|
179
|
-
// has_previous_page: false,
|
|
180
|
-
// };
|
|
181
|
-
// return Promise.resolve(response.data);
|
|
182
|
-
//}
|
|
183
|
-
|
|
184
|
-
//async function fakeSmokeData(): Promise<Array<HMSSmokeGeoJSON>> {
|
|
185
|
-
// const response: FetchHMSSmokeResponse["body"] = {
|
|
186
|
-
// data: [
|
|
187
|
-
// {
|
|
188
|
-
// id: "sM3kNpQrL1",
|
|
189
|
-
// date: "2026-04-13",
|
|
190
|
-
// satellite: "GOES-16",
|
|
191
|
-
// start: "2026-04-13T12:00:00Z",
|
|
192
|
-
// end: "2026-04-13T18:00:00Z",
|
|
193
|
-
// density: "light",
|
|
194
|
-
// geometry: {
|
|
195
|
-
// type: "MultiPolygon",
|
|
196
|
-
// coordinates: [
|
|
197
|
-
// [
|
|
198
|
-
// [
|
|
199
|
-
// [-122.5, 39.5],
|
|
200
|
-
// [-120.5, 39.5],
|
|
201
|
-
// [-120.5, 41.0],
|
|
202
|
-
// [-122.5, 41.0],
|
|
203
|
-
// [-122.5, 39.5]
|
|
204
|
-
// ]
|
|
205
|
-
// ]
|
|
206
|
-
// ]
|
|
207
|
-
// }
|
|
208
|
-
// },
|
|
209
|
-
// {
|
|
210
|
-
// id: "sM3kNpQrL2",
|
|
211
|
-
// date: "2026-04-13",
|
|
212
|
-
// satellite: "GOES-16",
|
|
213
|
-
// start: "2026-04-13T12:00:00Z",
|
|
214
|
-
// end: "2026-04-13T18:00:00Z",
|
|
215
|
-
// density: "medium",
|
|
216
|
-
// geometry: {
|
|
217
|
-
// type: "MultiPolygon",
|
|
218
|
-
// coordinates: [
|
|
219
|
-
// [
|
|
220
|
-
// [
|
|
221
|
-
// [-121.0, 36.5],
|
|
222
|
-
// [-119.0, 36.5],
|
|
223
|
-
// [-119.0, 38.0],
|
|
224
|
-
// [-121.0, 38.0],
|
|
225
|
-
// [-121.0, 36.5]
|
|
226
|
-
// ]
|
|
227
|
-
// ]
|
|
228
|
-
// ]
|
|
229
|
-
// }
|
|
230
|
-
// },
|
|
231
|
-
// {
|
|
232
|
-
// id: "sM3kNpQrL3",
|
|
233
|
-
// date: "2026-04-13",
|
|
234
|
-
// satellite: "GOES-16",
|
|
235
|
-
// start: "2026-04-13T12:00:00Z",
|
|
236
|
-
// end: "2026-04-13T18:00:00Z",
|
|
237
|
-
// density: "heavy",
|
|
238
|
-
// geometry: {
|
|
239
|
-
// type: "MultiPolygon",
|
|
240
|
-
// coordinates: [
|
|
241
|
-
// [
|
|
242
|
-
// [
|
|
243
|
-
// [-119.0, 33.5],
|
|
244
|
-
// [-116.5, 33.5],
|
|
245
|
-
// [-116.5, 35.0],
|
|
246
|
-
// [-119.0, 35.0],
|
|
247
|
-
// [-119.0, 33.5]
|
|
248
|
-
// ]
|
|
249
|
-
// ]
|
|
250
|
-
// ]
|
|
251
|
-
// }
|
|
252
|
-
// }
|
|
253
|
-
// ],
|
|
254
|
-
// page: 1,
|
|
255
|
-
// count: 3,
|
|
256
|
-
// pages: 1,
|
|
257
|
-
// has_next_page: false,
|
|
258
|
-
// has_previous_page: false
|
|
259
|
-
// };
|
|
260
|
-
// return Promise.resolve(response.data);
|
|
261
|
-
//}
|
|
262
|
-
|
|
263
|
-
export const hmsManager = new HmsManager();
|
|
264
|
-
export type { HmsManager as HMSManager };
|
|
@@ -1,45 +0,0 @@
|
|
|
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}
|
|
@@ -1,101 +0,0 @@
|
|
|
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
|
-
}
|
package/src/lib/map/Map.svelte
DELETED
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
<script lang="ts">
|
|
2
|
-
import "@maptiler/sdk/dist/maptiler-sdk.css";
|
|
3
|
-
import { initializeMap } from "./map-manager.svelte.js";
|
|
4
|
-
import { integrationsManager } from "$lib/map/integrations/integrations-manager";
|
|
5
|
-
import type { SomeMapIntegration } from "./integrations/types";
|
|
6
|
-
|
|
7
|
-
const { integrations }: { integrations: Array<SomeMapIntegration> } = $props();
|
|
8
|
-
|
|
9
|
-
// svelte-ignore state_referenced_locally (we only need the initial reference)
|
|
10
|
-
integrationsManager.register(...integrations);
|
|
11
|
-
</script>
|
|
12
|
-
|
|
13
|
-
<div {@attach initializeMap} style="width: 100%; height: 100%;"></div>
|