@carto/api-client 0.4.3 → 0.5.0-alpha.0

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 (93) hide show
  1. package/CHANGELOG.md +4 -0
  2. package/build/api/query.d.ts +1 -1
  3. package/build/api-client.cjs +2388 -261
  4. package/build/api-client.cjs.map +1 -1
  5. package/build/api-client.modern.js +2237 -262
  6. package/build/api-client.modern.js.map +1 -1
  7. package/build/constants.d.ts +22 -0
  8. package/build/filters/Filter.d.ts +13 -0
  9. package/build/filters/FilterTypes.d.ts +3 -0
  10. package/build/filters/geosjonFeatures.d.ts +8 -0
  11. package/build/filters/index.d.ts +6 -0
  12. package/build/filters/tileFeatures.d.ts +20 -0
  13. package/build/filters/tileFeaturesGeometries.d.ts +13 -0
  14. package/build/filters/tileFeaturesSpatialIndex.d.ts +10 -0
  15. package/build/index.d.ts +4 -0
  16. package/build/models/index.d.ts +1 -1
  17. package/build/models/model.d.ts +7 -1
  18. package/build/operations/aggregation.d.ts +8 -0
  19. package/build/operations/applySorting.d.ts +20 -0
  20. package/build/operations/groupBy.d.ts +15 -0
  21. package/build/operations/groupByDate.d.ts +11 -0
  22. package/build/operations/histogram.d.ts +13 -0
  23. package/build/operations/index.d.ts +6 -0
  24. package/build/operations/scatterPlot.d.ts +14 -0
  25. package/build/sources/h3-tileset-source.d.ts +2 -1
  26. package/build/sources/index.d.ts +1 -1
  27. package/build/sources/quadbin-tileset-source.d.ts +2 -1
  28. package/build/sources/types.d.ts +36 -41
  29. package/build/sources/vector-tileset-source.d.ts +2 -1
  30. package/build/spatial-index.d.ts +8 -0
  31. package/build/types-internal.d.ts +4 -0
  32. package/build/types.d.ts +61 -1
  33. package/build/utils/dateUtils.d.ts +10 -0
  34. package/build/utils/getTileFormat.d.ts +3 -0
  35. package/build/utils/makeIntervalComplete.d.ts +2 -0
  36. package/build/utils/transformTileCoordsToWGS84.d.ts +8 -0
  37. package/build/utils/transformToTileCoords.d.ts +9 -0
  38. package/build/utils.d.ts +1 -1
  39. package/build/widget-sources/index.d.ts +2 -1
  40. package/build/widget-sources/types.d.ts +40 -23
  41. package/build/widget-sources/widget-query-source.d.ts +2 -2
  42. package/build/widget-sources/widget-remote-source.d.ts +18 -0
  43. package/build/widget-sources/{widget-base-source.d.ts → widget-source.d.ts} +16 -41
  44. package/build/widget-sources/widget-table-source.d.ts +2 -2
  45. package/build/widget-sources/widget-tileset-source.d.ts +67 -0
  46. package/package.json +36 -35
  47. package/src/api/query.ts +1 -2
  48. package/src/constants.ts +25 -0
  49. package/src/filters/Filter.ts +169 -0
  50. package/src/filters/FilterTypes.ts +109 -0
  51. package/src/filters/geosjonFeatures.ts +32 -0
  52. package/src/filters/index.ts +6 -0
  53. package/src/filters/tileFeatures.ts +56 -0
  54. package/src/filters/tileFeaturesGeometries.ts +444 -0
  55. package/src/filters/tileFeaturesSpatialIndex.ts +119 -0
  56. package/src/index.ts +6 -0
  57. package/src/models/index.ts +1 -1
  58. package/src/models/model.ts +47 -24
  59. package/src/operations/aggregation.ts +154 -0
  60. package/src/operations/applySorting.ts +109 -0
  61. package/src/operations/groupBy.ts +59 -0
  62. package/src/operations/groupByDate.ts +98 -0
  63. package/src/operations/histogram.ts +66 -0
  64. package/src/operations/index.ts +6 -0
  65. package/src/operations/scatterPlot.ts +50 -0
  66. package/src/sources/h3-query-source.ts +7 -1
  67. package/src/sources/h3-table-source.ts +6 -1
  68. package/src/sources/h3-tileset-source.ts +18 -6
  69. package/src/sources/index.ts +1 -1
  70. package/src/sources/quadbin-query-source.ts +6 -1
  71. package/src/sources/quadbin-table-source.ts +6 -1
  72. package/src/sources/quadbin-tileset-source.ts +18 -6
  73. package/src/sources/raster-source.ts +1 -0
  74. package/src/sources/types.ts +41 -45
  75. package/src/sources/vector-query-source.ts +10 -3
  76. package/src/sources/vector-table-source.ts +10 -3
  77. package/src/sources/vector-tileset-source.ts +19 -6
  78. package/src/spatial-index.ts +111 -0
  79. package/src/types-internal.ts +6 -0
  80. package/src/types.ts +60 -2
  81. package/src/utils/dateUtils.ts +28 -0
  82. package/src/utils/getTileFormat.ts +9 -0
  83. package/src/utils/makeIntervalComplete.ts +17 -0
  84. package/src/utils/transformTileCoordsToWGS84.ts +77 -0
  85. package/src/utils/transformToTileCoords.ts +85 -0
  86. package/src/utils.ts +9 -6
  87. package/src/widget-sources/index.ts +2 -1
  88. package/src/widget-sources/types.ts +42 -23
  89. package/src/widget-sources/widget-query-source.ts +6 -3
  90. package/src/widget-sources/{widget-base-source.ts → widget-remote-source.ts} +169 -144
  91. package/src/widget-sources/widget-source.ts +160 -0
  92. package/src/widget-sources/widget-table-source.ts +6 -3
  93. package/src/widget-sources/widget-tileset-source.ts +396 -0
@@ -0,0 +1,444 @@
1
+ import bboxPolygon from '@turf/bbox-polygon';
2
+ import intersects from '@turf/boolean-intersects';
3
+ import booleanWithin from '@turf/boolean-within';
4
+ import intersect from '@turf/intersect';
5
+ import {transformToTileCoords} from '../utils/transformToTileCoords.js';
6
+ import {transformTileCoordsToWGS84} from '../utils/transformTileCoordsToWGS84.js';
7
+ import {TileFormat} from '../constants.js';
8
+ import {
9
+ BBox,
10
+ Feature,
11
+ Geometry,
12
+ LineString,
13
+ MultiPolygon,
14
+ Point,
15
+ Polygon,
16
+ Position,
17
+ } from 'geojson';
18
+ import {SpatialFilter, Tile} from '../types.js';
19
+ import {TileFeatureExtractOptions} from './tileFeatures.js';
20
+ import {featureCollection} from '@turf/helpers';
21
+ import {FeatureData} from '../types-internal.js';
22
+ import {
23
+ BinaryAttribute,
24
+ BinaryFeature,
25
+ BinaryGeometryType,
26
+ BinaryPointFeature,
27
+ TypedArrayConstructor,
28
+ } from '@loaders.gl/schema';
29
+
30
+ export const FEATURE_GEOM_PROPERTY = '__geomValue';
31
+
32
+ type TileMap = Map<unknown, unknown>;
33
+
34
+ type TileDataInternal = {
35
+ uniqueId: string | number | undefined;
36
+ properties: any;
37
+ numericProps: Record<string, number>;
38
+ };
39
+
40
+ export function tileFeaturesGeometries({
41
+ tiles,
42
+ tileFormat,
43
+ spatialFilter,
44
+ uniqueIdProperty,
45
+ options,
46
+ }: {
47
+ tiles: Tile[];
48
+ tileFormat?: TileFormat;
49
+ spatialFilter: SpatialFilter;
50
+ uniqueIdProperty?: string;
51
+ options?: {storeGeometry?: boolean};
52
+ }): FeatureData[] {
53
+ const map = new Map();
54
+
55
+ for (const tile of tiles) {
56
+ // Discard if it's not a visible tile (only check false value, not undefined)
57
+ // or tile has not data
58
+ if (tile.isVisible === false || !tile.data) {
59
+ continue;
60
+ }
61
+
62
+ const bbox = [
63
+ tile.bbox.west,
64
+ tile.bbox.south,
65
+ tile.bbox.east,
66
+ tile.bbox.north,
67
+ ] as BBox;
68
+ const bboxToGeom = bboxPolygon(bbox);
69
+ const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
70
+
71
+ // Clip the geometry to intersect with the tile
72
+ const spatialFilterFeature: Feature<Polygon | MultiPolygon> = {
73
+ type: 'Feature',
74
+ geometry: spatialFilter,
75
+ properties: {},
76
+ };
77
+ const clippedGeometryToIntersect = intersect(
78
+ featureCollection([bboxToGeom, spatialFilterFeature])
79
+ );
80
+
81
+ if (!clippedGeometryToIntersect) {
82
+ continue;
83
+ }
84
+
85
+ // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
86
+ // while in the case of 'geojson' or binary, the geometries are already in WGS84
87
+ const transformedGeometryToIntersect =
88
+ tileFormat === TileFormat.MVT
89
+ ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox)
90
+ : clippedGeometryToIntersect.geometry;
91
+
92
+ createIndicesForPoints(tile.data.points!);
93
+
94
+ calculateFeatures({
95
+ map,
96
+ tileIsFullyVisible,
97
+ geometryIntersection: transformedGeometryToIntersect,
98
+ data: tile.data.points!,
99
+ type: 'Point',
100
+ bbox,
101
+ tileFormat,
102
+ uniqueIdProperty,
103
+ options,
104
+ });
105
+ calculateFeatures({
106
+ map,
107
+ tileIsFullyVisible,
108
+ geometryIntersection: transformedGeometryToIntersect,
109
+ data: tile.data.lines!,
110
+ type: 'LineString',
111
+ bbox,
112
+ tileFormat,
113
+ uniqueIdProperty,
114
+ options,
115
+ });
116
+ calculateFeatures({
117
+ map,
118
+ tileIsFullyVisible,
119
+ geometryIntersection: transformedGeometryToIntersect,
120
+ data: tile.data.polygons!,
121
+ type: 'Polygon',
122
+ bbox,
123
+ tileFormat,
124
+ uniqueIdProperty,
125
+ options,
126
+ });
127
+ }
128
+ return Array.from(map.values());
129
+ }
130
+
131
+ function processTileFeatureProperties({
132
+ map,
133
+ data,
134
+ startIndex,
135
+ endIndex,
136
+ type,
137
+ bbox,
138
+ tileFormat,
139
+ uniqueIdProperty,
140
+ storeGeometry,
141
+ geometryIntersection,
142
+ }: {
143
+ map: TileMap;
144
+ data: BinaryFeature;
145
+ startIndex: number;
146
+ endIndex: number;
147
+ type: BinaryGeometryType;
148
+ bbox: BBox;
149
+ tileFormat?: TileFormat;
150
+ uniqueIdProperty?: string;
151
+ storeGeometry: boolean;
152
+ geometryIntersection?: Geometry;
153
+ }) {
154
+ const tileProps = getPropertiesFromTile(data, startIndex);
155
+ const uniquePropertyValue = getUniquePropertyValue(
156
+ tileProps,
157
+ uniqueIdProperty,
158
+ map
159
+ );
160
+
161
+ if (!uniquePropertyValue || map.has(uniquePropertyValue)) {
162
+ return;
163
+ }
164
+ let geometry: Geometry | null = null;
165
+
166
+ // Only calculate geometry if necessary
167
+ if (storeGeometry || geometryIntersection) {
168
+ const {positions} = data;
169
+ const ringCoordinates = getRingCoordinatesFor(
170
+ startIndex,
171
+ endIndex,
172
+ positions
173
+ );
174
+ geometry = getFeatureByType(ringCoordinates, type);
175
+ }
176
+
177
+ // If intersection is required, check before proceeding
178
+ if (
179
+ geometry &&
180
+ geometryIntersection &&
181
+ !intersects(geometry, geometryIntersection)
182
+ ) {
183
+ return;
184
+ }
185
+
186
+ const properties = parseProperties(tileProps);
187
+
188
+ // Only save geometry if necessary
189
+ if (storeGeometry && geometry) {
190
+ properties[FEATURE_GEOM_PROPERTY] =
191
+ tileFormat === TileFormat.MVT
192
+ ? transformTileCoordsToWGS84(geometry, bbox)
193
+ : geometry;
194
+ }
195
+ map.set(uniquePropertyValue, properties);
196
+ }
197
+
198
+ function addIntersectedFeaturesInTile({
199
+ map,
200
+ data,
201
+ geometryIntersection,
202
+ type,
203
+ bbox,
204
+ tileFormat,
205
+ uniqueIdProperty,
206
+ options,
207
+ }: {
208
+ map: TileMap;
209
+ data: BinaryFeature;
210
+ geometryIntersection: Geometry;
211
+ type: BinaryGeometryType;
212
+ bbox: BBox;
213
+ tileFormat?: TileFormat;
214
+ uniqueIdProperty?: string;
215
+ options?: TileFeatureExtractOptions;
216
+ }) {
217
+ const indices = getIndices(data);
218
+ const storeGeometry = options?.storeGeometry || false;
219
+
220
+ for (let i = 0; i < indices.length - 1; i++) {
221
+ const startIndex = indices[i];
222
+ const endIndex = indices[i + 1];
223
+ processTileFeatureProperties({
224
+ map,
225
+ data,
226
+ startIndex,
227
+ endIndex,
228
+ type,
229
+ bbox,
230
+ tileFormat,
231
+ uniqueIdProperty,
232
+ storeGeometry,
233
+ geometryIntersection,
234
+ });
235
+ }
236
+ }
237
+
238
+ function getIndices(data: BinaryFeature) {
239
+ let indices: BinaryAttribute;
240
+ switch (data.type) {
241
+ case 'Point':
242
+ // @ts-expect-error Missing or changed types?
243
+ indices = data.pointIndices;
244
+ break;
245
+ case 'LineString':
246
+ indices = data.pathIndices;
247
+ break;
248
+ case 'Polygon':
249
+ indices = data.primitivePolygonIndices;
250
+ break;
251
+ default:
252
+ throw new Error(`Unexpected type, "${(data as BinaryFeature).type}"`);
253
+ }
254
+ return indices.value;
255
+ }
256
+
257
+ function getFeatureId(data: BinaryFeature, startIndex: number) {
258
+ return data.featureIds.value[startIndex];
259
+ }
260
+
261
+ function getPropertiesFromTile(data: BinaryFeature, startIndex: number) {
262
+ const featureId = getFeatureId(data, startIndex);
263
+ const {properties, numericProps, fields} = data;
264
+ const result: TileDataInternal = {
265
+ uniqueId: (fields?.[featureId] as {id: string | number})?.id,
266
+ properties: properties[featureId],
267
+ numericProps: {},
268
+ };
269
+
270
+ for (const key in numericProps) {
271
+ result.numericProps[key] = numericProps[key].value[startIndex];
272
+ }
273
+
274
+ return result;
275
+ }
276
+
277
+ function parseProperties(tileProps: TileDataInternal) {
278
+ const {properties, numericProps} = tileProps;
279
+ return Object.assign({}, properties, numericProps);
280
+ }
281
+
282
+ function getUniquePropertyValue(
283
+ tileProps: TileDataInternal,
284
+ uniqueIdProperty: string | undefined,
285
+ map: TileMap
286
+ ) {
287
+ if (uniqueIdProperty) {
288
+ return getValueFromTileProps(tileProps, uniqueIdProperty);
289
+ }
290
+
291
+ if (tileProps.uniqueId) {
292
+ return tileProps.uniqueId;
293
+ }
294
+
295
+ const artificialId = map.size + 1; // a counter, assumed as a valid new id
296
+ return (
297
+ getValueFromTileProps(tileProps, 'cartodb_id') ||
298
+ getValueFromTileProps(tileProps, 'geoid') ||
299
+ artificialId
300
+ );
301
+ }
302
+
303
+ function getValueFromTileProps(
304
+ tileProps: TileDataInternal,
305
+ propertyName: string
306
+ ) {
307
+ const {properties, numericProps} = tileProps;
308
+ return numericProps[propertyName] || properties[propertyName];
309
+ }
310
+
311
+ function getFeatureByType(
312
+ coordinates: Position[],
313
+ type: BinaryGeometryType
314
+ ): Polygon | LineString | Point {
315
+ switch (type) {
316
+ case 'Polygon':
317
+ return {type: 'Polygon', coordinates: [coordinates]};
318
+ case 'LineString':
319
+ return {type: 'LineString', coordinates};
320
+ case 'Point':
321
+ return {type: 'Point', coordinates: coordinates[0]};
322
+ default:
323
+ throw new Error('Invalid geometry type');
324
+ }
325
+ }
326
+
327
+ function getRingCoordinatesFor(
328
+ startIndex: number,
329
+ endIndex: number,
330
+ positions: BinaryAttribute
331
+ ) {
332
+ const ringCoordinates = [];
333
+
334
+ for (let j = startIndex; j < endIndex; j++) {
335
+ ringCoordinates.push(
336
+ Array.from(
337
+ positions.value.subarray(j * positions.size, (j + 1) * positions.size)
338
+ )
339
+ );
340
+ }
341
+
342
+ return ringCoordinates;
343
+ }
344
+
345
+ function calculateFeatures({
346
+ map,
347
+ tileIsFullyVisible,
348
+ geometryIntersection,
349
+ data,
350
+ type,
351
+ bbox,
352
+ tileFormat,
353
+ uniqueIdProperty,
354
+ options,
355
+ }: {
356
+ map: TileMap;
357
+ tileIsFullyVisible: boolean;
358
+ geometryIntersection: SpatialFilter;
359
+ data: BinaryFeature;
360
+ type: BinaryGeometryType;
361
+ bbox: BBox;
362
+ tileFormat?: TileFormat;
363
+ uniqueIdProperty?: string;
364
+ options?: TileFeatureExtractOptions;
365
+ }) {
366
+ if (!data?.properties.length) {
367
+ return;
368
+ }
369
+
370
+ if (tileIsFullyVisible) {
371
+ addAllFeaturesInTile({
372
+ map,
373
+ data,
374
+ type,
375
+ bbox,
376
+ tileFormat,
377
+ uniqueIdProperty,
378
+ options,
379
+ });
380
+ } else {
381
+ addIntersectedFeaturesInTile({
382
+ map,
383
+ data,
384
+ geometryIntersection,
385
+ type,
386
+ bbox,
387
+ tileFormat,
388
+ uniqueIdProperty,
389
+ options,
390
+ });
391
+ }
392
+ }
393
+
394
+ function addAllFeaturesInTile({
395
+ map,
396
+ data,
397
+ type,
398
+ bbox,
399
+ tileFormat,
400
+ uniqueIdProperty,
401
+ options,
402
+ }: {
403
+ map: TileMap;
404
+ data: BinaryFeature;
405
+ type: BinaryGeometryType;
406
+ bbox: BBox;
407
+ tileFormat?: TileFormat;
408
+ uniqueIdProperty?: string;
409
+ options?: TileFeatureExtractOptions;
410
+ }) {
411
+ const indices = getIndices(data);
412
+ const storeGeometry = options?.storeGeometry || false;
413
+ for (let i = 0; i < indices.length - 1; i++) {
414
+ const startIndex = indices[i];
415
+ const endIndex = indices[i + 1];
416
+ processTileFeatureProperties({
417
+ map,
418
+ data,
419
+ startIndex,
420
+ endIndex,
421
+ type,
422
+ bbox,
423
+ tileFormat,
424
+ uniqueIdProperty,
425
+ storeGeometry,
426
+ });
427
+ }
428
+ }
429
+
430
+ function createIndicesForPoints(data: BinaryPointFeature) {
431
+ const featureIds = data.featureIds.value;
432
+ const lastFeatureId = featureIds[featureIds.length - 1];
433
+ const PointIndicesArray = featureIds.constructor as TypedArrayConstructor;
434
+
435
+ const pointIndices: BinaryAttribute = {
436
+ value: new PointIndicesArray(featureIds.length + 1),
437
+ size: 1,
438
+ };
439
+ pointIndices.value.set(featureIds);
440
+ pointIndices.value.set([lastFeatureId + 1], featureIds.length);
441
+
442
+ // @ts-expect-error Missing or changed types?
443
+ data.pointIndices = pointIndices;
444
+ }
@@ -0,0 +1,119 @@
1
+ import {SpatialIndex} from '../constants.js';
2
+ import {getResolution as quadbinGetResolution, geometryToCells} from 'quadbin';
3
+ import bboxClip from '@turf/bbox-clip';
4
+ import {SpatialFilter, SpatialIndexTile} from '../types.js';
5
+ import {BBox, Feature} from 'geojson';
6
+ import {getResolution as h3GetResolution, polygonToCells} from 'h3-js';
7
+ import {FeatureData} from '../types-internal.js';
8
+ import {SpatialDataType} from '../sources/types.js';
9
+
10
+ export type TileFeaturesSpatialIndexOptions = {
11
+ tiles: SpatialIndexTile[];
12
+ spatialFilter: SpatialFilter;
13
+ spatialDataColumn: string;
14
+ spatialDataType: SpatialDataType;
15
+ };
16
+
17
+ export function tileFeaturesSpatialIndex({
18
+ tiles,
19
+ spatialFilter,
20
+ spatialDataColumn,
21
+ spatialDataType,
22
+ }: TileFeaturesSpatialIndexOptions): FeatureData[] {
23
+ const map = new Map();
24
+ const spatialIndex = getSpatialIndex(spatialDataType);
25
+ const resolution = getResolution(tiles, spatialIndex);
26
+ const spatialIndexIDName = spatialDataColumn
27
+ ? spatialDataColumn
28
+ : spatialIndex;
29
+
30
+ if (!resolution) {
31
+ return [];
32
+ }
33
+ const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
34
+
35
+ if (!cells?.length) {
36
+ return [];
37
+ }
38
+
39
+ // We transform cells to Set to improve the performace
40
+ const cellsSet = new Set<bigint | string>(cells);
41
+
42
+ for (const tile of tiles) {
43
+ if (tile.isVisible === false || !tile.data) {
44
+ continue;
45
+ }
46
+
47
+ tile.data.forEach((d: Feature) => {
48
+ if (cellsSet.has(d.id as bigint | string)) {
49
+ map.set(d.id, {...d.properties, [spatialIndexIDName]: d.id});
50
+ }
51
+ });
52
+ }
53
+ return Array.from(map.values());
54
+ }
55
+
56
+ function getResolution(
57
+ tiles: SpatialIndexTile[],
58
+ spatialIndex: SpatialIndex
59
+ ): number | undefined {
60
+ const data = tiles.find((tile) => tile.data?.length)?.data;
61
+
62
+ if (!data) {
63
+ return;
64
+ }
65
+
66
+ if (spatialIndex === SpatialIndex.QUADBIN) {
67
+ return Number(quadbinGetResolution(data[0].id));
68
+ }
69
+
70
+ if (spatialIndex === SpatialIndex.H3) {
71
+ return h3GetResolution(data[0].id);
72
+ }
73
+ }
74
+
75
+ const bboxWest: BBox = [-180, -90, 0, 90];
76
+ const bboxEast: BBox = [0, -90, 180, 90];
77
+
78
+ function getCellsCoverGeometry(
79
+ geometry: SpatialFilter,
80
+ spatialIndex: SpatialIndex,
81
+ resolution: number
82
+ ) {
83
+ if (spatialIndex === SpatialIndex.QUADBIN) {
84
+ // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
85
+ return geometryToCells(geometry, resolution);
86
+ }
87
+
88
+ if (spatialIndex === SpatialIndex.H3) {
89
+ // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
90
+ // so we clip the geometry to be sure that none of them is greater than 180 degrees
91
+ // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
92
+ return polygonToCells(
93
+ bboxClip(geometry, bboxWest).geometry.coordinates as
94
+ | number[][]
95
+ | number[][][],
96
+ resolution,
97
+ true
98
+ ).concat(
99
+ polygonToCells(
100
+ bboxClip(geometry, bboxEast).geometry.coordinates as
101
+ | number[][]
102
+ | number[][][],
103
+ resolution,
104
+ true
105
+ )
106
+ );
107
+ }
108
+ }
109
+
110
+ function getSpatialIndex(spatialDataType: SpatialDataType): SpatialIndex {
111
+ switch (spatialDataType) {
112
+ case 'h3':
113
+ return SpatialIndex.H3;
114
+ case 'quadbin':
115
+ return SpatialIndex.QUADBIN;
116
+ default:
117
+ throw new Error('Unexpected spatial data type');
118
+ }
119
+ }
package/src/index.ts CHANGED
@@ -16,3 +16,9 @@ export {
16
16
  query,
17
17
  requestWithParameters,
18
18
  } from './api/index.js';
19
+
20
+ // For unit testing only.
21
+ export * from './filters/index.js';
22
+ export * from './operations/index.js';
23
+ export * from './utils/makeIntervalComplete.js';
24
+ export * from './utils/transformToTileCoords.js';
@@ -1,3 +1,3 @@
1
1
  export {executeModel} from './model.js';
2
- export type {Model} from './model.js';
2
+ export type {Model, ModelSource} from './model.js';
3
3
  export type {ModelRequestOptions} from './common.js';
@@ -7,9 +7,10 @@ import {
7
7
  SpatialFilter,
8
8
  } from '../types.js';
9
9
  import {$TODO} from '../types-internal.js';
10
- import {assert} from '../utils.js';
10
+ import {assert, isPureObject} from '../utils.js';
11
11
  import {ModelRequestOptions, makeCall} from './common.js';
12
12
  import {ApiVersion} from '../constants.js';
13
+ import {SpatialDataType, SpatialFilterPolyfillMode} from '../sources/types.js';
13
14
 
14
15
  /** @internalRemarks Source: @carto/react-api */
15
16
  const AVAILABLE_MODELS = [
@@ -35,9 +36,14 @@ export interface ModelSource {
35
36
  data: string;
36
37
  filters?: Record<string, Filter>;
37
38
  filtersLogicalOperator?: FilterLogicalOperator;
38
- geoColumn?: string;
39
39
  spatialFilter?: SpatialFilter;
40
40
  queryParameters?: QueryParameters;
41
+ spatialDataColumn?: string;
42
+ spatialDataType?: SpatialDataType;
43
+ spatialFiltersResolution?: number;
44
+ spatialFiltersMode?: SpatialFilterPolyfillMode;
45
+ /** original resolution of the spatial index data as stored in the DW */
46
+ dataResolution?: number;
41
47
  }
42
48
 
43
49
  const {V3} = ApiVersion;
@@ -79,50 +85,51 @@ export function executeModel(props: {
79
85
  data,
80
86
  filters,
81
87
  filtersLogicalOperator = 'and',
82
- geoColumn = DEFAULT_GEO_COLUMN,
88
+ spatialDataType = 'geo',
89
+ spatialFiltersMode = 'intersects',
90
+ spatialFiltersResolution = 0,
83
91
  } = source;
84
92
 
85
- const queryParameters = source.queryParameters
86
- ? JSON.stringify(source.queryParameters)
87
- : '';
88
-
89
- const queryParams: Record<string, string> = {
93
+ const queryParams: Record<string, unknown> = {
90
94
  type,
91
95
  client: clientId,
92
96
  source: data,
93
- params: JSON.stringify(params),
94
- queryParameters,
95
- filters: JSON.stringify(filters),
97
+ params,
98
+ queryParameters: source.queryParameters || '',
99
+ filters,
96
100
  filtersLogicalOperator,
97
101
  };
98
102
 
103
+ const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN;
104
+
99
105
  // Picking Model API requires 'spatialDataColumn'.
100
106
  if (model === 'pick') {
101
- queryParams.spatialDataColumn = geoColumn;
107
+ queryParams.spatialDataColumn = spatialDataColumn;
102
108
  }
103
109
 
104
- // API supports multiple filters, we apply it only to geoColumn
110
+ // API supports multiple filters, we apply it only to spatialDataColumn
105
111
  const spatialFilters = source.spatialFilter
106
- ? {[geoColumn]: source.spatialFilter}
112
+ ? {[spatialDataColumn]: source.spatialFilter}
107
113
  : undefined;
108
114
 
109
115
  if (spatialFilters) {
110
- queryParams.spatialFilters = JSON.stringify(spatialFilters);
116
+ queryParams.spatialFilters = spatialFilters;
117
+ queryParams.spatialDataColumn = spatialDataColumn;
118
+ queryParams.spatialDataType = spatialDataType;
119
+ }
120
+
121
+ if (spatialDataType !== 'geo') {
122
+ if (spatialFiltersResolution > 0) {
123
+ queryParams.spatialFiltersResolution = spatialFiltersResolution;
124
+ }
125
+ queryParams.spatialFiltersMode = spatialFiltersMode;
111
126
  }
112
127
 
113
128
  const urlWithSearchParams =
114
- url + '?' + new URLSearchParams(queryParams).toString();
129
+ url + '?' + objectToURLSearchParams(queryParams).toString();
115
130
  const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH;
116
131
  if (isGet) {
117
132
  url = urlWithSearchParams;
118
- } else {
119
- // undo the JSON.stringify, @TODO find a better pattern
120
- queryParams.params = params as $TODO;
121
- queryParams.filters = filters as $TODO;
122
- queryParams.queryParameters = source.queryParameters as $TODO;
123
- if (spatialFilters) {
124
- queryParams.spatialFilters = spatialFilters as $TODO;
125
- }
126
133
  }
127
134
  return makeCall({
128
135
  url,
@@ -134,3 +141,19 @@ export function executeModel(props: {
134
141
  },
135
142
  });
136
143
  }
144
+
145
+ function objectToURLSearchParams(object: Record<string, unknown>) {
146
+ const params = new URLSearchParams();
147
+ for (const key in object) {
148
+ if (isPureObject(object[key])) {
149
+ params.append(key, JSON.stringify(object[key]));
150
+ } else if (Array.isArray(object[key])) {
151
+ params.append(key, JSON.stringify(object[key]));
152
+ } else if (object[key] === null) {
153
+ params.append(key, 'null');
154
+ } else if (object[key] !== undefined) {
155
+ params.append(key, String(object[key]));
156
+ }
157
+ }
158
+ return params;
159
+ }