@carto/api-client 0.5.28 → 0.5.30-alpha.143d135.117

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.28",
11
+ "version": "0.5.30-alpha.143d135.117",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -302,6 +302,28 @@ export async function fetchMap({
302
302
  }
303
303
  });
304
304
 
305
+ // Flag datasets that need feature bounding boxes for label positioning.
306
+ // Some data waraehouse (notably when using MVT) only support clipped
307
+ // geometry. The server-provided bbox enables stable label placement.
308
+ const layers = map.keplerMapConfig.config.visState.layers;
309
+ const datasetsWithLabels = new Set<string>();
310
+ for (const layer of layers) {
311
+ const hasTextLabel = layer.config?.textLabel?.some(
312
+ (t: any) => t.field?.name
313
+ );
314
+ if (hasTextLabel) {
315
+ datasetsWithLabels.add(layer.config.dataId);
316
+ }
317
+ }
318
+ map.datasets.forEach((dataset: any) => {
319
+ if (
320
+ datasetsWithLabels.has(dataset.id) &&
321
+ (dataset.type === 'table' || dataset.type === 'query')
322
+ ) {
323
+ dataset.featureBbox = true;
324
+ }
325
+ });
326
+
305
327
  const [basemap] = await Promise.all([
306
328
  fetchBasemapProps({config: map.keplerMapConfig.config, errorContext}),
307
329
 
@@ -106,11 +106,13 @@ export function configureSource({
106
106
  tileResolution,
107
107
  ...(queryParameters && {queryParameters}),
108
108
  } as QuerySourceOptions;
109
+ const {featureBbox} = dataset;
109
110
  const vectorOptions = {
110
111
  spatialDataColumn,
111
112
  ...(columns && {columns}),
112
113
  ...(filters && {filters}),
113
114
  ...(aggregationExp && {aggregationExp}),
115
+ ...(featureBbox && {featureBbox}),
114
116
  } as VectorTableSourceOptions;
115
117
 
116
118
  if (type === 'raster') {
@@ -265,6 +265,7 @@ export type Dataset = {
265
265
  name?: string | null;
266
266
  spatialIndex?: string | null;
267
267
  exportToBucketAvailable?: boolean;
268
+ featureBbox?: boolean;
268
269
  };
269
270
 
270
271
  export type AttributeType = 'String' | 'Number' | 'Timestamp' | 'Boolean';
@@ -1,7 +1,7 @@
1
1
  import intersects from '@turf/boolean-intersects';
2
2
  import type {FeatureCollection} from 'geojson';
3
3
  import type {FeatureData} from '../types-internal.js';
4
- import type {SpatialFilter} from '../types.js';
4
+ import type {GeometrySpatialFilter} from '../types.js';
5
5
 
6
6
  export function geojsonFeatures({
7
7
  geojson,
@@ -9,7 +9,7 @@ export function geojsonFeatures({
9
9
  uniqueIdProperty,
10
10
  }: {
11
11
  geojson: FeatureCollection;
12
- spatialFilter: SpatialFilter;
12
+ spatialFilter: GeometrySpatialFilter;
13
13
  uniqueIdProperty?: string;
14
14
  }): FeatureData[] {
15
15
  let uniqueIdx = 0;
@@ -1,6 +1,6 @@
1
1
  import type {
2
2
  RasterTile,
3
- SpatialFilter,
3
+ GeometrySpatialFilter,
4
4
  SpatialIndexTile,
5
5
  Tile,
6
6
  } from '../types.js';
@@ -19,7 +19,7 @@ export type TileFeatures = {
19
19
  tileFormat: TileFormat;
20
20
  spatialDataType: SpatialDataType;
21
21
  spatialDataColumn?: string;
22
- spatialFilter?: SpatialFilter;
22
+ spatialFilter?: GeometrySpatialFilter;
23
23
  uniqueIdProperty?: string;
24
24
  rasterMetadata?: RasterMetadata;
25
25
  storeGeometry?: boolean;
@@ -9,7 +9,7 @@ import type {
9
9
  Polygon,
10
10
  Position,
11
11
  } from 'geojson';
12
- import type {SpatialFilter, Tile} from '../types.js';
12
+ import type {GeometrySpatialFilter, Tile} from '../types.js';
13
13
  import type {FeatureData} from '../types-internal.js';
14
14
  import type {
15
15
  BinaryAttribute,
@@ -45,7 +45,7 @@ export function tileFeaturesGeometries({
45
45
  }: {
46
46
  tiles: Tile[];
47
47
  tileFormat?: TileFormat;
48
- spatialFilter?: SpatialFilter;
48
+ spatialFilter?: GeometrySpatialFilter;
49
49
  uniqueIdProperty?: string;
50
50
  options?: GeometryExtractOptions;
51
51
  }): FeatureData[] {
@@ -334,7 +334,7 @@ function calculateFeatures({
334
334
  options,
335
335
  }: {
336
336
  map: TileMap;
337
- spatialFilter?: SpatialFilter;
337
+ spatialFilter?: GeometrySpatialFilter;
338
338
  data: BinaryFeature;
339
339
  type: BinaryGeometryType;
340
340
  bbox: BBox;
@@ -1,5 +1,5 @@
1
1
  import {cellToTile, getResolution, tileToCell} from 'quadbin';
2
- import type {RasterTile, SpatialFilter, Tile} from '../types.js';
2
+ import type {RasterTile, GeometrySpatialFilter, Tile} from '../types.js';
3
3
  import type {FeatureData} from '../types-internal.js';
4
4
  import type {
5
5
  RasterMetadata,
@@ -10,7 +10,7 @@ import {intersectTileRaster} from './tileIntersection.js';
10
10
 
11
11
  export type TileFeaturesRasterOptions = {
12
12
  tiles: RasterTile[];
13
- spatialFilter?: SpatialFilter;
13
+ spatialFilter?: GeometrySpatialFilter;
14
14
  spatialDataColumn: string;
15
15
  spatialDataType: SpatialDataType;
16
16
  rasterMetadata: RasterMetadata;
@@ -1,6 +1,6 @@
1
1
  import {SpatialIndex} from '../constants.js';
2
2
  import {getResolution as quadbinGetResolution} from 'quadbin';
3
- import type {SpatialFilter, SpatialIndexTile} from '../types.js';
3
+ import type {GeometrySpatialFilter, SpatialIndexTile} from '../types.js';
4
4
  import type {Feature} from 'geojson';
5
5
  import {getResolution as h3GetResolution} from 'h3-js';
6
6
  import type {FeatureData} from '../types-internal.js';
@@ -9,7 +9,7 @@ import {intersectTileH3, intersectTileQuadbin} from './tileIntersection.js';
9
9
 
10
10
  export type TileFeaturesSpatialIndexOptions = {
11
11
  tiles: SpatialIndexTile[];
12
- spatialFilter?: SpatialFilter;
12
+ spatialFilter?: GeometrySpatialFilter;
13
13
  spatialDataColumn: string;
14
14
  spatialDataType: SpatialDataType;
15
15
  };
@@ -1,5 +1,5 @@
1
1
  import type {BBox} from 'geojson';
2
- import type {SpatialFilter} from '../types.js';
2
+ import type {GeometrySpatialFilter} from '../types.js';
3
3
  import bboxPolygon from '@turf/bbox-polygon';
4
4
  import booleanWithin from '@turf/boolean-within';
5
5
  import intersect from '@turf/intersect';
@@ -19,7 +19,7 @@ import bboxClip from '@turf/bbox-clip';
19
19
  // Return types:
20
20
  // - true: All features in tile are within spatial filter.
21
21
  // - false: No features in tile are within spatial filter; skip tile.
22
- // - SpatialFilter/Set: Requires a more detailed per-feature check for each
22
+ // - GeometrySpatialFilter/Set: Requires a more detailed per-feature check for each
23
23
  // feature in the tile. Provides a clipped spatial filter local to the tile
24
24
  // or, for spatial indexes, a set of indices ("covering set").
25
25
  //
@@ -41,8 +41,8 @@ import bboxClip from '@turf/bbox-clip';
41
41
  export function intersectTileGeometry(
42
42
  tileBbox: BBox,
43
43
  tileFormat?: TileFormat,
44
- spatialFilter?: SpatialFilter
45
- ): boolean | SpatialFilter {
44
+ spatialFilter?: GeometrySpatialFilter
45
+ ): boolean | GeometrySpatialFilter {
46
46
  const tilePolygon = bboxPolygon(tileBbox);
47
47
 
48
48
  if (!spatialFilter || booleanWithin(tilePolygon, spatialFilter)) {
@@ -71,7 +71,7 @@ export function intersectTileGeometry(
71
71
  export function intersectTileRaster(
72
72
  parent: bigint,
73
73
  cellResolution: bigint,
74
- spatialFilter?: SpatialFilter
74
+ spatialFilter?: GeometrySpatialFilter
75
75
  ) {
76
76
  return intersectTileQuadbin(parent, cellResolution, spatialFilter);
77
77
  }
@@ -83,7 +83,7 @@ export function intersectTileRaster(
83
83
  export function intersectTileQuadbin(
84
84
  parent: bigint,
85
85
  cellResolution: bigint,
86
- spatialFilter?: SpatialFilter
86
+ spatialFilter?: GeometrySpatialFilter
87
87
  ): boolean | Set<bigint> {
88
88
  const tilePolygon = quadbinCellToBoundary(parent);
89
89
 
@@ -116,7 +116,7 @@ const BBOX_EAST: BBox = [0, -90, 180, 90];
116
116
  /** @internal */
117
117
  export function intersectTileH3(
118
118
  cellResolution: number,
119
- spatialFilter?: SpatialFilter
119
+ spatialFilter?: GeometrySpatialFilter
120
120
  ): true | Set<string> {
121
121
  if (!spatialFilter) {
122
122
  return true;
@@ -23,7 +23,15 @@ import type {
23
23
  export type VectorQuerySourceOptions = SourceOptions &
24
24
  QuerySourceOptions &
25
25
  FilterOptions &
26
- ColumnsOption;
26
+ ColumnsOption & {
27
+ /**
28
+ * If `true`, the server includes a `_carto_bbox` property on each polygon
29
+ * feature, containing the bounding box of the full (unclipped) geometry as
30
+ * a `"west,south,east,north"` string in WGS84. Used by clients to compute
31
+ * stable label positions for polygons that span multiple tiles.
32
+ */
33
+ featureBbox?: boolean;
34
+ };
27
35
 
28
36
  type UrlParameters = {
29
37
  columns?: string;
@@ -34,6 +42,7 @@ type UrlParameters = {
34
42
  q: string;
35
43
  queryParameters?: Record<string, unknown> | unknown[];
36
44
  aggregationExp?: string;
45
+ featureBbox?: boolean;
37
46
  };
38
47
 
39
48
  export type VectorQuerySourceResponse = TilejsonResult &
@@ -50,6 +59,7 @@ export const vectorQuerySource = async function (
50
59
  tileResolution = DEFAULT_TILE_RESOLUTION,
51
60
  queryParameters,
52
61
  aggregationExp,
62
+ featureBbox,
53
63
  } = options;
54
64
 
55
65
  const spatialDataType = 'geo';
@@ -73,6 +83,9 @@ export const vectorQuerySource = async function (
73
83
  if (aggregationExp) {
74
84
  urlParameters.aggregationExp = aggregationExp;
75
85
  }
86
+ if (featureBbox) {
87
+ urlParameters.featureBbox = true;
88
+ }
76
89
 
77
90
  return baseSource<UrlParameters>('query', options, urlParameters).then(
78
91
  (result) => ({
@@ -23,7 +23,15 @@ import type {
23
23
  export type VectorTableSourceOptions = SourceOptions &
24
24
  TableSourceOptions &
25
25
  FilterOptions &
26
- ColumnsOption;
26
+ ColumnsOption & {
27
+ /**
28
+ * If `true`, the server includes a `_carto_bbox` property on each polygon
29
+ * feature, containing the bounding box of the full (unclipped) geometry as
30
+ * a `"west,south,east,north"` string in WGS84. Used by clients to compute
31
+ * stable label positions for polygons that span multiple tiles.
32
+ */
33
+ featureBbox?: boolean;
34
+ };
27
35
 
28
36
  type UrlParameters = {
29
37
  columns?: string;
@@ -33,6 +41,7 @@ type UrlParameters = {
33
41
  tileResolution?: string;
34
42
  name: string;
35
43
  aggregationExp?: string;
44
+ featureBbox?: boolean;
36
45
  };
37
46
 
38
47
  export type VectorTableSourceResponse = TilejsonResult &
@@ -48,6 +57,7 @@ export const vectorTableSource = async function (
48
57
  tableName,
49
58
  tileResolution = DEFAULT_TILE_RESOLUTION,
50
59
  aggregationExp,
60
+ featureBbox,
51
61
  } = options;
52
62
 
53
63
  const spatialDataType = 'geo';
@@ -68,6 +78,9 @@ export const vectorTableSource = async function (
68
78
  if (aggregationExp) {
69
79
  urlParameters.aggregationExp = aggregationExp;
70
80
  }
81
+ if (featureBbox) {
82
+ urlParameters.featureBbox = true;
83
+ }
71
84
 
72
85
  return baseSource<UrlParameters>('table', options, urlParameters).then(
73
86
  (result) => ({
package/src/types.ts CHANGED
@@ -112,8 +112,37 @@ export type AggregationType =
112
112
  * FILTERS
113
113
  */
114
114
 
115
+ /** A spatial filter expressed as a GeoJSON polygon, applied client- or server-side. */
116
+ export type GeometrySpatialFilter = Polygon | MultiPolygon;
117
+
118
+ /**
119
+ * Filter restricting a widget query to a set of spatial-index cells (H3 or
120
+ * Quadbin). Used for point-and-click selection on cell-rendered layers. Sent to
121
+ * the server-side model API only; maps-api chooses the SQL strategy from
122
+ * `spatialDataType`, so the same payload works for both natively-indexed
123
+ * sources and point/geometry sources dynamically aggregated into cells.
124
+ *
125
+ * @privateRemarks Source: @carto/query-builder (SpatialIndexFilter)
126
+ */
127
+ export interface SpatialIndexFilter {
128
+ /**
129
+ * Selected cell ids. h3 = hex string; h3int = `BigInt(\`0x${id}\`)`; quadbin
130
+ * = decimal or `0x`-hex string.
131
+ */
132
+ indexes: string[];
133
+ /** Must match the column type; `h3` and `h3int` are interchangeable. */
134
+ type: 'h3' | 'h3int' | 'quadbin';
135
+ }
136
+
115
137
  /** @privateRemarks Source: @carto/react-api */
116
- export type SpatialFilter = Polygon | MultiPolygon;
138
+ export type SpatialFilter = GeometrySpatialFilter | SpatialIndexFilter;
139
+
140
+ /** True when a {@link SpatialFilter} selects spatial-index cells rather than a polygon. */
141
+ export function isSpatialIndexFilter(
142
+ spatialFilter: SpatialFilter
143
+ ): spatialFilter is SpatialIndexFilter {
144
+ return Array.isArray((spatialFilter as SpatialIndexFilter).indexes);
145
+ }
117
146
 
118
147
  /** @privateRemarks Source: @deck.gl/carto */
119
148
  export interface Filters {
@@ -20,7 +20,13 @@ import type {
20
20
  TimeSeriesResponse,
21
21
  } from './types.js';
22
22
  import {InvalidColumnError, assert, assignOptional} from '../utils.js';
23
- import type {Filter, SpatialFilter, Tile} from '../types.js';
23
+ import type {
24
+ Filter,
25
+ GeometrySpatialFilter,
26
+ SpatialFilter,
27
+ Tile,
28
+ } from '../types.js';
29
+ import {isSpatialIndexFilter} from '../types.js';
24
30
 
25
31
  import {
26
32
  type TileFeatureExtractOptions,
@@ -59,8 +65,9 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
59
65
  private _tiles: Tile[] = [];
60
66
  private _features: FeatureData[] = [];
61
67
  private _tileFeatureExtractOptions: TileFeatureExtractOptions = {};
62
- private _tileFeatureExtractPreviousInputs: {spatialFilter?: SpatialFilter} =
63
- {};
68
+ private _tileFeatureExtractPreviousInputs: {
69
+ spatialFilter?: GeometrySpatialFilter;
70
+ } = {};
64
71
 
65
72
  /**
66
73
  * Loads features as a list of tiles (typically provided by deck.gl).
@@ -79,12 +86,19 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
79
86
  }
80
87
 
81
88
  protected _extractTileFeatures(spatialFilter?: SpatialFilter) {
89
+ // Tilesets are filtered client-side, which only supports polygon filters;
90
+ // spatial-index filters are a server-side concern and don't apply here.
91
+ const geometryFilter =
92
+ spatialFilter && !isSpatialIndexFilter(spatialFilter)
93
+ ? spatialFilter
94
+ : undefined;
95
+
82
96
  // When spatial filter has not changed, don't redo extraction. If tiles or
83
97
  // tile extract options change, features will have been cleared already.
84
98
  const prevInputs = this._tileFeatureExtractPreviousInputs;
85
99
  if (
86
100
  this._features.length &&
87
- spatialFilterEquals(prevInputs.spatialFilter, spatialFilter)
101
+ spatialFilterEquals(prevInputs.spatialFilter, geometryFilter)
88
102
  ) {
89
103
  return;
90
104
  }
@@ -92,10 +106,10 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
92
106
  this._features = tileFeatures({
93
107
  ...assignOptional({}, this.props, this._tileFeatureExtractOptions),
94
108
  tiles: this._tiles,
95
- spatialFilter,
109
+ spatialFilter: geometryFilter,
96
110
  });
97
111
 
98
- prevInputs.spatialFilter = spatialFilter;
112
+ prevInputs.spatialFilter = geometryFilter;
99
113
  }
100
114
 
101
115
  /**
@@ -108,7 +122,7 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
108
122
  spatialFilter,
109
123
  }: {
110
124
  geojson: FeatureCollection;
111
- spatialFilter: SpatialFilter;
125
+ spatialFilter: GeometrySpatialFilter;
112
126
  }) {
113
127
  this._features = geojsonFeatures({
114
128
  geojson,
@@ -493,7 +507,10 @@ function normalizeColumns(columns: string | string[]): string[] {
493
507
  : [];
494
508
  }
495
509
 
496
- function spatialFilterEquals(a?: SpatialFilter, b?: SpatialFilter) {
510
+ function spatialFilterEquals(
511
+ a?: GeometrySpatialFilter,
512
+ b?: GeometrySpatialFilter
513
+ ) {
497
514
  if (a === b) return true;
498
515
  if (!a || !b) return false;
499
516
  return booleanEqual(a, b);
@@ -18,7 +18,7 @@ import type {
18
18
  TimeSeriesRequestOptions,
19
19
  TimeSeriesResponse,
20
20
  } from './types.js';
21
- import type {SpatialFilter, Tile} from '../types.js';
21
+ import type {GeometrySpatialFilter, Tile} from '../types.js';
22
22
  import type {TileFeatureExtractOptions} from '../filters/index.js';
23
23
  import type {BBox, FeatureCollection} from 'geojson';
24
24
  import {WidgetSource, type WidgetSourceProps} from './widget-source.js';
@@ -257,7 +257,7 @@ export class WidgetTilesetSource<
257
257
  spatialFilter,
258
258
  }: {
259
259
  geojson: FeatureCollection;
260
- spatialFilter: SpatialFilter;
260
+ spatialFilter: GeometrySpatialFilter;
261
261
  }) {
262
262
  if (!this._workerEnabled) {
263
263
  return this._localImpl!.loadGeoJSON({geojson, spatialFilter});