@carto/api-client 0.5.0-alpha.13 → 0.5.0-alpha.14

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/package.json CHANGED
@@ -8,7 +8,7 @@
8
8
  "homepage": "https://github.com/CartoDB/carto-api-client#readme",
9
9
  "author": "Don McCurdy <donmccurdy@carto.com>",
10
10
  "packageManager": "yarn@4.3.1",
11
- "version": "0.5.0-alpha.13",
11
+ "version": "0.5.0-alpha.14",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -49,7 +49,7 @@
49
49
  "format:check": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --check",
50
50
  "clean": "rimraf build/*",
51
51
  "postversion": "yarn postversion:check && yarn postversion:commit && yarn postversion:push",
52
- "postversion:check": "yarn lint && yarn test",
52
+ "postversion:check": "yarn lint && yarn format:check && yarn test",
53
53
  "postversion:commit": "node scripts/postversion-commit.js",
54
54
  "postversion:push": "git push && git push --tags",
55
55
  "prepublish": "yarn lint && yarn test",
@@ -73,45 +73,46 @@
73
73
  "@turf/invariant": "^7.2.0",
74
74
  "@turf/union": "^7.2.0",
75
75
  "@types/geojson": "^7946.0.16",
76
- "h3-js": "4.1.0"
76
+ "h3-js": "4.1.0",
77
+ "quadbin": "^0.4.0-alpha.2"
77
78
  },
78
79
  "devDependencies": {
79
- "@deck.gl/aggregation-layers": "~9.1.0",
80
- "@deck.gl/carto": "~9.1.0",
81
- "@deck.gl/core": "~9.1.0",
82
- "@deck.gl/extensions": "~9.1.0",
83
- "@deck.gl/geo-layers": "~9.1.0",
84
- "@deck.gl/layers": "~9.1.0",
85
- "@deck.gl/mesh-layers": "~9.1.0",
86
- "@eslint/js": "^9.20.0",
80
+ "@deck.gl/aggregation-layers": "~9.1.4",
81
+ "@deck.gl/carto": "~9.1.4",
82
+ "@deck.gl/core": "~9.1.4",
83
+ "@deck.gl/extensions": "~9.1.4",
84
+ "@deck.gl/geo-layers": "~9.1.4",
85
+ "@deck.gl/layers": "~9.1.4",
86
+ "@deck.gl/mesh-layers": "~9.1.4",
87
+ "@eslint/js": "^9.21.0",
87
88
  "@lit/react": "^1.0.7",
88
89
  "@lit/task": "^1.0.2",
89
90
  "@loaders.gl/core": "^4.3.3",
90
- "@luma.gl/core": "~9.1.0",
91
- "@luma.gl/engine": "~9.1.0",
92
- "@luma.gl/shadertools": "~9.1.0",
91
+ "@luma.gl/core": "~9.1.4",
92
+ "@luma.gl/engine": "~9.1.4",
93
+ "@luma.gl/shadertools": "~9.1.4",
93
94
  "@turf/buffer": "^7.2.0",
94
95
  "@turf/random": "^7.2.0",
95
96
  "@types/json-schema": "^7.0.15",
96
97
  "@types/react": "^18.3.18",
97
98
  "@types/semver": "^7.5.8",
98
- "@vitest/coverage-istanbul": "^3.0.5",
99
+ "@vitest/coverage-istanbul": "^3.0.7",
99
100
  "@webcomponents/webcomponentsjs": "^2.8.0",
100
101
  "concurrently": "^9.1.2",
101
102
  "echarts": "^5.6.0",
102
- "eslint": "^9.20.1",
103
+ "eslint": "^9.21.0",
103
104
  "lit": "^3.2.1",
104
105
  "lit-analyzer": "^2.0.3",
105
- "maplibre-gl": "^5.1.0",
106
- "prettier": "^3.4.2",
106
+ "maplibre-gl": "^5.2.0",
107
+ "prettier": "^3.5.3",
107
108
  "rimraf": "^6.0.1",
108
109
  "semver": "^7.7.1",
109
110
  "thenby": "^1.3.4",
110
111
  "tinybench": "^3.1.1",
111
112
  "tsup": "^8.3.6",
112
113
  "typescript": "~5.8.2",
113
- "typescript-eslint": "^8.24.0",
114
- "vite": "^6.1.0",
114
+ "typescript-eslint": "^8.26.0",
115
+ "vite": "^6.2.0",
115
116
  "vitest": "3.0.7"
116
117
  },
117
118
  "stableVersion": "0.4.6"
@@ -1,10 +1,12 @@
1
- import {SpatialFilter, SpatialIndexTile, Tile} from '../types.js';
1
+ import {RasterTile, SpatialFilter, SpatialIndexTile, Tile} from '../types.js';
2
2
  import {tileFeaturesGeometries} from './tileFeaturesGeometries.js';
3
3
  import {tileFeaturesSpatialIndex} from './tileFeaturesSpatialIndex.js';
4
4
  import {TileFormat} from '../constants.js';
5
5
  import {DEFAULT_GEO_COLUMN} from '../constants-internal.js';
6
6
  import {FeatureData} from '../types-internal.js';
7
- import {SpatialDataType} from '../sources/types.js';
7
+ import {RasterMetadata, SpatialDataType} from '../sources/types.js';
8
+ import {isRasterTile, tileFeaturesRaster} from './tileFeaturesRaster.js';
9
+ import {assert} from '../utils.js';
8
10
 
9
11
  /** @privateRemarks Source: @carto/react-core */
10
12
  export type TileFeatures = {
@@ -14,6 +16,7 @@ export type TileFeatures = {
14
16
  spatialDataColumn?: string;
15
17
  spatialFilter: SpatialFilter;
16
18
  uniqueIdProperty?: string;
19
+ rasterMetadata?: RasterMetadata;
17
20
  options?: TileFeatureExtractOptions;
18
21
  };
19
22
 
@@ -31,21 +34,34 @@ export function tileFeatures({
31
34
  tileFormat,
32
35
  spatialDataColumn = DEFAULT_GEO_COLUMN,
33
36
  spatialDataType,
37
+ rasterMetadata,
34
38
  options = {},
35
39
  }: TileFeatures): FeatureData[] {
36
- if (spatialDataType !== 'geo') {
37
- return tileFeaturesSpatialIndex({
38
- tiles: tiles as SpatialIndexTile[],
40
+ if (spatialDataType === 'geo') {
41
+ return tileFeaturesGeometries({
42
+ tiles,
43
+ tileFormat,
44
+ spatialFilter,
45
+ uniqueIdProperty,
46
+ options,
47
+ });
48
+ }
49
+
50
+ if (tiles.some(isRasterTile)) {
51
+ assert(rasterMetadata, 'Missing raster metadata');
52
+ return tileFeaturesRaster({
53
+ tiles: tiles as RasterTile[],
39
54
  spatialFilter,
40
55
  spatialDataColumn,
41
56
  spatialDataType,
57
+ rasterMetadata,
42
58
  });
43
59
  }
44
- return tileFeaturesGeometries({
45
- tiles,
46
- tileFormat,
60
+
61
+ return tileFeaturesSpatialIndex({
62
+ tiles: tiles as SpatialIndexTile[],
47
63
  spatialFilter,
48
- uniqueIdProperty,
49
- options,
64
+ spatialDataColumn,
65
+ spatialDataType,
50
66
  });
51
67
  }
@@ -0,0 +1,122 @@
1
+ import {
2
+ cellToChildren as _cellToChildren,
3
+ cellToTile,
4
+ geometryToCells,
5
+ getResolution,
6
+ } from 'quadbin';
7
+ import {RasterTile, SpatialFilter, Tile} from '../types.js';
8
+ import {FeatureData} from '../types-internal.js';
9
+ import {
10
+ RasterMetadata,
11
+ RasterMetadataBand,
12
+ SpatialDataType,
13
+ } from '../sources/types.js';
14
+
15
+ export type TileFeaturesRasterOptions = {
16
+ tiles: RasterTile[];
17
+ spatialFilter: SpatialFilter;
18
+ spatialDataColumn: string;
19
+ spatialDataType: SpatialDataType;
20
+ rasterMetadata: RasterMetadata;
21
+ };
22
+
23
+ export function tileFeaturesRaster({
24
+ tiles,
25
+ ...options
26
+ }: TileFeaturesRasterOptions): FeatureData[] {
27
+ // Cache band metadata for faster lookup while iterating over pixels.
28
+ const metadataByBand: Record<string, RasterMetadataBand & {nodata: number}> =
29
+ {};
30
+ for (const band of options.rasterMetadata.bands) {
31
+ // TODO(cleanup): Remove copy and cast after API is updated to return 'nodata' as a number.
32
+ metadataByBand[band.name] = {...band, nodata: Number(band.nodata)};
33
+ }
34
+
35
+ // Omit empty and invisible tiles for simpler processing and types.
36
+ tiles = tiles.filter(isRasterTileVisible);
37
+ if (tiles.length === 0) return [];
38
+
39
+ // Raster tiles, and all pixels, are quadbin cells. Resolution of a pixel is
40
+ // the resolution of the tile, plus the number of subdivisions. Block size
41
+ // must be square, N x N, where N is a power of two.
42
+ const tileResolution = getResolution(tiles[0].index.q);
43
+ const tileBlockSize = tiles[0].data!.blockSize;
44
+ const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
45
+
46
+ // Compute covering cells for the spatial filter, at same resolution as the
47
+ // raster pixels, to be used as a mask.
48
+ const spatialFilterCells = new Set(
49
+ geometryToCells(options.spatialFilter, cellResolution)
50
+ );
51
+
52
+ const data = new Map<bigint, FeatureData>();
53
+
54
+ for (const tile of tiles as Required<RasterTile>[]) {
55
+ const parent = tile.index.q;
56
+
57
+ const children = cellToChildrenSorted(parent, cellResolution);
58
+
59
+ // For each pixel/cell within the spatial filter, create a FeatureData.
60
+ // Order is row-major, starting from NW and ending at SE.
61
+ for (let i = 0; i < children.length; i++) {
62
+ if (!spatialFilterCells.has(children[i])) continue;
63
+
64
+ const cellData: FeatureData = {};
65
+ let cellDataExists = false;
66
+
67
+ for (const band in tile.data.cells.numericProps) {
68
+ const value = tile.data.cells.numericProps[band].value[i];
69
+ const bandMetadata = metadataByBand[band];
70
+
71
+ if (isValidBandValue(value, bandMetadata.nodata)) {
72
+ cellData[band] = tile.data.cells.numericProps[band].value[i];
73
+ cellDataExists = true;
74
+ }
75
+ }
76
+
77
+ if (cellDataExists) {
78
+ data.set(children[i], cellData);
79
+ }
80
+ }
81
+ }
82
+
83
+ return Array.from(data.values());
84
+ }
85
+
86
+ /**
87
+ * Detects whether a given {@link Tile} is a {@link RasterTile}.
88
+ * @privateRemarks Method of detection is arbitrary, and may be changed.
89
+ */
90
+ export function isRasterTile(tile: Tile): tile is RasterTile {
91
+ return !!(tile.data as Record<string, unknown>)?.cells;
92
+ }
93
+
94
+ function isRasterTileVisible(tile: RasterTile): tile is Required<RasterTile> {
95
+ return !!(tile.isVisible && tile.data?.cells?.numericProps);
96
+ }
97
+
98
+ /**
99
+ * For the raster format, children are sorted in row-major order, starting from
100
+ * NW and ending at SE. Order returned by quadbin's cellToChildren() is not
101
+ * defined (and not related to the raster format), so sort explicitly here.
102
+ */
103
+ function cellToChildrenSorted(parent: bigint, resolution: bigint): bigint[] {
104
+ return _cellToChildren(parent, resolution).sort(
105
+ (cellA: bigint, cellB: bigint) => {
106
+ const tileA = cellToTile(cellA);
107
+ const tileB = cellToTile(cellB);
108
+ if (tileA.y !== tileB.y) {
109
+ return tileA.y > tileB.y ? 1 : -1;
110
+ }
111
+ return tileA.x > tileB.x ? 1 : -1;
112
+ }
113
+ );
114
+ }
115
+
116
+ /**
117
+ * Returns true if the given value is valid (not NaN, not 'nodata')
118
+ * for the given raster band.
119
+ */
120
+ function isValidBandValue(value: unknown, nodata: number): value is number {
121
+ return Number.isNaN(value) ? false : nodata !== value;
122
+ }
@@ -3,6 +3,12 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {baseSource} from './base-source.js';
6
+ import {getTileFormat} from '../utils/getTileFormat.js';
7
+ import {
8
+ WidgetRasterSource,
9
+ WidgetRasterSourceResult,
10
+ } from '../widget-sources/index.js';
11
+
6
12
  import type {
7
13
  FilterOptions,
8
14
  SourceOptions,
@@ -18,7 +24,7 @@ type UrlParameters = {
18
24
  filters?: Record<string, unknown>;
19
25
  };
20
26
 
21
- export type RasterSourceResponse = TilejsonResult;
27
+ export type RasterSourceResponse = TilejsonResult & WidgetRasterSourceResult;
22
28
 
23
29
  export const rasterSource = async function (
24
30
  options: RasterSourceOptions
@@ -29,9 +35,16 @@ export const rasterSource = async function (
29
35
  urlParameters.filters = filters;
30
36
  }
31
37
 
32
- return baseSource<UrlParameters>(
33
- 'raster',
34
- options,
35
- urlParameters
38
+ return baseSource<UrlParameters>('raster', options, urlParameters).then(
39
+ (result) => ({
40
+ ...(result as TilejsonResult),
41
+ widgetSource: new WidgetRasterSource({
42
+ ...options,
43
+ tileFormat: getTileFormat(result as TilejsonResult),
44
+ spatialDataColumn: 'quadbin',
45
+ spatialDataType: 'quadbin',
46
+ rasterMetadata: (result as TilejsonResult).raster_metadata!,
47
+ }),
48
+ })
36
49
  ) as Promise<RasterSourceResponse>;
37
50
  };
@@ -364,8 +364,20 @@ export enum RasterBandColorinterp {
364
364
  Palette = 'palette',
365
365
  }
366
366
 
367
+ export type RasterBandType =
368
+ | 'uint8'
369
+ | 'int8'
370
+ | 'uint16'
371
+ | 'int16'
372
+ | 'uint32'
373
+ | 'int32'
374
+ | 'uint64'
375
+ | 'int64'
376
+ | 'float32'
377
+ | 'float64';
378
+
367
379
  export type RasterMetadataBand = {
368
- type: string;
380
+ type: RasterBandType;
369
381
  name: string;
370
382
  stats: RasterMetadataBandStats;
371
383
  /**
@@ -379,7 +391,7 @@ export type RasterMetadataBand = {
379
391
  /**
380
392
  * Default color mapping for unique values (or if coloprinterp is `palette`)
381
393
  */
382
- colortable?: Record<string, number[]>;
394
+ colortable?: Record<string, [number, number, number, number]>;
383
395
 
384
396
  /**
385
397
  * No value representation.
package/src/types.ts CHANGED
@@ -28,8 +28,6 @@ export type Viewport = [number, number, number, number];
28
28
  export type Tile = {
29
29
  index: {x: number; y: number; z: number};
30
30
  id: string;
31
- content: unknown;
32
- zoom: number;
33
31
  bbox: {west: number; east: number; north: number; south: number};
34
32
  isVisible: boolean;
35
33
  data?: BinaryFeatureCollection;
@@ -40,6 +38,12 @@ export type SpatialIndexTile = Tile & {
40
38
  data?: (Feature & {id: bigint})[];
41
39
  };
42
40
 
41
+ export type RasterTile = Tile & {
42
+ id: string;
43
+ index: {q: bigint; i: string};
44
+ data?: Raster;
45
+ };
46
+
43
47
  /** @privateRemarks Source: @deck.gl/carto */
44
48
  export type Raster = {
45
49
  blockSize: number;
@@ -1,5 +1,6 @@
1
1
  export * from './widget-source.js';
2
2
  export * from './widget-query-source.js';
3
+ export * from './widget-raster-source.js';
3
4
  export * from './widget-remote-source.js';
4
5
  export * from './widget-table-source.js';
5
6
  export * from './widget-tileset-source.js';
@@ -0,0 +1,14 @@
1
+ import {RasterMetadata} from '../sources/index.js';
2
+ import {
3
+ WidgetTilesetSource,
4
+ WidgetTilesetSourceProps,
5
+ } from './widget-tileset-source.js';
6
+
7
+ export type WidgetRasterSourceProps = WidgetTilesetSourceProps & {
8
+ rasterMetadata: RasterMetadata;
9
+ spatialDataType: 'quadbin';
10
+ };
11
+
12
+ export type WidgetRasterSourceResult = {widgetSource: WidgetRasterSource};
13
+
14
+ export class WidgetRasterSource extends WidgetTilesetSource<WidgetRasterSourceProps> {}
@@ -84,13 +84,10 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
84
84
  }
85
85
 
86
86
  this._features = tileFeatures({
87
- tiles: this._tiles,
88
- tileFormat: this.props.tileFormat,
87
+ ...this.props,
89
88
  ...this._tileFeatureExtractOptions,
90
-
89
+ tiles: this._tiles,
91
90
  spatialFilter,
92
- spatialDataColumn: this.props.spatialDataColumn,
93
- spatialDataType: this.props.spatialDataType,
94
91
  });
95
92
 
96
93
  prevInputs.spatialFilter = spatialFilter;
@@ -128,15 +125,6 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
128
125
  filterOwner,
129
126
  spatialFilter,
130
127
  }: FormulaRequestOptions): Promise<FormulaResponse> {
131
- if (operation === 'custom') {
132
- throw new Error('Custom aggregation not supported for tilesets');
133
- }
134
-
135
- // Column is required except when operation is 'count'.
136
- if ((column && column !== '*') || operation !== 'count') {
137
- assertColumn(this._features, column);
138
- }
139
-
140
128
  const filteredFeatures = this._getFilteredFeatures(
141
129
  spatialFilter,
142
130
  filters,
@@ -147,6 +135,15 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
147
135
  return {value: null};
148
136
  }
149
137
 
138
+ if (operation === 'custom') {
139
+ throw new Error('Custom aggregation not supported for tilesets');
140
+ }
141
+
142
+ // Column is required except when operation is 'count'.
143
+ if ((column && column !== '*') || operation !== 'count') {
144
+ assertColumn(this._features, column);
145
+ }
146
+
150
147
  const targetOperation = aggregationFunctions[operation];
151
148
  return {
152
149
  value: targetOperation(filteredFeatures, column, joinOperation),
@@ -347,8 +344,6 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
347
344
  filterOwner,
348
345
  spatialFilter,
349
346
  }: RangeRequestOptions): Promise<RangeResponse> {
350
- assertColumn(this._features, column);
351
-
352
347
  const filteredFeatures = this._getFilteredFeatures(
353
348
  spatialFilter,
354
349
  filters,
@@ -361,6 +356,8 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
361
356
  return null;
362
357
  }
363
358
 
359
+ assertColumn(this._features, column);
360
+
364
361
  return {
365
362
  min: aggregationFunctions.min(filteredFeatures, column),
366
363
  max: aggregationFunctions.max(filteredFeatures, column),
@@ -55,14 +55,16 @@ export type WidgetTilesetSourceResult = {widgetSource: WidgetTilesetSource};
55
55
  * const { widgetSource } = await data;
56
56
  * ```
57
57
  */
58
- export class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps> {
58
+ export class WidgetTilesetSource<
59
+ Props extends WidgetTilesetSourceProps = WidgetTilesetSourceProps,
60
+ > extends WidgetSource<Props> {
59
61
  protected _localImpl: WidgetTilesetSourceImpl | null = null;
60
62
 
61
63
  protected _workerImpl: Worker | null = null;
62
64
  protected _workerEnabled: boolean;
63
65
  protected _workerNextRequestId = 1;
64
66
 
65
- constructor(props: WidgetTilesetSourceProps) {
67
+ constructor(props: Props) {
66
68
  super(props);
67
69
 
68
70
  this._workerEnabled =
@@ -198,11 +200,22 @@ export class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps>
198
200
 
199
201
  const worker = this._getWorker();
200
202
 
201
- tiles = (tiles as Tile[]).map(({id, bbox, data}) => ({
202
- id,
203
- bbox,
204
- data,
205
- }));
203
+ // We cannot pass an instance of Tile2DHeader to a Web Worker, and must
204
+ // extract properties required for widget calculations. Note that the
205
+ // `tile: Tile = {...}` assignment is structured so TS will warn if any
206
+ // types required by the internal 'Tile' type are missing.
207
+ tiles = (tiles as Tile[]).map(
208
+ ({id, index, bbox, isVisible, data}: Tile) => {
209
+ const tile: Tile = {
210
+ id,
211
+ index,
212
+ isVisible,
213
+ data,
214
+ bbox,
215
+ };
216
+ return tile;
217
+ }
218
+ );
206
219
 
207
220
  worker.postMessage({
208
221
  method: Method.LOAD_TILES,