@loaders.gl/scan 5.0.0-alpha.3

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 (52) hide show
  1. package/LICENSE +52 -0
  2. package/README.md +219 -0
  3. package/dist/delta.cjs +24246 -0
  4. package/dist/delta.cjs.map +6 -0
  5. package/dist/delta.d.ts +11 -0
  6. package/dist/delta.d.ts.map +1 -0
  7. package/dist/delta.js +12 -0
  8. package/dist/delta.js.map +1 -0
  9. package/dist/dist.min.js +18475 -0
  10. package/dist/federated-table-scan-source.d.ts +116 -0
  11. package/dist/federated-table-scan-source.d.ts.map +1 -0
  12. package/dist/federated-table-scan-source.js +691 -0
  13. package/dist/federated-table-scan-source.js.map +1 -0
  14. package/dist/hudi.cjs +24065 -0
  15. package/dist/hudi.cjs.map +6 -0
  16. package/dist/hudi.d.ts +54 -0
  17. package/dist/hudi.d.ts.map +1 -0
  18. package/dist/hudi.js +124 -0
  19. package/dist/hudi.js.map +1 -0
  20. package/dist/iceberg.cjs +25862 -0
  21. package/dist/iceberg.cjs.map +6 -0
  22. package/dist/iceberg.d.ts +12 -0
  23. package/dist/iceberg.d.ts.map +1 -0
  24. package/dist/iceberg.js +12 -0
  25. package/dist/iceberg.js.map +1 -0
  26. package/dist/index.cjs +3485 -0
  27. package/dist/index.cjs.map +6 -0
  28. package/dist/index.d.ts +15 -0
  29. package/dist/index.d.ts.map +1 -0
  30. package/dist/index.js +14 -0
  31. package/dist/index.js.map +1 -0
  32. package/dist/scan-engine.d.ts +44 -0
  33. package/dist/scan-engine.d.ts.map +1 -0
  34. package/dist/scan-engine.js +49 -0
  35. package/dist/scan-engine.js.map +1 -0
  36. package/dist/scan-query.d.ts +24 -0
  37. package/dist/scan-query.d.ts.map +1 -0
  38. package/dist/scan-query.js +5 -0
  39. package/dist/scan-query.js.map +1 -0
  40. package/dist/vector-table-scan-source.d.ts +90 -0
  41. package/dist/vector-table-scan-source.d.ts.map +1 -0
  42. package/dist/vector-table-scan-source.js +215 -0
  43. package/dist/vector-table-scan-source.js.map +1 -0
  44. package/package.json +66 -0
  45. package/src/delta.ts +18 -0
  46. package/src/federated-table-scan-source.ts +991 -0
  47. package/src/hudi.ts +200 -0
  48. package/src/iceberg.ts +36 -0
  49. package/src/index.ts +97 -0
  50. package/src/scan-engine.ts +96 -0
  51. package/src/scan-query.ts +27 -0
  52. package/src/vector-table-scan-source.ts +294 -0
@@ -0,0 +1,294 @@
1
+ // loaders.gl
2
+ // SPDX-License-Identifier: MIT
3
+ // Copyright (c) vis.gl contributors
4
+
5
+ import type {ArrowTable, ArrowTableBatch} from '@loaders.gl/schema';
6
+ import {convertArrowToSchema} from '@loaders.gl/schema-utils';
7
+ import type {
8
+ GetFeaturesParameters,
9
+ GetTileParameters,
10
+ ScanQueryMetadata,
11
+ ScanQueryMetadataOptions,
12
+ ScanSpatialMetadata,
13
+ TableQueryExplain,
14
+ VectorSource,
15
+ VectorTileSource
16
+ } from '@loaders.gl/loader-utils';
17
+ import {createScanQueryMetadata, makeTableScanBatch} from '@loaders.gl/loader-utils';
18
+ import {
19
+ ARROW_TABLE_QUERY_CAPABILITIES,
20
+ explainArrowTableQuery,
21
+ queryArrowTable,
22
+ type ArrowQueryOptions,
23
+ type SQLPredicate
24
+ } from '@loaders.gl/sql';
25
+
26
+ /** Shared display and provenance options for an addressed vector-table scan. */
27
+ export type VectorTableScanSourceOptions = Readonly<{
28
+ /** Stable source type reported through query metadata. */
29
+ sourceType?: string;
30
+ /** Optional display name shown by metadata-driven query controls. */
31
+ name?: string;
32
+ /** Optional description of the addressed table view. */
33
+ description?: string;
34
+ }>;
35
+
36
+ /** Options that bind one vector tile before portable table-query planning begins. */
37
+ export type VectorTileTableScanSourceOptions = VectorTableScanSourceOptions &
38
+ Readonly<{
39
+ /** Tile address and source-specific layer selection, deliberately outside `TableQuery`. */
40
+ tile: Omit<GetTileParameters, 'signal'>;
41
+ }>;
42
+
43
+ /** Options that bind one bounded vector-service request before table-query planning begins. */
44
+ export type VectorFeatureTableScanSourceOptions = VectorTableScanSourceOptions &
45
+ Readonly<{
46
+ /** Layers, bounds, and output CRS, deliberately outside `TableQuery`. */
47
+ request: Omit<GetFeaturesParameters, 'format' | 'signal'>;
48
+ }>;
49
+
50
+ /**
51
+ * Base source for a physical vector selection that resolves to one Arrow feature table.
52
+ *
53
+ * The physical address is immutable and owned by a specialized adapter. Portable predicates,
54
+ * projection, ordering, aggregates, and limits are applied only after that selection resolves.
55
+ */
56
+ export abstract class AddressedVectorTableScanSource {
57
+ /** Stable adapter type reported by `getQueryMetadata()`. */
58
+ readonly sourceType: string;
59
+ /** Optional display name shown by query controls. */
60
+ readonly name?: string;
61
+ /** Human-readable description of the bound physical selection. */
62
+ readonly description: string;
63
+ /** Spatial extent of the bound physical selection. */
64
+ readonly spatial?: ScanSpatialMetadata;
65
+
66
+ private tablePromise: Promise<ArrowTable> | null = null;
67
+
68
+ /** Creates a table-scan view over one immutable physical selection. */
69
+ protected constructor(
70
+ options: VectorTableScanSourceOptions,
71
+ defaults: Readonly<{
72
+ sourceType: string;
73
+ description: string;
74
+ spatial?: ScanSpatialMetadata;
75
+ }>
76
+ ) {
77
+ this.sourceType = options.sourceType || defaults.sourceType;
78
+ this.name = options.name;
79
+ this.description = options.description || defaults.description;
80
+ this.spatial = defaults.spatial;
81
+ }
82
+
83
+ /** Loads the physically addressed data as one Arrow table. */
84
+ protected abstract loadTable(signal?: AbortSignal): Promise<ArrowTable | null>;
85
+
86
+ /** Discovers the addressed result schema and portable residual-query capabilities. */
87
+ async getQueryMetadata(options: ScanQueryMetadataOptions = {}): Promise<ScanQueryMetadata> {
88
+ const table = await this.getTable(options.signal);
89
+ const schema = table.schema || convertArrowToSchema(table.data.schema);
90
+ return createScanQueryMetadata({
91
+ sourceType: this.sourceType,
92
+ queryType: 'table',
93
+ execution: {status: 'supported', method: 'read'},
94
+ name: this.name,
95
+ description: this.description,
96
+ schema,
97
+ capabilities: {
98
+ table: ARROW_TABLE_QUERY_CAPABILITIES,
99
+ bounds: 'unsupported',
100
+ levelOfDetail: 'unsupported'
101
+ },
102
+ columnRoles: inferVectorColumnRoles(schema.fields.map(field => field.name)),
103
+ spatial: this.spatial,
104
+ statistics: {rowCount: table.data.numRows}
105
+ });
106
+ }
107
+
108
+ /** Applies the portable Arrow query to the addressed feature table. */
109
+ async query(options: ArrowQueryOptions = {}): Promise<ArrowTable> {
110
+ const table = await this.getTable(options.signal);
111
+ return queryArrowTable(table, options);
112
+ }
113
+
114
+ /** Explains the residual Arrow plan after resolving the addressed feature table. */
115
+ async explain(options: ArrowQueryOptions = {}): Promise<TableQueryExplain<SQLPredicate>> {
116
+ const table = await this.getTable(options.signal);
117
+ return explainArrowTableQuery(table, options);
118
+ }
119
+
120
+ /** Emits the portable query result as one bounded Arrow batch. */
121
+ async *read(options: ArrowQueryOptions = {}): AsyncIterableIterator<ArrowTableBatch> {
122
+ const result = await this.query(options);
123
+ yield makeTableScanBatch(result);
124
+ }
125
+
126
+ /** Resolves and caches the addressed table while allowing failed loads to be retried. */
127
+ private async getTable(signal?: AbortSignal): Promise<ArrowTable> {
128
+ throwIfAborted(signal);
129
+ if (!this.tablePromise) {
130
+ this.tablePromise = this.loadTable()
131
+ .then(table => {
132
+ if (!table) {
133
+ throw new Error('The addressed vector selection did not return a feature table.');
134
+ }
135
+ if (table.shape !== 'arrow-table') {
136
+ throw new Error(
137
+ 'The addressed vector selection must return an Arrow table. Configure the source with shape "arrow-table".'
138
+ );
139
+ }
140
+ return table;
141
+ })
142
+ .catch(error => {
143
+ this.tablePromise = null;
144
+ throw error;
145
+ });
146
+ }
147
+ return await waitForPromise(this.tablePromise, signal);
148
+ }
149
+ }
150
+
151
+ /**
152
+ * Portable table-query view over one explicitly addressed vector tile.
153
+ *
154
+ * MVT and vector PMTiles sources participate when configured to return `shape: 'arrow-table'`.
155
+ * Tile coordinates and layer selection remain source parameters rather than relational operators.
156
+ */
157
+ export class VectorTileTableScanSource extends AddressedVectorTableScanSource {
158
+ /** Specialized tile source that owns tile addressing and decoding. */
159
+ readonly source: VectorTileSource;
160
+ /** Immutable tile address bound to this table view. */
161
+ readonly tile: Omit<GetTileParameters, 'signal'>;
162
+
163
+ /** Creates a portable table view over one vector tile. */
164
+ constructor(source: VectorTileSource, options: VectorTileTableScanSourceOptions) {
165
+ const tile = Object.freeze({
166
+ ...options.tile,
167
+ layers: cloneAndFreezeLayers(options.tile.layers)
168
+ });
169
+ super(options, {
170
+ sourceType: 'vector-tile-table',
171
+ description: `Vector tile ${tile.z}/${tile.x}/${tile.y}`,
172
+ spatial: {
173
+ bounds: getWGS84TileBounds(tile),
174
+ coordinateReferenceSystems: ['EPSG:4326']
175
+ }
176
+ });
177
+ this.source = source;
178
+ this.tile = tile;
179
+ }
180
+
181
+ /** Loads the bound vector tile using the source's requested Arrow output shape. */
182
+ protected loadTable(signal?: AbortSignal): Promise<ArrowTable | null> {
183
+ return this.source.getVectorTile({...this.tile, signal}) as Promise<ArrowTable | null>;
184
+ }
185
+ }
186
+
187
+ /**
188
+ * Portable table-query view over one explicitly bounded vector-service request.
189
+ *
190
+ * WFS and ArcGIS feature sources participate through their existing Arrow feature output. Layers,
191
+ * service bounds, and output CRS remain source parameters rather than relational operators.
192
+ */
193
+ export class VectorFeatureTableScanSource extends AddressedVectorTableScanSource {
194
+ /** Specialized vector source that owns service request construction and parsing. */
195
+ readonly source: VectorSource;
196
+ /** Immutable bounded feature request bound to this table view. */
197
+ readonly request: Omit<GetFeaturesParameters, 'format' | 'signal'>;
198
+
199
+ /** Creates a portable table view over one bounded vector-service request. */
200
+ constructor(source: VectorSource, options: VectorFeatureTableScanSourceOptions) {
201
+ const request = Object.freeze({
202
+ ...options.request,
203
+ layers: cloneAndFreezeLayers(options.request.layers),
204
+ boundingBox: Object.freeze([
205
+ Object.freeze([...options.request.boundingBox[0]]),
206
+ Object.freeze([...options.request.boundingBox[1]])
207
+ ]) as GetFeaturesParameters['boundingBox']
208
+ });
209
+ const coordinateReferenceSystems = typeof request.crs === 'string' ? [request.crs] : undefined;
210
+ super(options, {
211
+ sourceType: 'vector-feature-table',
212
+ description: `Vector feature request for ${normalizeLayers(request.layers).join(', ')}`,
213
+ spatial: {
214
+ bounds: {
215
+ minimum: Object.freeze([...request.boundingBox[0]]),
216
+ maximum: Object.freeze([...request.boundingBox[1]])
217
+ },
218
+ coordinateReferenceSystems
219
+ }
220
+ });
221
+ this.source = source;
222
+ this.request = request;
223
+ }
224
+
225
+ /** Loads the bound service request through the source's Arrow feature output. */
226
+ protected loadTable(signal?: AbortSignal): Promise<ArrowTable | null> {
227
+ return this.source.getFeatures({
228
+ ...this.request,
229
+ format: 'arrow',
230
+ signal
231
+ }) as Promise<ArrowTable | null>;
232
+ }
233
+ }
234
+
235
+ /** Returns the WGS84 extent of one XYZ tile address. */
236
+ function getWGS84TileBounds(tile: Pick<GetTileParameters, 'x' | 'y' | 'z'>) {
237
+ const scale = 2 ** tile.z;
238
+ return {
239
+ minimum: [(tile.x / scale) * 360 - 180, tileYToLatitude(tile.y + 1, scale)],
240
+ maximum: [((tile.x + 1) / scale) * 360 - 180, tileYToLatitude(tile.y, scale)]
241
+ };
242
+ }
243
+
244
+ /** Converts one XYZ tile row to its WGS84 latitude edge. */
245
+ function tileYToLatitude(tileY: number, scale: number): number {
246
+ return (Math.atan(Math.sinh(Math.PI * (1 - (2 * tileY) / scale))) * 180) / Math.PI;
247
+ }
248
+
249
+ /** Infers the small semantic-role set useful for vector feature query controls. */
250
+ function inferVectorColumnRoles(columnNames: readonly string[]) {
251
+ return Object.fromEntries(
252
+ columnNames.map(columnName => [
253
+ columnName,
254
+ columnName.toLowerCase() === 'geometry' ? 'geometry' : 'attribute'
255
+ ])
256
+ ) as Readonly<Record<string, 'geometry' | 'attribute'>>;
257
+ }
258
+
259
+ /** Normalizes a single layer name to the service API's list shape. */
260
+ function normalizeLayers(layers: string | string[]): string[] {
261
+ return Array.isArray(layers) ? layers : [layers];
262
+ }
263
+
264
+ /** Clones and freezes a possibly plural layer selection for an immutable physical address. */
265
+ function cloneAndFreezeLayers<LayersT extends string | string[] | undefined>(
266
+ layers: LayersT
267
+ ): LayersT {
268
+ return (Array.isArray(layers) ? Object.freeze([...layers]) : layers) as LayersT;
269
+ }
270
+
271
+ /** Waits for a shared cached load while preserving one caller's independent cancellation. */
272
+ async function waitForPromise<T>(promise: Promise<T>, signal?: AbortSignal): Promise<T> {
273
+ if (!signal) {
274
+ return await promise;
275
+ }
276
+ throwIfAborted(signal);
277
+ return await new Promise<T>((resolve, reject) => {
278
+ const abort = () => reject(getAbortReason(signal));
279
+ signal.addEventListener('abort', abort, {once: true});
280
+ void promise.then(resolve, reject).finally(() => signal.removeEventListener('abort', abort));
281
+ });
282
+ }
283
+
284
+ /** Throws before physical or residual work begins when cancellation was requested. */
285
+ function throwIfAborted(signal?: AbortSignal): void {
286
+ if (signal?.aborted) {
287
+ throw getAbortReason(signal);
288
+ }
289
+ }
290
+
291
+ /** Returns the caller's cancellation reason or a portable AbortError fallback. */
292
+ function getAbortReason(signal: AbortSignal): unknown {
293
+ return signal.reason || new DOMException('Request aborted', 'AbortError');
294
+ }