@sqlrooms/deck 0.29.0-rc.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.
Files changed (86) hide show
  1. package/README.md +357 -0
  2. package/dist/ColorScaleLegend.d.ts +8 -0
  3. package/dist/ColorScaleLegend.d.ts.map +1 -0
  4. package/dist/ColorScaleLegend.js +11 -0
  5. package/dist/ColorScaleLegend.js.map +1 -0
  6. package/dist/DeckMap.d.ts +4 -0
  7. package/dist/DeckMap.d.ts.map +1 -0
  8. package/dist/DeckMap.js +157 -0
  9. package/dist/DeckMap.js.map +1 -0
  10. package/dist/datasets/normalizeDatasets.d.ts +5 -0
  11. package/dist/datasets/normalizeDatasets.d.ts.map +1 -0
  12. package/dist/datasets/normalizeDatasets.js +57 -0
  13. package/dist/datasets/normalizeDatasets.js.map +1 -0
  14. package/dist/datasets/usePreparedDeckDatasets.d.ts +3 -0
  15. package/dist/datasets/usePreparedDeckDatasets.d.ts.map +1 -0
  16. package/dist/datasets/usePreparedDeckDatasets.js +61 -0
  17. package/dist/datasets/usePreparedDeckDatasets.js.map +1 -0
  18. package/dist/index.d.ts +11 -0
  19. package/dist/index.d.ts.map +1 -0
  20. package/dist/index.js +9 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/json/compileColorScale.d.ts +35 -0
  23. package/dist/json/compileColorScale.d.ts.map +1 -0
  24. package/dist/json/compileColorScale.js +527 -0
  25. package/dist/json/compileColorScale.js.map +1 -0
  26. package/dist/json/compileGeoArrowAccessor.d.ts +11 -0
  27. package/dist/json/compileGeoArrowAccessor.d.ts.map +1 -0
  28. package/dist/json/compileGeoArrowAccessor.js +74 -0
  29. package/dist/json/compileGeoArrowAccessor.js.map +1 -0
  30. package/dist/json/createDeckJsonConfiguration.d.ts +9 -0
  31. package/dist/json/createDeckJsonConfiguration.d.ts.map +1 -0
  32. package/dist/json/createDeckJsonConfiguration.js +112 -0
  33. package/dist/json/createDeckJsonConfiguration.js.map +1 -0
  34. package/dist/json/defaultClasses.d.ts +24 -0
  35. package/dist/json/defaultClasses.d.ts.map +1 -0
  36. package/dist/json/defaultClasses.js +21 -0
  37. package/dist/json/defaultClasses.js.map +1 -0
  38. package/dist/json/extractColorScaleLegends.d.ts +8 -0
  39. package/dist/json/extractColorScaleLegends.d.ts.map +1 -0
  40. package/dist/json/extractColorScaleLegends.js +48 -0
  41. package/dist/json/extractColorScaleLegends.js.map +1 -0
  42. package/dist/json/layerCompatibility.d.ts +15 -0
  43. package/dist/json/layerCompatibility.d.ts.map +1 -0
  44. package/dist/json/layerCompatibility.js +38 -0
  45. package/dist/json/layerCompatibility.js.map +1 -0
  46. package/dist/json/layerConfig.d.ts +10 -0
  47. package/dist/json/layerConfig.d.ts.map +1 -0
  48. package/dist/json/layerConfig.js +43 -0
  49. package/dist/json/layerConfig.js.map +1 -0
  50. package/dist/json/rewriteGeoArrowAccessors.d.ts +7 -0
  51. package/dist/json/rewriteGeoArrowAccessors.d.ts.map +1 -0
  52. package/dist/json/rewriteGeoArrowAccessors.js +53 -0
  53. package/dist/json/rewriteGeoArrowAccessors.js.map +1 -0
  54. package/dist/prepare/detectGeometryColumn.d.ts +10 -0
  55. package/dist/prepare/detectGeometryColumn.d.ts.map +1 -0
  56. package/dist/prepare/detectGeometryColumn.js +84 -0
  57. package/dist/prepare/detectGeometryColumn.js.map +1 -0
  58. package/dist/prepare/geoarrow.d.ts +18 -0
  59. package/dist/prepare/geoarrow.d.ts.map +1 -0
  60. package/dist/prepare/geoarrow.js +114 -0
  61. package/dist/prepare/geoarrow.js.map +1 -0
  62. package/dist/prepare/geometryDecoder.d.ts +9 -0
  63. package/dist/prepare/geometryDecoder.d.ts.map +1 -0
  64. package/dist/prepare/geometryDecoder.js +2 -0
  65. package/dist/prepare/geometryDecoder.js.map +1 -0
  66. package/dist/prepare/prepareDeckDataset.d.ts +11 -0
  67. package/dist/prepare/prepareDeckDataset.d.ts.map +1 -0
  68. package/dist/prepare/prepareDeckDataset.js +66 -0
  69. package/dist/prepare/prepareDeckDataset.js.map +1 -0
  70. package/dist/prepare/toGeoJsonBinary.d.ts +9 -0
  71. package/dist/prepare/toGeoJsonBinary.d.ts.map +1 -0
  72. package/dist/prepare/toGeoJsonBinary.js +25 -0
  73. package/dist/prepare/toGeoJsonBinary.js.map +1 -0
  74. package/dist/prepare/types.d.ts +25 -0
  75. package/dist/prepare/types.d.ts.map +1 -0
  76. package/dist/prepare/types.js +2 -0
  77. package/dist/prepare/types.js.map +1 -0
  78. package/dist/prepare/wkbDecoder.d.ts +3 -0
  79. package/dist/prepare/wkbDecoder.d.ts.map +1 -0
  80. package/dist/prepare/wkbDecoder.js +106 -0
  81. package/dist/prepare/wkbDecoder.js.map +1 -0
  82. package/dist/types.d.ts +115 -0
  83. package/dist/types.d.ts.map +1 -0
  84. package/dist/types.js +2 -0
  85. package/dist/types.js.map +1 -0
  86. package/package.json +62 -0
package/README.md ADDED
@@ -0,0 +1,357 @@
1
+ Deck.gl integration for SQLRooms with JSON-driven map specs, dataset registry
2
+ binding, DuckDB-backed or in-memory Arrow datasets, and GeoArrow-first geometry
3
+ preparation.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ npm install @sqlrooms/deck @sqlrooms/duckdb @sqlrooms/ui
9
+ ```
10
+
11
+ ## What This Package Does
12
+
13
+ `@sqlrooms/deck` is the JSON-spec bridge between SQLRooms data and deck.gl:
14
+
15
+ - render a DeckGL map from a serializable `DeckJsonMap` spec
16
+ - bind one or more datasets through a `datasets` registry
17
+ - generate starter JSON specs from datasets with `createDeckJsonSpecFromDatasets`
18
+ - validate SQLRooms-specific layer bindings under `_sqlroomsBinding`
19
+ - prepare geometry for GeoArrow-native layers from
20
+ [`@geoarrow/deck.gl-layers`](https://github.com/geoarrow/deck.gl-layers)
21
+ and GeoJSON fallback layers
22
+ - support shared declarative color scales through `@sqlrooms/color-scales`
23
+
24
+ Use this package when you want deck.gl layers to be driven by a JSON-like spec
25
+ instead of hand-constructing deck layer instances in React code.
26
+
27
+ ## Quick Start
28
+
29
+ ```tsx
30
+ import {DeckJsonMap} from '@sqlrooms/deck';
31
+
32
+ const spec = {
33
+ initialViewState: {
34
+ longitude: -122.4,
35
+ latitude: 37.74,
36
+ zoom: 10,
37
+ pitch: 0,
38
+ bearing: 0,
39
+ },
40
+ controller: true,
41
+ layers: [
42
+ {
43
+ '@@type': 'GeoArrowScatterplotLayer',
44
+ id: 'airports',
45
+ _sqlroomsBinding: {
46
+ dataset: 'airports',
47
+ geometryColumn: 'geom',
48
+ },
49
+ getFillColor: {
50
+ '@@function': 'colorScale',
51
+ field: 'scalerank',
52
+ type: 'sequential',
53
+ scheme: 'YlOrRd',
54
+ domain: 'auto',
55
+ },
56
+ getRadius: '@@=6',
57
+ radiusMinPixels: 2,
58
+ },
59
+ ],
60
+ };
61
+
62
+ export function AirportsMap() {
63
+ return (
64
+ <DeckJsonMap
65
+ spec={spec}
66
+ datasets={{
67
+ airports: {
68
+ sqlQuery:
69
+ 'SELECT name, abbrev, scalerank, ST_AsWKB(geom) AS geom FROM airports',
70
+ geometryColumn: 'geom',
71
+ geometryEncodingHint: 'wkb',
72
+ },
73
+ }}
74
+ mapStyle="https://basemaps.cartocdn.com/gl/positron-gl-style/style.json"
75
+ />
76
+ );
77
+ }
78
+ ```
79
+
80
+ ## Auto Spec Generation
81
+
82
+ If you want a starter JSON spec instead of writing every layer manually, use
83
+ `createDeckJsonSpecFromDatasets(...)`:
84
+
85
+ ```tsx
86
+ import {createDeckJsonSpecFromDatasets, DeckJsonMap} from '@sqlrooms/deck';
87
+
88
+ const datasets = {
89
+ earthquakes: {
90
+ arrowTable,
91
+ geometryColumn: 'geom',
92
+ geometryEncodingHint: 'wkb',
93
+ },
94
+ };
95
+
96
+ const spec = createDeckJsonSpecFromDatasets({datasets});
97
+ ```
98
+
99
+ By default, the helper is conservative:
100
+
101
+ - point / multipoint -> `GeoArrowScatterplotLayer`
102
+ - linestring / multilinestring -> `GeoArrowPathLayer`
103
+ - polygon / multipolygon -> `GeoArrowPolygonLayer`
104
+ - mixed, unknown, or unsupported -> `GeoJsonLayer`
105
+
106
+ You can provide semantic hints for special layers:
107
+
108
+ ```tsx
109
+ const spec = createDeckJsonSpecFromDatasets({
110
+ datasets,
111
+ hints: {
112
+ earthquakes: {prefer: 'heatmap'},
113
+ trips: {
114
+ type: 'GeoArrowTripsLayer',
115
+ timestampColumn: 'timestamps',
116
+ },
117
+ flows: {
118
+ type: 'GeoArrowArcLayer',
119
+ sourceGeometryColumn: 'source_geom',
120
+ targetGeometryColumn: 'target_geom',
121
+ },
122
+ hexes: {
123
+ type: 'GeoArrowH3HexagonLayer',
124
+ hexagonColumn: 'h3',
125
+ },
126
+ },
127
+ });
128
+ ```
129
+
130
+ ## Core Concepts
131
+
132
+ ### `DeckJsonMap`
133
+
134
+ `DeckJsonMap` is the main React component exported by this package. It takes:
135
+
136
+ - `spec`: a JSON-like deck.gl spec object or JSON string
137
+ - `datasets`: a dataset registry keyed by dataset id
138
+ - `deckProps`: runtime-only deck props such as `getTooltip`, `onHover`, `onClick`
139
+ - `mapProps`: runtime-only MapLibre props
140
+ - `showLegends`: whether SQLRooms-generated color legends should render
141
+
142
+ `spec` stays serializable; callbacks and runtime behavior belong in `deckProps`
143
+ or `mapProps`.
144
+
145
+ ### Dataset Registry
146
+
147
+ Each SQLRooms-managed layer binds to exactly one dataset through
148
+ `_sqlroomsBinding.dataset`.
149
+
150
+ ```tsx
151
+ <DeckJsonMap
152
+ spec={spec}
153
+ datasets={{
154
+ earthquakes: {sqlQuery: 'SELECT * FROM earthquakes'},
155
+ faults: {sqlQuery: 'SELECT * FROM faults'},
156
+ }}
157
+ />
158
+ ```
159
+
160
+ Dataset ids are layer-binding labels. Internally, prepared geometry is cached
161
+ by the resolved data identity, not by dataset id, so multiple maps or layers
162
+ can reuse the same preparation work when they point at the same table/query.
163
+
164
+ ### Dataset Input Kinds
165
+
166
+ Each dataset entry is one of:
167
+
168
+ ```tsx
169
+ datasets={{
170
+ airports: {
171
+ sqlQuery: 'SELECT * FROM airports',
172
+ geometryColumn: 'geom',
173
+ geometryEncodingHint: 'wkb',
174
+ },
175
+ preview: {
176
+ arrowTable,
177
+ geometryColumn: 'geom',
178
+ geometryEncodingHint: 'wkb',
179
+ },
180
+ }}
181
+ ```
182
+
183
+ - `sqlQuery`
184
+ Runs through the DuckDB slice execution path.
185
+ - `arrowTable`
186
+ Uses an already available Apache Arrow table. This is the right input for
187
+ Arrow-native SQLRooms hooks such as `useSql` and `useMosaicClient`.
188
+
189
+ For in-memory Arrow datasets, `arrowTable` may be temporarily `undefined` while
190
+ data is still loading. `DeckJsonMap` will keep rendering the basemap and treat
191
+ that dataset as loading until a table is provided.
192
+
193
+ ## SQLRooms Layer Bindings
194
+
195
+ SQLRooms-specific layer metadata lives under `_sqlroomsBinding`:
196
+
197
+ ```tsx
198
+ {
199
+ '@@type': 'GeoArrowScatterplotLayer',
200
+ id: 'earthquakes',
201
+ _sqlroomsBinding: {
202
+ dataset: 'earthquakes',
203
+ geometryColumn: 'geom',
204
+ geometryEncodingHint: 'wkb',
205
+ },
206
+ getFillColor: {
207
+ '@@function': 'colorScale',
208
+ field: 'Magnitude',
209
+ type: 'sequential',
210
+ scheme: 'YlOrRd',
211
+ domain: 'auto',
212
+ },
213
+ }
214
+ ```
215
+
216
+ Currently supported SQLRooms binding fields are:
217
+
218
+ - `dataset`: binds the layer to one dataset id
219
+ - `geometryColumn`: overrides geometry column detection for that layer
220
+ - `geometryEncodingHint`: helps geometry detection when the source table needs it
221
+ - `sourceGeometryColumn`: source point geometry for `GeoArrowArcLayer`
222
+ - `targetGeometryColumn`: target point geometry for `GeoArrowArcLayer`
223
+ - `timestampColumn`: timestamp list column for `GeoArrowTripsLayer`
224
+ - `hexagonColumn`: H3 index column for `GeoArrowH3HexagonLayer`
225
+
226
+ The surrounding deck spec remains intentionally loose so normal deck.gl JSON
227
+ props still pass through, while `_sqlroomsBinding` is validated strictly.
228
+
229
+ ## Color Scales and Legends
230
+
231
+ You can ask SQLRooms to derive colors from a field with the
232
+ `colorScale` JSON function instead of writing long `@@=` color
233
+ expressions:
234
+
235
+ ```tsx
236
+ getFillColor: {
237
+ '@@function': 'colorScale',
238
+ field: 'Magnitude',
239
+ type: 'sequential',
240
+ scheme: 'YlOrRd',
241
+ domain: 'auto',
242
+ clamp: true,
243
+ }
244
+ ```
245
+
246
+ Discrete numeric palettes are supported too:
247
+
248
+ ```tsx
249
+ getFillColor: {
250
+ '@@function': 'colorScale',
251
+ field: 'Magnitude',
252
+ type: 'quantize',
253
+ scheme: 'PuBuGn',
254
+ domain: [0, 8],
255
+ bins: 5,
256
+ }
257
+ ```
258
+
259
+ `DeckJsonMap` renders SQLRooms-generated legends by default for layers that use
260
+ `colorScale`. To disable them globally:
261
+
262
+ ```tsx
263
+ <DeckJsonMap spec={spec} datasets={datasets} showLegends={false} />
264
+ ```
265
+
266
+ To override the title:
267
+
268
+ ```tsx
269
+ getFillColor: {
270
+ '@@function': 'colorScale',
271
+ field: 'Magnitude',
272
+ type: 'sequential',
273
+ scheme: 'YlOrRd',
274
+ domain: 'auto',
275
+ legend: {
276
+ title: 'Magnitude (Mw)',
277
+ },
278
+ }
279
+ ```
280
+
281
+ Supported scale types come from `@sqlrooms/color-scales`:
282
+
283
+ - `sequential`
284
+ - `diverging`
285
+ - `quantize`
286
+ - `quantile`
287
+ - `threshold`
288
+ - `categorical`
289
+
290
+ When `domain` is set to `'auto'`, the domain is computed from the currently
291
+ bound dataset, so colors may shift as filters change. Use explicit domains when
292
+ you want colors to stay stable across filtering.
293
+
294
+ ## Geometry Preparation
295
+
296
+ `prepareDeckDataset(...)` is the deck-specific preparation step behind the
297
+ scenes. It accepts resolved Arrow tables with geometry stored as:
298
+
299
+ - native GeoArrow
300
+ - WKB / GeoArrow WKB
301
+ - WKT / GeoArrow WKT
302
+
303
+ It then produces canonical deck-facing geometry outputs for:
304
+
305
+ - GeoArrow-native layers such as `GeoArrowScatterplotLayer`
306
+ - GeoJSON-binary fallback layers such as `GeoJsonLayer`
307
+
308
+ Specialized layers such as `GeoArrowArcLayer`, `GeoArrowTripsLayer`, and
309
+ `GeoArrowH3HexagonLayer` reuse the prepared table but bind additional
310
+ configured columns on top for source/target geometry,
311
+ timestamps, or index cells.
312
+
313
+ This work is cached internally in a module-global prepared dataset store. That
314
+ cache is separate from any upstream query cache:
315
+
316
+ - Mosaic-driven queries already benefit from Mosaic's own query cache
317
+ - DuckDB SQL datasets still use the DuckDB slice execution path
318
+
319
+ Deck caches only the expensive geometry preparation layer on top.
320
+
321
+ ## Supported Layers
322
+
323
+ The current curated layer set is:
324
+
325
+ - `GeoArrowScatterplotLayer`
326
+ - `GeoArrowHeatmapLayer`
327
+ - `GeoArrowColumnLayer`
328
+ - `GeoArrowPathLayer`
329
+ - `GeoArrowPolygonLayer`
330
+ - `GeoArrowSolidPolygonLayer`
331
+ - `GeoArrowArcLayer`
332
+ - `GeoArrowTripsLayer`
333
+ - `GeoArrowH3HexagonLayer`
334
+ - `GeoJsonLayer`
335
+
336
+ GeoArrow-native geometry columns are the efficient path. WKB/WKT geometry falls
337
+ back to decoding and GeoJSON-binary preparation, with point promotion available
338
+ for point-focused GeoArrow layers such as `GeoArrowScatterplotLayer`,
339
+ `GeoArrowHeatmapLayer`, and `GeoArrowColumnLayer`.
340
+
341
+ The GeoArrow layer implementations themselves come from
342
+ [`@geoarrow/deck.gl-layers`](https://github.com/geoarrow/deck.gl-layers).
343
+
344
+ When querying DuckDB spatial `GEOMETRY` columns directly, convert them first
345
+ with `ST_AsWKB(...)` or `ST_AsText(...)`. DuckDB's internal geometry payload is
346
+ not the same as standard WKB.
347
+
348
+ ## Runtime Props and Children
349
+
350
+ Keep the spec serializable, then pass runtime behavior separately:
351
+
352
+ - `deckProps` for deck callbacks such as `getTooltip`, `onHover`, `onClick`
353
+ - `mapProps` for MapLibre props such as `projection`
354
+ - `children` for controls, overlays, and popups rendered inside the map
355
+
356
+ This lets the spec stay stable for storage, validation, and future AI-assisted
357
+ generation while still supporting interactive React behavior at runtime.
@@ -0,0 +1,8 @@
1
+ import type { ResolvedColorLegend } from './json/compileColorScale';
2
+ type ColorScaleLegendProps = {
3
+ legends: ResolvedColorLegend[];
4
+ className?: string;
5
+ };
6
+ export declare function ColorScaleLegend({ legends, className }: ColorScaleLegendProps): import("react/jsx-runtime").JSX.Element | null;
7
+ export {};
8
+ //# sourceMappingURL=ColorScaleLegend.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorScaleLegend.d.ts","sourceRoot":"","sources":["../src/ColorScaleLegend.tsx"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAC,mBAAmB,EAAC,MAAM,0BAA0B,CAAC;AAElE,KAAK,qBAAqB,GAAG;IAC3B,OAAO,EAAE,mBAAmB,EAAE,CAAC;IAC/B,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,CAAC;AAEF,wBAAgB,gBAAgB,CAAC,EAAC,OAAO,EAAE,SAAS,EAAC,EAAE,qBAAqB,kDA8D3E"}
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { cn } from '@sqlrooms/ui';
3
+ export function ColorScaleLegend({ legends, className }) {
4
+ if (!legends.length) {
5
+ return null;
6
+ }
7
+ return (_jsx("div", { className: cn('pointer-events-none absolute bottom-2 left-2 z-10 flex max-w-56 flex-col gap-3', className), children: legends.map((legend) => (_jsxs("div", { className: "bg-background/70 rounded-md px-3 py-2 shadow-sm backdrop-blur-sm", children: [_jsx("div", { className: "text-foreground mb-2 text-xs font-semibold", children: legend.title }), legend.type === 'continuous' ? (_jsxs("div", { children: [_jsx("div", { className: "h-3 rounded-sm border", style: { background: legend.gradient } }), _jsx("div", { className: "text-muted-foreground mt-1 flex justify-between gap-2 text-[0.6rem]", children: legend.ticks.map((tick) => (_jsx("span", { className: cn(tick.offset === 50 ? 'text-center' : '', tick.offset === 100 ? 'text-right' : ''), children: tick.label }, `${legend.title}-${tick.offset}-${tick.label}`))) })] })) : (_jsx("div", { className: "space-y-1.5", children: legend.items.map((item) => (_jsxs("div", { className: "text-foreground flex items-center gap-2 text-xs", children: [_jsx("span", { className: "h-3 w-3 rounded-sm border", style: {
8
+ backgroundColor: `rgba(${item.color[0]}, ${item.color[1]}, ${item.color[2]}, ${item.color[3] / 255})`,
9
+ } }), _jsx("span", { className: "truncate", children: item.label })] }, `${legend.title}-${item.label}`))) }))] }, `${legend.title}-${legend.type}`))) }));
10
+ }
11
+ //# sourceMappingURL=ColorScaleLegend.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"ColorScaleLegend.js","sourceRoot":"","sources":["../src/ColorScaleLegend.tsx"],"names":[],"mappings":";AAAA,OAAO,EAAC,EAAE,EAAC,MAAM,cAAc,CAAC;AAQhC,MAAM,UAAU,gBAAgB,CAAC,EAAC,OAAO,EAAE,SAAS,EAAwB;IAC1E,IAAI,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,CACL,cACE,SAAS,EAAE,EAAE,CACX,gFAAgF,EAChF,SAAS,CACV,YAEA,OAAO,CAAC,GAAG,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,CACvB,eAEE,SAAS,EAAC,kEAAkE,aAE5E,cAAK,SAAS,EAAC,4CAA4C,YACxD,MAAM,CAAC,KAAK,GACT,EACL,MAAM,CAAC,IAAI,KAAK,YAAY,CAAC,CAAC,CAAC,CAC9B,0BACE,cACE,SAAS,EAAC,uBAAuB,EACjC,KAAK,EAAE,EAAC,UAAU,EAAE,MAAM,CAAC,QAAQ,EAAC,GACpC,EACF,cAAK,SAAS,EAAC,qEAAqE,YACjF,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC1B,eAEE,SAAS,EAAE,EAAE,CACX,IAAI,CAAC,MAAM,KAAK,EAAE,CAAC,CAAC,CAAC,aAAa,CAAC,CAAC,CAAC,EAAE,EACvC,IAAI,CAAC,MAAM,KAAK,GAAG,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,CACxC,YAEA,IAAI,CAAC,KAAK,IANN,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,MAAM,IAAI,IAAI,CAAC,KAAK,EAAE,CAO9C,CACR,CAAC,GACE,IACF,CACP,CAAC,CAAC,CAAC,CACF,cAAK,SAAS,EAAC,aAAa,YACzB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,CAC1B,eAEE,SAAS,EAAC,iDAAiD,aAE3D,eACE,SAAS,EAAC,2BAA2B,EACrC,KAAK,EAAE;oCACL,eAAe,EAAE,QAAQ,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,GAAG,GAAG;iCACtG,GACD,EACF,eAAM,SAAS,EAAC,UAAU,YAAE,IAAI,CAAC,KAAK,GAAQ,KATzC,GAAG,MAAM,CAAC,KAAK,IAAI,IAAI,CAAC,KAAK,EAAE,CAUhC,CACP,CAAC,GACE,CACP,KA3CI,GAAG,MAAM,CAAC,KAAK,IAAI,MAAM,CAAC,IAAI,EAAE,CA4CjC,CACP,CAAC,GACE,CACP,CAAC;AACJ,CAAC","sourcesContent":["import {cn} from '@sqlrooms/ui';\nimport type {ResolvedColorLegend} from './json/compileColorScale';\n\ntype ColorScaleLegendProps = {\n legends: ResolvedColorLegend[];\n className?: string;\n};\n\nexport function ColorScaleLegend({legends, className}: ColorScaleLegendProps) {\n if (!legends.length) {\n return null;\n }\n\n return (\n <div\n className={cn(\n 'pointer-events-none absolute bottom-2 left-2 z-10 flex max-w-56 flex-col gap-3',\n className,\n )}\n >\n {legends.map((legend) => (\n <div\n key={`${legend.title}-${legend.type}`}\n className=\"bg-background/70 rounded-md px-3 py-2 shadow-sm backdrop-blur-sm\"\n >\n <div className=\"text-foreground mb-2 text-xs font-semibold\">\n {legend.title}\n </div>\n {legend.type === 'continuous' ? (\n <div>\n <div\n className=\"h-3 rounded-sm border\"\n style={{background: legend.gradient}}\n />\n <div className=\"text-muted-foreground mt-1 flex justify-between gap-2 text-[0.6rem]\">\n {legend.ticks.map((tick) => (\n <span\n key={`${legend.title}-${tick.offset}-${tick.label}`}\n className={cn(\n tick.offset === 50 ? 'text-center' : '',\n tick.offset === 100 ? 'text-right' : '',\n )}\n >\n {tick.label}\n </span>\n ))}\n </div>\n </div>\n ) : (\n <div className=\"space-y-1.5\">\n {legend.items.map((item) => (\n <div\n key={`${legend.title}-${item.label}`}\n className=\"text-foreground flex items-center gap-2 text-xs\"\n >\n <span\n className=\"h-3 w-3 rounded-sm border\"\n style={{\n backgroundColor: `rgba(${item.color[0]}, ${item.color[1]}, ${item.color[2]}, ${item.color[3] / 255})`,\n }}\n />\n <span className=\"truncate\">{item.label}</span>\n </div>\n ))}\n </div>\n )}\n </div>\n ))}\n </div>\n );\n}\n"]}
@@ -0,0 +1,4 @@
1
+ import 'maplibre-gl/dist/maplibre-gl.css';
2
+ import type { DeckMapProps } from './types';
3
+ export declare function DeckMap({ spec, datasets, sqlQuery, arrowTable, queryResult, geometryColumn, geometryEncodingHint, mapStyle, deckProps, mapProps, className, children, }: DeckMapProps): import("react/jsx-runtime").JSX.Element;
4
+ //# sourceMappingURL=DeckMap.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeckMap.d.ts","sourceRoot":"","sources":["../src/DeckMap.tsx"],"names":[],"mappings":"AAGA,OAAO,kCAAkC,CAAC;AAU1C,OAAO,KAAK,EAAC,YAAY,EAA2B,MAAM,SAAS,CAAC;AA6GpE,wBAAgB,OAAO,CAAC,EACtB,IAAI,EACJ,QAAQ,EACR,QAAQ,EACR,UAAU,EACV,WAAW,EACX,cAAc,EACd,oBAAoB,EACpB,QAAQ,EACR,SAAS,EACT,QAAQ,EACR,SAAS,EACT,QAAQ,GACT,EAAE,YAAY,2CAgId"}
@@ -0,0 +1,157 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { JSONConverter } from '@deck.gl/json';
3
+ import DeckGL from '@deck.gl/react';
4
+ import { cn } from '@sqlrooms/ui';
5
+ import 'maplibre-gl/dist/maplibre-gl.css';
6
+ import { useEffect, useMemo } from 'react';
7
+ import Map from 'react-map-gl/maplibre';
8
+ import { normalizeDatasets } from './datasets/normalizeDatasets';
9
+ import { usePreparedDeckDatasets } from './datasets/usePreparedDeckDatasets';
10
+ import { ColorScaleLegend } from './ColorScaleLegend';
11
+ import { createDeckJsonConfiguration } from './json/createDeckJsonConfiguration';
12
+ import { extractColorScaleLegends } from './json/extractColorScaleLegends';
13
+ import { resolveDatasetId } from './json/layerConfig';
14
+ import { getLayerCompatibility } from './json/layerCompatibility';
15
+ const DEFAULT_MAP_STYLE = 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';
16
+ function parseSpec(spec) {
17
+ try {
18
+ return {
19
+ spec: typeof spec === 'string' ? JSON.parse(spec) : spec,
20
+ error: null,
21
+ };
22
+ }
23
+ catch (error) {
24
+ return {
25
+ spec: null,
26
+ error: error instanceof Error ? error : new Error(String(error)),
27
+ };
28
+ }
29
+ }
30
+ function extractFallbackDeckProps(spec) {
31
+ if (!spec) {
32
+ return {};
33
+ }
34
+ const fallbackProps = {};
35
+ for (const key of ['initialViewState', 'viewState', 'controller']) {
36
+ if (key in spec) {
37
+ fallbackProps[key] = spec[key];
38
+ }
39
+ }
40
+ return fallbackProps;
41
+ }
42
+ function filterUnavailableLayers(spec, datasetIds, datasetStates) {
43
+ const layers = Array.isArray(spec.layers) ? spec.layers : [];
44
+ const filteredLayers = layers.filter((layer) => {
45
+ if (!layer || typeof layer !== 'object') {
46
+ return true;
47
+ }
48
+ const layerProps = layer;
49
+ const layerName = String(layerProps['@@type'] ?? '');
50
+ const compatibility = getLayerCompatibility(layerName);
51
+ if (!compatibility) {
52
+ return true;
53
+ }
54
+ const datasetId = resolveDatasetId(layerProps, datasetIds);
55
+ if (!datasetId) {
56
+ return true;
57
+ }
58
+ const datasetState = datasetStates[datasetId];
59
+ if (!datasetState) {
60
+ return false;
61
+ }
62
+ return datasetState.status === 'ready';
63
+ });
64
+ return {
65
+ ...spec,
66
+ layers: filteredLayers,
67
+ };
68
+ }
69
+ function renderDatasetStatusOverlay(datasetStates) {
70
+ const loadingDatasets = Object.entries(datasetStates)
71
+ .filter(([, state]) => state.status === 'loading')
72
+ .map(([datasetId]) => datasetId);
73
+ const failedDatasets = Object.entries(datasetStates).filter((entry) => entry[1].status === 'error');
74
+ if (!loadingDatasets.length && !failedDatasets.length) {
75
+ return null;
76
+ }
77
+ return (_jsxs("div", { className: "pointer-events-none absolute inset-x-4 top-4 z-10 space-y-2", children: [loadingDatasets.length > 0 ? (_jsxs("div", { className: "rounded-md border border-black/10 bg-white/90 px-3 py-2 text-sm text-slate-700 shadow-sm", children: ["Loading datasets: ", loadingDatasets.join(', ')] })) : null, 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)))] }));
78
+ }
79
+ export function DeckMap({ spec, datasets, sqlQuery, arrowTable, queryResult, geometryColumn, geometryEncodingHint, mapStyle, deckProps, mapProps, className, children, }) {
80
+ const normalizedDatasets = useMemo(() => normalizeDatasets({
81
+ datasets,
82
+ sqlQuery,
83
+ arrowTable,
84
+ queryResult,
85
+ geometryColumn,
86
+ geometryEncodingHint,
87
+ }), [
88
+ datasets,
89
+ sqlQuery,
90
+ arrowTable,
91
+ queryResult,
92
+ geometryColumn,
93
+ geometryEncodingHint,
94
+ ]);
95
+ const datasetIds = useMemo(() => Object.keys(normalizedDatasets), [normalizedDatasets]);
96
+ const datasetStates = usePreparedDeckDatasets(normalizedDatasets);
97
+ const { spec: parsedSpec, error: specError } = useMemo(() => parseSpec(spec), [spec]);
98
+ const availableSpec = useMemo(() => parsedSpec
99
+ ? filterUnavailableLayers(parsedSpec, datasetIds, datasetStates)
100
+ : null, [parsedSpec, datasetIds, datasetStates]);
101
+ const converter = useMemo(() => new JSONConverter({
102
+ configuration: createDeckJsonConfiguration({
103
+ datasetStates,
104
+ datasetIds,
105
+ }),
106
+ onJSONChange: () => { },
107
+ }), [datasetIds, datasetStates]);
108
+ const convertedDeckPropsResult = useMemo(() => {
109
+ if (!availableSpec) {
110
+ return { props: null, error: specError };
111
+ }
112
+ try {
113
+ return {
114
+ props: converter.convert(availableSpec),
115
+ error: null,
116
+ };
117
+ }
118
+ catch (error) {
119
+ return {
120
+ props: null,
121
+ error: error instanceof Error ? error : new Error(String(error)),
122
+ };
123
+ }
124
+ }, [availableSpec, converter, specError]);
125
+ useEffect(() => {
126
+ if (convertedDeckPropsResult.error) {
127
+ console.error(convertedDeckPropsResult.error);
128
+ }
129
+ }, [convertedDeckPropsResult.error]);
130
+ const fallbackDeckProps = useMemo(() => extractFallbackDeckProps(availableSpec), [availableSpec]);
131
+ const convertedDeckProps = (convertedDeckPropsResult.props ??
132
+ fallbackDeckProps ??
133
+ {});
134
+ const extraDeckProps = (deckProps ?? {});
135
+ const extraMapProps = (mapProps ?? {});
136
+ const hasRenderingError = Boolean(convertedDeckPropsResult.error);
137
+ const mergedDeckProps = {
138
+ ...convertedDeckProps,
139
+ ...extraDeckProps,
140
+ layers: hasRenderingError
141
+ ? []
142
+ : (deckProps?.layers ??
143
+ convertedDeckProps.layers ??
144
+ []),
145
+ };
146
+ const mergedMapProps = {
147
+ ...extraMapProps,
148
+ mapStyle: mapStyle ?? mapProps?.mapStyle ?? DEFAULT_MAP_STYLE,
149
+ };
150
+ const legends = useMemo(() => extractColorScaleLegends({
151
+ spec: availableSpec,
152
+ datasetIds,
153
+ datasetStates,
154
+ }), [availableSpec, datasetIds, datasetStates]);
155
+ 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, _jsx(DeckGL, { ...mergedDeckProps, children: _jsx(Map, { ...mergedMapProps, children: children }) }), renderDatasetStatusOverlay(datasetStates), !hasRenderingError ? _jsx(ColorScaleLegend, { legends: legends }) : null] }));
156
+ }
157
+ //# sourceMappingURL=DeckMap.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"DeckMap.js","sourceRoot":"","sources":["../src/DeckMap.tsx"],"names":[],"mappings":"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","sourcesContent":["import {JSONConverter} from '@deck.gl/json';\nimport DeckGL from '@deck.gl/react';\nimport {cn} from '@sqlrooms/ui';\nimport 'maplibre-gl/dist/maplibre-gl.css';\nimport {useEffect, useMemo} from 'react';\nimport Map from 'react-map-gl/maplibre';\nimport {normalizeDatasets} from './datasets/normalizeDatasets';\nimport {usePreparedDeckDatasets} from './datasets/usePreparedDeckDatasets';\nimport {ColorScaleLegend} from './ColorScaleLegend';\nimport {createDeckJsonConfiguration} from './json/createDeckJsonConfiguration';\nimport {extractColorScaleLegends} from './json/extractColorScaleLegends';\nimport {resolveDatasetId} from './json/layerConfig';\nimport {getLayerCompatibility} from './json/layerCompatibility';\nimport type {DeckMapProps, PreparedDeckDatasetState} from './types';\n\nconst DEFAULT_MAP_STYLE =\n 'https://basemaps.cartocdn.com/gl/positron-gl-style/style.json';\n\nfunction parseSpec(spec: DeckMapProps['spec']) {\n try {\n return {\n spec: typeof spec === 'string' ? JSON.parse(spec) : spec,\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 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 renderDatasetStatusOverlay(\n datasetStates: Record<string, PreparedDeckDatasetState>,\n) {\n const loadingDatasets = Object.entries(datasetStates)\n .filter(([, state]) => state.status === 'loading')\n .map(([datasetId]) => datasetId);\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 (!loadingDatasets.length && !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 {loadingDatasets.length > 0 ? (\n <div className=\"rounded-md border border-black/10 bg-white/90 px-3 py-2 text-sm text-slate-700 shadow-sm\">\n Loading datasets: {loadingDatasets.join(', ')}\n </div>\n ) : null}\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\nexport function DeckMap({\n spec,\n datasets,\n sqlQuery,\n arrowTable,\n queryResult,\n geometryColumn,\n geometryEncodingHint,\n mapStyle,\n deckProps,\n mapProps,\n className,\n children,\n}: DeckMapProps) {\n const normalizedDatasets = useMemo(\n () =>\n normalizeDatasets({\n datasets,\n sqlQuery,\n arrowTable,\n queryResult,\n geometryColumn,\n geometryEncodingHint,\n }),\n [\n datasets,\n sqlQuery,\n arrowTable,\n queryResult,\n geometryColumn,\n geometryEncodingHint,\n ],\n );\n const datasetIds = useMemo(\n () => Object.keys(normalizedDatasets),\n [normalizedDatasets],\n );\n const datasetStates = usePreparedDeckDatasets(normalizedDatasets);\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 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\n const mergedDeckProps = {\n ...convertedDeckProps,\n ...extraDeckProps,\n layers: hasRenderingError\n ? []\n : (deckProps?.layers ??\n (convertedDeckProps.layers as unknown[] | undefined) ??\n []),\n };\n\n const mergedMapProps = {\n ...extraMapProps,\n mapStyle: mapStyle ?? mapProps?.mapStyle ?? DEFAULT_MAP_STYLE,\n };\n const legends = useMemo(\n () =>\n extractColorScaleLegends({\n spec: availableSpec,\n datasetIds,\n datasetStates,\n }),\n [availableSpec, datasetIds, datasetStates],\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 <DeckGL {...(mergedDeckProps as object)}>\n <Map {...(mergedMapProps as object)}>{children}</Map>\n </DeckGL>\n\n {renderDatasetStatusOverlay(datasetStates)}\n {!hasRenderingError ? <ColorScaleLegend legends={legends} /> : null}\n </div>\n );\n}\n"]}
@@ -0,0 +1,5 @@
1
+ import type * as arrow from 'apache-arrow';
2
+ import type { DeckDatasetInput, DeckMapProps } from '../types';
3
+ export declare function resolveArrowTable(input: DeckDatasetInput): arrow.Table | undefined;
4
+ export declare function normalizeDatasets(props: Pick<DeckMapProps, 'datasets' | 'sqlQuery' | 'arrowTable' | 'queryResult' | 'geometryColumn' | 'geometryEncodingHint'>): Record<string, DeckDatasetInput>;
5
+ //# sourceMappingURL=normalizeDatasets.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeDatasets.d.ts","sourceRoot":"","sources":["../../src/datasets/normalizeDatasets.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,KAAK,KAAK,MAAM,cAAc,CAAC;AAC3C,OAAO,KAAK,EAAC,gBAAgB,EAAE,YAAY,EAAsB,MAAM,UAAU,CAAC;AA8ClF,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,gBAAgB,GACtB,KAAK,CAAC,KAAK,GAAG,SAAS,CAEzB;AAED,wBAAgB,iBAAiB,CAC/B,KAAK,EAAE,IAAI,CACT,YAAY,EACV,UAAU,GACV,UAAU,GACV,YAAY,GACZ,aAAa,GACb,gBAAgB,GAChB,sBAAsB,CACzB,GACA,MAAM,CAAC,MAAM,EAAE,gBAAgB,CAAC,CA2BlC"}
@@ -0,0 +1,57 @@
1
+ function getArrowTableFromQueryResult(queryResult) {
2
+ return queryResult?.arrowTable;
3
+ }
4
+ function countDefinedInputs(input) {
5
+ return Number(Boolean(input.sqlQuery)) +
6
+ Number(Boolean(input.arrowTable)) +
7
+ Number(Boolean(input.queryResult));
8
+ }
9
+ function assertArrowTable(value, datasetId) {
10
+ if (!value) {
11
+ throw new Error(`Dataset "${datasetId}" queryResult did not expose an arrowTable.`);
12
+ }
13
+ }
14
+ function normalizeDatasetEntry(datasetId, input) {
15
+ const normalized = {
16
+ sqlQuery: input.sqlQuery,
17
+ arrowTable: input.arrowTable,
18
+ queryResult: input.queryResult,
19
+ geometryColumn: input.geometryColumn,
20
+ geometryEncodingHint: input.geometryEncodingHint,
21
+ };
22
+ const definedInputs = countDefinedInputs(normalized);
23
+ if (definedInputs !== 1) {
24
+ throw new Error(`Dataset "${datasetId}" must provide exactly one of sqlQuery, arrowTable, or queryResult.`);
25
+ }
26
+ if (normalized.queryResult) {
27
+ assertArrowTable(getArrowTableFromQueryResult(normalized.queryResult), datasetId);
28
+ }
29
+ return normalized;
30
+ }
31
+ export function resolveArrowTable(input) {
32
+ return input.arrowTable ?? getArrowTableFromQueryResult(input.queryResult);
33
+ }
34
+ export function normalizeDatasets(props) {
35
+ if (props.datasets) {
36
+ const entries = Object.entries(props.datasets).map(([datasetId, input]) => [
37
+ datasetId,
38
+ normalizeDatasetEntry(datasetId, input),
39
+ ]);
40
+ return Object.fromEntries(entries);
41
+ }
42
+ const singleInput = {
43
+ sqlQuery: props.sqlQuery,
44
+ arrowTable: props.arrowTable,
45
+ queryResult: props.queryResult,
46
+ geometryColumn: props.geometryColumn,
47
+ geometryEncodingHint: props.geometryEncodingHint,
48
+ };
49
+ const definedInputs = countDefinedInputs(singleInput);
50
+ if (definedInputs === 0) {
51
+ throw new Error('DeckMap requires either datasets or one of sqlQuery, arrowTable, or queryResult.');
52
+ }
53
+ return {
54
+ default: normalizeDatasetEntry('default', singleInput),
55
+ };
56
+ }
57
+ //# sourceMappingURL=normalizeDatasets.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"normalizeDatasets.js","sourceRoot":"","sources":["../../src/datasets/normalizeDatasets.ts"],"names":[],"mappings":"AAGA,SAAS,4BAA4B,CAAC,WAA4C;IAChF,OAAO,WAAW,EAAE,UAAU,CAAC;AACjC,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAuB;IACjD,OAAO,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,UAAU,CAAC,CAAC;QACjC,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC,CAAC;AACvC,CAAC;AAED,SAAS,gBAAgB,CAAC,KAA8B,EAAE,SAAiB;IACzE,IAAI,CAAC,KAAK,EAAE,CAAC;QACX,MAAM,IAAI,KAAK,CACb,YAAY,SAAS,6CAA6C,CACnE,CAAC;IACJ,CAAC;AACH,CAAC;AAED,SAAS,qBAAqB,CAC5B,SAAiB,EACjB,KAAuB;IAEvB,MAAM,UAAU,GAAqB;QACnC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;KACjD,CAAC;IAEF,MAAM,aAAa,GAAG,kBAAkB,CAAC,UAAU,CAAC,CAAC;IACrD,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,YAAY,SAAS,qEAAqE,CAC3F,CAAC;IACJ,CAAC;IAED,IAAI,UAAU,CAAC,WAAW,EAAE,CAAC;QAC3B,gBAAgB,CAAC,4BAA4B,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,SAAS,CAAC,CAAC;IACpF,CAAC;IAED,OAAO,UAAU,CAAC;AACpB,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAAuB;IAEvB,OAAO,KAAK,CAAC,UAAU,IAAI,4BAA4B,CAAC,KAAK,CAAC,WAAW,CAAC,CAAC;AAC7E,CAAC;AAED,MAAM,UAAU,iBAAiB,CAC/B,KAQC;IAED,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;QACnB,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,SAAS,EAAE,KAAK,CAAC,EAAE,EAAE,CAAC;YACzE,SAAS;YACT,qBAAqB,CAAC,SAAS,EAAE,KAAK,CAAC;SACxC,CAAC,CAAC;QACH,OAAO,MAAM,CAAC,WAAW,CAAC,OAAO,CAAC,CAAC;IACrC,CAAC;IAED,MAAM,WAAW,GAAqB;QACpC,QAAQ,EAAE,KAAK,CAAC,QAAQ;QACxB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,WAAW,EAAE,KAAK,CAAC,WAAW;QAC9B,cAAc,EAAE,KAAK,CAAC,cAAc;QACpC,oBAAoB,EAAE,KAAK,CAAC,oBAAoB;KACjD,CAAC;IAEF,MAAM,aAAa,GAAG,kBAAkB,CAAC,WAAW,CAAC,CAAC;IACtD,IAAI,aAAa,KAAK,CAAC,EAAE,CAAC;QACxB,MAAM,IAAI,KAAK,CACb,kFAAkF,CACnF,CAAC;IACJ,CAAC;IAED,OAAO;QACL,OAAO,EAAE,qBAAqB,CAAC,SAAS,EAAE,WAAW,CAAC;KACvD,CAAC;AACJ,CAAC","sourcesContent":["import type * as arrow from 'apache-arrow';\nimport type {DeckDatasetInput, DeckMapProps, DeckQueryResultLike} from '../types';\n\nfunction getArrowTableFromQueryResult(queryResult: DeckQueryResultLike | undefined) {\n return queryResult?.arrowTable;\n}\n\nfunction countDefinedInputs(input: DeckDatasetInput) {\n return Number(Boolean(input.sqlQuery)) +\n Number(Boolean(input.arrowTable)) +\n Number(Boolean(input.queryResult));\n}\n\nfunction assertArrowTable(value: arrow.Table | undefined, datasetId: string) {\n if (!value) {\n throw new Error(\n `Dataset \"${datasetId}\" queryResult did not expose an arrowTable.`,\n );\n }\n}\n\nfunction normalizeDatasetEntry(\n datasetId: string,\n input: DeckDatasetInput,\n): DeckDatasetInput {\n const normalized: DeckDatasetInput = {\n sqlQuery: input.sqlQuery,\n arrowTable: input.arrowTable,\n queryResult: input.queryResult,\n geometryColumn: input.geometryColumn,\n geometryEncodingHint: input.geometryEncodingHint,\n };\n\n const definedInputs = countDefinedInputs(normalized);\n if (definedInputs !== 1) {\n throw new Error(\n `Dataset \"${datasetId}\" must provide exactly one of sqlQuery, arrowTable, or queryResult.`,\n );\n }\n\n if (normalized.queryResult) {\n assertArrowTable(getArrowTableFromQueryResult(normalized.queryResult), datasetId);\n }\n\n return normalized;\n}\n\nexport function resolveArrowTable(\n input: DeckDatasetInput,\n): arrow.Table | undefined {\n return input.arrowTable ?? getArrowTableFromQueryResult(input.queryResult);\n}\n\nexport function normalizeDatasets(\n props: Pick<\n DeckMapProps,\n | 'datasets'\n | 'sqlQuery'\n | 'arrowTable'\n | 'queryResult'\n | 'geometryColumn'\n | 'geometryEncodingHint'\n >,\n): Record<string, DeckDatasetInput> {\n if (props.datasets) {\n const entries = Object.entries(props.datasets).map(([datasetId, input]) => [\n datasetId,\n normalizeDatasetEntry(datasetId, input),\n ]);\n return Object.fromEntries(entries);\n }\n\n const singleInput: DeckDatasetInput = {\n sqlQuery: props.sqlQuery,\n arrowTable: props.arrowTable,\n queryResult: props.queryResult,\n geometryColumn: props.geometryColumn,\n geometryEncodingHint: props.geometryEncodingHint,\n };\n\n const definedInputs = countDefinedInputs(singleInput);\n if (definedInputs === 0) {\n throw new Error(\n 'DeckMap requires either datasets or one of sqlQuery, arrowTable, or queryResult.',\n );\n }\n\n return {\n default: normalizeDatasetEntry('default', singleInput),\n };\n}\n"]}