@sqlrooms/deck 0.29.0-rc.8 → 0.29.0-rc.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +31 -6
- package/dist/DeckJsonMap.d.ts +2 -2
- package/dist/DeckJsonMap.d.ts.map +1 -1
- package/dist/DeckJsonMap.js +202 -19
- package/dist/DeckJsonMap.js.map +1 -1
- package/dist/DeckJsonMapSpec.d.ts +36 -36
- package/dist/DeckJsonMapSpec.d.ts.map +1 -1
- package/dist/DeckJsonMapSpec.js +1 -0
- package/dist/DeckJsonMapSpec.js.map +1 -1
- package/dist/MapSettings.d.ts.map +1 -1
- package/dist/MapSettings.js +202 -25
- package/dist/MapSettings.js.map +1 -1
- package/dist/ai.d.ts +55 -8
- package/dist/ai.d.ts.map +1 -1
- package/dist/ai.js +190 -41
- package/dist/ai.js.map +1 -1
- package/dist/block.d.ts +20 -0
- package/dist/block.d.ts.map +1 -0
- package/dist/block.js +119 -0
- package/dist/block.js.map +1 -0
- package/dist/dashboard.d.ts +2 -8
- package/dist/dashboard.d.ts.map +1 -1
- package/dist/dashboard.js +194 -289
- package/dist/dashboard.js.map +1 -1
- package/dist/dashboardConfig.d.ts +11 -3
- package/dist/dashboardConfig.d.ts.map +1 -1
- package/dist/dashboardConfig.js +82 -7
- package/dist/dashboardConfig.js.map +1 -1
- package/dist/index.d.ts +4 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +3 -2
- package/dist/index.js.map +1 -1
- package/dist/json/aggregateWaypoints.d.ts +14 -0
- package/dist/json/aggregateWaypoints.d.ts.map +1 -0
- package/dist/json/aggregateWaypoints.js +190 -0
- package/dist/json/aggregateWaypoints.js.map +1 -0
- package/dist/json/compileColorScale.d.ts +1 -1
- package/dist/json/compileColorScale.d.ts.map +1 -1
- package/dist/json/compileColorScale.js +42 -20
- package/dist/json/compileColorScale.js.map +1 -1
- package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -1
- package/dist/json/createDeckJsonConfiguration.js +106 -8
- package/dist/json/createDeckJsonConfiguration.js.map +1 -1
- package/dist/json/defaultClasses.d.ts +5 -4
- package/dist/json/defaultClasses.d.ts.map +1 -1
- package/dist/json/defaultClasses.js +9 -4
- package/dist/json/defaultClasses.js.map +1 -1
- package/dist/json/extractColorScaleLegends.d.ts.map +1 -1
- package/dist/json/extractColorScaleLegends.js +43 -5
- package/dist/json/extractColorScaleLegends.js.map +1 -1
- package/dist/json/heatmapDefaults.d.ts +8 -0
- package/dist/json/heatmapDefaults.d.ts.map +1 -0
- package/dist/json/heatmapDefaults.js +14 -0
- package/dist/json/heatmapDefaults.js.map +1 -0
- package/dist/json/layerCompatibility.d.ts.map +1 -1
- package/dist/json/layerCompatibility.js +5 -1
- package/dist/json/layerCompatibility.js.map +1 -1
- package/dist/json/layers/DeckColumnLayer.d.ts +12 -0
- package/dist/json/layers/DeckColumnLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckColumnLayer.js +26 -0
- package/dist/json/layers/DeckColumnLayer.js.map +1 -0
- package/dist/json/layers/DeckH3HexagonLayer.d.ts +37 -0
- package/dist/json/layers/DeckH3HexagonLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckH3HexagonLayer.js +96 -0
- package/dist/json/layers/DeckH3HexagonLayer.js.map +1 -0
- package/dist/json/layers/DeckTripsLayer.d.ts +45 -0
- package/dist/json/layers/DeckTripsLayer.d.ts.map +1 -0
- package/dist/json/layers/DeckTripsLayer.js +169 -0
- package/dist/json/layers/DeckTripsLayer.js.map +1 -0
- package/dist/json/layers/index.d.ts +4 -0
- package/dist/json/layers/index.d.ts.map +1 -0
- package/dist/json/layers/index.js +4 -0
- package/dist/json/layers/index.js.map +1 -0
- package/dist/json/rewriteGeoArrowAccessors.d.ts.map +1 -1
- package/dist/json/rewriteGeoArrowAccessors.js +5 -0
- package/dist/json/rewriteGeoArrowAccessors.js.map +1 -1
- package/dist/mapConfigUtils.d.ts +102 -1
- package/dist/mapConfigUtils.d.ts.map +1 -1
- package/dist/mapConfigUtils.js +21 -3
- package/dist/mapConfigUtils.js.map +1 -1
- package/dist/mapLayerConfigUtils.d.ts +15 -0
- package/dist/mapLayerConfigUtils.d.ts.map +1 -1
- package/dist/mapLayerConfigUtils.js +109 -7
- package/dist/mapLayerConfigUtils.js.map +1 -1
- package/dist/prepare/detectGeometryColumn.d.ts +10 -1
- package/dist/prepare/detectGeometryColumn.d.ts.map +1 -1
- package/dist/prepare/detectGeometryColumn.js +48 -6
- package/dist/prepare/detectGeometryColumn.js.map +1 -1
- package/dist/prepare/wkbDecoder.d.ts.map +1 -1
- package/dist/prepare/wkbDecoder.js +88 -1
- package/dist/prepare/wkbDecoder.js.map +1 -1
- package/dist/prepare/wkbParser.d.ts +5 -0
- package/dist/prepare/wkbParser.d.ts.map +1 -1
- package/dist/prepare/wkbParser.js +24 -0
- package/dist/prepare/wkbParser.js.map +1 -1
- package/dist/types.d.ts +14 -4
- package/dist/types.d.ts.map +1 -1
- package/dist/types.js.map +1 -1
- package/dist/useDeckMapDatasets.d.ts +17 -0
- package/dist/useDeckMapDatasets.d.ts.map +1 -0
- package/dist/useDeckMapDatasets.js +69 -0
- package/dist/useDeckMapDatasets.js.map +1 -0
- package/dist/useDeckMapFitToBounds.d.ts +29 -0
- package/dist/useDeckMapFitToBounds.d.ts.map +1 -0
- package/dist/useDeckMapFitToBounds.js +333 -0
- package/dist/useDeckMapFitToBounds.js.map +1 -0
- package/package.json +7 -6
package/README.md
CHANGED
|
@@ -196,6 +196,22 @@ dataset extent using the declared longitude/latitude columns and fits the
|
|
|
196
196
|
initial map view once, instead of inferring bounds from the loaded Arrow
|
|
197
197
|
payload in React.
|
|
198
198
|
|
|
199
|
+
Use `createDeckMapPanelFromNativeConfig(...)` when a host surface already has a
|
|
200
|
+
native Deck map config, for example from AI tooling, and needs the same
|
|
201
|
+
dashboard-compatible `deck-json-map` panel shape that the dashboard map tool
|
|
202
|
+
creates.
|
|
203
|
+
|
|
204
|
+
## Embeddable Map Blocks
|
|
205
|
+
|
|
206
|
+
Host applications that expose document-like block surfaces can render maps
|
|
207
|
+
without first creating a visible dashboard. Use `ensureDeckMapBlockState(...)`
|
|
208
|
+
to initialize persisted map state for a durable block id, then render it with
|
|
209
|
+
`DeckMapBlockRenderer`.
|
|
210
|
+
|
|
211
|
+
The block primitive reuses the dashboard map panel runtime internally, while
|
|
212
|
+
leaving each host surface free to adapt its own block/document semantics around
|
|
213
|
+
it.
|
|
214
|
+
|
|
199
215
|
## Core Concepts
|
|
200
216
|
|
|
201
217
|
### `DeckJsonMap`
|
|
@@ -204,7 +220,7 @@ payload in React.
|
|
|
204
220
|
|
|
205
221
|
- `spec`: a JSON-like deck.gl spec object or JSON string
|
|
206
222
|
- `datasets`: a dataset registry keyed by dataset id
|
|
207
|
-
- `interleaved`: when true, deck layers
|
|
223
|
+
- `interleaved`: when true, deck layers render in MapLibre's own WebGL context instead of a separate overlay canvas. This halves the number of WebGL contexts per map panel (from 2 to 1), which matters because browsers limit active contexts to ~8–16 per page. Default: `true`
|
|
208
224
|
- `deckProps`: runtime-only deck props such as `getTooltip`, `onHover`, `onClick`
|
|
209
225
|
- `mapProps`: runtime-only MapLibre props
|
|
210
226
|
- `showLegends`: whether SQLRooms-generated color legends should render
|
|
@@ -212,13 +228,22 @@ payload in React.
|
|
|
212
228
|
`spec` stays serializable; callbacks and runtime behavior belong in `deckProps`
|
|
213
229
|
or `mapProps`.
|
|
214
230
|
|
|
215
|
-
By default,
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
231
|
+
By default, deck.gl renders interleaved into MapLibre's layer stack, sharing a
|
|
232
|
+
single WebGL context. This allows deck layers to be inserted between basemap
|
|
233
|
+
layers (e.g. render points under map labels) and reduces WebGL context usage.
|
|
234
|
+
Set `interleaved` to `false` to render deck layers in a separate overlay canvas
|
|
235
|
+
on top of all basemap layers (uses an additional WebGL context per map).
|
|
219
236
|
|
|
220
237
|
```tsx
|
|
221
|
-
|
|
238
|
+
{
|
|
239
|
+
/* Default (interleaved): */
|
|
240
|
+
}
|
|
241
|
+
<DeckJsonMap spec={spec} datasets={datasets} />;
|
|
242
|
+
|
|
243
|
+
{
|
|
244
|
+
/* Opt out to separate overlay canvas: */
|
|
245
|
+
}
|
|
246
|
+
<DeckJsonMap spec={spec} datasets={datasets} interleaved={false} />;
|
|
222
247
|
```
|
|
223
248
|
|
|
224
249
|
### Dataset Registry
|
package/dist/DeckJsonMap.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
1
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
2
|
-
import type { DeckJsonMapProps } from './types';
|
|
3
|
-
export declare
|
|
2
|
+
import type { DeckJsonMapHandle, DeckJsonMapProps } from './types';
|
|
3
|
+
export declare const DeckJsonMap: import("react").ForwardRefExoticComponent<DeckJsonMapProps & import("react").RefAttributes<DeckJsonMapHandle>>;
|
|
4
4
|
//# sourceMappingURL=DeckJsonMap.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeckJsonMap.d.ts","sourceRoot":"","sources":["../src/DeckJsonMap.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;
|
|
1
|
+
{"version":3,"file":"DeckJsonMap.d.ts","sourceRoot":"","sources":["../src/DeckJsonMap.tsx"],"names":[],"mappings":"AAIA,OAAO,kCAAkC,CAAC;AAoB1C,OAAO,KAAK,EACV,iBAAiB,EACjB,gBAAgB,EAEjB,MAAM,SAAS,CAAC;AAgPjB,eAAO,MAAM,WAAW,gHAyVvB,CAAC"}
|
package/dist/DeckJsonMap.js
CHANGED
|
@@ -4,7 +4,8 @@ import { MapboxOverlay } from '@deck.gl/mapbox';
|
|
|
4
4
|
import { ColorScaleLegend } from '@sqlrooms/color-scales';
|
|
5
5
|
import { cn, useTheme } from '@sqlrooms/ui';
|
|
6
6
|
import 'maplibre-gl/dist/maplibre-gl.css';
|
|
7
|
-
import { useEffect, useMemo, useRef } from 'react';
|
|
7
|
+
import { useCallback, useEffect, useImperativeHandle, useMemo, useRef, useState, } from 'react';
|
|
8
|
+
import { forwardRef } from 'react';
|
|
8
9
|
import Map, { useControl } from 'react-map-gl/maplibre';
|
|
9
10
|
import { DeckJsonMapSpec } from './DeckJsonMapSpec';
|
|
10
11
|
import { normalizeDatasets } from './datasets/normalizeDatasets';
|
|
@@ -78,7 +79,7 @@ function filterUnavailableLayers(spec, datasetIds, datasetStates) {
|
|
|
78
79
|
if (!datasetState) {
|
|
79
80
|
return false;
|
|
80
81
|
}
|
|
81
|
-
return datasetState.status
|
|
82
|
+
return datasetState.status !== 'error';
|
|
82
83
|
});
|
|
83
84
|
return {
|
|
84
85
|
...spec,
|
|
@@ -92,12 +93,54 @@ function renderDatasetErrorOverlay(datasetStates) {
|
|
|
92
93
|
}
|
|
93
94
|
return (_jsx("div", { className: "pointer-events-none absolute inset-x-4 top-4 z-10 space-y-2", children: failedDatasets.map(([datasetId, state]) => (_jsx("div", { className: "rounded-md border border-red-200 bg-red-50/95 px-3 py-2 text-sm text-red-700 shadow-sm", children: `Dataset "${datasetId}" failed: ${state.error.message}` }, datasetId))) }));
|
|
94
95
|
}
|
|
96
|
+
const TripsTimeControl = ({ currentTime, maxTime, playing, onPlayPause, onSeek }) => {
|
|
97
|
+
const [collapsed, setCollapsed] = useState(false);
|
|
98
|
+
const progress = maxTime > 0 ? currentTime / maxTime : 0;
|
|
99
|
+
if (collapsed) {
|
|
100
|
+
return (_jsx("button", { type: "button", onClick: () => setCollapsed(false), className: "border-border/70 bg-background/90 text-muted-foreground hover:text-foreground pointer-events-auto flex w-fit items-center gap-1 rounded-md border px-2 py-1 text-[10px] leading-none font-medium shadow-sm backdrop-blur-sm transition-colors", children: "Timeline" }));
|
|
101
|
+
}
|
|
102
|
+
return (_jsxs("div", { className: "bg-background/90 border-border/70 pointer-events-auto relative flex items-center gap-1.5 rounded-md border px-2 py-1.5 pr-7 shadow-sm backdrop-blur-sm", children: [_jsx("button", { type: "button", onClick: onPlayPause, className: "text-foreground hover:bg-accent flex h-5 w-5 shrink-0 items-center justify-center rounded", title: playing ? 'Pause' : 'Play', children: playing ? (_jsxs("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "currentColor", children: [_jsx("rect", { x: "1", y: "1", width: "3", height: "8" }), _jsx("rect", { x: "6", y: "1", width: "3", height: "8" })] })) : (_jsx("svg", { width: "10", height: "10", viewBox: "0 0 10 10", fill: "currentColor", children: _jsx("polygon", { points: "2,1 9,5 2,9" }) })) }), _jsx("input", { type: "range", min: 0, max: 1000, value: Math.round(progress * 1000), onChange: (e) => onSeek((Number(e.target.value) / 1000) * maxTime), className: "h-1 w-24 flex-1 cursor-pointer" }), _jsx("button", { type: "button", onClick: () => setCollapsed(true), className: "text-muted-foreground hover:text-foreground absolute top-1.5 right-1.5 flex h-4 w-4 items-center justify-center rounded-sm transition-colors", title: "Collapse", children: _jsx("svg", { width: "8", height: "8", viewBox: "0 0 8 8", children: _jsx("path", { d: "M1 1L7 7M7 1L1 7", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round" }) }) })] }));
|
|
103
|
+
};
|
|
104
|
+
// Workaround for deck.gl HeatmapLayer not releasing its WebGL framebuffer
|
|
105
|
+
// when replaced by another layer type (e.g. switching heatmap → scatterplot).
|
|
106
|
+
// We detect layer class changes and clear layers for one animation frame,
|
|
107
|
+
// giving deck.gl a chance to finalize the old layer's resources before
|
|
108
|
+
// initializing the new one.
|
|
95
109
|
function DeckOverlayControl({ interleaved, ...deckProps }) {
|
|
96
110
|
const overlay = useControl(() => new MapboxOverlay({ interleaved }));
|
|
97
|
-
|
|
111
|
+
const prevLayerKeyRef = useRef('');
|
|
112
|
+
const [clearing, setClearing] = useState(false);
|
|
113
|
+
const layers = deckProps.layers;
|
|
114
|
+
const layerKey = Array.isArray(layers)
|
|
115
|
+
? layers
|
|
116
|
+
.map((l) => {
|
|
117
|
+
if (!l || typeof l !== 'object')
|
|
118
|
+
return '?';
|
|
119
|
+
const ctor = l.constructor;
|
|
120
|
+
return ctor?.name ?? '?';
|
|
121
|
+
})
|
|
122
|
+
.join(',')
|
|
123
|
+
: '';
|
|
124
|
+
useEffect(() => {
|
|
125
|
+
if (prevLayerKeyRef.current &&
|
|
126
|
+
prevLayerKeyRef.current !== layerKey &&
|
|
127
|
+
!clearing) {
|
|
128
|
+
setClearing(true);
|
|
129
|
+
}
|
|
130
|
+
prevLayerKeyRef.current = layerKey;
|
|
131
|
+
}, [layerKey, clearing]);
|
|
132
|
+
useEffect(() => {
|
|
133
|
+
if (clearing && overlay) {
|
|
134
|
+
overlay.setProps({ layers: [] });
|
|
135
|
+
requestAnimationFrame(() => setClearing(false));
|
|
136
|
+
}
|
|
137
|
+
}, [clearing, overlay]);
|
|
138
|
+
if (!clearing && overlay) {
|
|
139
|
+
overlay.setProps(deckProps);
|
|
140
|
+
}
|
|
98
141
|
return null;
|
|
99
142
|
}
|
|
100
|
-
export function DeckJsonMap({ spec, datasets, mapStyle, interleaved =
|
|
143
|
+
export const DeckJsonMap = forwardRef(function DeckJsonMap({ spec, datasets, mapStyle, interleaved = true, deckProps, mapProps, showLegends = true, className, children, onDatasetStatesChange, onRenderingError, }, ref) {
|
|
101
144
|
const normalizedDatasets = useMemo(() => normalizeDatasets(datasets), [datasets]);
|
|
102
145
|
const datasetIds = useMemo(() => Object.keys(normalizedDatasets), [normalizedDatasets]);
|
|
103
146
|
const datasetStates = usePreparedDatasetStates(normalizedDatasets);
|
|
@@ -130,11 +173,62 @@ export function DeckJsonMap({ spec, datasets, mapStyle, interleaved = false, dec
|
|
|
130
173
|
};
|
|
131
174
|
}
|
|
132
175
|
}, [availableSpec, converter, specError]);
|
|
176
|
+
// Animation for TripsLayer — update currentTime on each frame.
|
|
177
|
+
// Only activate when the trips dataset is ready to avoid spinning the RAF
|
|
178
|
+
// loop while data is still loading.
|
|
179
|
+
const hasTripsLayer = useMemo(() => {
|
|
180
|
+
if (!availableSpec || !Array.isArray(availableSpec.layers))
|
|
181
|
+
return false;
|
|
182
|
+
return availableSpec.layers.some((l) => {
|
|
183
|
+
if (!l ||
|
|
184
|
+
typeof l !== 'object' ||
|
|
185
|
+
l['@@type'] !== 'GeoArrowTripsLayer')
|
|
186
|
+
return false;
|
|
187
|
+
const dsId = resolveDatasetId(l, datasetIds);
|
|
188
|
+
if (!dsId)
|
|
189
|
+
return true;
|
|
190
|
+
const state = datasetStates[dsId];
|
|
191
|
+
return state?.status === 'ready';
|
|
192
|
+
});
|
|
193
|
+
}, [availableSpec, datasetIds, datasetStates]);
|
|
194
|
+
const [tripsTime, setTripsTime] = useState(0);
|
|
195
|
+
const [tripsPlaying, setTripsPlaying] = useState(true);
|
|
196
|
+
const tripsTimeRef = useRef(0);
|
|
133
197
|
useEffect(() => {
|
|
134
|
-
if (
|
|
135
|
-
|
|
198
|
+
if (!hasTripsLayer || !tripsPlaying)
|
|
199
|
+
return;
|
|
200
|
+
let raf;
|
|
201
|
+
let lastFrame = Date.now();
|
|
202
|
+
const tick = () => {
|
|
203
|
+
const now = Date.now();
|
|
204
|
+
tripsTimeRef.current += now - lastFrame;
|
|
205
|
+
lastFrame = now;
|
|
206
|
+
setTripsTime(tripsTimeRef.current);
|
|
207
|
+
raf = requestAnimationFrame(tick);
|
|
208
|
+
};
|
|
209
|
+
lastFrame = Date.now();
|
|
210
|
+
raf = requestAnimationFrame(tick);
|
|
211
|
+
return () => cancelAnimationFrame(raf);
|
|
212
|
+
}, [hasTripsLayer, tripsPlaying]);
|
|
213
|
+
const handleTripsPlayPause = useCallback(() => {
|
|
214
|
+
setTripsPlaying((p) => !p);
|
|
215
|
+
}, []);
|
|
216
|
+
const handleTripsSeek = useCallback((time) => {
|
|
217
|
+
const rawTime = time * 30;
|
|
218
|
+
tripsTimeRef.current = rawTime;
|
|
219
|
+
setTripsTime(rawTime);
|
|
220
|
+
}, []);
|
|
221
|
+
const finalDeckPropsResult = convertedDeckPropsResult;
|
|
222
|
+
const onRenderingErrorRef = useRef(onRenderingError);
|
|
223
|
+
useEffect(() => {
|
|
224
|
+
onRenderingErrorRef.current = onRenderingError;
|
|
225
|
+
}, [onRenderingError]);
|
|
226
|
+
useEffect(() => {
|
|
227
|
+
if (finalDeckPropsResult.error) {
|
|
228
|
+
console.error(finalDeckPropsResult.error);
|
|
229
|
+
onRenderingErrorRef.current?.(finalDeckPropsResult.error);
|
|
136
230
|
}
|
|
137
|
-
}, [
|
|
231
|
+
}, [finalDeckPropsResult.error]);
|
|
138
232
|
useEffect(() => {
|
|
139
233
|
onDatasetStatesChangeRef.current = onDatasetStatesChange;
|
|
140
234
|
}, [onDatasetStatesChange]);
|
|
@@ -142,24 +236,113 @@ export function DeckJsonMap({ spec, datasets, mapStyle, interleaved = false, dec
|
|
|
142
236
|
onDatasetStatesChangeRef.current?.(datasetStates);
|
|
143
237
|
}, [datasetStates]);
|
|
144
238
|
const fallbackDeckProps = useMemo(() => extractFallbackDeckProps(availableSpec), [availableSpec]);
|
|
145
|
-
const convertedDeckProps = (
|
|
239
|
+
const convertedDeckProps = (finalDeckPropsResult.props ??
|
|
146
240
|
fallbackDeckProps ??
|
|
147
241
|
{});
|
|
148
242
|
const extraDeckProps = (deckProps ?? {});
|
|
149
243
|
const extraMapProps = (mapProps ?? {});
|
|
150
|
-
const hasRenderingError = Boolean(
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
[]
|
|
244
|
+
const hasRenderingError = Boolean(finalDeckPropsResult.error);
|
|
245
|
+
// Separate stable layer analysis from per-frame animation to avoid
|
|
246
|
+
// re-running .map() over all layers 60 times per second.
|
|
247
|
+
const baseLayers = useMemo(() => {
|
|
248
|
+
const mergedLayers = hasRenderingError
|
|
249
|
+
? []
|
|
250
|
+
: (deckProps?.layers ??
|
|
251
|
+
convertedDeckProps.layers ??
|
|
252
|
+
[]);
|
|
253
|
+
if (!hasTripsLayer || !Array.isArray(mergedLayers))
|
|
254
|
+
return { layers: mergedLayers, maxTs: 0, tripsIndices: [] };
|
|
255
|
+
let computedMaxTs = 0;
|
|
256
|
+
const tripsIndices = [];
|
|
257
|
+
for (let i = 0; i < mergedLayers.length; i++) {
|
|
258
|
+
const layer = mergedLayers[i];
|
|
259
|
+
if (!layer || typeof layer !== 'object')
|
|
260
|
+
continue;
|
|
261
|
+
const layerObj = layer;
|
|
262
|
+
const maxTs = layerObj.props
|
|
263
|
+
?._tripsMaxTimestamp;
|
|
264
|
+
if (maxTs && maxTs > 0) {
|
|
265
|
+
computedMaxTs = Math.max(computedMaxTs, maxTs);
|
|
266
|
+
tripsIndices.push(i);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { layers: mergedLayers, maxTs: computedMaxTs, tripsIndices };
|
|
270
|
+
}, [
|
|
271
|
+
hasTripsLayer,
|
|
272
|
+
hasRenderingError,
|
|
273
|
+
deckProps?.layers,
|
|
274
|
+
convertedDeckProps.layers,
|
|
275
|
+
]);
|
|
276
|
+
// Per-frame: only clone trips layers with updated currentTime
|
|
277
|
+
const animatedLayers = useMemo(() => {
|
|
278
|
+
const { layers, tripsIndices } = baseLayers;
|
|
279
|
+
if (!tripsIndices.length)
|
|
280
|
+
return layers;
|
|
281
|
+
if (!Array.isArray(layers))
|
|
282
|
+
return layers;
|
|
283
|
+
const result = layers.slice();
|
|
284
|
+
for (const idx of tripsIndices) {
|
|
285
|
+
const layer = result[idx];
|
|
286
|
+
if (!layer || typeof layer !== 'object')
|
|
287
|
+
continue;
|
|
288
|
+
const layerObj = layer;
|
|
289
|
+
if (!layerObj.clone)
|
|
290
|
+
continue;
|
|
291
|
+
const props = layerObj.props;
|
|
292
|
+
const maxTs = props?._tripsMaxTimestamp ?? 0;
|
|
293
|
+
const trailFraction = props?._trailLengthFactor ?? 0.4;
|
|
294
|
+
const widthPx = props?.widthMinPixels ?? 3;
|
|
295
|
+
const animationSpeed = 30;
|
|
296
|
+
const currentTime = (tripsTime / animationSpeed) % maxTs;
|
|
297
|
+
result[idx] = layerObj.clone({
|
|
298
|
+
currentTime,
|
|
299
|
+
trailLength: maxTs * trailFraction,
|
|
300
|
+
widthMinPixels: widthPx,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
return result;
|
|
304
|
+
}, [baseLayers, tripsTime]);
|
|
305
|
+
const tripsMaxTimeValue = baseLayers.maxTs;
|
|
306
|
+
const tripsCurrentTime = useMemo(() => {
|
|
307
|
+
if (!tripsMaxTimeValue || tripsMaxTimeValue <= 0)
|
|
308
|
+
return 0;
|
|
309
|
+
return (tripsTime / 30) % tripsMaxTimeValue;
|
|
310
|
+
}, [tripsTime, tripsMaxTimeValue]);
|
|
156
311
|
const mergedDeckProps = {
|
|
157
312
|
...convertedDeckProps,
|
|
158
313
|
...extraDeckProps,
|
|
159
|
-
layers:
|
|
314
|
+
layers: animatedLayers,
|
|
160
315
|
};
|
|
161
316
|
// overlayDeckProps should not contain viewState/initialViewState
|
|
162
|
-
const { initialViewState, viewState:
|
|
317
|
+
const { initialViewState, viewState: _vs, onViewStateChange: _onViewStateChange, ...overlayDeckProps } = mergedDeckProps;
|
|
318
|
+
void _vs;
|
|
319
|
+
void _onViewStateChange;
|
|
320
|
+
const mapRef = useRef(null);
|
|
321
|
+
const pendingJumpRef = useRef(null);
|
|
322
|
+
useImperativeHandle(ref, () => ({
|
|
323
|
+
jumpTo(opts) {
|
|
324
|
+
const jumpOpts = {
|
|
325
|
+
center: [opts.longitude, opts.latitude],
|
|
326
|
+
zoom: opts.zoom,
|
|
327
|
+
bearing: opts.bearing ?? 0,
|
|
328
|
+
pitch: opts.pitch ?? 0,
|
|
329
|
+
};
|
|
330
|
+
if (mapRef.current) {
|
|
331
|
+
mapRef.current.jumpTo(jumpOpts);
|
|
332
|
+
}
|
|
333
|
+
else {
|
|
334
|
+
pendingJumpRef.current = jumpOpts;
|
|
335
|
+
}
|
|
336
|
+
},
|
|
337
|
+
}), []);
|
|
338
|
+
const handleMapLoad = useCallback((event) => {
|
|
339
|
+
if (pendingJumpRef.current && mapRef.current) {
|
|
340
|
+
mapRef.current.jumpTo(pendingJumpRef.current);
|
|
341
|
+
pendingJumpRef.current = null;
|
|
342
|
+
}
|
|
343
|
+
const externalOnLoad = extraMapProps.onLoad;
|
|
344
|
+
externalOnLoad?.(event);
|
|
345
|
+
}, [extraMapProps]);
|
|
163
346
|
const { resolvedTheme } = useTheme();
|
|
164
347
|
const mergedMapProps = {
|
|
165
348
|
...extraMapProps,
|
|
@@ -172,8 +355,8 @@ export function DeckJsonMap({ spec, datasets, mapStyle, interleaved = false, dec
|
|
|
172
355
|
datasetStates,
|
|
173
356
|
})
|
|
174
357
|
: [], [availableSpec, datasetIds, datasetStates, showLegends]);
|
|
175
|
-
return (_jsxs("div", { className: cn('relative h-full w-full', className), children: [hasRenderingError ? (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center p-4", children: _jsx("div", { className: "max-w-sm rounded-md border border-red-200 bg-red-50/95 p-4 text-sm text-red-700 shadow-sm", children: `Map couldn't be rendered. Check the console for details.` }) })) : null, _jsxs(Map, { ...mergedMapProps, ...(initialViewState
|
|
358
|
+
return (_jsxs("div", { className: cn('relative h-full w-full', className), children: [hasRenderingError && !onRenderingError ? (_jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center p-4", children: _jsx("div", { className: "max-w-sm rounded-md border border-red-200 bg-red-50/95 p-4 text-sm text-red-700 shadow-sm", children: `Map couldn't be rendered. Check the console for details.` }) })) : null, _jsxs(Map, { ref: mapRef, ...mergedMapProps, ...(initialViewState
|
|
176
359
|
? { initialViewState: initialViewState }
|
|
177
|
-
: {}), style: { width: '100%', height: '100%', ...mapProps?.style }, children: [_jsx(DeckOverlayControl, { interleaved: interleaved, ...overlayDeckProps }), children] }), renderDatasetErrorOverlay(datasetStates), !hasRenderingError && showLegends ? (
|
|
178
|
-
}
|
|
360
|
+
: {}), onLoad: handleMapLoad, style: { width: '100%', height: '100%', ...mapProps?.style }, children: [_jsx(DeckOverlayControl, { interleaved: interleaved, ...overlayDeckProps }), children] }), renderDatasetErrorOverlay(datasetStates), !hasRenderingError && showLegends ? (_jsxs("div", { className: "pointer-events-none absolute bottom-2 left-2 z-10 max-w-56", children: [hasTripsLayer && tripsMaxTimeValue > 0 ? (_jsx("div", { className: "mb-1", children: _jsx(TripsTimeControl, { currentTime: tripsCurrentTime, maxTime: tripsMaxTimeValue, playing: tripsPlaying, onPlayPause: handleTripsPlayPause, onSeek: handleTripsSeek }) })) : null, _jsx(ColorScaleLegend, { legends: legends })] })) : hasTripsLayer && tripsMaxTimeValue > 0 ? (_jsx("div", { className: "pointer-events-none absolute bottom-2 left-2 z-10", children: _jsx(TripsTimeControl, { currentTime: tripsCurrentTime, maxTime: tripsMaxTimeValue, playing: tripsPlaying, onPlayPause: handleTripsPlayPause, onSeek: handleTripsSeek }) })) : null] }));
|
|
361
|
+
});
|
|
179
362
|
//# sourceMappingURL=DeckJsonMap.js.map
|
package/dist/DeckJsonMap.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"DeckJsonMap.js","sourceRoot":"","sources":["../src/DeckJsonMap.tsx"],"names":[],"mappings":"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","sourcesContent":["import {JSONConverter} from '@deck.gl/json';\nimport {MapboxOverlay} from '@deck.gl/mapbox';\nimport {ColorScaleLegend} from '@sqlrooms/color-scales';\nimport {cn, ResolvedTheme, useTheme} from '@sqlrooms/ui';\nimport 'maplibre-gl/dist/maplibre-gl.css';\nimport {useEffect, useMemo, useRef} from 'react';\nimport Map, {useControl} from 'react-map-gl/maplibre';\nimport {ZodError} from 'zod';\nimport {DeckJsonMapSpec} from './DeckJsonMapSpec';\nimport {normalizeDatasets} from './datasets/normalizeDatasets';\nimport {usePreparedDatasetStates} from './datasets/usePreparedDatasetStates';\nimport {createDeckJsonConfiguration} from './json/createDeckJsonConfiguration';\nimport {extractColorScaleLegends} from './json/extractColorScaleLegends';\nimport {getLayerCompatibility} from './json/layerCompatibility';\nimport {resolveDatasetId} from './json/layerConfig';\nimport type {DeckJsonMapProps, PreparedDeckDatasetState} from './types';\n\nconst DEFAULT_MAP_STYLES: Record<ResolvedTheme, string> = {\n light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n};\n\nfunction parseSpec(spec: DeckJsonMapProps['spec']) {\n try {\n const parsedValue = typeof spec === 'string' ? JSON.parse(spec) : spec;\n const validatedSpec = DeckJsonMapSpec.safeParse(parsedValue);\n if (!validatedSpec.success) {\n return {\n spec: null,\n error: new Error(formatSpecValidationError(validatedSpec.error)),\n };\n }\n\n return {\n spec: validatedSpec.data,\n error: null,\n };\n } catch (error) {\n return {\n spec: null,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n}\n\nfunction formatSpecValidationError(error: ZodError) {\n const issues = error.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : 'spec';\n return `${path}: ${issue.message}`;\n });\n\n return `Invalid DeckJsonMap spec. ${issues.join('; ')}`;\n}\n\nfunction extractFallbackDeckProps(spec: Record<string, unknown> | null) {\n if (!spec) {\n return {};\n }\n\n const fallbackProps: Record<string, unknown> = {};\n for (const key of ['initialViewState', 'viewState', 'controller']) {\n if (key in spec) {\n fallbackProps[key] = spec[key];\n }\n }\n\n return fallbackProps;\n}\n\nfunction filterUnavailableLayers(\n spec: Record<string, unknown>,\n datasetIds: string[],\n datasetStates: Record<string, PreparedDeckDatasetState>,\n) {\n const layers = Array.isArray(spec.layers) ? spec.layers : [];\n const filteredLayers = layers.filter((layer) => {\n if (!layer || typeof layer !== 'object') {\n return true;\n }\n\n const layerProps = layer as Record<string, unknown>;\n const layerName = String(layerProps['@@type'] ?? '');\n const compatibility = getLayerCompatibility(layerName);\n if (!compatibility) {\n return true;\n }\n\n const datasetId = resolveDatasetId(layerProps, datasetIds);\n if (!datasetId) {\n return true;\n }\n\n const datasetState = datasetStates[datasetId];\n if (!datasetState) {\n return false;\n }\n\n return datasetState.status === 'ready';\n });\n\n return {\n ...spec,\n layers: filteredLayers,\n };\n}\n\nfunction renderDatasetErrorOverlay(\n datasetStates: Record<string, PreparedDeckDatasetState>,\n) {\n const failedDatasets = Object.entries(datasetStates).filter(\n (\n entry,\n ): entry is [\n string,\n Extract<PreparedDeckDatasetState, {status: 'error'}>,\n ] => entry[1].status === 'error',\n );\n\n if (!failedDatasets.length) {\n return null;\n }\n\n return (\n <div className=\"pointer-events-none absolute inset-x-4 top-4 z-10 space-y-2\">\n {failedDatasets.map(([datasetId, state]) => (\n <div\n key={datasetId}\n className=\"rounded-md border border-red-200 bg-red-50/95 px-3 py-2 text-sm text-red-700 shadow-sm\"\n >\n {`Dataset \"${datasetId}\" failed: ${state.error.message}`}\n </div>\n ))}\n </div>\n );\n}\n\nfunction DeckOverlayControl({\n interleaved,\n ...deckProps\n}: {interleaved: boolean} & Record<string, unknown>) {\n const overlay = useControl<MapboxOverlay>(\n () => new MapboxOverlay({interleaved}),\n );\n overlay.setProps(deckProps);\n return null;\n}\n\nexport function DeckJsonMap({\n spec,\n datasets,\n mapStyle,\n interleaved = false,\n deckProps,\n mapProps,\n showLegends = true,\n className,\n children,\n onDatasetStatesChange,\n}: DeckJsonMapProps) {\n const normalizedDatasets = useMemo(\n () => normalizeDatasets(datasets),\n [datasets],\n );\n const datasetIds = useMemo(\n () => Object.keys(normalizedDatasets),\n [normalizedDatasets],\n );\n const datasetStates = usePreparedDatasetStates(normalizedDatasets);\n const onDatasetStatesChangeRef = useRef(onDatasetStatesChange);\n\n const {spec: parsedSpec, error: specError} = useMemo(\n () => parseSpec(spec),\n [spec],\n );\n\n const availableSpec = useMemo(\n () =>\n parsedSpec\n ? filterUnavailableLayers(parsedSpec, datasetIds, datasetStates)\n : null,\n [parsedSpec, datasetIds, datasetStates],\n );\n\n const converter = useMemo(\n () =>\n new JSONConverter({\n configuration: createDeckJsonConfiguration({\n datasetStates,\n datasetIds,\n }),\n onJSONChange: () => {},\n }),\n [datasetIds, datasetStates],\n );\n\n const convertedDeckPropsResult = useMemo(() => {\n if (!availableSpec) {\n return {props: null, error: specError};\n }\n\n try {\n return {\n props: converter.convert(availableSpec) as Record<string, unknown>,\n error: null,\n };\n } catch (error) {\n return {\n props: null,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }, [availableSpec, converter, specError]);\n\n useEffect(() => {\n if (convertedDeckPropsResult.error) {\n console.error(convertedDeckPropsResult.error);\n }\n }, [convertedDeckPropsResult.error]);\n\n useEffect(() => {\n onDatasetStatesChangeRef.current = onDatasetStatesChange;\n }, [onDatasetStatesChange]);\n\n useEffect(() => {\n onDatasetStatesChangeRef.current?.(datasetStates);\n }, [datasetStates]);\n\n const fallbackDeckProps = useMemo(\n () => extractFallbackDeckProps(availableSpec),\n [availableSpec],\n );\n const convertedDeckProps = (convertedDeckPropsResult.props ??\n fallbackDeckProps ??\n {}) as Record<string, unknown>;\n const extraDeckProps = (deckProps ?? {}) as Record<string, unknown>;\n const extraMapProps = (mapProps ?? {}) as Record<string, unknown>;\n const hasRenderingError = Boolean(convertedDeckPropsResult.error);\n const mergedLayers = hasRenderingError\n ? []\n : (deckProps?.layers ??\n (convertedDeckProps.layers as unknown[] | undefined) ??\n []);\n\n const mergedDeckProps = {\n ...convertedDeckProps,\n ...extraDeckProps,\n layers: mergedLayers,\n };\n\n // overlayDeckProps should not contain viewState/initialViewState\n const {\n initialViewState,\n viewState: _viewState,\n ...overlayDeckProps\n } = mergedDeckProps as Record<string, unknown>;\n\n const {resolvedTheme} = useTheme();\n\n const mergedMapProps = {\n ...extraMapProps,\n mapStyle:\n mapStyle ?? mapProps?.mapStyle ?? DEFAULT_MAP_STYLES[resolvedTheme],\n };\n const legends = useMemo(\n () =>\n showLegends\n ? extractColorScaleLegends({\n spec: availableSpec,\n datasetIds,\n datasetStates,\n })\n : [],\n [availableSpec, datasetIds, datasetStates, showLegends],\n );\n\n return (\n <div className={cn('relative h-full w-full', className)}>\n {hasRenderingError ? (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center p-4\">\n <div className=\"max-w-sm rounded-md border border-red-200 bg-red-50/95 p-4 text-sm text-red-700 shadow-sm\">\n {`Map couldn't be rendered. Check the console for details.`}\n </div>\n </div>\n ) : null}\n\n <Map\n {...(mergedMapProps as object)}\n {...(initialViewState\n ? {initialViewState: initialViewState as object}\n : {})}\n style={{width: '100%', height: '100%', ...mapProps?.style}}\n >\n <DeckOverlayControl interleaved={interleaved} {...overlayDeckProps} />\n {children}\n </Map>\n\n {renderDatasetErrorOverlay(datasetStates)}\n {!hasRenderingError && showLegends ? (\n <div className=\"pointer-events-none absolute bottom-2 left-2 z-10 max-w-56\">\n <ColorScaleLegend legends={legends} />\n </div>\n ) : null}\n </div>\n );\n}\n"]}
|
|
1
|
+
{"version":3,"file":"DeckJsonMap.js","sourceRoot":"","sources":["../src/DeckJsonMap.tsx"],"names":[],"mappings":"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","sourcesContent":["import {JSONConverter} from '@deck.gl/json';\nimport {MapboxOverlay} from '@deck.gl/mapbox';\nimport {ColorScaleLegend} from '@sqlrooms/color-scales';\nimport {cn, ResolvedTheme, useTheme} from '@sqlrooms/ui';\nimport 'maplibre-gl/dist/maplibre-gl.css';\nimport {\n useCallback,\n useEffect,\n useImperativeHandle,\n useMemo,\n useRef,\n useState,\n type FC,\n} from 'react';\nimport {forwardRef} from 'react';\nimport Map, {useControl} from 'react-map-gl/maplibre';\nimport {ZodError} from 'zod';\nimport {DeckJsonMapSpec} from './DeckJsonMapSpec';\nimport {normalizeDatasets} from './datasets/normalizeDatasets';\nimport {usePreparedDatasetStates} from './datasets/usePreparedDatasetStates';\nimport {createDeckJsonConfiguration} from './json/createDeckJsonConfiguration';\nimport {extractColorScaleLegends} from './json/extractColorScaleLegends';\nimport {getLayerCompatibility} from './json/layerCompatibility';\nimport {resolveDatasetId} from './json/layerConfig';\nimport type {\n DeckJsonMapHandle,\n DeckJsonMapProps,\n PreparedDeckDatasetState,\n} from './types';\n\nconst DEFAULT_MAP_STYLES: Record<ResolvedTheme, string> = {\n light: 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json',\n dark: 'https://basemaps.cartocdn.com/gl/dark-matter-gl-style/style.json',\n};\n\nfunction parseSpec(spec: DeckJsonMapProps['spec']) {\n try {\n const parsedValue = typeof spec === 'string' ? JSON.parse(spec) : spec;\n const validatedSpec = DeckJsonMapSpec.safeParse(parsedValue);\n if (!validatedSpec.success) {\n return {\n spec: null,\n error: new Error(formatSpecValidationError(validatedSpec.error)),\n };\n }\n\n return {\n spec: validatedSpec.data,\n error: null,\n };\n } catch (error) {\n return {\n spec: null,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n}\n\nfunction formatSpecValidationError(error: ZodError) {\n const issues = error.issues.map((issue) => {\n const path = issue.path.length > 0 ? issue.path.join('.') : 'spec';\n return `${path}: ${issue.message}`;\n });\n\n return `Invalid DeckJsonMap spec. ${issues.join('; ')}`;\n}\n\nfunction extractFallbackDeckProps(spec: Record<string, unknown> | null) {\n if (!spec) {\n return {};\n }\n\n const fallbackProps: Record<string, unknown> = {};\n for (const key of ['initialViewState', 'viewState', 'controller']) {\n if (key in spec) {\n fallbackProps[key] = spec[key];\n }\n }\n\n return fallbackProps;\n}\n\nfunction filterUnavailableLayers(\n spec: Record<string, unknown>,\n datasetIds: string[],\n datasetStates: Record<string, PreparedDeckDatasetState>,\n) {\n const layers = Array.isArray(spec.layers) ? spec.layers : [];\n const filteredLayers = layers.filter((layer) => {\n if (!layer || typeof layer !== 'object') {\n return true;\n }\n\n const layerProps = layer as Record<string, unknown>;\n const layerName = String(layerProps['@@type'] ?? '');\n const compatibility = getLayerCompatibility(layerName);\n if (!compatibility) {\n return true;\n }\n\n const datasetId = resolveDatasetId(layerProps, datasetIds);\n if (!datasetId) {\n return true;\n }\n\n const datasetState = datasetStates[datasetId];\n if (!datasetState) {\n return false;\n }\n\n return datasetState.status !== 'error';\n });\n\n return {\n ...spec,\n layers: filteredLayers,\n };\n}\n\nfunction renderDatasetErrorOverlay(\n datasetStates: Record<string, PreparedDeckDatasetState>,\n) {\n const failedDatasets = Object.entries(datasetStates).filter(\n (\n entry,\n ): entry is [\n string,\n Extract<PreparedDeckDatasetState, {status: 'error'}>,\n ] => entry[1].status === 'error',\n );\n\n if (!failedDatasets.length) {\n return null;\n }\n\n return (\n <div className=\"pointer-events-none absolute inset-x-4 top-4 z-10 space-y-2\">\n {failedDatasets.map(([datasetId, state]) => (\n <div\n key={datasetId}\n className=\"rounded-md border border-red-200 bg-red-50/95 px-3 py-2 text-sm text-red-700 shadow-sm\"\n >\n {`Dataset \"${datasetId}\" failed: ${state.error.message}`}\n </div>\n ))}\n </div>\n );\n}\n\nconst TripsTimeControl: FC<{\n currentTime: number;\n maxTime: number;\n playing: boolean;\n onPlayPause: () => void;\n onSeek: (time: number) => void;\n}> = ({currentTime, maxTime, playing, onPlayPause, onSeek}) => {\n const [collapsed, setCollapsed] = useState(false);\n const progress = maxTime > 0 ? currentTime / maxTime : 0;\n\n if (collapsed) {\n return (\n <button\n type=\"button\"\n onClick={() => setCollapsed(false)}\n className=\"border-border/70 bg-background/90 text-muted-foreground hover:text-foreground pointer-events-auto flex w-fit items-center gap-1 rounded-md border px-2 py-1 text-[10px] leading-none font-medium shadow-sm backdrop-blur-sm transition-colors\"\n >\n Timeline\n </button>\n );\n }\n\n return (\n <div className=\"bg-background/90 border-border/70 pointer-events-auto relative flex items-center gap-1.5 rounded-md border px-2 py-1.5 pr-7 shadow-sm backdrop-blur-sm\">\n <button\n type=\"button\"\n onClick={onPlayPause}\n className=\"text-foreground hover:bg-accent flex h-5 w-5 shrink-0 items-center justify-center rounded\"\n title={playing ? 'Pause' : 'Play'}\n >\n {playing ? (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\">\n <rect x=\"1\" y=\"1\" width=\"3\" height=\"8\" />\n <rect x=\"6\" y=\"1\" width=\"3\" height=\"8\" />\n </svg>\n ) : (\n <svg width=\"10\" height=\"10\" viewBox=\"0 0 10 10\" fill=\"currentColor\">\n <polygon points=\"2,1 9,5 2,9\" />\n </svg>\n )}\n </button>\n <input\n type=\"range\"\n min={0}\n max={1000}\n value={Math.round(progress * 1000)}\n onChange={(e) => onSeek((Number(e.target.value) / 1000) * maxTime)}\n className=\"h-1 w-24 flex-1 cursor-pointer\"\n />\n <button\n type=\"button\"\n onClick={() => setCollapsed(true)}\n className=\"text-muted-foreground hover:text-foreground absolute top-1.5 right-1.5 flex h-4 w-4 items-center justify-center rounded-sm transition-colors\"\n title=\"Collapse\"\n >\n <svg width=\"8\" height=\"8\" viewBox=\"0 0 8 8\">\n <path\n d=\"M1 1L7 7M7 1L1 7\"\n stroke=\"currentColor\"\n strokeWidth=\"1.5\"\n strokeLinecap=\"round\"\n />\n </svg>\n </button>\n </div>\n );\n};\n\n// Workaround for deck.gl HeatmapLayer not releasing its WebGL framebuffer\n// when replaced by another layer type (e.g. switching heatmap → scatterplot).\n// We detect layer class changes and clear layers for one animation frame,\n// giving deck.gl a chance to finalize the old layer's resources before\n// initializing the new one.\nfunction DeckOverlayControl({\n interleaved,\n ...deckProps\n}: {interleaved: boolean} & Record<string, unknown>) {\n const overlay = useControl<MapboxOverlay>(\n () => new MapboxOverlay({interleaved}),\n );\n const prevLayerKeyRef = useRef<string>('');\n const [clearing, setClearing] = useState(false);\n\n const layers = deckProps.layers as unknown[] | undefined;\n const layerKey = Array.isArray(layers)\n ? layers\n .map((l) => {\n if (!l || typeof l !== 'object') return '?';\n const ctor = (l as {constructor?: {name?: string}}).constructor;\n return ctor?.name ?? '?';\n })\n .join(',')\n : '';\n\n useEffect(() => {\n if (\n prevLayerKeyRef.current &&\n prevLayerKeyRef.current !== layerKey &&\n !clearing\n ) {\n setClearing(true);\n }\n prevLayerKeyRef.current = layerKey;\n }, [layerKey, clearing]);\n\n useEffect(() => {\n if (clearing && overlay) {\n overlay.setProps({layers: []});\n requestAnimationFrame(() => setClearing(false));\n }\n }, [clearing, overlay]);\n\n if (!clearing && overlay) {\n overlay.setProps(deckProps);\n }\n\n return null;\n}\n\nexport const DeckJsonMap = forwardRef<DeckJsonMapHandle, DeckJsonMapProps>(\n function DeckJsonMap(\n {\n spec,\n datasets,\n mapStyle,\n interleaved = true,\n deckProps,\n mapProps,\n showLegends = true,\n className,\n children,\n onDatasetStatesChange,\n onRenderingError,\n },\n ref,\n ) {\n const normalizedDatasets = useMemo(\n () => normalizeDatasets(datasets),\n [datasets],\n );\n const datasetIds = useMemo(\n () => Object.keys(normalizedDatasets),\n [normalizedDatasets],\n );\n const datasetStates = usePreparedDatasetStates(normalizedDatasets);\n const onDatasetStatesChangeRef = useRef(onDatasetStatesChange);\n\n const {spec: parsedSpec, error: specError} = useMemo(\n () => parseSpec(spec),\n [spec],\n );\n\n const availableSpec = useMemo(\n () =>\n parsedSpec\n ? filterUnavailableLayers(parsedSpec, datasetIds, datasetStates)\n : null,\n [parsedSpec, datasetIds, datasetStates],\n );\n\n const converter = useMemo(\n () =>\n new JSONConverter({\n configuration: createDeckJsonConfiguration({\n datasetStates,\n datasetIds,\n }),\n onJSONChange: () => {},\n }),\n [datasetIds, datasetStates],\n );\n\n const convertedDeckPropsResult = useMemo(() => {\n if (!availableSpec) {\n return {props: null, error: specError};\n }\n\n try {\n return {\n props: converter.convert(availableSpec) as Record<string, unknown>,\n error: null,\n };\n } catch (error) {\n return {\n props: null,\n error: error instanceof Error ? error : new Error(String(error)),\n };\n }\n }, [availableSpec, converter, specError]);\n\n // Animation for TripsLayer — update currentTime on each frame.\n // Only activate when the trips dataset is ready to avoid spinning the RAF\n // loop while data is still loading.\n const hasTripsLayer = useMemo(() => {\n if (!availableSpec || !Array.isArray(availableSpec.layers)) return false;\n return availableSpec.layers.some((l: unknown) => {\n if (\n !l ||\n typeof l !== 'object' ||\n (l as {'@@type'?: string})['@@type'] !== 'GeoArrowTripsLayer'\n )\n return false;\n const dsId = resolveDatasetId(l as Record<string, unknown>, datasetIds);\n if (!dsId) return true;\n const state = datasetStates[dsId];\n return state?.status === 'ready';\n });\n }, [availableSpec, datasetIds, datasetStates]);\n\n const [tripsTime, setTripsTime] = useState(0);\n const [tripsPlaying, setTripsPlaying] = useState(true);\n const tripsTimeRef = useRef(0);\n\n useEffect(() => {\n if (!hasTripsLayer || !tripsPlaying) return;\n let raf: number;\n let lastFrame = Date.now();\n const tick = () => {\n const now = Date.now();\n tripsTimeRef.current += now - lastFrame;\n lastFrame = now;\n setTripsTime(tripsTimeRef.current);\n raf = requestAnimationFrame(tick);\n };\n lastFrame = Date.now();\n raf = requestAnimationFrame(tick);\n return () => cancelAnimationFrame(raf);\n }, [hasTripsLayer, tripsPlaying]);\n\n const handleTripsPlayPause = useCallback(() => {\n setTripsPlaying((p) => !p);\n }, []);\n\n const handleTripsSeek = useCallback((time: number) => {\n const rawTime = time * 30;\n tripsTimeRef.current = rawTime;\n setTripsTime(rawTime);\n }, []);\n\n const finalDeckPropsResult = convertedDeckPropsResult;\n\n const onRenderingErrorRef = useRef(onRenderingError);\n useEffect(() => {\n onRenderingErrorRef.current = onRenderingError;\n }, [onRenderingError]);\n\n useEffect(() => {\n if (finalDeckPropsResult.error) {\n console.error(finalDeckPropsResult.error);\n onRenderingErrorRef.current?.(finalDeckPropsResult.error);\n }\n }, [finalDeckPropsResult.error]);\n\n useEffect(() => {\n onDatasetStatesChangeRef.current = onDatasetStatesChange;\n }, [onDatasetStatesChange]);\n\n useEffect(() => {\n onDatasetStatesChangeRef.current?.(datasetStates);\n }, [datasetStates]);\n\n const fallbackDeckProps = useMemo(\n () => extractFallbackDeckProps(availableSpec),\n [availableSpec],\n );\n const convertedDeckProps = (finalDeckPropsResult.props ??\n fallbackDeckProps ??\n {}) as Record<string, unknown>;\n const extraDeckProps = (deckProps ?? {}) as Record<string, unknown>;\n const extraMapProps = (mapProps ?? {}) as Record<string, unknown>;\n const hasRenderingError = Boolean(finalDeckPropsResult.error);\n\n // Separate stable layer analysis from per-frame animation to avoid\n // re-running .map() over all layers 60 times per second.\n const baseLayers = useMemo(() => {\n const mergedLayers = hasRenderingError\n ? []\n : (deckProps?.layers ??\n (convertedDeckProps.layers as unknown[] | undefined) ??\n []);\n if (!hasTripsLayer || !Array.isArray(mergedLayers))\n return {layers: mergedLayers, maxTs: 0, tripsIndices: [] as number[]};\n let computedMaxTs = 0;\n const tripsIndices: number[] = [];\n for (let i = 0; i < mergedLayers.length; i++) {\n const layer = mergedLayers[i];\n if (!layer || typeof layer !== 'object') continue;\n const layerObj = layer as {props?: Record<string, unknown>};\n const maxTs = (layerObj.props as Record<string, unknown> | undefined)\n ?._tripsMaxTimestamp as number | undefined;\n if (maxTs && maxTs > 0) {\n computedMaxTs = Math.max(computedMaxTs, maxTs);\n tripsIndices.push(i);\n }\n }\n return {layers: mergedLayers, maxTs: computedMaxTs, tripsIndices};\n }, [\n hasTripsLayer,\n hasRenderingError,\n deckProps?.layers,\n convertedDeckProps.layers,\n ]);\n\n // Per-frame: only clone trips layers with updated currentTime\n const animatedLayers = useMemo(() => {\n const {layers, tripsIndices} = baseLayers;\n if (!tripsIndices.length) return layers;\n if (!Array.isArray(layers)) return layers;\n const result = layers.slice();\n for (const idx of tripsIndices) {\n const layer = result[idx];\n if (!layer || typeof layer !== 'object') continue;\n const layerObj = layer as {\n props?: Record<string, unknown>;\n clone?: (props: Record<string, unknown>) => unknown;\n };\n if (!layerObj.clone) continue;\n const props = layerObj.props as Record<string, unknown> | undefined;\n const maxTs = (props?._tripsMaxTimestamp as number | undefined) ?? 0;\n const trailFraction =\n (props?._trailLengthFactor as number | undefined) ?? 0.4;\n const widthPx = (props?.widthMinPixels as number | undefined) ?? 3;\n const animationSpeed = 30;\n const currentTime = (tripsTime / animationSpeed) % maxTs;\n result[idx] = layerObj.clone({\n currentTime,\n trailLength: maxTs * trailFraction,\n widthMinPixels: widthPx,\n });\n }\n return result;\n }, [baseLayers, tripsTime]);\n const tripsMaxTimeValue = baseLayers.maxTs;\n\n const tripsCurrentTime = useMemo(() => {\n if (!tripsMaxTimeValue || tripsMaxTimeValue <= 0) return 0;\n return (tripsTime / 30) % tripsMaxTimeValue;\n }, [tripsTime, tripsMaxTimeValue]);\n\n const mergedDeckProps = {\n ...convertedDeckProps,\n ...extraDeckProps,\n layers: animatedLayers,\n };\n\n // overlayDeckProps should not contain viewState/initialViewState\n const {\n initialViewState,\n viewState: _vs,\n onViewStateChange: _onViewStateChange,\n ...overlayDeckProps\n } = mergedDeckProps as Record<string, unknown>;\n void _vs;\n void _onViewStateChange;\n\n const mapRef = useRef<{jumpTo: (opts: object) => void} | null>(null);\n const pendingJumpRef = useRef<object | null>(null);\n\n useImperativeHandle(\n ref,\n () => ({\n jumpTo(opts) {\n const jumpOpts = {\n center: [opts.longitude, opts.latitude] as [number, number],\n zoom: opts.zoom,\n bearing: opts.bearing ?? 0,\n pitch: opts.pitch ?? 0,\n };\n if (mapRef.current) {\n mapRef.current.jumpTo(jumpOpts);\n } else {\n pendingJumpRef.current = jumpOpts;\n }\n },\n }),\n [],\n );\n\n const handleMapLoad = useCallback(\n (event?: unknown) => {\n if (pendingJumpRef.current && mapRef.current) {\n mapRef.current.jumpTo(pendingJumpRef.current);\n pendingJumpRef.current = null;\n }\n const externalOnLoad = (\n extraMapProps as {onLoad?: (e?: unknown) => void}\n ).onLoad;\n externalOnLoad?.(event);\n },\n [extraMapProps],\n );\n\n const {resolvedTheme} = useTheme();\n\n const mergedMapProps = {\n ...extraMapProps,\n mapStyle:\n mapStyle ?? mapProps?.mapStyle ?? DEFAULT_MAP_STYLES[resolvedTheme],\n };\n const legends = useMemo(\n () =>\n showLegends\n ? extractColorScaleLegends({\n spec: availableSpec,\n datasetIds,\n datasetStates,\n })\n : [],\n [availableSpec, datasetIds, datasetStates, showLegends],\n );\n\n return (\n <div className={cn('relative h-full w-full', className)}>\n {hasRenderingError && !onRenderingError ? (\n <div className=\"absolute inset-0 z-10 flex items-center justify-center p-4\">\n <div className=\"max-w-sm rounded-md border border-red-200 bg-red-50/95 p-4 text-sm text-red-700 shadow-sm\">\n {`Map couldn't be rendered. Check the console for details.`}\n </div>\n </div>\n ) : null}\n\n <Map\n ref={mapRef as any}\n {...(mergedMapProps as object)}\n {...(initialViewState\n ? {initialViewState: initialViewState as object}\n : {})}\n onLoad={handleMapLoad}\n style={{width: '100%', height: '100%', ...mapProps?.style}}\n >\n <DeckOverlayControl interleaved={interleaved} {...overlayDeckProps} />\n {children}\n </Map>\n\n {renderDatasetErrorOverlay(datasetStates)}\n {!hasRenderingError && showLegends ? (\n <div className=\"pointer-events-none absolute bottom-2 left-2 z-10 max-w-56\">\n {hasTripsLayer && tripsMaxTimeValue > 0 ? (\n <div className=\"mb-1\">\n <TripsTimeControl\n currentTime={tripsCurrentTime}\n maxTime={tripsMaxTimeValue}\n playing={tripsPlaying}\n onPlayPause={handleTripsPlayPause}\n onSeek={handleTripsSeek}\n />\n </div>\n ) : null}\n <ColorScaleLegend legends={legends} />\n </div>\n ) : hasTripsLayer && tripsMaxTimeValue > 0 ? (\n <div className=\"pointer-events-none absolute bottom-2 left-2 z-10\">\n <TripsTimeControl\n currentTime={tripsCurrentTime}\n maxTime={tripsMaxTimeValue}\n playing={tripsPlaying}\n onPlayPause={handleTripsPlayPause}\n onSeek={handleTripsSeek}\n />\n </div>\n ) : null}\n </div>\n );\n },\n);\n"]}
|