@sjvair/map-sdk 0.0.0-stage → 0.1.0-beta.10
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 +713 -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 +274 -0
- package/dist/core/create-map.svelte.js +591 -0
- package/dist/core/gl.d.ts +97 -0
- package/dist/core/gl.js +1 -0
- package/dist/core/interactions.svelte.d.ts +50 -0
- package/dist/core/interactions.svelte.js +330 -0
- package/dist/core/plugin-host.svelte.d.ts +71 -0
- package/dist/core/plugin-host.svelte.js +445 -0
- package/dist/core/plugin.d.ts +190 -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/core/shape-view.d.ts +27 -0
- package/dist/core/shape-view.js +323 -0
- package/dist/data/format.d.ts +10 -0
- package/dist/data/format.js +37 -0
- package/dist/data/index.d.ts +9 -0
- package/dist/data/index.js +9 -0
- package/dist/data/messages/en.json +61 -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 +13 -0
- package/dist/data/selection.js +52 -0
- package/dist/data/static-source.d.ts +4 -0
- package/dist/data/static-source.js +3 -0
- package/dist/data/types.d.ts +74 -0
- package/dist/data/types.js +1 -0
- package/dist/index.d.ts +59 -0
- package/dist/index.js +42 -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 +105 -0
- package/dist/plugins/points.svelte.js +502 -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 +34 -0
- package/dist/plugins/shapes.svelte.js +325 -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/FireTooltip.svelte +70 -0
- package/dist/presets/FireTooltip.svelte.d.ts +13 -0
- package/dist/presets/MonitorClusterTooltip.svelte +168 -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/cluster-area.d.ts +13 -0
- package/dist/presets/cluster-area.js +34 -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 +30 -0
- package/dist/presets/hms-fire.js +86 -0
- package/dist/presets/hms-smoke.d.ts +20 -0
- package/dist/presets/hms-smoke.js +22 -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 +38 -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 +501 -0
- package/dist/ui/FeatureList.svelte.d.ts +14 -0
- package/dist/ui/Legend.svelte +214 -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 +397 -0
- package/dist/ui/MapTooltip.svelte.d.ts +37 -0
- package/dist/ui/PickNotice.svelte +56 -0
- package/dist/ui/PickNotice.svelte.d.ts +7 -0
- package/dist/ui/SelectionBar.svelte +123 -0
- package/dist/ui/SelectionBar.svelte.d.ts +10 -0
- package/dist/ui/TooltipItem.svelte +66 -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/card.d.ts +29 -0
- package/dist/ui/card.js +1 -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 +46 -0
- package/dist/ui/feature-list.js +115 -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 +8 -0
- package/dist/ui/value-format.js +20 -0
- package/dist/ui/view-summary.d.ts +11 -0
- package/dist/ui/view-summary.js +33 -0
- package/dist/view/MapView.svelte +443 -0
- package/dist/view/MapView.svelte.d.ts +51 -0
- package/dist/view/create-gl-map.d.ts +34 -0
- package/dist/view/create-gl-map.js +186 -0
- package/dist/view/list-placement.d.ts +21 -0
- package/dist/view/list-placement.js +13 -0
- package/package.json +104 -5
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export function encodeRef(ref) {
|
|
2
|
+
switch (ref.kind) {
|
|
3
|
+
case "monitor":
|
|
4
|
+
return `monitor:${ref.id}`;
|
|
5
|
+
case "region":
|
|
6
|
+
return `region:${ref.type}:${ref.code}`;
|
|
7
|
+
case "feature":
|
|
8
|
+
return `feature:${ref.id}`;
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
export function decodeRef(key) {
|
|
12
|
+
const first = key.indexOf(":");
|
|
13
|
+
if (first < 0)
|
|
14
|
+
return null;
|
|
15
|
+
const kind = key.slice(0, first);
|
|
16
|
+
const rest = key.slice(first + 1);
|
|
17
|
+
if (kind === "monitor" || kind === "feature") {
|
|
18
|
+
return rest ? { kind, id: rest } : null;
|
|
19
|
+
}
|
|
20
|
+
if (kind === "region") {
|
|
21
|
+
const second = rest.indexOf(":");
|
|
22
|
+
if (second <= 0)
|
|
23
|
+
return null;
|
|
24
|
+
const code = rest.slice(second + 1);
|
|
25
|
+
return code ? { kind, type: rest.slice(0, second), code } : null;
|
|
26
|
+
}
|
|
27
|
+
return null;
|
|
28
|
+
}
|
|
29
|
+
export function selectionId(ref) {
|
|
30
|
+
switch (ref.kind) {
|
|
31
|
+
case "monitor":
|
|
32
|
+
return ref.id;
|
|
33
|
+
case "region":
|
|
34
|
+
return `${ref.type}:${ref.code}`;
|
|
35
|
+
case "feature":
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
export function toPlaceRef(ref) {
|
|
40
|
+
switch (ref.kind) {
|
|
41
|
+
case "monitor":
|
|
42
|
+
return { kind: "monitor", id: ref.id };
|
|
43
|
+
case "region":
|
|
44
|
+
return { kind: "region", type: ref.type, code: ref.code };
|
|
45
|
+
case "feature":
|
|
46
|
+
return null;
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
export function fromPlaceRef(place) {
|
|
50
|
+
if (place.kind === "monitor" && typeof place.id === "string" && place.id) {
|
|
51
|
+
return { kind: "monitor", id: place.id };
|
|
52
|
+
}
|
|
53
|
+
if (place.kind === "region" &&
|
|
54
|
+
typeof place.type === "string" &&
|
|
55
|
+
typeof place.code === "string" &&
|
|
56
|
+
place.type &&
|
|
57
|
+
place.code) {
|
|
58
|
+
return { kind: "region", type: place.type, code: place.code };
|
|
59
|
+
}
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { Level, Scale } from "./scale-types.js";
|
|
2
|
+
/** Returned by `levelFor` when no level covers the value. */
|
|
3
|
+
export declare const NO_LEVEL: unique symbol;
|
|
4
|
+
/**
|
|
5
|
+
* A reading as a number: a finite number, or a non-blank string that reads as one; else null.
|
|
6
|
+
* The one rule for what counts as a reading (levels, averages, tooltips).
|
|
7
|
+
*/
|
|
8
|
+
export declare function numericValue(value: number | string | null | undefined): number | null;
|
|
9
|
+
/**
|
|
10
|
+
* Find the level a value falls in. Range levels are half-open, [min, max), and a null max
|
|
11
|
+
* is unbounded. Numeric strings are coerced for range scales only.
|
|
12
|
+
*/
|
|
13
|
+
export declare function levelFor(value: number | string | null | undefined, scale: Scale): Level | typeof NO_LEVEL;
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** Returned by `levelFor` when no level covers the value. */
|
|
2
|
+
export const NO_LEVEL = Symbol("no-level");
|
|
3
|
+
/**
|
|
4
|
+
* A reading as a number: a finite number, or a non-blank string that reads as one; else null.
|
|
5
|
+
* The one rule for what counts as a reading (levels, averages, tooltips).
|
|
6
|
+
*/
|
|
7
|
+
export function numericValue(value) {
|
|
8
|
+
if (typeof value !== "number" && (typeof value !== "string" || value.trim() === ""))
|
|
9
|
+
return null;
|
|
10
|
+
const n = typeof value === "number" ? value : Number(value);
|
|
11
|
+
return Number.isFinite(n) ? n : null;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Find the level a value falls in. Range levels are half-open, [min, max), and a null max
|
|
15
|
+
* is unbounded. Numeric strings are coerced for range scales only.
|
|
16
|
+
*/
|
|
17
|
+
export function levelFor(value, scale) {
|
|
18
|
+
if (value === null || value === undefined)
|
|
19
|
+
return NO_LEVEL;
|
|
20
|
+
if (scale.kind === "category") {
|
|
21
|
+
return scale.levels.find((level) => level.value === value) ?? NO_LEVEL;
|
|
22
|
+
}
|
|
23
|
+
const n = numericValue(value);
|
|
24
|
+
if (n === null)
|
|
25
|
+
return NO_LEVEL;
|
|
26
|
+
return (scale.levels.find((level) => level.min !== null && n >= level.min && (level.max == null || n < level.max)) ?? NO_LEVEL);
|
|
27
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import type { FeatureRef, SelectionMode, SpatialSelection } from "./types.js";
|
|
2
|
+
export interface PickGesture {
|
|
3
|
+
additive: boolean;
|
|
4
|
+
}
|
|
5
|
+
export declare function selectionKind(ref: FeatureRef): "monitors" | "regions" | null;
|
|
6
|
+
export declare function applyPick(current: SpatialSelection | null, ref: FeatureRef, mode: SelectionMode, gesture: PickGesture): SpatialSelection | null;
|
|
7
|
+
/**
|
|
8
|
+
* A modifier-click (or a card's Select/Remove): toggles `ref`. In `single` mode it is the
|
|
9
|
+
* selection, or `null` when it already is; in `multiple` mode it is added or removed (another
|
|
10
|
+
* kind starts a new selection, and removing the last id gives `null`).
|
|
11
|
+
*/
|
|
12
|
+
export declare function toggleInSelection(current: SpatialSelection | null, ref: FeatureRef, mode: SelectionMode): SpatialSelection | null;
|
|
13
|
+
export declare function selectionKeys(sel: SpatialSelection | null): Set<string>;
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { selectionId } from "./refs.js";
|
|
2
|
+
export function selectionKind(ref) {
|
|
3
|
+
switch (ref.kind) {
|
|
4
|
+
case "monitor":
|
|
5
|
+
return "monitors";
|
|
6
|
+
case "region":
|
|
7
|
+
return "regions";
|
|
8
|
+
case "feature":
|
|
9
|
+
return null;
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
export function applyPick(current, ref, mode, gesture) {
|
|
13
|
+
const kind = selectionKind(ref);
|
|
14
|
+
const id = selectionId(ref);
|
|
15
|
+
if (mode === "none" || kind === null || id === null)
|
|
16
|
+
return current;
|
|
17
|
+
if (mode === "single" || !gesture.additive || !current || current.kind !== kind) {
|
|
18
|
+
return { kind, ids: [id] };
|
|
19
|
+
}
|
|
20
|
+
if (current.ids.includes(id)) {
|
|
21
|
+
const ids = current.ids.filter((x) => x !== id);
|
|
22
|
+
return ids.length ? { kind, ids } : null;
|
|
23
|
+
}
|
|
24
|
+
return { kind, ids: [...current.ids, id] };
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* A modifier-click (or a card's Select/Remove): toggles `ref`. In `single` mode it is the
|
|
28
|
+
* selection, or `null` when it already is; in `multiple` mode it is added or removed (another
|
|
29
|
+
* kind starts a new selection, and removing the last id gives `null`).
|
|
30
|
+
*/
|
|
31
|
+
export function toggleInSelection(current, ref, mode) {
|
|
32
|
+
const kind = selectionKind(ref);
|
|
33
|
+
const id = selectionId(ref);
|
|
34
|
+
if (mode === "none" || kind === null || id === null)
|
|
35
|
+
return current;
|
|
36
|
+
const has = current?.kind === kind && current.ids.includes(id);
|
|
37
|
+
if (mode === "single")
|
|
38
|
+
return has ? null : { kind, ids: [id] };
|
|
39
|
+
if (!current || current.kind !== kind)
|
|
40
|
+
return { kind, ids: [id] };
|
|
41
|
+
if (has) {
|
|
42
|
+
const ids = current.ids.filter((x) => x !== id);
|
|
43
|
+
return ids.length ? { kind, ids } : null;
|
|
44
|
+
}
|
|
45
|
+
return { kind, ids: [...current.ids, id] };
|
|
46
|
+
}
|
|
47
|
+
export function selectionKeys(sel) {
|
|
48
|
+
if (!sel)
|
|
49
|
+
return new Set();
|
|
50
|
+
const prefix = sel.kind === "monitors" ? "monitor:" : "region:";
|
|
51
|
+
return new Set(sel.ids.map((id) => prefix + id));
|
|
52
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import type { FeatureCollection, Geometry } from "geojson";
|
|
2
|
+
import type { Scale, ScaleRef } from "./scale-types.js";
|
|
3
|
+
import type { DataSource, MapFeatureProperties } from "./types.js";
|
|
4
|
+
export declare function staticSource<P extends MapFeatureProperties>(features: FeatureCollection<Geometry, P>, scale: ScaleRef | Scale | null): DataSource<P>;
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
import type { FeatureCollection, Geometry } from "geojson";
|
|
2
|
+
import type { Scale, ScaleRef } from "./scale-types.js";
|
|
3
|
+
export type FeatureRef = {
|
|
4
|
+
kind: "monitor";
|
|
5
|
+
id: string;
|
|
6
|
+
} | {
|
|
7
|
+
kind: "region";
|
|
8
|
+
type: string;
|
|
9
|
+
code: string;
|
|
10
|
+
} | {
|
|
11
|
+
kind: "feature";
|
|
12
|
+
id: string;
|
|
13
|
+
};
|
|
14
|
+
export type SpatialSelection = {
|
|
15
|
+
kind: "monitors" | "regions";
|
|
16
|
+
ids: string[];
|
|
17
|
+
};
|
|
18
|
+
export type SelectionMode = "none" | "single" | "multiple";
|
|
19
|
+
export interface MapFeatureProperties {
|
|
20
|
+
refKey: string;
|
|
21
|
+
name: string;
|
|
22
|
+
value: number | string | null;
|
|
23
|
+
}
|
|
24
|
+
/** An HMS fire on the map: a group of nearby detections, or a single detection. */
|
|
25
|
+
export interface FireFeatureProperties extends MapFeatureProperties {
|
|
26
|
+
/** Set on a group: how many detections it averages. Absent on a single detection. */
|
|
27
|
+
count?: number;
|
|
28
|
+
}
|
|
29
|
+
/** A monitor on the map: what the monitors preset draws and its tooltips show. */
|
|
30
|
+
export interface MonitorFeatureProperties extends MapFeatureProperties {
|
|
31
|
+
/** The server's monitor type (e.g. "purpleair", "airnow"); picks the marker shape. */
|
|
32
|
+
type: string;
|
|
33
|
+
isActive: boolean;
|
|
34
|
+
isSjvair: boolean;
|
|
35
|
+
location: "inside" | "outside";
|
|
36
|
+
/** When the reading was taken (ISO 8601), for the tooltip. */
|
|
37
|
+
timestamp?: string | null;
|
|
38
|
+
}
|
|
39
|
+
export type DataStatus = {
|
|
40
|
+
state: "loading";
|
|
41
|
+
} | {
|
|
42
|
+
state: "ready";
|
|
43
|
+
} | {
|
|
44
|
+
state: "error";
|
|
45
|
+
error: unknown;
|
|
46
|
+
retry(): void;
|
|
47
|
+
} | {
|
|
48
|
+
state: "unavailable";
|
|
49
|
+
reason: string;
|
|
50
|
+
};
|
|
51
|
+
export interface DataSource<P extends MapFeatureProperties = MapFeatureProperties> {
|
|
52
|
+
readonly status: DataStatus;
|
|
53
|
+
readonly features: FeatureCollection<Geometry, P> | null;
|
|
54
|
+
readonly scale: ScaleRef | Scale | null;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* A collocation site: drawn at its reference monitor with that monitor's type, location,
|
|
58
|
+
* activity, and reading, plus the collocated monitor's reading. Readings are null when a
|
|
59
|
+
* monitor isn't in the monitors store (inactive, filtered out, or not loaded yet).
|
|
60
|
+
*/
|
|
61
|
+
export interface CollocationFeatureProperties extends MonitorFeatureProperties {
|
|
62
|
+
/**
|
|
63
|
+
* The reference monitor's own name (the tooltip shows it); null when it isn't in the
|
|
64
|
+
* monitors store.
|
|
65
|
+
*/
|
|
66
|
+
referenceName: string | null;
|
|
67
|
+
colocatedId: string;
|
|
68
|
+
/** Null when the collocated monitor isn't in the monitors store. */
|
|
69
|
+
colocatedName: string | null;
|
|
70
|
+
/** The collocated monitor's type, for its tooltip's "(N avg)" note; null when unknown. */
|
|
71
|
+
colocatedType: string | null;
|
|
72
|
+
colocatedValue: number | null;
|
|
73
|
+
colocatedTimestamp: string | null;
|
|
74
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { type CreateMapOptions, type MapContext } from "./core/create-map.svelte.js";
|
|
2
|
+
export { defaultBasemaps } from "./view/create-gl-map.js";
|
|
3
|
+
export { default as MapView } from "./view/MapView.svelte";
|
|
4
|
+
export type { FeatureListOptions, ListPosition } from "./view/list-placement.js";
|
|
5
|
+
export { default as Legend } from "./ui/Legend.svelte";
|
|
6
|
+
export { default as FeatureList } from "./ui/FeatureList.svelte";
|
|
7
|
+
export { default as MapTooltip } from "./ui/MapTooltip.svelte";
|
|
8
|
+
export { default as SelectionBar } from "./ui/SelectionBar.svelte";
|
|
9
|
+
export { default as ViewSummary } from "./ui/ViewSummary.svelte";
|
|
10
|
+
export { default as MapControls } from "./ui/MapControls.svelte";
|
|
11
|
+
export { legendSection } from "./ui/legend-data.js";
|
|
12
|
+
export type { LegendBar as LegendBarData, LegendSection } from "./ui/legend-data.js";
|
|
13
|
+
export { rowLabel } from "./ui/feature-list.js";
|
|
14
|
+
export type { SortBy } from "./ui/feature-list.js";
|
|
15
|
+
export { geometryBbox, intersectsBounds } from "./core/shape-view.js";
|
|
16
|
+
export type { Bbox } from "./core/shape-view.js";
|
|
17
|
+
export { pointsPlugin } from "./plugins/points.svelte.js";
|
|
18
|
+
export type { ClusterInfo, ClusterLayerArgs, PointsOptions, PointsTooltipEnv } from "./plugins/points.svelte.js";
|
|
19
|
+
export type { ClusterLayers } from "./plugins/points-clusters.js";
|
|
20
|
+
export { colorExpression, levelKeyExpression, opacityExpression } from "./plugins/style.js";
|
|
21
|
+
export type { ThemeColors, ValueInput } from "./plugins/style.js";
|
|
22
|
+
export { shapesPlugin } from "./plugins/shapes.svelte.js";
|
|
23
|
+
export type { ShapesOptions } from "./plugins/shapes.svelte.js";
|
|
24
|
+
export { monitorShape, monitorsPlugin } from "./presets/monitors.js";
|
|
25
|
+
export type { MonitorShape, MonitorsPluginOptions } from "./presets/monitors.js";
|
|
26
|
+
export { regionsPlugin } from "./presets/regions.js";
|
|
27
|
+
export type { RegionsPluginOptions } from "./presets/regions.js";
|
|
28
|
+
export { calHeatScorePlugin } from "./presets/calheatscore.js";
|
|
29
|
+
export type { CalHeatScorePluginOptions } from "./presets/calheatscore.js";
|
|
30
|
+
export { forecastZonesPlugin } from "./presets/forecast-zones.js";
|
|
31
|
+
export type { ForecastZonesPluginOptions } from "./presets/forecast-zones.js";
|
|
32
|
+
export { summaryText } from "./ui/view-summary.js";
|
|
33
|
+
export { getMapContext } from "./core/context.js";
|
|
34
|
+
export type { BlockingClick, FeatureHit, FeatureListItem, LayerSpecWithoutId, LegendData, MapPlugin, PluginContext, Providers } from "./core/plugin.js";
|
|
35
|
+
export type { GlMapLike } from "./core/gl.js";
|
|
36
|
+
export type { CustomLayerInterface } from "@maptiler/sdk";
|
|
37
|
+
export type { Hovered } from "./core/interactions.svelte.js";
|
|
38
|
+
export type { BasemapConfig } from "./core/basemap.svelte.js";
|
|
39
|
+
export type { Bounds, CameraState, CameraTarget, CreateMapOptions, FeatureClickHandler, MapConfig, MapContext, PluginOptions } from "./core/create-map.svelte.js";
|
|
40
|
+
/**
|
|
41
|
+
* The attached MapTiler `Map`, or `null` while detached: an escape hatch for markers and popups
|
|
42
|
+
* only. The host must never add sources or layers through it; plugins own those.
|
|
43
|
+
*/
|
|
44
|
+
export declare function maptilerMap(map: MapContext): import("@maptiler/sdk").Map | null;
|
|
45
|
+
/**
|
|
46
|
+
* Creates a map's state and services; show it with `<MapView map={…} />`. Without
|
|
47
|
+
* `config.basemaps`, the basemaps are MapTiler Streets v4 and its variants, as on today's map.
|
|
48
|
+
*/
|
|
49
|
+
export declare function createMap(options: CreateMapOptions): MapContext;
|
|
50
|
+
export { hmsSmokePlugin } from "./presets/hms-smoke.js";
|
|
51
|
+
export type { HmsSmokePluginOptions } from "./presets/hms-smoke.js";
|
|
52
|
+
export { hmsFirePlugin } from "./presets/hms-fire.js";
|
|
53
|
+
export type { HmsFirePluginOptions } from "./presets/hms-fire.js";
|
|
54
|
+
export { cleanFires, groupFires } from "./presets/fire-groups.js";
|
|
55
|
+
export type { FireDetection, FireGroup } from "./presets/fire-groups.js";
|
|
56
|
+
export { collocationSitesPlugin, COLLOCATION_CROSSHAIR_COLOR } from "./presets/collocation-sites.js";
|
|
57
|
+
export type { CollocationSitesPluginOptions } from "./presets/collocation-sites.js";
|
|
58
|
+
export { circle, CROSSHAIR_ICON_SIZE, square, triangle, withCrosshair } from "./plugins/icons.js";
|
|
59
|
+
export type { CardContext, CardOptions } from "./ui/card.js";
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { createMap as createMapContext } from "./core/create-map.svelte.js";
|
|
2
|
+
import { defaultBasemaps } from "./view/create-gl-map.js";
|
|
3
|
+
export { defaultBasemaps } from "./view/create-gl-map.js";
|
|
4
|
+
export { default as MapView } from "./view/MapView.svelte";
|
|
5
|
+
export { default as Legend } from "./ui/Legend.svelte";
|
|
6
|
+
export { default as FeatureList } from "./ui/FeatureList.svelte";
|
|
7
|
+
export { default as MapTooltip } from "./ui/MapTooltip.svelte";
|
|
8
|
+
export { default as SelectionBar } from "./ui/SelectionBar.svelte";
|
|
9
|
+
export { default as ViewSummary } from "./ui/ViewSummary.svelte";
|
|
10
|
+
export { default as MapControls } from "./ui/MapControls.svelte";
|
|
11
|
+
export { legendSection } from "./ui/legend-data.js";
|
|
12
|
+
export { rowLabel } from "./ui/feature-list.js";
|
|
13
|
+
export { geometryBbox, intersectsBounds } from "./core/shape-view.js";
|
|
14
|
+
export { pointsPlugin } from "./plugins/points.svelte.js";
|
|
15
|
+
export { colorExpression, levelKeyExpression, opacityExpression } from "./plugins/style.js";
|
|
16
|
+
export { shapesPlugin } from "./plugins/shapes.svelte.js";
|
|
17
|
+
export { monitorShape, monitorsPlugin } from "./presets/monitors.js";
|
|
18
|
+
export { regionsPlugin } from "./presets/regions.js";
|
|
19
|
+
export { calHeatScorePlugin } from "./presets/calheatscore.js";
|
|
20
|
+
export { forecastZonesPlugin } from "./presets/forecast-zones.js";
|
|
21
|
+
export { summaryText } from "./ui/view-summary.js";
|
|
22
|
+
export { getMapContext } from "./core/context.js";
|
|
23
|
+
/**
|
|
24
|
+
* The attached MapTiler `Map`, or `null` while detached: an escape hatch for markers and popups
|
|
25
|
+
* only. The host must never add sources or layers through it; plugins own those.
|
|
26
|
+
*/
|
|
27
|
+
export function maptilerMap(map) {
|
|
28
|
+
return map.gl;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Creates a map's state and services; show it with `<MapView map={…} />`. Without
|
|
32
|
+
* `config.basemaps`, the basemaps are MapTiler Streets v4 and its variants, as on today's map.
|
|
33
|
+
*/
|
|
34
|
+
export function createMap(options) {
|
|
35
|
+
const basemaps = options.config.basemaps ?? defaultBasemaps();
|
|
36
|
+
return createMapContext({ ...options, config: { ...options.config, basemaps } });
|
|
37
|
+
}
|
|
38
|
+
export { hmsSmokePlugin } from "./presets/hms-smoke.js";
|
|
39
|
+
export { hmsFirePlugin } from "./presets/hms-fire.js";
|
|
40
|
+
export { cleanFires, groupFires } from "./presets/fire-groups.js";
|
|
41
|
+
export { collocationSitesPlugin, COLLOCATION_CROSSHAIR_COLOR } from "./presets/collocation-sites.js";
|
|
42
|
+
export { circle, CROSSHAIR_ICON_SIZE, square, triangle, withCrosshair } from "./plugins/icons.js";
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
export declare function square(color: string, borderWidth: number, borderColor?: string): string;
|
|
2
|
+
export declare function circle(color: string, borderWidth: number, borderColor?: string): string;
|
|
3
|
+
export declare function triangle(color: string, borderWidth: number, borderColor?: string): string;
|
|
4
|
+
/** The size of a `withCrosshair` image: a 24px marker plus the crosshair around it (56px). */
|
|
5
|
+
export declare const CROSSHAIR_ICON_SIZE: number;
|
|
6
|
+
/**
|
|
7
|
+
* A 24px marker SVG (`circle`, `square`, `triangle`) centered in a crosshair of `color`, as
|
|
8
|
+
* monitor-map draws collocation sites. The result is `CROSSHAIR_ICON_SIZE` pixels square.
|
|
9
|
+
*/
|
|
10
|
+
export declare function withCrosshair(baseSvg: string, color: string): string;
|
|
11
|
+
export declare function fireIcon(color: string, size: number): string;
|
|
12
|
+
/** Room around the marker for the ring and the check badge. */
|
|
13
|
+
export declare const SELECTED_OVERLAY_PADDING = 6;
|
|
14
|
+
/**
|
|
15
|
+
* The selection mark drawn over a selected marker `size` pixels across: a ring around it in
|
|
16
|
+
* `highlight` plus a check mark badge, so selection never relies on color alone. The image is
|
|
17
|
+
* `size + 2 * SELECTED_OVERLAY_PADDING` pixels square, centered on the marker. Colors are
|
|
18
|
+
* concrete values read from the `--highlight` and `--highlight-foreground` tokens at setup.
|
|
19
|
+
*/
|
|
20
|
+
export declare function selectedOverlay(highlight: string, checkColor: string, size: number): string;
|
|
21
|
+
export declare function asDataURI(svg: string): string;
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import { darken } from "color2k";
|
|
2
|
+
// Marker shapes ported from monitor-map (src/lib/map/icons.ts). Shapes and sizes are
|
|
3
|
+
// presentation; every color comes in from a scale level or a theme token.
|
|
4
|
+
/**
|
|
5
|
+
* A slightly darker stroke for a fill color. color2k throws on an empty or unparseable color
|
|
6
|
+
* (monitor-map crashed on `darken("")`), so those keep the color itself.
|
|
7
|
+
*/
|
|
8
|
+
function strokeFor(color) {
|
|
9
|
+
try {
|
|
10
|
+
return darken(color, 0.1);
|
|
11
|
+
}
|
|
12
|
+
catch {
|
|
13
|
+
return color;
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export function square(color, borderWidth, borderColor) {
|
|
17
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
18
|
+
<rect x="${borderWidth / 2}" y="${borderWidth / 2}" width="${24 - borderWidth}" height="${24 - borderWidth}"
|
|
19
|
+
fill="${color}" stroke="${strokeFor(borderColor ?? color)}" stroke-width="${borderWidth}"/>
|
|
20
|
+
</svg>`;
|
|
21
|
+
}
|
|
22
|
+
export function circle(color, borderWidth, borderColor) {
|
|
23
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
24
|
+
<circle cx="12" cy="12" r="${12 - borderWidth}" fill="${color}" stroke="${strokeFor(borderColor ?? color)}" stroke-width="${borderWidth}"/>
|
|
25
|
+
</svg>`;
|
|
26
|
+
}
|
|
27
|
+
export function triangle(color, borderWidth, borderColor) {
|
|
28
|
+
const points = `${12},${borderWidth} ${24 - borderWidth},${24 - borderWidth} ${borderWidth},${24 - borderWidth}`;
|
|
29
|
+
return `<svg width="24" height="24" viewBox="0 0 24 24" xmlns="http://www.w3.org/2000/svg">
|
|
30
|
+
<polygon points="${points}"
|
|
31
|
+
fill="${color}" stroke="${strokeFor(borderColor ?? color)}" stroke-width="${borderWidth}"/>
|
|
32
|
+
</svg>`;
|
|
33
|
+
}
|
|
34
|
+
// monitor-map's collocation crosshair (src/lib/map/icons.ts): a ring with ticks around a marker.
|
|
35
|
+
const CROSSHAIR_PATH = "M495.9 248H392c-4.4 0-8 3.6-8 8s3.6 8 8 8H495.9C491.7 390.2 390.2 491.7 264 495.9V392c0-4.4-3.6-8-8-8s-8 3.6-8 8V495.9C121.8 491.7 20.3 390.2 16.1 264H120c4.4 0 8-3.6 8-8s-3.6-8-8-8H16.1C20.3 121.8 121.8 20.3 248 16.1V120c0 4.4 3.6 8 8 8s8-3.6 8-8V16.1C390.2 20.3 491.7 121.8 495.9 248zM0 256a256 256 0 1 0 512 0A256 256 0 1 0 0 256z";
|
|
36
|
+
const CROSSHAIR_PADDING = 16;
|
|
37
|
+
/** The size of a `withCrosshair` image: a 24px marker plus the crosshair around it (56px). */
|
|
38
|
+
export const CROSSHAIR_ICON_SIZE = 24 + CROSSHAIR_PADDING * 2;
|
|
39
|
+
/**
|
|
40
|
+
* A 24px marker SVG (`circle`, `square`, `triangle`) centered in a crosshair of `color`, as
|
|
41
|
+
* monitor-map draws collocation sites. The result is `CROSSHAIR_ICON_SIZE` pixels square.
|
|
42
|
+
*/
|
|
43
|
+
export function withCrosshair(baseSvg, color) {
|
|
44
|
+
const size = CROSSHAIR_ICON_SIZE;
|
|
45
|
+
const inner = baseSvg.replace("<svg ", `<svg x="${CROSSHAIR_PADDING}" y="${CROSSHAIR_PADDING}" `);
|
|
46
|
+
return `<svg width="${size}" height="${size}" viewBox="0 0 ${size} ${size}" xmlns="http://www.w3.org/2000/svg">${inner}<svg x="0" y="0" width="${size}" height="${size}" viewBox="-8 -8 528 528"><path d="${CROSSHAIR_PATH}" fill="${color}" stroke="${color}" stroke-width="5"/></svg></svg>`;
|
|
47
|
+
}
|
|
48
|
+
export function fireIcon(color, size) {
|
|
49
|
+
return `<svg width="${size}" height="${size}" viewBox="0 -960 960 960" xmlns="http://www.w3.org/2000/svg" fill="${color}"><path d="M200-400q0 52 21 98.5t60 81.5q-1-5-1-9v-9q0-32 12-60t35-51l113-111 113 111q23 23 35 51t12 60v9q0 4-1 9 39-35 60-81.5t21-98.5q0-50-18.5-94.5T608-574q-20 13-42 19.5t-45 6.5q-62 0-107.5-41T361-690q-39 33-69 68.5t-50.5 72Q221-513 210.5-475T200-400Zm240 52-57 56q-11 11-17 25t-6 29q0 32 23.5 55t56.5 23q33 0 56.5-23t23.5-55q0-16-6-29.5T497-292l-57-56Zm0-492v132q0 34 23.5 57t57.5 23q18 0 33.5-7.5T582-658l18-22q74 42 117 117t43 163q0 134-93 227T440-80q-134 0-227-93t-93-227q0-129 86.5-245T440-840Zm400 320q-17 0-28.5-11.5T800-560q0-17 11.5-28.5T840-600q17 0 28.5 11.5T880-560q0 17-11.5 28.5T840-520Zm-40-120v-200h80v200h-80Z"/></svg>`;
|
|
50
|
+
}
|
|
51
|
+
/** Room around the marker for the ring and the check badge. */
|
|
52
|
+
export const SELECTED_OVERLAY_PADDING = 6;
|
|
53
|
+
/**
|
|
54
|
+
* The selection mark drawn over a selected marker `size` pixels across: a ring around it in
|
|
55
|
+
* `highlight` plus a check mark badge, so selection never relies on color alone. The image is
|
|
56
|
+
* `size + 2 * SELECTED_OVERLAY_PADDING` pixels square, centered on the marker. Colors are
|
|
57
|
+
* concrete values read from the `--highlight` and `--highlight-foreground` tokens at setup.
|
|
58
|
+
*/
|
|
59
|
+
export function selectedOverlay(highlight, checkColor, size) {
|
|
60
|
+
const total = size + 2 * SELECTED_OVERLAY_PADDING;
|
|
61
|
+
const c = total / 2;
|
|
62
|
+
const ring = size / 2 + 2.5;
|
|
63
|
+
const b = SELECTED_OVERLAY_PADDING;
|
|
64
|
+
const bx = total - b;
|
|
65
|
+
return `<svg width="${total}" height="${total}" viewBox="0 0 ${total} ${total}" xmlns="http://www.w3.org/2000/svg">
|
|
66
|
+
<circle cx="${c}" cy="${c}" r="${ring}" fill="none" stroke="${highlight}" stroke-width="3"/>
|
|
67
|
+
<circle cx="${bx}" cy="${b}" r="${b - 0.5}" fill="${highlight}" stroke="${checkColor}" stroke-width="1"/>
|
|
68
|
+
<path d="M${bx - 2.5} ${b} l1.75 1.75 l3.25 -3.5" fill="none" stroke="${checkColor}" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round"/>
|
|
69
|
+
</svg>`;
|
|
70
|
+
}
|
|
71
|
+
export function asDataURI(svg) {
|
|
72
|
+
return `data:image/svg+xml;utf8,${encodeURIComponent(svg)}`;
|
|
73
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { ExpressionSpecification, FilterSpecification } from "@maptiler/sdk";
|
|
2
|
+
import type { GlMapLike } from "../core/gl.js";
|
|
3
|
+
import type { BlockingClick, FeatureHit, LayerSpecWithoutId } from "../core/plugin.js";
|
|
4
|
+
import type { Scale } from "../data/scale-types.js";
|
|
5
|
+
import type { MapFeatureProperties } from "../data/types.js";
|
|
6
|
+
import type { ThemeColors } from "./style.js";
|
|
7
|
+
/** A hovered cluster bubble. */
|
|
8
|
+
export interface ClusterInfo<P extends MapFeatureProperties> {
|
|
9
|
+
group: string;
|
|
10
|
+
pointCount: number;
|
|
11
|
+
/** The cluster feature's properties, including `cluster.properties` aggregates. */
|
|
12
|
+
properties: Readonly<Record<string, unknown>>;
|
|
13
|
+
/** Every point in the cluster; rejects if the cluster is gone, empty once torn down. */
|
|
14
|
+
leaves(): Promise<Array<{
|
|
15
|
+
props: P;
|
|
16
|
+
lngLat: [number, number];
|
|
17
|
+
}>>;
|
|
18
|
+
}
|
|
19
|
+
/** What custom cluster layers are built from. */
|
|
20
|
+
export interface ClusterLayerArgs {
|
|
21
|
+
group: string;
|
|
22
|
+
sourceId: string;
|
|
23
|
+
scale: Scale;
|
|
24
|
+
theme: ThemeColors;
|
|
25
|
+
/**
|
|
26
|
+
* The `icon-image` for the plugin's icon of `category` at the level `levelKey` evaluates to
|
|
27
|
+
* (use `levelKeyExpression`); registers that category's images.
|
|
28
|
+
*/
|
|
29
|
+
iconImage(category: string, levelKey: ExpressionSpecification): ExpressionSpecification;
|
|
30
|
+
/** The `icon-size` that shows an icon at its `size` (images are drawn larger for HiDPI). */
|
|
31
|
+
iconSizeScale: number;
|
|
32
|
+
}
|
|
33
|
+
export type ClusterLayers = Array<{
|
|
34
|
+
local: string;
|
|
35
|
+
spec: LayerSpecWithoutId;
|
|
36
|
+
}>;
|
|
37
|
+
export declare const CLUSTERED: FilterSpecification;
|
|
38
|
+
/** Cluster bubbles: a neutral circle with the count. */
|
|
39
|
+
export declare function defaultClusterLayers({ sourceId, theme }: ClusterLayerArgs): ClusterLayers;
|
|
40
|
+
/** What a cluster hover hit names: the data generation it was hovered in, the id, the group. */
|
|
41
|
+
export interface ClusterRef {
|
|
42
|
+
generation: number;
|
|
43
|
+
clusterId: number;
|
|
44
|
+
group: string;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The hover hit for a bubble: a `{ kind: "feature", id: "cluster:<generation>:<id>:<group>" }`
|
|
48
|
+
* ref (cluster ids are only meaningful for the data they were made from) carrying the bubble's
|
|
49
|
+
* properties. No side effects: lookups call it too.
|
|
50
|
+
*/
|
|
51
|
+
export declare function clusterHit(pluginId: string, generation: number, group: string, props: Record<string, unknown>): FeatureHit | null;
|
|
52
|
+
/** The cluster a hover hit names, or null for any other hit. */
|
|
53
|
+
export declare function parseClusterHit(hit: FeatureHit): ClusterRef | null;
|
|
54
|
+
/** A cluster's points, from its source; `[]` once `isDead`. */
|
|
55
|
+
export declare function clusterLeaves<P extends MapFeatureProperties>(gl: GlMapLike, sourceId: string, clusterId: number, pointCount: number, isDead: () => boolean): Promise<Array<{
|
|
56
|
+
props: P;
|
|
57
|
+
lngLat: [number, number];
|
|
58
|
+
}>>;
|
|
59
|
+
/** Zooms in far enough to expand a clicked bubble; no animation under reduced motion. */
|
|
60
|
+
export declare function zoomIntoCluster(gl: GlMapLike, sourceId: string, feature: BlockingClick, isDead: () => boolean, pluginId: string): void;
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { encodeRef } from "../data/refs.js";
|
|
2
|
+
export const CLUSTERED = ["has", "point_count"];
|
|
3
|
+
function prefersReducedMotion() {
|
|
4
|
+
return globalThis.matchMedia?.("(prefers-reduced-motion: reduce)").matches ?? false;
|
|
5
|
+
}
|
|
6
|
+
/** Cluster bubbles: a neutral circle with the count. */
|
|
7
|
+
export function defaultClusterLayers({ sourceId, theme }) {
|
|
8
|
+
return [
|
|
9
|
+
{
|
|
10
|
+
local: "clusters",
|
|
11
|
+
spec: {
|
|
12
|
+
type: "circle",
|
|
13
|
+
source: sourceId,
|
|
14
|
+
filter: CLUSTERED,
|
|
15
|
+
paint: {
|
|
16
|
+
"circle-radius": ["step", ["get", "point_count"], 12, 10, 16, 25, 20],
|
|
17
|
+
"circle-color": theme.neutral,
|
|
18
|
+
"circle-stroke-color": theme.foreground,
|
|
19
|
+
"circle-stroke-width": 1
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
},
|
|
23
|
+
{
|
|
24
|
+
local: "cluster-count",
|
|
25
|
+
spec: {
|
|
26
|
+
type: "symbol",
|
|
27
|
+
source: sourceId,
|
|
28
|
+
filter: CLUSTERED,
|
|
29
|
+
layout: {
|
|
30
|
+
"text-field": ["get", "point_count_abbreviated"],
|
|
31
|
+
"text-size": 12,
|
|
32
|
+
"text-allow-overlap": true
|
|
33
|
+
},
|
|
34
|
+
paint: { "text-color": theme.foreground }
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
];
|
|
38
|
+
}
|
|
39
|
+
const CLUSTER_ID = /^cluster:(\d+):(\d+):(.*)$/s;
|
|
40
|
+
/**
|
|
41
|
+
* The hover hit for a bubble: a `{ kind: "feature", id: "cluster:<generation>:<id>:<group>" }`
|
|
42
|
+
* ref (cluster ids are only meaningful for the data they were made from) carrying the bubble's
|
|
43
|
+
* properties. No side effects: lookups call it too.
|
|
44
|
+
*/
|
|
45
|
+
export function clusterHit(pluginId, generation, group, props) {
|
|
46
|
+
const clusterId = props.cluster_id;
|
|
47
|
+
if (typeof clusterId !== "number")
|
|
48
|
+
return null;
|
|
49
|
+
const ref = { kind: "feature", id: `cluster:${generation}:${clusterId}:${group}` };
|
|
50
|
+
return { pluginId, ref, refKey: encodeRef(ref), name: "", properties: { ...props } };
|
|
51
|
+
}
|
|
52
|
+
/** The cluster a hover hit names, or null for any other hit. */
|
|
53
|
+
export function parseClusterHit(hit) {
|
|
54
|
+
if (hit.ref.kind !== "feature")
|
|
55
|
+
return null;
|
|
56
|
+
const match = CLUSTER_ID.exec(hit.ref.id);
|
|
57
|
+
if (!match)
|
|
58
|
+
return null;
|
|
59
|
+
return { generation: Number(match[1]), clusterId: Number(match[2]), group: match[3] };
|
|
60
|
+
}
|
|
61
|
+
/** A cluster's points, from its source; `[]` once `isDead`. */
|
|
62
|
+
export async function clusterLeaves(gl, sourceId, clusterId, pointCount, isDead) {
|
|
63
|
+
const clusters = isDead() ? undefined : gl.getSource(sourceId);
|
|
64
|
+
if (!clusters)
|
|
65
|
+
return [];
|
|
66
|
+
const leaves = await clusters.getClusterLeaves(clusterId, pointCount, 0);
|
|
67
|
+
if (isDead())
|
|
68
|
+
return [];
|
|
69
|
+
return leaves.flatMap((leaf) => leaf.geometry.type === "Point"
|
|
70
|
+
? [
|
|
71
|
+
{
|
|
72
|
+
props: leaf.properties,
|
|
73
|
+
lngLat: leaf.geometry.coordinates.slice(0, 2)
|
|
74
|
+
}
|
|
75
|
+
]
|
|
76
|
+
: []);
|
|
77
|
+
}
|
|
78
|
+
/** Zooms in far enough to expand a clicked bubble; no animation under reduced motion. */
|
|
79
|
+
export function zoomIntoCluster(gl, sourceId, feature, isDead, pluginId) {
|
|
80
|
+
const clusterId = feature.properties.cluster_id;
|
|
81
|
+
if (typeof clusterId !== "number" || feature.geometry.type !== "Point")
|
|
82
|
+
return;
|
|
83
|
+
const [lng, lat] = feature.geometry.coordinates;
|
|
84
|
+
const clusters = gl.getSource(sourceId);
|
|
85
|
+
clusters?.getClusterExpansionZoom(clusterId).then((zoom) => {
|
|
86
|
+
if (isDead())
|
|
87
|
+
return;
|
|
88
|
+
gl.easeTo({ center: [lng, lat], zoom, ...(prefersReducedMotion() && { duration: 0 }) });
|
|
89
|
+
}, (error) => {
|
|
90
|
+
if (!isDead())
|
|
91
|
+
console.warn(`map-sdk: plugin "${pluginId}" could not expand a cluster`, error);
|
|
92
|
+
});
|
|
93
|
+
}
|