@carto/api-client 0.5.28 → 0.5.30-alpha.3cf7d80.116

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.3cf7d80.116",
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';
@@ -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) => ({
@@ -22,6 +22,12 @@ export interface ViewState {
22
22
  longitude: number;
23
23
  }
24
24
 
25
+ /**
26
+ * Topology of the features referenced by {@link BaseRequestOptions#featureIds}.
27
+ * Must match the geometry type of the layer the features were picked from.
28
+ */
29
+ export type WidgetFeatureGeometryType = 'points' | 'lines' | 'polygons';
30
+
25
31
  /** Common options for {@link WidgetRemoteSource} requests. */
26
32
  export interface BaseRequestOptions {
27
33
  signal?: AbortSignal;
@@ -30,6 +36,23 @@ export interface BaseRequestOptions {
30
36
  /** Overrides source filters, if any. */
31
37
  filters?: Filters;
32
38
  filterOwner?: string;
39
+ /**
40
+ * Restricts the request to a selection of features, identified by their
41
+ * `_carto_feature_id` (a hash of geometry). Unlike {@link filters}, this does
42
+ * not require `_carto_feature_id` to exist as a column on the source: the
43
+ * server regenerates the hash on-the-fly and AND-s the resulting predicate
44
+ * onto the existing source filters.
45
+ *
46
+ * Capped at 1000 IDs server-side. {@link geometryType} is required whenever
47
+ * `featureIds` is provided.
48
+ */
49
+ featureIds?: string[];
50
+ /**
51
+ * Geometry topology of the features in {@link featureIds}, required whenever
52
+ * `featureIds` is provided. Used by the server to regenerate the feature-id
53
+ * hash for the correct geometry type.
54
+ */
55
+ geometryType?: WidgetFeatureGeometryType;
33
56
  }
34
57
 
35
58
  export type CategoryOrderBy =
@@ -100,7 +123,7 @@ export interface FeaturesRequestOptions extends BaseRequestOptions {
100
123
  columns: string[];
101
124
 
102
125
  /** Topology of objects to be picked. */
103
- dataType: 'points' | 'lines' | 'polygons';
126
+ dataType: WidgetFeatureGeometryType;
104
127
 
105
128
  /** Zoom level, required if using 'points' data type. */
106
129
  z?: number;
@@ -20,6 +20,7 @@ import type {
20
20
  TableResponse,
21
21
  TimeSeriesRequestOptions,
22
22
  TimeSeriesResponse,
23
+ WidgetFeatureGeometryType,
23
24
  } from './types.js';
24
25
  import {assert, normalizeObjectKeys} from '../utils.js';
25
26
  import {DEFAULT_TILE_RESOLUTION} from '../constants-internal.js';
@@ -33,6 +34,31 @@ import type {APIErrorContext} from '../api/carto-api-error.js';
33
34
 
34
35
  export type WidgetRemoteSourceProps = WidgetSourceProps;
35
36
 
37
+ /** Maximum number of feature IDs accepted server-side per request. */
38
+ const FEATURE_IDS_LIMIT = 1000;
39
+
40
+ /**
41
+ * Validates and normalizes feature-selection options shared by widget
42
+ * requests. Returns the model params to forward, or an empty object when no
43
+ * selection is requested. `geometryType` is mandatory whenever `featureIds`
44
+ * is provided, mirroring the server-side contract.
45
+ */
46
+ function getFeatureSelectionParams(options: {
47
+ featureIds?: string[];
48
+ geometryType?: WidgetFeatureGeometryType;
49
+ }): {featureIds?: string[]; geometryType?: WidgetFeatureGeometryType} {
50
+ const {featureIds, geometryType} = options;
51
+ if (!featureIds || featureIds.length === 0) {
52
+ return {};
53
+ }
54
+ assert(geometryType, 'geometryType is required when featureIds are provided');
55
+ assert(
56
+ featureIds.length <= FEATURE_IDS_LIMIT,
57
+ `featureIds is limited to ${FEATURE_IDS_LIMIT} values, received ${featureIds.length}`
58
+ );
59
+ return {featureIds, geometryType};
60
+ }
61
+
36
62
  /**
37
63
  * Source for Widget API requests.
38
64
  *
@@ -110,6 +136,7 @@ export abstract class WidgetRemoteSource<
110
136
  operationColumn: operationColumn || column,
111
137
  othersThreshold,
112
138
  orderBy,
139
+ ...getFeatureSelectionParams(options),
113
140
  },
114
141
  opts: {signal, headers: this.props.headers},
115
142
  });
@@ -193,6 +220,7 @@ export abstract class WidgetRemoteSource<
193
220
  column: column ?? '*',
194
221
  operation: operation ?? AggregationTypes.Count,
195
222
  operationExp,
223
+ ...getFeatureSelectionParams(options),
196
224
  },
197
225
  opts: {signal, headers: this.props.headers},
198
226
  }).then((res: FormulaModelResponse) => normalizeObjectKeys(res.rows[0]));
@@ -220,7 +248,7 @@ export abstract class WidgetRemoteSource<
220
248
  spatialFiltersMode,
221
249
  spatialFilter,
222
250
  },
223
- params: {column, operation, ticks},
251
+ params: {column, operation, ticks, ...getFeatureSelectionParams(options)},
224
252
  opts: {signal, headers: this.props.headers},
225
253
  }).then((res: HistogramModelResponse) => normalizeObjectKeys(res.rows));
226
254
 
@@ -257,7 +285,7 @@ export abstract class WidgetRemoteSource<
257
285
  spatialFiltersMode,
258
286
  spatialFilter,
259
287
  },
260
- params: {column},
288
+ params: {column, ...getFeatureSelectionParams(options)},
261
289
  opts: {signal, headers: this.props.headers},
262
290
  }).then((res: RangeModelResponse) => normalizeObjectKeys(res.rows[0]));
263
291
  }
@@ -292,6 +320,7 @@ export abstract class WidgetRemoteSource<
292
320
  yAxisColumn,
293
321
  yAxisJoinOperation,
294
322
  limit: HARD_LIMIT,
323
+ ...getFeatureSelectionParams(options),
295
324
  },
296
325
  opts: {signal, headers: this.props.headers},
297
326
  })
@@ -328,6 +357,7 @@ export abstract class WidgetRemoteSource<
328
357
  sortDirection,
329
358
  limit,
330
359
  offset,
360
+ ...getFeatureSelectionParams(options),
331
361
  },
332
362
  opts: {signal, headers: this.props.headers},
333
363
  }).then((res: TableModelResponse) => ({
@@ -388,6 +418,7 @@ export abstract class WidgetRemoteSource<
388
418
  splitByCategory,
389
419
  splitByCategoryLimit,
390
420
  splitByCategoryValues,
421
+ ...getFeatureSelectionParams(options),
391
422
  },
392
423
  opts: {signal, headers: this.props.headers},
393
424
  }).then((res: TimeSeriesModelResponse) => ({
@@ -417,6 +448,7 @@ export abstract class WidgetRemoteSource<
417
448
  },
418
449
  params: {
419
450
  aggregations,
451
+ ...getFeatureSelectionParams(options),
420
452
  },
421
453
  opts: {signal, headers: this.props.headers},
422
454
  }).then((res: AggregationsResponse) => ({