@deck.gl/carto 9.0.28 → 9.0.30

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 (42) hide show
  1. package/dist/api/common.d.ts +1 -1
  2. package/dist/api/common.d.ts.map +1 -1
  3. package/dist/api/common.js +2 -1
  4. package/dist/api/fetch-map.d.ts +30 -1
  5. package/dist/api/fetch-map.d.ts.map +1 -1
  6. package/dist/api/fetch-map.js +20 -18
  7. package/dist/api/query.d.ts.map +1 -1
  8. package/dist/api/query.js +3 -2
  9. package/dist/api/request-with-parameters.d.ts +2 -1
  10. package/dist/api/request-with-parameters.d.ts.map +1 -1
  11. package/dist/api/request-with-parameters.js +3 -3
  12. package/dist/cartoRasterTile-worker.js +1 -1
  13. package/dist/cartoSpatialTile-worker.js +1 -1
  14. package/dist/cartoVectorTile-worker.js +1 -1
  15. package/dist/dist.dev.js +61 -30
  16. package/dist/index.cjs +52 -32
  17. package/dist/index.cjs.map +2 -2
  18. package/dist/layers/cluster-tile-layer.d.ts.map +1 -1
  19. package/dist/layers/cluster-tile-layer.js +12 -2
  20. package/dist/layers/cluster-utils.d.ts +1 -1
  21. package/dist/layers/cluster-utils.d.ts.map +1 -1
  22. package/dist/layers/cluster-utils.js +5 -5
  23. package/dist/layers/heatmap.d.ts.map +1 -1
  24. package/dist/layers/heatmap.js +1 -0
  25. package/dist/layers/schema/carto-raster-tile-loader.js +1 -1
  26. package/dist/layers/schema/carto-spatial-tile-loader.js +1 -1
  27. package/dist/layers/schema/carto-vector-tile-loader.js +1 -1
  28. package/dist/sources/base-source.d.ts.map +1 -1
  29. package/dist/sources/base-source.js +10 -6
  30. package/dist/sources/types.d.ts +6 -0
  31. package/dist/sources/types.d.ts.map +1 -1
  32. package/dist.min.js +9 -9
  33. package/package.json +4 -4
  34. package/src/api/common.ts +3 -1
  35. package/src/api/fetch-map.ts +71 -21
  36. package/src/api/query.ts +3 -1
  37. package/src/api/request-with-parameters.ts +5 -3
  38. package/src/layers/cluster-tile-layer.ts +14 -1
  39. package/src/layers/cluster-utils.ts +5 -4
  40. package/src/layers/heatmap.ts +1 -0
  41. package/src/sources/base-source.ts +10 -6
  42. package/src/sources/types.ts +7 -0
package/package.json CHANGED
@@ -3,7 +3,7 @@
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.0.28",
6
+ "version": "9.0.30",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -47,8 +47,8 @@
47
47
  "@loaders.gl/mvt": "^4.2.0",
48
48
  "@loaders.gl/schema": "^4.2.0",
49
49
  "@loaders.gl/tiles": "^4.2.0",
50
- "@luma.gl/core": "^9.0.15",
51
- "@luma.gl/shadertools": "^9.0.15",
50
+ "@luma.gl/core": "~9.0.27",
51
+ "@luma.gl/shadertools": "~9.0.27",
52
52
  "@math.gl/web-mercator": "^4.0.0",
53
53
  "@types/d3-array": "^3.0.2",
54
54
  "@types/d3-color": "^1.4.2",
@@ -72,5 +72,5 @@
72
72
  "@deck.gl/layers": "^9.0.0",
73
73
  "@loaders.gl/core": "^4.2.0"
74
74
  },
75
- "gitHead": "58a2526b9b910d3ce086d627643839c3cd8ee944"
75
+ "gitHead": "255576946233ccf28f6e1a7709f9360d06ca64a2"
76
76
  }
package/src/api/common.ts CHANGED
@@ -1,4 +1,6 @@
1
1
  export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
2
2
  export const DEFAULT_CLIENT = 'deck-gl-carto';
3
3
  export const V3_MINOR_VERSION = '3.4';
4
- export const MAX_GET_LENGTH = 8192;
4
+
5
+ // Fastly default limit is 8192; leave some padding.
6
+ export const DEFAULT_MAX_LENGTH_URL = 7000;
@@ -1,9 +1,6 @@
1
1
  /* eslint-disable camelcase */
2
- /**
3
- * Maps API Client for Carto 3
4
- */
5
2
  import {CartoAPIError} from './carto-api-error';
6
- import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT} from './common';
3
+ import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from './common';
7
4
  import {buildPublicMapUrl, buildStatsUrl} from './endpoints';
8
5
  import {
9
6
  GeojsonResult,
@@ -39,13 +36,14 @@ type Dataset = {
39
36
  };
40
37
 
41
38
  /* global clearInterval, setInterval, URL */
42
- /* eslint-disable complexity, max-statements */
39
+ /* eslint-disable complexity, max-statements, max-params */
43
40
  async function _fetchMapDataset(
44
41
  dataset: Dataset,
45
42
  accessToken: string,
46
43
  apiBaseUrl: string,
47
44
  clientId?: string,
48
- headers?: Record<string, string>
45
+ headers?: Record<string, string>,
46
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
49
47
  ) {
50
48
  const {
51
49
  aggregationExp,
@@ -67,7 +65,8 @@ async function _fetchMapDataset(
67
65
  clientId,
68
66
  connectionName,
69
67
  format,
70
- headers
68
+ headers,
69
+ maxLengthURL
71
70
  };
72
71
 
73
72
  if (type === 'tileset') {
@@ -117,7 +116,8 @@ async function _fetchTilestats(
117
116
  attribute: string,
118
117
  dataset: Dataset,
119
118
  accessToken: string,
120
- apiBaseUrl: string
119
+ apiBaseUrl: string,
120
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
121
121
  ) {
122
122
  const {connectionName, data, id, source, type, queryParameters} = dataset;
123
123
  const errorContext: APIErrorContext = {
@@ -147,7 +147,8 @@ async function _fetchTilestats(
147
147
  baseUrl,
148
148
  headers,
149
149
  parameters,
150
- errorContext
150
+ errorContext,
151
+ maxLengthURL
151
152
  });
152
153
 
153
154
  // Replace tilestats for attribute with value from API
@@ -161,17 +162,19 @@ async function fillInMapDatasets(
161
162
  {datasets, token}: {datasets: Dataset[]; token: string},
162
163
  clientId: string,
163
164
  apiBaseUrl: string,
164
- headers?: Record<string, string>
165
+ headers?: Record<string, string>,
166
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
165
167
  ) {
166
168
  const promises = datasets.map(dataset =>
167
- _fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers)
169
+ _fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers, maxLengthURL)
168
170
  );
169
171
  return await Promise.all(promises);
170
172
  }
171
173
 
172
174
  async function fillInTileStats(
173
175
  {datasets, keplerMapConfig, token}: {datasets: Dataset[]; keplerMapConfig: any; token: string},
174
- apiBaseUrl: string
176
+ apiBaseUrl: string,
177
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
175
178
  ) {
176
179
  const attributes: {attribute: string; dataset: any}[] = [];
177
180
  const {layers} = keplerMapConfig.config.visState;
@@ -200,18 +203,53 @@ async function fillInTileStats(
200
203
  }
201
204
 
202
205
  const promises = filteredAttributes.map(({attribute, dataset}) =>
203
- _fetchTilestats(attribute, dataset, token, apiBaseUrl)
206
+ _fetchTilestats(attribute, dataset, token, apiBaseUrl, maxLengthURL)
204
207
  );
205
208
  return await Promise.all(promises);
206
209
  }
207
210
 
208
211
  export type FetchMapOptions = {
212
+ /**
213
+ * CARTO platform access token. Only required for private maps.
214
+ */
215
+ accessToken?: string;
216
+
217
+ /**
218
+ * Base URL of the CARTO Maps API.
219
+ *
220
+ * Example for account located in EU-west region: `https://gcp-eu-west1.api.carto.com`
221
+ *
222
+ * @default https://gcp-us-east1.api.carto.com
223
+ */
209
224
  apiBaseUrl?: string;
225
+
226
+ /**
227
+ * Identifier of map created in CARTO Builder.
228
+ */
210
229
  cartoMapId: string;
211
230
  clientId?: string;
231
+
232
+ /**
233
+ * Custom HTTP headers added to map instantiation and data requests.
234
+ */
212
235
  headers?: Record<string, string>;
236
+
237
+ /**
238
+ * Interval in seconds at which to autoRefresh the data. If provided, `onNewData` must also be provided.
239
+ */
213
240
  autoRefresh?: number;
241
+
242
+ /**
243
+ * Callback function that will be invoked whenever data in layers is changed. If provided, `autoRefresh` must also be provided.
244
+ */
214
245
  onNewData?: (map: any) => void;
246
+
247
+ /**
248
+ * Maximum URL character length. Above this limit, requests use POST.
249
+ * Used to avoid browser and CDN limits.
250
+ * @default {@link DEFAULT_MAX_LENGTH_URL}
251
+ */
252
+ maxLengthURL?: number;
215
253
  };
216
254
 
217
255
  export type FetchMapResult = ParseMapResult & {
@@ -224,16 +262,22 @@ export type FetchMapResult = ParseMapResult & {
224
262
 
225
263
  /* eslint-disable max-statements */
226
264
  export async function fetchMap({
265
+ accessToken,
227
266
  apiBaseUrl = DEFAULT_API_BASE_URL,
228
267
  cartoMapId,
229
268
  clientId = DEFAULT_CLIENT,
230
269
  headers = {},
231
270
  autoRefresh,
232
- onNewData
271
+ onNewData,
272
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
233
273
  }: FetchMapOptions): Promise<FetchMapResult> {
234
274
  assert(cartoMapId, 'Must define CARTO map id: fetchMap({cartoMapId: "XXXX-XXXX-XXXX"})');
235
275
  assert(apiBaseUrl, 'Must define apiBaseUrl');
236
276
 
277
+ if (accessToken) {
278
+ headers = {Authorization: `Bearer ${accessToken}`, ...headers};
279
+ }
280
+
237
281
  if (autoRefresh || onNewData) {
238
282
  assert(onNewData, 'Must define `onNewData` when using autoRefresh');
239
283
  assert(typeof onNewData === 'function', '`onNewData` must be a function');
@@ -245,7 +289,7 @@ export async function fetchMap({
245
289
 
246
290
  const baseUrl = buildPublicMapUrl({apiBaseUrl, cartoMapId});
247
291
  const errorContext: APIErrorContext = {requestType: 'Public map', mapId: cartoMapId};
248
- const map = await requestWithParameters({baseUrl, headers, errorContext});
292
+ const map = await requestWithParameters({baseUrl, headers, errorContext, maxLengthURL});
249
293
 
250
294
  // Periodically check if the data has changed. Note that this
251
295
  // will not update when a map is published.
@@ -253,10 +297,16 @@ export async function fetchMap({
253
297
  if (autoRefresh) {
254
298
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
255
299
  const intervalId = setInterval(async () => {
256
- const changed = await fillInMapDatasets(map, clientId, apiBaseUrl, {
257
- ...headers,
258
- 'If-Modified-Since': new Date().toUTCString()
259
- });
300
+ const changed = await fillInMapDatasets(
301
+ map,
302
+ clientId,
303
+ apiBaseUrl,
304
+ {
305
+ ...headers,
306
+ 'If-Modified-Since': new Date().toUTCString()
307
+ },
308
+ maxLengthURL
309
+ );
260
310
  if (onNewData && changed.some(v => v === true)) {
261
311
  onNewData(parseMap(map));
262
312
  }
@@ -285,11 +335,11 @@ export async function fetchMap({
285
335
  fetchBasemapProps({config: map.keplerMapConfig.config, errorContext}),
286
336
 
287
337
  // Mutates map.datasets so that dataset.data contains data
288
- fillInMapDatasets(map, clientId, apiBaseUrl, headers)
338
+ fillInMapDatasets(map, clientId, apiBaseUrl, headers, maxLengthURL)
289
339
  ]);
290
340
 
291
341
  // Mutates attributes in visualChannels to contain tile stats
292
- await fillInTileStats(map, apiBaseUrl);
342
+ await fillInTileStats(map, apiBaseUrl, maxLengthURL);
293
343
 
294
344
  const out = {...parseMap(map), basemap, ...{stopAutoRefresh}};
295
345
 
package/src/api/query.ts CHANGED
@@ -11,6 +11,7 @@ export const query = async function (options: QueryOptions): Promise<QueryResult
11
11
  const {
12
12
  apiBaseUrl = SOURCE_DEFAULTS.apiBaseUrl,
13
13
  clientId = SOURCE_DEFAULTS.clientId,
14
+ maxLengthURL = SOURCE_DEFAULTS.maxLengthURL,
14
15
  connectionName,
15
16
  sqlQuery,
16
17
  queryParameters
@@ -35,6 +36,7 @@ export const query = async function (options: QueryOptions): Promise<QueryResult
35
36
  baseUrl,
36
37
  parameters,
37
38
  headers,
38
- errorContext
39
+ errorContext,
40
+ maxLengthURL
39
41
  });
40
42
  };
@@ -1,7 +1,7 @@
1
1
  import {VERSION} from '@deck.gl/core';
2
2
  import {isPureObject} from '../utils';
3
3
  import {CartoAPIError} from './carto-api-error';
4
- import {MAX_GET_LENGTH, V3_MINOR_VERSION} from './common';
4
+ import {DEFAULT_MAX_LENGTH_URL, V3_MINOR_VERSION} from './common';
5
5
  import type {APIErrorContext} from './types';
6
6
 
7
7
  /**
@@ -25,12 +25,14 @@ export async function requestWithParameters<T = any>({
25
25
  baseUrl,
26
26
  parameters = {},
27
27
  headers: customHeaders = {},
28
- errorContext
28
+ errorContext,
29
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
29
30
  }: {
30
31
  baseUrl: string;
31
32
  parameters?: Record<string, unknown>;
32
33
  headers?: Record<string, string>;
33
34
  errorContext: APIErrorContext;
35
+ maxLengthURL?: number;
34
36
  }): Promise<T> {
35
37
  parameters = {...DEFAULT_PARAMETERS, ...parameters};
36
38
  baseUrl = excludeURLParameters(baseUrl, Object.keys(parameters));
@@ -44,7 +46,7 @@ export async function requestWithParameters<T = any>({
44
46
 
45
47
  /* global fetch */
46
48
  const fetchPromise =
47
- url.length > MAX_GET_LENGTH
49
+ url.length > maxLengthURL
48
50
  ? fetch(baseUrl, {method: 'POST', body: JSON.stringify(parameters), headers})
49
51
  : fetch(url, {headers});
50
52
 
@@ -106,8 +106,14 @@ class ClusterGeoJsonLayer<
106
106
  clusterIds: bigint[];
107
107
  hoveredFeatureId: bigint | number | null;
108
108
  highlightColor: number[];
109
+ aggregationCache: WeakMap<any, Map<number, ClusteredFeaturePropertiesT<FeaturePropertiesT>[]>>;
109
110
  };
110
111
 
112
+ initializeState() {
113
+ super.initializeState();
114
+ this.state.aggregationCache = new WeakMap();
115
+ }
116
+
111
117
  renderLayers(): Layer | null | LayersList {
112
118
  const visibleTiles = this.state.tileset?.tiles.filter((tile: Tile2DHeader) => {
113
119
  return tile.isLoaded && tile.content && this.state.tileset!.isTileVisible(tile);
@@ -119,6 +125,7 @@ class ClusterGeoJsonLayer<
119
125
 
120
126
  const {zoom} = this.context.viewport;
121
127
  const {clusterLevel, getPosition, getWeight} = this.props;
128
+ const {aggregationCache} = this.state;
122
129
 
123
130
  const properties = extractAggregationProperties(visibleTiles[0]);
124
131
  const data = [] as ClusteredFeaturePropertiesT<FeaturePropertiesT>[];
@@ -127,15 +134,21 @@ class ClusterGeoJsonLayer<
127
134
  // Calculate aggregation based on viewport zoom
128
135
  const overZoom = Math.round(zoom - tile.zoom);
129
136
  const aggregationLevels = Math.round(clusterLevel) - overZoom;
137
+ let tileAggregationCache = aggregationCache.get(tile.content);
138
+ if (!tileAggregationCache) {
139
+ tileAggregationCache = new Map();
140
+ aggregationCache.set(tile.content, tileAggregationCache);
141
+ }
130
142
  const didAggregate = aggregateTile(
131
143
  tile,
144
+ tileAggregationCache,
132
145
  aggregationLevels,
133
146
  properties,
134
147
  getPosition,
135
148
  getWeight
136
149
  );
137
150
  needsUpdate ||= didAggregate;
138
- data.push(...tile.userData![aggregationLevels]);
151
+ data.push(...tileAggregationCache.get(aggregationLevels)!);
139
152
  }
140
153
 
141
154
  data.sort((a, b) => Number(b.count - a.count));
@@ -23,6 +23,7 @@ export type ParsedQuadbinTile<FeaturePropertiesT> = ParsedQuadbinCell<FeaturePro
23
23
  */
24
24
  export function aggregateTile<FeaturePropertiesT>(
25
25
  tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>,
26
+ tileAggregationCache: Map<number, ClusteredFeaturePropertiesT<FeaturePropertiesT>[]>,
26
27
  aggregationLevels: number,
27
28
  properties: AggregationProperties<FeaturePropertiesT> = [],
28
29
  getPosition: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>,
@@ -32,7 +33,7 @@ export function aggregateTile<FeaturePropertiesT>(
32
33
 
33
34
  // Aggregate on demand and cache result
34
35
  if (!tile.userData) tile.userData = {};
35
- const cell0 = tile.userData[aggregationLevels]?.[0];
36
+ const cell0 = tileAggregationCache.get(aggregationLevels)?.[0];
36
37
  if (cell0) {
37
38
  // Have already aggregated this tile
38
39
  if (properties.every(property => property.name in cell0)) {
@@ -41,7 +42,7 @@ export function aggregateTile<FeaturePropertiesT>(
41
42
  }
42
43
 
43
44
  // Aggregated properties have changed, re-aggregate
44
- tile.userData = {};
45
+ tileAggregationCache.clear();
45
46
  }
46
47
 
47
48
  const out: Record<number, any> = {};
@@ -49,7 +50,7 @@ export function aggregateTile<FeaturePropertiesT>(
49
50
  let id = cell.id;
50
51
  const position = typeof getPosition === 'function' ? getPosition(cell, {} as any) : getPosition;
51
52
 
52
- // Aggregate by parent id
53
+ // Aggregate by parent rid
53
54
  for (let i = 0; i < aggregationLevels - 1; i++) {
54
55
  id = cellToParent(id);
55
56
  }
@@ -93,7 +94,7 @@ export function aggregateTile<FeaturePropertiesT>(
93
94
  }
94
95
  }
95
96
 
96
- tile.userData[aggregationLevels] = Object.values(out);
97
+ tileAggregationCache.set(aggregationLevels, Object.values(out));
97
98
  return true;
98
99
  }
99
100
 
@@ -192,6 +192,7 @@ export const heatmap: ShaderPass<HeatmapProps, HeatmapUniforms> = {
192
192
  intensity: 'f32',
193
193
  opacity: 'f32'
194
194
  },
195
+ // @ts-expect-error stricter luma gl types
195
196
  getUniforms: opts => {
196
197
  const {
197
198
  delta = [1, 0],
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable camelcase */
2
- import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT} from '../api/common';
2
+ import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from '../api/common';
3
3
  import {buildSourceUrl} from '../api/endpoints';
4
4
  import {requestWithParameters} from '../api/request-with-parameters';
5
5
  import type {APIErrorContext, MapType} from '../api/types';
@@ -16,7 +16,8 @@ export const SOURCE_DEFAULTS: SourceOptionalOptions = {
16
16
  apiBaseUrl: DEFAULT_API_BASE_URL,
17
17
  clientId: DEFAULT_CLIENT,
18
18
  format: 'tilejson',
19
- headers: {}
19
+ headers: {},
20
+ maxLengthURL: DEFAULT_MAX_LENGTH_URL
20
21
  };
21
22
 
22
23
  export async function baseSource<UrlParameters extends Record<string, unknown>>(
@@ -32,7 +33,7 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
32
33
  }
33
34
  }
34
35
  const baseUrl = buildSourceUrl(mergedOptions);
35
- const {clientId, format} = mergedOptions;
36
+ const {clientId, maxLengthURL, format} = mergedOptions;
36
37
  const headers = {Authorization: `Bearer ${options.accessToken}`, ...options.headers};
37
38
  const parameters = {client: clientId, ...urlParameters};
38
39
 
@@ -46,7 +47,8 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
46
47
  baseUrl,
47
48
  parameters,
48
49
  headers,
49
- errorContext
50
+ errorContext,
51
+ maxLengthURL
50
52
  });
51
53
 
52
54
  const dataUrl = mapInstantiation[format].url[0];
@@ -59,7 +61,8 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
59
61
  const json = await requestWithParameters<TilejsonResult>({
60
62
  baseUrl: dataUrl,
61
63
  headers,
62
- errorContext
64
+ errorContext,
65
+ maxLengthURL
63
66
  });
64
67
  if (accessToken) {
65
68
  json.accessToken = accessToken;
@@ -70,6 +73,7 @@ export async function baseSource<UrlParameters extends Record<string, unknown>>(
70
73
  return await requestWithParameters<GeojsonResult | JsonResult>({
71
74
  baseUrl: dataUrl,
72
75
  headers,
73
- errorContext
76
+ errorContext,
77
+ maxLengthURL
74
78
  });
75
79
  }
@@ -35,6 +35,13 @@ export type SourceOptionalOptions = {
35
35
  clientId: string;
36
36
  /** @deprecated use `query` instead **/
37
37
  format: Format;
38
+
39
+ /**
40
+ * Maximum URL character length. Above this limit, requests use POST.
41
+ * Used to avoid browser and CDN limits.
42
+ * @default {@link DEFAULT_MAX_LENGTH_URL}
43
+ */
44
+ maxLengthURL?: number;
38
45
  };
39
46
 
40
47
  export type SourceOptions = SourceRequiredOptions & Partial<SourceOptionalOptions>;