@deck.gl/carto 9.0.29 → 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 (39) 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 +7 -1
  5. package/dist/api/fetch-map.d.ts.map +1 -1
  6. package/dist/api/fetch-map.js +17 -15
  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 +57 -30
  16. package/dist/index.cjs +49 -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/schema/carto-raster-tile-loader.js +1 -1
  24. package/dist/layers/schema/carto-spatial-tile-loader.js +1 -1
  25. package/dist/layers/schema/carto-vector-tile-loader.js +1 -1
  26. package/dist/sources/base-source.d.ts.map +1 -1
  27. package/dist/sources/base-source.js +10 -6
  28. package/dist/sources/types.d.ts +6 -0
  29. package/dist/sources/types.d.ts.map +1 -1
  30. package/dist.min.js +9 -9
  31. package/package.json +2 -2
  32. package/src/api/common.ts +3 -1
  33. package/src/api/fetch-map.ts +38 -18
  34. package/src/api/query.ts +3 -1
  35. package/src/api/request-with-parameters.ts +5 -3
  36. package/src/layers/cluster-tile-layer.ts +14 -1
  37. package/src/layers/cluster-utils.ts +5 -4
  38. package/src/sources/base-source.ts +10 -6
  39. 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.29",
6
+ "version": "9.0.30",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -72,5 +72,5 @@
72
72
  "@deck.gl/layers": "^9.0.0",
73
73
  "@loaders.gl/core": "^4.2.0"
74
74
  },
75
- "gitHead": "eb43281ae27f0a2859d0f76d3f1b68b261ebbe38"
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,6 +1,6 @@
1
1
  /* eslint-disable camelcase */
2
2
  import {CartoAPIError} from './carto-api-error';
3
- import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT} from './common';
3
+ import {DEFAULT_API_BASE_URL, DEFAULT_CLIENT, DEFAULT_MAX_LENGTH_URL} from './common';
4
4
  import {buildPublicMapUrl, buildStatsUrl} from './endpoints';
5
5
  import {
6
6
  GeojsonResult,
@@ -36,13 +36,14 @@ type Dataset = {
36
36
  };
37
37
 
38
38
  /* global clearInterval, setInterval, URL */
39
- /* eslint-disable complexity, max-statements */
39
+ /* eslint-disable complexity, max-statements, max-params */
40
40
  async function _fetchMapDataset(
41
41
  dataset: Dataset,
42
42
  accessToken: string,
43
43
  apiBaseUrl: string,
44
44
  clientId?: string,
45
- headers?: Record<string, string>
45
+ headers?: Record<string, string>,
46
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
46
47
  ) {
47
48
  const {
48
49
  aggregationExp,
@@ -64,7 +65,8 @@ async function _fetchMapDataset(
64
65
  clientId,
65
66
  connectionName,
66
67
  format,
67
- headers
68
+ headers,
69
+ maxLengthURL
68
70
  };
69
71
 
70
72
  if (type === 'tileset') {
@@ -114,7 +116,8 @@ async function _fetchTilestats(
114
116
  attribute: string,
115
117
  dataset: Dataset,
116
118
  accessToken: string,
117
- apiBaseUrl: string
119
+ apiBaseUrl: string,
120
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
118
121
  ) {
119
122
  const {connectionName, data, id, source, type, queryParameters} = dataset;
120
123
  const errorContext: APIErrorContext = {
@@ -144,7 +147,8 @@ async function _fetchTilestats(
144
147
  baseUrl,
145
148
  headers,
146
149
  parameters,
147
- errorContext
150
+ errorContext,
151
+ maxLengthURL
148
152
  });
149
153
 
150
154
  // Replace tilestats for attribute with value from API
@@ -158,17 +162,19 @@ async function fillInMapDatasets(
158
162
  {datasets, token}: {datasets: Dataset[]; token: string},
159
163
  clientId: string,
160
164
  apiBaseUrl: string,
161
- headers?: Record<string, string>
165
+ headers?: Record<string, string>,
166
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
162
167
  ) {
163
168
  const promises = datasets.map(dataset =>
164
- _fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers)
169
+ _fetchMapDataset(dataset, token, apiBaseUrl, clientId, headers, maxLengthURL)
165
170
  );
166
171
  return await Promise.all(promises);
167
172
  }
168
173
 
169
174
  async function fillInTileStats(
170
175
  {datasets, keplerMapConfig, token}: {datasets: Dataset[]; keplerMapConfig: any; token: string},
171
- apiBaseUrl: string
176
+ apiBaseUrl: string,
177
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
172
178
  ) {
173
179
  const attributes: {attribute: string; dataset: any}[] = [];
174
180
  const {layers} = keplerMapConfig.config.visState;
@@ -197,7 +203,7 @@ async function fillInTileStats(
197
203
  }
198
204
 
199
205
  const promises = filteredAttributes.map(({attribute, dataset}) =>
200
- _fetchTilestats(attribute, dataset, token, apiBaseUrl)
206
+ _fetchTilestats(attribute, dataset, token, apiBaseUrl, maxLengthURL)
201
207
  );
202
208
  return await Promise.all(promises);
203
209
  }
@@ -237,6 +243,13 @@ export type FetchMapOptions = {
237
243
  * Callback function that will be invoked whenever data in layers is changed. If provided, `autoRefresh` must also be provided.
238
244
  */
239
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;
240
253
  };
241
254
 
242
255
  export type FetchMapResult = ParseMapResult & {
@@ -255,7 +268,8 @@ export async function fetchMap({
255
268
  clientId = DEFAULT_CLIENT,
256
269
  headers = {},
257
270
  autoRefresh,
258
- onNewData
271
+ onNewData,
272
+ maxLengthURL = DEFAULT_MAX_LENGTH_URL
259
273
  }: FetchMapOptions): Promise<FetchMapResult> {
260
274
  assert(cartoMapId, 'Must define CARTO map id: fetchMap({cartoMapId: "XXXX-XXXX-XXXX"})');
261
275
  assert(apiBaseUrl, 'Must define apiBaseUrl');
@@ -275,7 +289,7 @@ export async function fetchMap({
275
289
 
276
290
  const baseUrl = buildPublicMapUrl({apiBaseUrl, cartoMapId});
277
291
  const errorContext: APIErrorContext = {requestType: 'Public map', mapId: cartoMapId};
278
- const map = await requestWithParameters({baseUrl, headers, errorContext});
292
+ const map = await requestWithParameters({baseUrl, headers, errorContext, maxLengthURL});
279
293
 
280
294
  // Periodically check if the data has changed. Note that this
281
295
  // will not update when a map is published.
@@ -283,10 +297,16 @@ export async function fetchMap({
283
297
  if (autoRefresh) {
284
298
  // eslint-disable-next-line @typescript-eslint/no-misused-promises
285
299
  const intervalId = setInterval(async () => {
286
- const changed = await fillInMapDatasets(map, clientId, apiBaseUrl, {
287
- ...headers,
288
- 'If-Modified-Since': new Date().toUTCString()
289
- });
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
+ );
290
310
  if (onNewData && changed.some(v => v === true)) {
291
311
  onNewData(parseMap(map));
292
312
  }
@@ -315,11 +335,11 @@ export async function fetchMap({
315
335
  fetchBasemapProps({config: map.keplerMapConfig.config, errorContext}),
316
336
 
317
337
  // Mutates map.datasets so that dataset.data contains data
318
- fillInMapDatasets(map, clientId, apiBaseUrl, headers)
338
+ fillInMapDatasets(map, clientId, apiBaseUrl, headers, maxLengthURL)
319
339
  ]);
320
340
 
321
341
  // Mutates attributes in visualChannels to contain tile stats
322
- await fillInTileStats(map, apiBaseUrl);
342
+ await fillInTileStats(map, apiBaseUrl, maxLengthURL);
323
343
 
324
344
  const out = {...parseMap(map), basemap, ...{stopAutoRefresh}};
325
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
 
@@ -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>;