@deck.gl/carto 8.7.7 → 8.8.0-alpha.2

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
@@ -2,7 +2,7 @@
2
2
  "name": "@deck.gl/carto",
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
- "version": "8.7.7",
5
+ "version": "8.8.0-alpha.2",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -31,12 +31,12 @@
31
31
  "prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env.dev"
32
32
  },
33
33
  "dependencies": {
34
- "@loaders.gl/gis": "^3.1.5",
35
- "@loaders.gl/loader-utils": "^3.1.5",
36
- "@loaders.gl/mvt": "^3.1.5",
37
- "@loaders.gl/tiles": "^3.1.5",
34
+ "@loaders.gl/gis": "^3.2.0-alpha.4",
35
+ "@loaders.gl/loader-utils": "^3.2.0-alpha.4",
36
+ "@loaders.gl/mvt": "^3.2.0-alpha.4",
37
+ "@loaders.gl/tiles": "^3.2.0-alpha.4",
38
38
  "@luma.gl/constants": "^8.5.10",
39
- "@math.gl/web-mercator": "^3.5.7",
39
+ "@math.gl/web-mercator": "3.6.0-alpha.5",
40
40
  "cartocolor": "^4.0.2",
41
41
  "d3-array": "^2.8.0",
42
42
  "d3-color": "^2.0.0",
@@ -53,9 +53,10 @@
53
53
  "peerDependencies": {
54
54
  "@deck.gl/aggregation-layers": "^8.0.0",
55
55
  "@deck.gl/core": "^8.0.0",
56
+ "@deck.gl/extensions": "^8.0.0",
56
57
  "@deck.gl/geo-layers": "^8.0.0",
57
58
  "@deck.gl/layers": "^8.0.0",
58
- "@loaders.gl/core": "^3.1.5"
59
+ "@loaders.gl/core": "^3.0.0"
59
60
  },
60
- "gitHead": "5a7e586411cb538d3319e35f8e07d61c9a3efb6d"
61
+ "gitHead": "a52215449dff06fb683f5afd6399526353d11f35"
61
62
  }
@@ -7,7 +7,8 @@ import {
7
7
  scalePoint,
8
8
  scaleQuantile,
9
9
  scaleQuantize,
10
- scaleSqrt
10
+ scaleSqrt,
11
+ scaleThreshold
11
12
  } from 'd3-scale';
12
13
  import {format as d3Format} from 'd3-format';
13
14
  import moment from 'moment-timezone';
@@ -27,10 +28,13 @@ const SCALE_FUNCS = {
27
28
  point: scalePoint,
28
29
  quantile: scaleQuantile,
29
30
  quantize: scaleQuantize,
30
- sqrt: scaleSqrt
31
+ sqrt: scaleSqrt,
32
+ custom: scaleThreshold
31
33
  };
32
34
  export type SCALE_TYPE = keyof typeof SCALE_FUNCS;
33
35
 
36
+ const UNKNOWN_COLOR = '#868d91';
37
+
34
38
  export const AGGREGATION = {
35
39
  average: 'MEAN',
36
40
  maximum: 'MAX',
@@ -179,7 +183,11 @@ function domainFromAttribute(attribute, scaleType: SCALE_TYPE) {
179
183
 
180
184
  function domainFromValues(values, scaleType: SCALE_TYPE) {
181
185
  if (scaleType === 'ordinal') {
182
- return [...new Set(values)].sort();
186
+ return groupSort(
187
+ values,
188
+ g => -g.length,
189
+ d => d
190
+ );
183
191
  } else if (scaleType === 'quantile') {
184
192
  return values.sort((a, b) => a - b);
185
193
  } else if (scaleType === 'log') {
@@ -226,13 +234,31 @@ export function getColorValueAccessor({name}, colorAggregation, data: any) {
226
234
  export function getColorAccessor(
227
235
  {name},
228
236
  scaleType: SCALE_TYPE,
229
- {colors},
237
+ {colors, colorMap},
230
238
  opacity: number | undefined,
231
239
  data: any
232
240
  ) {
233
241
  const scale = SCALE_FUNCS[scaleType as any]();
234
- scale.domain(calculateDomain(data, name, scaleType));
235
- scale.range(colors);
242
+ let domain: (string | number)[] = [];
243
+ let scaleColor: string[] = [];
244
+
245
+ if (Array.isArray(colorMap)) {
246
+ colorMap.forEach(([value, color]) => {
247
+ domain.push(value);
248
+ scaleColor.push(color);
249
+ });
250
+ } else {
251
+ domain = calculateDomain(data, name, scaleType);
252
+ scaleColor = colors;
253
+ }
254
+
255
+ if (scaleType === 'ordinal') {
256
+ domain = domain.slice(0, scaleColor.length);
257
+ }
258
+
259
+ scale.domain(domain);
260
+ scale.range(scaleColor);
261
+ scale.unknown(UNKNOWN_COLOR);
236
262
  const alpha = opacity !== undefined ? Math.round(255 * Math.pow(opacity, 1 / 2.2)) : 255;
237
263
 
238
264
  const accessor = properties => {
@@ -20,8 +20,8 @@ export const MAP_TYPES = {
20
20
  TILESET: 'tileset'
21
21
  } as const;
22
22
  export type MapType = ValuesOf<typeof MAP_TYPES>;
23
- export const COLUMNS_SUPPORT = [MAP_TYPES.TABLE];
24
- export const GEO_COLUMN_SUPPORT = [MAP_TYPES.QUERY, MAP_TYPES.TABLE];
23
+ export const COLUMNS_SUPPORT: MapType[] = [MAP_TYPES.TABLE];
24
+ export const GEO_COLUMN_SUPPORT: MapType[] = [MAP_TYPES.QUERY, MAP_TYPES.TABLE];
25
25
 
26
26
  // AVAILABLE FORMATS
27
27
  export const FORMATS = {
@@ -59,7 +59,7 @@ export interface SchemaField {
59
59
  export interface MapInstantiation extends MapInstantiationFormats {
60
60
  nrows: number;
61
61
  size?: number;
62
- schema?: SchemaField[];
62
+ schema: SchemaField[];
63
63
  }
64
64
 
65
65
  type MapInstantiationFormats = Record<
@@ -112,10 +112,11 @@ type FetchLayerDataParams = {
112
112
  credentials: CloudNativeCredentials;
113
113
  geoColumn?: string;
114
114
  columns?: string[];
115
- schema?: boolean;
116
115
  clientId?: string;
117
116
  format?: Format;
118
117
  formatTiles?: TileFormat;
118
+ aggregationExp?: string;
119
+ aggregationResLevel?: number;
119
120
  };
120
121
 
121
122
  /**
@@ -126,23 +127,27 @@ function getParameters({
126
127
  source,
127
128
  geoColumn,
128
129
  columns,
129
- schema,
130
- clientId
130
+ clientId,
131
+ aggregationExp,
132
+ aggregationResLevel
131
133
  }: Omit<FetchLayerDataParams, 'connection' | 'credentials'>) {
132
134
  const parameters = [encodeParameter('client', clientId || DEFAULT_CLIENT)];
133
- if (schema) {
134
- parameters.push(encodeParameter('schema', true));
135
- }
136
135
 
137
136
  const sourceName = type === MAP_TYPES.QUERY ? 'q' : 'name';
138
137
  parameters.push(encodeParameter(sourceName, source));
139
138
 
140
- if (GEO_COLUMN_SUPPORT.includes(type) && geoColumn) {
139
+ if (geoColumn) {
141
140
  parameters.push(encodeParameter('geo_column', geoColumn));
142
141
  }
143
- if (COLUMNS_SUPPORT.includes(type) && columns) {
142
+ if (columns) {
144
143
  parameters.push(encodeParameter('columns', columns.join(',')));
145
144
  }
145
+ if (aggregationExp) {
146
+ parameters.push(encodeParameter('aggregationExp', aggregationExp));
147
+ }
148
+ if (aggregationResLevel) {
149
+ parameters.push(encodeParameter('aggregationResLevel', aggregationResLevel));
150
+ }
146
151
 
147
152
  return parameters.join('&');
148
153
  }
@@ -154,11 +159,20 @@ export async function mapInstantiation({
154
159
  credentials,
155
160
  geoColumn,
156
161
  columns,
157
- schema,
158
- clientId
162
+ clientId,
163
+ aggregationExp,
164
+ aggregationResLevel
159
165
  }: FetchLayerDataParams): Promise<MapInstantiation> {
160
166
  const baseUrl = `${credentials.mapsUrl}/${connection}/${type}`;
161
- const url = `${baseUrl}?${getParameters({type, source, geoColumn, columns, schema, clientId})}`;
167
+ const url = `${baseUrl}?${getParameters({
168
+ type,
169
+ source,
170
+ geoColumn,
171
+ columns,
172
+ clientId,
173
+ aggregationExp,
174
+ aggregationResLevel
175
+ })}`;
162
176
  const {accessToken} = credentials;
163
177
 
164
178
  if (url.length > MAX_GET_LENGTH && type === MAP_TYPES.QUERY) {
@@ -187,7 +201,11 @@ function checkFetchLayerDataParameters({
187
201
  type,
188
202
  source,
189
203
  connection,
190
- credentials
204
+ credentials,
205
+ geoColumn,
206
+ columns,
207
+ aggregationExp,
208
+ aggregationResLevel
191
209
  }: FetchLayerDataParams) {
192
210
  assert(connection, 'Must define connection');
193
211
  assert(type, 'Must define a type');
@@ -196,12 +214,35 @@ function checkFetchLayerDataParameters({
196
214
  assert(credentials.apiVersion === API_VERSIONS.V3, 'Method only available for v3');
197
215
  assert(credentials.apiBaseUrl, 'Must define apiBaseUrl');
198
216
  assert(credentials.accessToken, 'Must define an accessToken');
217
+
218
+ if (columns) {
219
+ assert(
220
+ COLUMNS_SUPPORT.includes(type),
221
+ `The columns parameter is not supported by type ${type}`
222
+ );
223
+ }
224
+ if (geoColumn) {
225
+ assert(
226
+ GEO_COLUMN_SUPPORT.includes(type),
227
+ `The geoColumn parameter is not supported by type ${type}`
228
+ );
229
+ } else {
230
+ assert(!aggregationExp, 'Have aggregationExp, but geoColumn parameter is missing');
231
+ assert(!aggregationResLevel, 'Have aggregationResLevel, but geoColumn parameter is missing');
232
+ }
233
+
234
+ if (!aggregationExp) {
235
+ assert(
236
+ !aggregationResLevel,
237
+ 'Have aggregationResLevel, but aggregationExp parameter is missing'
238
+ );
239
+ }
199
240
  }
200
241
 
201
242
  export interface FetchLayerDataResult {
202
243
  data: any;
203
- format: Format;
204
- schema?: SchemaField[];
244
+ format?: Format;
245
+ schema: SchemaField[];
205
246
  }
206
247
  export async function fetchLayerData({
207
248
  type,
@@ -212,8 +253,9 @@ export async function fetchLayerData({
212
253
  columns,
213
254
  format,
214
255
  formatTiles,
215
- schema,
216
- clientId
256
+ clientId,
257
+ aggregationExp,
258
+ aggregationResLevel
217
259
  }: FetchLayerDataParams): Promise<FetchLayerDataResult> {
218
260
  // Internally we split data fetching into two parts to allow us to
219
261
  // conditionally fetch the actual data, depending on the metadata state
@@ -226,16 +268,13 @@ export async function fetchLayerData({
226
268
  columns,
227
269
  format,
228
270
  formatTiles,
229
- schema,
230
- clientId
271
+ clientId,
272
+ aggregationExp,
273
+ aggregationResLevel
231
274
  });
232
275
 
233
276
  const data = await requestData({url, format: mapFormat, accessToken});
234
- const result: FetchLayerDataResult = {data, format: mapFormat};
235
- if (schema) {
236
- result.schema = metadata.schema;
237
- }
238
-
277
+ const result: FetchLayerDataResult = {data, format: mapFormat, schema: metadata.schema};
239
278
  return result;
240
279
  }
241
280
 
@@ -248,8 +287,9 @@ async function _fetchDataUrl({
248
287
  columns,
249
288
  format,
250
289
  formatTiles,
251
- schema,
252
- clientId
290
+ clientId,
291
+ aggregationExp,
292
+ aggregationResLevel
253
293
  }: FetchLayerDataParams) {
254
294
  const defaultCredentials = getDefaultCredentials();
255
295
  // Only pick up default credentials if they have been defined for
@@ -258,7 +298,16 @@ async function _fetchDataUrl({
258
298
  ...(defaultCredentials.apiVersion === API_VERSIONS.V3 && defaultCredentials),
259
299
  ...credentials
260
300
  };
261
- checkFetchLayerDataParameters({type, source, connection, credentials: localCreds});
301
+ checkFetchLayerDataParameters({
302
+ type,
303
+ source,
304
+ connection,
305
+ credentials: localCreds,
306
+ geoColumn,
307
+ columns,
308
+ aggregationExp,
309
+ aggregationResLevel
310
+ });
262
311
 
263
312
  if (!localCreds.mapsUrl) {
264
313
  localCreds.mapsUrl = buildMapsUrlFromBase(localCreds.apiBaseUrl);
@@ -271,8 +320,9 @@ async function _fetchDataUrl({
271
320
  credentials: localCreds,
272
321
  geoColumn,
273
322
  columns,
274
- schema,
275
- clientId
323
+ clientId,
324
+ aggregationExp,
325
+ aggregationResLevel
276
326
  });
277
327
  let url: string | null = null;
278
328
  let mapFormat: Format | undefined;
@@ -325,7 +375,6 @@ export async function getData({
325
375
  geoColumn,
326
376
  columns,
327
377
  format,
328
- schema: false,
329
378
  clientId
330
379
  });
331
380
  return layerData.data;
package/src/index.ts CHANGED
@@ -16,3 +16,5 @@ export {
16
16
  getData,
17
17
  mapInstantiation as _mapInstantiation
18
18
  } from './api';
19
+
20
+ export type {CartoLayerProps} from './layers/carto-layer';
@@ -1,16 +1,28 @@
1
- import {CompositeLayer, log} from '@deck.gl/core';
1
+ import {CompositeLayer, Layer, log} from '@deck.gl/core';
2
2
  import CartoTileLayer from './carto-tile-layer';
3
3
  import {MVTLayer} from '@deck.gl/geo-layers';
4
4
  import {GeoJsonLayer} from '@deck.gl/layers';
5
5
  import {fetchLayerData, getDataV2, API_VERSIONS} from '../api';
6
6
  import {
7
7
  COLUMNS_SUPPORT,
8
+ Format,
8
9
  FORMATS,
9
10
  GEO_COLUMN_SUPPORT,
11
+ MapType,
10
12
  MAP_TYPES,
13
+ TileFormat,
11
14
  TILE_FORMATS
12
15
  } from '../api/maps-api-common';
13
- import {getDefaultCredentials} from '../config';
16
+ import {
17
+ ClassicCredentials,
18
+ CloudNativeCredentials,
19
+ Credentials,
20
+ getDefaultCredentials
21
+ } from '../config';
22
+ import {CompositeLayerProps, LayerProps} from 'modules/core/src/types/layer-props';
23
+ import {ChangeFlags} from 'modules/core/src/lib/layer-state';
24
+ import {FetchLayerDataResult} from '../api/maps-v3-client';
25
+ import {assert} from '../utils';
14
26
 
15
27
  const defaultProps = {
16
28
  // (String, required): data resource to load. table name, sql query or tileset name.
@@ -46,19 +58,105 @@ const defaultProps = {
46
58
  columns: {type: 'array', value: null}
47
59
  };
48
60
 
49
- export default class CartoLayer extends CompositeLayer {
50
- initializeState() {
61
+ export interface CartoLayerProps<DataT = any> extends CompositeLayerProps<DataT> {
62
+ /**
63
+ * Either a SQL query or a name of dataset/tileset.
64
+ */
65
+ data: string;
66
+
67
+ /**
68
+ * Data type.
69
+ *
70
+ * Possible values are:
71
+ * * `MAP_TYPES.QUERY`, if data is a SQL query.
72
+ * * `MAP_TYPES.TILESET`, if data is a tileset name.
73
+ * * `MAP_TYPES.TABLE`, if data is a dataset name. Only supported with API v3.
74
+ */
75
+ type: MapType;
76
+
77
+ /**
78
+ * Name of the connection registered in the CARTO workspace.
79
+ *
80
+ * Required when apiVersion is `API_VERSIONS.V3`.
81
+ */
82
+ connection?: string;
83
+
84
+ /**
85
+ * Use to override the default data format.
86
+ *
87
+ * Only supported when apiVersion is `API_VERSIONS.V3`.
88
+ *
89
+ * Possible values are: `FORMATS.GEOJSON`, `FORMATS.JSON` and `FORMATS.TILEJSON`.
90
+ */
91
+ format?: Format;
92
+
93
+ /**
94
+ * Use to override the default tile data format.
95
+ *
96
+ * Only supported when apiVersion is `API_VERSIONS.V3` and format is `FORMATS.TILEJSON`.
97
+ *
98
+ * Possible values are: `TILE_FORMATS.BINARY`, `TILE_FORMATS.GEOJSON` and `TILE_FORMATS.MVT`.
99
+ */
100
+ formatTiles?: TileFormat;
101
+
102
+ /**
103
+ * Name of the geo_column in the CARTO platform.
104
+ *
105
+ * Use this override the default column (`'geom'`), from which the geometry information should be fetched.
106
+ *
107
+ * Only supported when apiVersion is `API_VERSIONS.V3` and type is `MAP_TYPES.TABLE`.
108
+ */
109
+ geoColumn?: string;
110
+
111
+ /**
112
+ * Names of columns to fetch.
113
+ *
114
+ * By default, all columns are fetched
115
+ *
116
+ * Only supported when apiVersion is `API_VERSIONS.V3` and type is `MAP_TYPES.TABLE`.
117
+ */
118
+ columns?: string[];
119
+
120
+ /**
121
+ * A string pointing to a unique attribute at the result of the query.
122
+ *
123
+ * A unique attribute is needed for highlighting with vector tiles when a feature is split across two or more tiles.
124
+ */
125
+ uniqueIdProperty?: string;
126
+
127
+ /**
128
+ * Optional. Overrides the configuration to connect with CARTO.
129
+ *
130
+ * @see Credentials
131
+ */
132
+ credentials?: Credentials;
133
+
134
+ /**
135
+ * Called when the request to the CARTO Maps API failed.
136
+ *
137
+ * By default the Error is thrown.
138
+ */
139
+ onDataError?: (err: unknown) => void;
140
+
141
+ clientId?: string;
142
+ }
143
+
144
+ export default class CartoLayer<DataT = any> extends CompositeLayer<CartoLayerProps<DataT>> {
145
+ static layerName = 'CartoLayer';
146
+ static defaultProps = defaultProps as any;
147
+
148
+ initializeState(): void {
51
149
  this.state = {
52
150
  data: null,
53
151
  apiVersion: null
54
152
  };
55
153
  }
56
154
 
57
- get isLoaded() {
155
+ get isLoaded(): boolean {
58
156
  return this.getSubLayers().length > 0 && super.isLoaded;
59
157
  }
60
158
 
61
- _checkProps(props) {
159
+ _checkProps(props: CartoLayerProps): void {
62
160
  const {type, credentials, connection, geoColumn, columns} = props;
63
161
  const localCreds = {...getDefaultCredentials(), ...credentials};
64
162
  const {apiVersion} = localCreds;
@@ -97,7 +195,16 @@ export default class CartoLayer extends CompositeLayer {
97
195
  }
98
196
  }
99
197
 
100
- updateState({props, oldProps, changeFlags}) {
198
+ updateState({
199
+ props,
200
+ oldProps,
201
+ changeFlags
202
+ }: {
203
+ props: CartoLayerProps;
204
+ oldProps: CartoLayerProps;
205
+ context: any;
206
+ changeFlags: ChangeFlags;
207
+ }): void {
101
208
  this._checkProps(props);
102
209
  const shouldUpdateData =
103
210
  changeFlags.dataChanged ||
@@ -111,40 +218,57 @@ export default class CartoLayer extends CompositeLayer {
111
218
 
112
219
  if (shouldUpdateData) {
113
220
  this.setState({data: null, apiVersion: null});
221
+ // eslint-disable-next-line @typescript-eslint/no-floating-promises
114
222
  this._updateData();
115
223
  }
116
224
  }
117
225
 
118
- async _updateData() {
226
+ async _updateData(): Promise<void> {
119
227
  try {
120
- const {type, data: source, clientId, credentials, ...rest} = this.props;
228
+ const {type, data: source, clientId, credentials, connection, ...rest} = this.props;
121
229
  const localConfig = {...getDefaultCredentials(), ...credentials};
122
230
  const {apiVersion} = localConfig;
123
231
 
124
- let result;
232
+ let result: FetchLayerDataResult;
125
233
  if (apiVersion === API_VERSIONS.V1 || apiVersion === API_VERSIONS.V2) {
126
- result = {data: await getDataV2({type, source, credentials})};
234
+ result = {
235
+ data: await getDataV2({type, source, credentials: credentials as ClassicCredentials})
236
+ };
127
237
  } else {
128
- result = await fetchLayerData({type, source, clientId, credentials, ...rest});
238
+ result = await fetchLayerData({
239
+ type,
240
+ source,
241
+ clientId,
242
+ credentials: credentials as CloudNativeCredentials,
243
+ connection: connection as string,
244
+ ...rest
245
+ });
129
246
  }
130
247
 
131
248
  this.setState({...result, apiVersion});
132
- this.props.onDataLoad(result.data);
249
+
250
+ this.props.onDataLoad?.(result.data, {
251
+ propName: 'data',
252
+ layer: this
253
+ });
133
254
  } catch (err) {
134
255
  if (this.props.onDataError) {
135
- this.props.onDataError(err);
256
+ this.props.onDataError(err as Error);
136
257
  } else {
137
258
  throw err;
138
259
  }
139
260
  }
140
261
  }
141
262
 
142
- _getSubLayerAndProps() {
263
+ _getSubLayerAndProps(): [any, LayerProps] {
264
+ assert(this.state);
265
+
143
266
  const {data, format, apiVersion} = this.state;
144
267
 
145
268
  const {uniqueIdProperty} = defaultProps;
146
- const props = {uniqueIdProperty, ...this.props};
147
- delete props.data;
269
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
270
+ const {data: _notUsed, ...propsNoData} = this.props;
271
+ const props = {uniqueIdProperty, ...propsNoData};
148
272
 
149
273
  if (apiVersion === API_VERSIONS.V1 || apiVersion === API_VERSIONS.V2) {
150
274
  return [MVTLayer, props];
@@ -155,7 +279,9 @@ export default class CartoLayer extends CompositeLayer {
155
279
  const tileUrl = new URL(data.tiles[0]);
156
280
 
157
281
  props.formatTiles =
158
- props.formatTiles || tileUrl.searchParams.get('formatTiles') || TILE_FORMATS.MVT;
282
+ props.formatTiles ||
283
+ (tileUrl.searchParams.get('formatTiles') as TileFormat) ||
284
+ TILE_FORMATS.MVT;
159
285
 
160
286
  return props.formatTiles === TILE_FORMATS.MVT ? [MVTLayer, props] : [CartoTileLayer, props];
161
287
  }
@@ -164,7 +290,9 @@ export default class CartoLayer extends CompositeLayer {
164
290
  return [GeoJsonLayer, props];
165
291
  }
166
292
 
167
- renderLayers() {
293
+ renderLayers(): Layer | null {
294
+ assert(this.state);
295
+
168
296
  const {data} = this.state;
169
297
 
170
298
  if (!data) return null;
@@ -184,6 +312,3 @@ export default class CartoLayer extends CompositeLayer {
184
312
  );
185
313
  }
186
314
  }
187
-
188
- CartoLayer.layerName = 'CartoLayer';
189
- CartoLayer.defaultProps = defaultProps;