@sjvair/map-sdk 0.0.0-stage → 0.1.0-beta.2
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 +506 -2
- package/dist/core/basemap.svelte.d.ts +38 -0
- package/dist/core/basemap.svelte.js +163 -0
- package/dist/core/context.d.ts +5 -0
- package/dist/core/context.js +13 -0
- package/dist/core/create-map.svelte.d.ts +207 -0
- package/dist/core/create-map.svelte.js +376 -0
- package/dist/core/gl.d.ts +92 -0
- package/dist/core/gl.js +1 -0
- package/dist/core/interactions.svelte.d.ts +43 -0
- package/dist/core/interactions.svelte.js +230 -0
- package/dist/core/plugin-host.svelte.d.ts +46 -0
- package/dist/core/plugin-host.svelte.js +366 -0
- package/dist/core/plugin.d.ts +155 -0
- package/dist/core/plugin.js +1 -0
- package/dist/core/scoped-context.d.ts +7 -0
- package/dist/core/scoped-context.js +34 -0
- package/dist/data/format.d.ts +10 -0
- package/dist/data/format.js +24 -0
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.js +9 -0
- package/dist/data/messages/en.json +52 -0
- package/dist/data/messages.d.ts +10 -0
- package/dist/data/messages.js +17 -0
- package/dist/data/metadata.d.ts +22 -0
- package/dist/data/metadata.js +110 -0
- package/dist/data/refs.d.ts +18 -0
- package/dist/data/refs.js +61 -0
- package/dist/data/scale-types.d.ts +7 -0
- package/dist/data/scale-types.js +1 -0
- package/dist/data/scale.d.ts +13 -0
- package/dist/data/scale.js +27 -0
- package/dist/data/selection.d.ts +7 -0
- package/dist/data/selection.js +31 -0
- package/dist/data/static-source.d.ts +4 -0
- package/dist/data/static-source.js +3 -0
- package/dist/data/types.d.ts +69 -0
- package/dist/data/types.js +1 -0
- package/dist/index.d.ts +56 -0
- package/dist/index.js +41 -0
- package/dist/plugins/icons.d.ts +21 -0
- package/dist/plugins/icons.js +73 -0
- package/dist/plugins/points-clusters.d.ts +60 -0
- package/dist/plugins/points-clusters.js +93 -0
- package/dist/plugins/points.svelte.d.ts +100 -0
- package/dist/plugins/points.svelte.js +501 -0
- package/dist/plugins/scale-resolution.svelte.d.ts +27 -0
- package/dist/plugins/scale-resolution.svelte.js +111 -0
- package/dist/plugins/shapes.svelte.d.ts +29 -0
- package/dist/plugins/shapes.svelte.js +307 -0
- package/dist/plugins/shared.d.ts +6 -0
- package/dist/plugins/shared.js +18 -0
- package/dist/plugins/style.d.ts +57 -0
- package/dist/plugins/style.js +193 -0
- package/dist/presets/CollocationTooltip.svelte +132 -0
- package/dist/presets/CollocationTooltip.svelte.d.ts +16 -0
- package/dist/presets/MonitorClusterTooltip.svelte +149 -0
- package/dist/presets/MonitorClusterTooltip.svelte.d.ts +16 -0
- package/dist/presets/MonitorReadingBox.svelte +58 -0
- package/dist/presets/MonitorReadingBox.svelte.d.ts +12 -0
- package/dist/presets/MonitorTooltip.svelte +88 -0
- package/dist/presets/MonitorTooltip.svelte.d.ts +20 -0
- package/dist/presets/calheatscore.d.ts +24 -0
- package/dist/presets/calheatscore.js +22 -0
- package/dist/presets/collocation-sites.d.ts +18 -0
- package/dist/presets/collocation-sites.js +67 -0
- package/dist/presets/fire-groups.d.ts +26 -0
- package/dist/presets/fire-groups.js +51 -0
- package/dist/presets/forecast-zones.d.ts +25 -0
- package/dist/presets/forecast-zones.js +23 -0
- package/dist/presets/hms-fire.d.ts +26 -0
- package/dist/presets/hms-fire.js +87 -0
- package/dist/presets/hms-smoke.d.ts +19 -0
- package/dist/presets/hms-smoke.js +20 -0
- package/dist/presets/monitors.d.ts +48 -0
- package/dist/presets/monitors.js +243 -0
- package/dist/presets/reactive-props.svelte.d.ts +4 -0
- package/dist/presets/reactive-props.svelte.js +7 -0
- package/dist/presets/regions.d.ts +24 -0
- package/dist/presets/regions.js +22 -0
- package/dist/stores/abort.d.ts +16 -0
- package/dist/stores/abort.js +52 -0
- package/dist/stores/collocation-store.svelte.d.ts +25 -0
- package/dist/stores/collocation-store.svelte.js +72 -0
- package/dist/stores/hms-stores.svelte.d.ts +35 -0
- package/dist/stores/hms-stores.svelte.js +97 -0
- package/dist/stores/index.d.ts +4 -0
- package/dist/stores/index.js +4 -0
- package/dist/stores/load-once.svelte.d.ts +25 -0
- package/dist/stores/load-once.svelte.js +93 -0
- package/dist/stores/monitors-store.svelte.d.ts +85 -0
- package/dist/stores/monitors-store.svelte.js +340 -0
- package/dist/theme.css +34 -0
- package/dist/ui/FeatureList.svelte +505 -0
- package/dist/ui/FeatureList.svelte.d.ts +14 -0
- package/dist/ui/Legend.svelte +213 -0
- package/dist/ui/Legend.svelte.d.ts +22 -0
- package/dist/ui/LegendBar.svelte +161 -0
- package/dist/ui/LegendBar.svelte.d.ts +11 -0
- package/dist/ui/MapControls.svelte +135 -0
- package/dist/ui/MapControls.svelte.d.ts +11 -0
- package/dist/ui/MapTooltip.svelte +158 -0
- package/dist/ui/MapTooltip.svelte.d.ts +28 -0
- package/dist/ui/SelectionBar.svelte +123 -0
- package/dist/ui/SelectionBar.svelte.d.ts +10 -0
- package/dist/ui/TooltipItem.svelte +72 -0
- package/dist/ui/TooltipItem.svelte.d.ts +11 -0
- package/dist/ui/ViewSummary.svelte +97 -0
- package/dist/ui/ViewSummary.svelte.d.ts +7 -0
- package/dist/ui/controls.css +45 -0
- package/dist/ui/data-credits.svelte.d.ts +9 -0
- package/dist/ui/data-credits.svelte.js +52 -0
- package/dist/ui/feature-list.d.ts +38 -0
- package/dist/ui/feature-list.js +95 -0
- package/dist/ui/legend-data.d.ts +41 -0
- package/dist/ui/legend-data.js +40 -0
- package/dist/ui/level-chip.d.ts +11 -0
- package/dist/ui/level-chip.js +15 -0
- package/dist/ui/value-format.d.ts +7 -0
- package/dist/ui/value-format.js +13 -0
- package/dist/ui/view-summary.d.ts +11 -0
- package/dist/ui/view-summary.js +33 -0
- package/dist/view/MapView.svelte +373 -0
- package/dist/view/MapView.svelte.d.ts +43 -0
- package/dist/view/create-gl-map.d.ts +34 -0
- package/dist/view/create-gl-map.js +182 -0
- package/dist/view/list-placement.d.ts +21 -0
- package/dist/view/list-placement.js +13 -0
- package/package.json +103 -5
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
// Ported from monitor-map (src/lib/abort.ts): the building blocks for cancelling requests.
|
|
2
|
+
/**
|
|
3
|
+
* A signal that aborts as soon as any input does, with that input's reason. Already aborted if
|
|
4
|
+
* any input is. Undefined inputs are ignored. Call `dispose()` to drop the listeners.
|
|
5
|
+
*/
|
|
6
|
+
export function anySignal(...signals) {
|
|
7
|
+
const controller = new AbortController();
|
|
8
|
+
const cleanups = [];
|
|
9
|
+
const dispose = () => {
|
|
10
|
+
for (const cleanup of cleanups.splice(0))
|
|
11
|
+
cleanup();
|
|
12
|
+
};
|
|
13
|
+
for (const input of signals) {
|
|
14
|
+
if (!input)
|
|
15
|
+
continue;
|
|
16
|
+
if (input.aborted) {
|
|
17
|
+
controller.abort(input.reason);
|
|
18
|
+
dispose();
|
|
19
|
+
break;
|
|
20
|
+
}
|
|
21
|
+
const onAbort = () => {
|
|
22
|
+
controller.abort(input.reason);
|
|
23
|
+
dispose();
|
|
24
|
+
};
|
|
25
|
+
input.addEventListener("abort", onAbort, { once: true });
|
|
26
|
+
cleanups.push(() => input.removeEventListener("abort", onAbort));
|
|
27
|
+
}
|
|
28
|
+
return { signal: controller.signal, dispose };
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Settles like `promise`, unless `signal` aborts first, in which case it rejects with
|
|
32
|
+
* `signal.reason`. The inner promise's later outcome is swallowed.
|
|
33
|
+
*/
|
|
34
|
+
export function untilAborted(promise, signal) {
|
|
35
|
+
if (!signal)
|
|
36
|
+
return promise;
|
|
37
|
+
if (signal.aborted) {
|
|
38
|
+
promise.catch(() => { });
|
|
39
|
+
return Promise.reject(signal.reason);
|
|
40
|
+
}
|
|
41
|
+
return new Promise((resolve, reject) => {
|
|
42
|
+
const onAbort = () => reject(signal.reason);
|
|
43
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
44
|
+
promise.then((value) => {
|
|
45
|
+
signal.removeEventListener("abort", onAbort);
|
|
46
|
+
resolve(value);
|
|
47
|
+
}, (error) => {
|
|
48
|
+
signal.removeEventListener("abort", onAbort);
|
|
49
|
+
reject(error);
|
|
50
|
+
});
|
|
51
|
+
});
|
|
52
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { CollocationFeatureProperties, DataSource, DataStatus } from "../data/types.js";
|
|
2
|
+
import type { SignalOptions } from "./abort.js";
|
|
3
|
+
import type { MonitorsStore } from "./monitors-store.svelte.js";
|
|
4
|
+
export interface CollocationSitesStoreOptions {
|
|
5
|
+
/** The monitors and readings the sites join to; its entry type's scale colors them. */
|
|
6
|
+
monitors: MonitorsStore;
|
|
7
|
+
}
|
|
8
|
+
export interface CollocationSitesStore {
|
|
9
|
+
/** `loading` until the sites load, then `ready` or `error` (with `retry`). */
|
|
10
|
+
readonly status: DataStatus;
|
|
11
|
+
/**
|
|
12
|
+
* The sites for `collocationSitesPlugin`, each at its own position. Readings follow the
|
|
13
|
+
* monitors store's polling; a monitor missing from it gives null readings.
|
|
14
|
+
*/
|
|
15
|
+
readonly dataSource: DataSource<CollocationFeatureProperties>;
|
|
16
|
+
/**
|
|
17
|
+
* Loads the sites once. Overlapping calls share one run, and a failed run can be retried. A
|
|
18
|
+
* caller's abort rejects only that caller; the run stops when every caller aborted. A no-op
|
|
19
|
+
* once loaded. It doesn't load the monitors store: init that too.
|
|
20
|
+
*/
|
|
21
|
+
init(options?: SignalOptions): Promise<void>;
|
|
22
|
+
/** Cancels the in-flight request. A disposed store does nothing. */
|
|
23
|
+
dispose(): void;
|
|
24
|
+
}
|
|
25
|
+
export declare function createCollocationSitesStore(options: CollocationSitesStoreOptions): CollocationSitesStore;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { getCollocationSites } from "@sjvair/sdk";
|
|
2
|
+
import { encodeRef } from "../data/refs.js";
|
|
3
|
+
import { createLoadOnce } from "./load-once.svelte.js";
|
|
4
|
+
function join(sites, monitors) {
|
|
5
|
+
const list = monitors.monitors;
|
|
6
|
+
const latest = monitors.latest;
|
|
7
|
+
const features = [];
|
|
8
|
+
for (const site of sites) {
|
|
9
|
+
if (!site.position)
|
|
10
|
+
continue;
|
|
11
|
+
const reference = list.get(site.reference_id);
|
|
12
|
+
const colocated = list.get(site.colocated_id);
|
|
13
|
+
const referenceReading = latest.get(site.reference_id);
|
|
14
|
+
const colocatedReading = latest.get(site.colocated_id);
|
|
15
|
+
features.push({
|
|
16
|
+
type: "Feature",
|
|
17
|
+
geometry: { type: "Point", coordinates: site.position.coordinates },
|
|
18
|
+
properties: {
|
|
19
|
+
refKey: encodeRef({ kind: "feature", id: site.id }),
|
|
20
|
+
name: site.name,
|
|
21
|
+
value: referenceReading?.value ?? null,
|
|
22
|
+
timestamp: referenceReading?.timestamp ?? null,
|
|
23
|
+
// A missing reference monitor draws as a sensor in the neutral color, not inactive
|
|
24
|
+
// gray.
|
|
25
|
+
type: reference?.type ?? "",
|
|
26
|
+
isActive: reference?.is_active ?? true,
|
|
27
|
+
isSjvair: reference?.is_sjvair ?? false,
|
|
28
|
+
location: reference?.location === "inside" ? "inside" : "outside",
|
|
29
|
+
referenceName: reference?.name ?? null,
|
|
30
|
+
colocatedId: site.colocated_id,
|
|
31
|
+
colocatedName: colocated?.name ?? null,
|
|
32
|
+
colocatedType: colocated?.type ?? null,
|
|
33
|
+
colocatedValue: colocatedReading?.value ?? null,
|
|
34
|
+
colocatedTimestamp: colocatedReading?.timestamp ?? null
|
|
35
|
+
}
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
return { type: "FeatureCollection", features };
|
|
39
|
+
}
|
|
40
|
+
export function createCollocationSitesStore(options) {
|
|
41
|
+
const { monitors } = options;
|
|
42
|
+
const loader = createLoadOnce((signal) => getCollocationSites({ signal }));
|
|
43
|
+
// Rebuilt only when an input changes, so every reader shares one object. Reading the inputs
|
|
44
|
+
// in the getter keeps it reactive to the monitors store's state.
|
|
45
|
+
let cached = null;
|
|
46
|
+
return {
|
|
47
|
+
get status() {
|
|
48
|
+
return loader.status;
|
|
49
|
+
},
|
|
50
|
+
dataSource: {
|
|
51
|
+
get status() {
|
|
52
|
+
return loader.status;
|
|
53
|
+
},
|
|
54
|
+
get features() {
|
|
55
|
+
const sites = loader.value;
|
|
56
|
+
if (sites === null)
|
|
57
|
+
return null;
|
|
58
|
+
const list = monitors.monitors;
|
|
59
|
+
const latest = monitors.latest;
|
|
60
|
+
if (cached?.sites !== sites || cached.list !== list || cached.latest !== latest) {
|
|
61
|
+
cached = { sites, list, latest, features: join(sites, monitors) };
|
|
62
|
+
}
|
|
63
|
+
return cached.features;
|
|
64
|
+
},
|
|
65
|
+
get scale() {
|
|
66
|
+
return monitors.dataSource.scale;
|
|
67
|
+
}
|
|
68
|
+
},
|
|
69
|
+
init: (initOptions) => loader.init(initOptions),
|
|
70
|
+
dispose: () => loader.dispose()
|
|
71
|
+
};
|
|
72
|
+
}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { type MessageProvider } from "../data/messages.js";
|
|
2
|
+
import type { DataSource, DataStatus } from "../data/types.js";
|
|
3
|
+
import type { SignalOptions } from "./abort.js";
|
|
4
|
+
export interface HmsStoreOptions {
|
|
5
|
+
/** Names features ("Smoke", "Fire detection"). Default: the built-in English messages. */
|
|
6
|
+
messages?: MessageProvider;
|
|
7
|
+
}
|
|
8
|
+
export interface HmsSmokeStore {
|
|
9
|
+
/** `loading` until loaded, then `ready` (also when today has no plumes) or `error`. */
|
|
10
|
+
readonly status: DataStatus;
|
|
11
|
+
/** Today's plumes for `hmsSmokePlugin`; each feature's `value` is its density key. */
|
|
12
|
+
readonly dataSource: DataSource;
|
|
13
|
+
/**
|
|
14
|
+
* Loads today's smoke once. Overlapping calls share one run, and a failed run can be
|
|
15
|
+
* retried. A caller's abort rejects only that caller; the run stops when every caller
|
|
16
|
+
* aborted. A no-op once loaded.
|
|
17
|
+
*/
|
|
18
|
+
init(options?: SignalOptions): Promise<void>;
|
|
19
|
+
/** Cancels the in-flight request. A disposed store does nothing. */
|
|
20
|
+
dispose(): void;
|
|
21
|
+
}
|
|
22
|
+
export interface HmsFireStore {
|
|
23
|
+
/** `loading` until loaded, then `ready` (also when today has no fires) or `error`. */
|
|
24
|
+
readonly status: DataStatus;
|
|
25
|
+
/** Nearby detections merged (`groupFires`), for `hmsFirePlugin`; `value` is the mean FRP. */
|
|
26
|
+
readonly groups: DataSource;
|
|
27
|
+
/** Each detection (`cleanFires`), for `hmsFirePlugin`; `value` is its FRP. */
|
|
28
|
+
readonly hotspots: DataSource;
|
|
29
|
+
/** As `HmsSmokeStore.init`. */
|
|
30
|
+
init(options?: SignalOptions): Promise<void>;
|
|
31
|
+
/** Cancels the in-flight request. A disposed store does nothing. */
|
|
32
|
+
dispose(): void;
|
|
33
|
+
}
|
|
34
|
+
export declare function createHmsSmokeStore(options?: HmsStoreOptions): HmsSmokeStore;
|
|
35
|
+
export declare function createHmsFireStore(options?: HmsStoreOptions): HmsFireStore;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { getHMSFire, getHMSSmoke } from "@sjvair/sdk";
|
|
2
|
+
import { encodeRef } from "../data/refs.js";
|
|
3
|
+
import { defaultMessages } from "../data/messages.js";
|
|
4
|
+
import { cleanFires, groupFires } from "../presets/fire-groups.js";
|
|
5
|
+
import { createLoadOnce } from "./load-once.svelte.js";
|
|
6
|
+
const SMOKE_SCALE = { dataset: "hms-smoke", entryType: "smoke-density" };
|
|
7
|
+
const FIRE_SCALE = { dataset: "hms-fire", entryType: "fire-frp" };
|
|
8
|
+
/** A source over a loader's value; `features` stays null until it loads. */
|
|
9
|
+
function sourceOf(loader, scale, pick) {
|
|
10
|
+
// Built once per loaded value, so every reader shares one object.
|
|
11
|
+
let cached = null;
|
|
12
|
+
return {
|
|
13
|
+
get status() {
|
|
14
|
+
return loader.status;
|
|
15
|
+
},
|
|
16
|
+
get features() {
|
|
17
|
+
const value = loader.value;
|
|
18
|
+
if (value === null)
|
|
19
|
+
return null;
|
|
20
|
+
if (cached?.value !== value)
|
|
21
|
+
cached = { value, features: pick(value) };
|
|
22
|
+
return cached.features;
|
|
23
|
+
},
|
|
24
|
+
scale
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
export function createHmsSmokeStore(options = {}) {
|
|
28
|
+
const messages = options.messages ?? defaultMessages;
|
|
29
|
+
const loader = createLoadOnce(async (signal) => {
|
|
30
|
+
const smoke = await getHMSSmoke(undefined, { signal });
|
|
31
|
+
const name = messages("mapsdk_hms_smoke_name");
|
|
32
|
+
const features = smoke.map((plume) => ({
|
|
33
|
+
type: "Feature",
|
|
34
|
+
geometry: plume.geometry,
|
|
35
|
+
properties: {
|
|
36
|
+
refKey: encodeRef({ kind: "feature", id: plume.id }),
|
|
37
|
+
name,
|
|
38
|
+
value: plume.density
|
|
39
|
+
}
|
|
40
|
+
}));
|
|
41
|
+
return { type: "FeatureCollection", features };
|
|
42
|
+
});
|
|
43
|
+
return {
|
|
44
|
+
get status() {
|
|
45
|
+
return loader.status;
|
|
46
|
+
},
|
|
47
|
+
dataSource: sourceOf(loader, SMOKE_SCALE, (collection) => collection),
|
|
48
|
+
init: (initOptions) => loader.init(initOptions),
|
|
49
|
+
dispose: () => loader.dispose()
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
export function createHmsFireStore(options = {}) {
|
|
53
|
+
const messages = options.messages ?? defaultMessages;
|
|
54
|
+
const loader = createLoadOnce(async (signal) => {
|
|
55
|
+
const fires = await getHMSFire(undefined, { signal });
|
|
56
|
+
const detections = fires.map((fire) => ({
|
|
57
|
+
id: fire.id,
|
|
58
|
+
coordinates: fire.geometry.coordinates,
|
|
59
|
+
frp: fire.frp,
|
|
60
|
+
timestamp: fire.timestamp
|
|
61
|
+
}));
|
|
62
|
+
const detection = messages("mapsdk_hms_fire_detection");
|
|
63
|
+
const hotspots = cleanFires(detections).map((fire) => ({
|
|
64
|
+
type: "Feature",
|
|
65
|
+
geometry: { type: "Point", coordinates: fire.coordinates },
|
|
66
|
+
properties: {
|
|
67
|
+
refKey: encodeRef({ kind: "feature", id: fire.id }),
|
|
68
|
+
name: detection,
|
|
69
|
+
value: fire.frp
|
|
70
|
+
}
|
|
71
|
+
}));
|
|
72
|
+
const groups = groupFires(detections).map((group) => ({
|
|
73
|
+
type: "Feature",
|
|
74
|
+
geometry: { type: "Point", coordinates: group.coordinates },
|
|
75
|
+
properties: {
|
|
76
|
+
refKey: encodeRef({ kind: "feature", id: group.id }),
|
|
77
|
+
name: group.count === 1
|
|
78
|
+
? detection
|
|
79
|
+
: messages("mapsdk_hms_fire_detections", { count: group.count }),
|
|
80
|
+
value: group.avgFrp
|
|
81
|
+
}
|
|
82
|
+
}));
|
|
83
|
+
return {
|
|
84
|
+
hotspots: { type: "FeatureCollection", features: hotspots },
|
|
85
|
+
groups: { type: "FeatureCollection", features: groups }
|
|
86
|
+
};
|
|
87
|
+
});
|
|
88
|
+
return {
|
|
89
|
+
get status() {
|
|
90
|
+
return loader.status;
|
|
91
|
+
},
|
|
92
|
+
groups: sourceOf(loader, FIRE_SCALE, (value) => value.groups),
|
|
93
|
+
hotspots: sourceOf(loader, FIRE_SCALE, (value) => value.hotspots),
|
|
94
|
+
init: (initOptions) => loader.init(initOptions),
|
|
95
|
+
dispose: () => loader.dispose()
|
|
96
|
+
};
|
|
97
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMonitorsStore, type MonitorReading, type MonitorsInitOptions, type MonitorsListItem, type MonitorsStore, type MonitorsStoreOptions } from "./monitors-store.svelte.js";
|
|
2
|
+
export { cleanFires, type FireDetection } from "../presets/fire-groups.js";
|
|
3
|
+
export { createHmsFireStore, createHmsSmokeStore, type HmsFireStore, type HmsSmokeStore, type HmsStoreOptions } from "./hms-stores.svelte.js";
|
|
4
|
+
export { createCollocationSitesStore, type CollocationSitesStore, type CollocationSitesStoreOptions } from "./collocation-store.svelte.js";
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { createMonitorsStore } from "./monitors-store.svelte.js";
|
|
2
|
+
export { cleanFires } from "../presets/fire-groups.js";
|
|
3
|
+
export { createHmsFireStore, createHmsSmokeStore } from "./hms-stores.svelte.js";
|
|
4
|
+
export { createCollocationSitesStore } from "./collocation-store.svelte.js";
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { DataStatus } from "../data/types.js";
|
|
2
|
+
import { type SignalOptions } from "./abort.js";
|
|
3
|
+
export interface LoadOnceInitOptions extends SignalOptions {
|
|
4
|
+
/**
|
|
5
|
+
* Stops waiting: the returned promise rejects with the signal's reason. The shared run keeps
|
|
6
|
+
* going for the other calls waiting on it, and is cancelled only when every call waiting on it
|
|
7
|
+
* passed a signal and all of those aborted. Ignored once loaded.
|
|
8
|
+
*/
|
|
9
|
+
signal?: AbortSignal;
|
|
10
|
+
}
|
|
11
|
+
export interface LoadOnce<T> {
|
|
12
|
+
/** `loading` until the first load succeeds; `error` (with `retry`) after a failed run. */
|
|
13
|
+
readonly status: DataStatus;
|
|
14
|
+
/** The loaded value, or null before the first successful load. */
|
|
15
|
+
readonly value: T | null;
|
|
16
|
+
/**
|
|
17
|
+
* Loads once. Overlapping calls share one run, a failed run can be retried (call again, or
|
|
18
|
+
* `status.retry()`), and a no-op once loaded or disposed.
|
|
19
|
+
*/
|
|
20
|
+
init(options?: LoadOnceInitOptions): Promise<void>;
|
|
21
|
+
/** Cancels the in-flight run. A disposed loader does nothing. */
|
|
22
|
+
dispose(): void;
|
|
23
|
+
}
|
|
24
|
+
/** A loader that fetches once with `load`. Nothing is fetched until `init()`. */
|
|
25
|
+
export declare function createLoadOnce<T>(load: (signal: AbortSignal) => Promise<T>): LoadOnce<T>;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { untilAborted } from "./abort.js";
|
|
2
|
+
class LoadOnceCore {
|
|
3
|
+
status = $state.raw({ state: "loading" });
|
|
4
|
+
value = $state.raw(null);
|
|
5
|
+
#load;
|
|
6
|
+
#run = null;
|
|
7
|
+
#loaded = false;
|
|
8
|
+
#disposed = false;
|
|
9
|
+
constructor(load) {
|
|
10
|
+
this.#load = load;
|
|
11
|
+
}
|
|
12
|
+
init(options = {}) {
|
|
13
|
+
if (this.#loaded || this.#disposed)
|
|
14
|
+
return Promise.resolve();
|
|
15
|
+
const { signal } = options;
|
|
16
|
+
if (signal?.aborted)
|
|
17
|
+
return Promise.reject(signal.reason);
|
|
18
|
+
const run = (this.#run ??= this.#start());
|
|
19
|
+
if (!signal) {
|
|
20
|
+
run.unsignalled = true;
|
|
21
|
+
return run.promise;
|
|
22
|
+
}
|
|
23
|
+
run.signalled++;
|
|
24
|
+
const onAbort = () => {
|
|
25
|
+
run.aborted++;
|
|
26
|
+
if (!run.unsignalled && run.aborted === run.signalled)
|
|
27
|
+
this.#abort(run);
|
|
28
|
+
};
|
|
29
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
30
|
+
const cleanup = () => signal.removeEventListener("abort", onAbort);
|
|
31
|
+
run.promise.then(cleanup, cleanup);
|
|
32
|
+
return untilAborted(run.promise, signal);
|
|
33
|
+
}
|
|
34
|
+
#start() {
|
|
35
|
+
const controller = new AbortController();
|
|
36
|
+
if (this.status.state !== "loading")
|
|
37
|
+
this.status = { state: "loading" };
|
|
38
|
+
const run = {
|
|
39
|
+
// Cancelling rejects at once, even while a request that ignores the signal is pending.
|
|
40
|
+
promise: untilAborted(this.#fetch(controller.signal), controller.signal).then(() => {
|
|
41
|
+
if (this.#run === run)
|
|
42
|
+
this.#run = null;
|
|
43
|
+
}, (error) => {
|
|
44
|
+
if (this.#run === run)
|
|
45
|
+
this.#run = null;
|
|
46
|
+
if (!controller.signal.aborted) {
|
|
47
|
+
this.status = {
|
|
48
|
+
state: "error",
|
|
49
|
+
error,
|
|
50
|
+
retry: () => {
|
|
51
|
+
this.init().catch(() => { });
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
throw error;
|
|
56
|
+
}),
|
|
57
|
+
controller,
|
|
58
|
+
signalled: 0,
|
|
59
|
+
aborted: 0,
|
|
60
|
+
unsignalled: false
|
|
61
|
+
};
|
|
62
|
+
return run;
|
|
63
|
+
}
|
|
64
|
+
async #fetch(signal) {
|
|
65
|
+
const value = await this.#load(signal);
|
|
66
|
+
// Cancelled while a request that ignores the signal was settling: leave state to the next
|
|
67
|
+
// run.
|
|
68
|
+
if (signal.aborted)
|
|
69
|
+
throw signal.reason;
|
|
70
|
+
this.value = value;
|
|
71
|
+
this.status = { state: "ready" };
|
|
72
|
+
this.#loaded = true;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* Cancels a run nobody is waiting on. It is detached at once, so a call arriving before its
|
|
76
|
+
* rejection settles starts a fresh run instead of joining the cancelled one.
|
|
77
|
+
*/
|
|
78
|
+
#abort(run) {
|
|
79
|
+
if (this.#run !== run)
|
|
80
|
+
return;
|
|
81
|
+
this.#run = null;
|
|
82
|
+
run.controller.abort();
|
|
83
|
+
}
|
|
84
|
+
dispose() {
|
|
85
|
+
this.#disposed = true;
|
|
86
|
+
if (this.#run)
|
|
87
|
+
this.#abort(this.#run);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
/** A loader that fetches once with `load`. Nothing is fetched until `init()`. */
|
|
91
|
+
export function createLoadOnce(load) {
|
|
92
|
+
return new LoadOnceCore(load);
|
|
93
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { type MonitorData, type MonitorsMeta } from "@sjvair/sdk";
|
|
2
|
+
import type { DataSource, MonitorFeatureProperties } from "../data/types.js";
|
|
3
|
+
import { type SignalOptions } from "./abort.js";
|
|
4
|
+
/** A monitor from the monitors list. */
|
|
5
|
+
export type MonitorsListItem = MonitorData;
|
|
6
|
+
/** A monitor's latest reading for the store's entry type. */
|
|
7
|
+
export interface MonitorReading {
|
|
8
|
+
/** Null when the server sent no numeric value. */
|
|
9
|
+
value: number | null;
|
|
10
|
+
/** When the reading was taken (ISO 8601). */
|
|
11
|
+
timestamp: string;
|
|
12
|
+
}
|
|
13
|
+
export interface MonitorsStoreOptions {
|
|
14
|
+
/** How often polling refreshes the data, in milliseconds. Default 120 000 (2 minutes). */
|
|
15
|
+
pollIntervalMs?: number;
|
|
16
|
+
}
|
|
17
|
+
export interface MonitorsInitOptions extends SignalOptions {
|
|
18
|
+
/**
|
|
19
|
+
* Start polling once the initial load finishes. Defaults to true; pass false to load the data
|
|
20
|
+
* without showing a map (`resumeAutoUpdate()` starts polling later).
|
|
21
|
+
*/
|
|
22
|
+
autoUpdate?: boolean;
|
|
23
|
+
/**
|
|
24
|
+
* Stops waiting: the returned promise rejects with the signal's reason. The shared run keeps
|
|
25
|
+
* going for the other calls waiting on it, and is cancelled only when every call waiting on it
|
|
26
|
+
* passed a signal and all of those aborted. An aborted call's entry type and `autoUpdate` stay
|
|
27
|
+
* recorded. Ignored once initialized.
|
|
28
|
+
*/
|
|
29
|
+
signal?: AbortSignal;
|
|
30
|
+
}
|
|
31
|
+
export interface MonitorsStore {
|
|
32
|
+
/** True once the first load finished. */
|
|
33
|
+
readonly initialized: boolean;
|
|
34
|
+
/** The entry type the readings are for, such as "pm25". Null before init. */
|
|
35
|
+
readonly entryType: string | null;
|
|
36
|
+
/** Every monitor, by id, from the monitors list. */
|
|
37
|
+
readonly monitors: ReadonlyMap<string, MonitorsListItem>;
|
|
38
|
+
/** Each monitor's latest reading for `entryType`, by monitor id. */
|
|
39
|
+
readonly latest: ReadonlyMap<string, MonitorReading>;
|
|
40
|
+
/** The entry types the server publishes on the map (monitors meta `available_pollutants`). */
|
|
41
|
+
readonly availableEntryTypes: readonly string[];
|
|
42
|
+
/** The monitors meta from init (the SDK's `MonitorsMeta`), or null before init. Read-only. */
|
|
43
|
+
readonly meta: MonitorsMeta | null;
|
|
44
|
+
/**
|
|
45
|
+
* Sets the entry type. Once initialized, an entry type the server doesn't list falls back to
|
|
46
|
+
* its default, and the readings blank at once (so markers never show the previous entry
|
|
47
|
+
* type's readings) and are fetched again. Before init it is replaced by the entry type init
|
|
48
|
+
* settles on: pass the entry type to `init` instead.
|
|
49
|
+
*/
|
|
50
|
+
entryTypeFor(next: string): void;
|
|
51
|
+
/**
|
|
52
|
+
* Loads the monitors, their latest readings, and the meta. Safe to call from several places:
|
|
53
|
+
* overlapping calls share one run, and a failed run can be retried. An explicit entry type
|
|
54
|
+
* wins over a call without one, whichever came first; between two explicit values the most
|
|
55
|
+
* recent wins. An entry type the server doesn't list falls back to its default. Polling
|
|
56
|
+
* starts when the run finishes unless every call sharing it passed `autoUpdate: false`. A
|
|
57
|
+
* no-op once initialized, so it never restarts polling either: that is `resumeAutoUpdate()`.
|
|
58
|
+
*/
|
|
59
|
+
init(entryType?: string, options?: MonitorsInitOptions): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Refreshes the list and the latest readings. Starting an update aborts the previous
|
|
62
|
+
* in-flight one, which then resolves without touching state. Pass `signal` to cancel from
|
|
63
|
+
* the outside: that rejects with the signal's reason. A no-op before init.
|
|
64
|
+
*/
|
|
65
|
+
update(options?: SignalOptions): Promise<void>;
|
|
66
|
+
/** Stops polling (for example when the last map showing the data goes away). */
|
|
67
|
+
pauseAutoUpdate(): void;
|
|
68
|
+
/**
|
|
69
|
+
* Starts polling when it is not running: after `pauseAutoUpdate()` or after an init that
|
|
70
|
+
* opted out of it. Refreshes first since the data may be stale. A no-op before init or while
|
|
71
|
+
* polling.
|
|
72
|
+
*/
|
|
73
|
+
resumeAutoUpdate(): void;
|
|
74
|
+
/** The monitors and their readings for `monitorsPlugin`, with the entry type's scale. */
|
|
75
|
+
readonly dataSource: DataSource<MonitorFeatureProperties>;
|
|
76
|
+
/** Stops polling and cancels in-flight requests. A disposed store does nothing. */
|
|
77
|
+
dispose(): void;
|
|
78
|
+
}
|
|
79
|
+
/**
|
|
80
|
+
* The live monitors data for an app: the monitor list, the latest readings for one entry type,
|
|
81
|
+
* and polling every 2 minutes. One store can feed any number of maps and screens, or none: its
|
|
82
|
+
* `dataSource` goes straight to `monitorsPlugin`. Call `init()` to load, and `dispose()` when the
|
|
83
|
+
* app is done with it.
|
|
84
|
+
*/
|
|
85
|
+
export declare function createMonitorsStore(options?: MonitorsStoreOptions): MonitorsStore;
|