@carto/api-client 0.5.14 → 0.5.15-alpha.raster-1

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.14",
11
+ "version": "0.5.15-alpha.raster-1",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -71,6 +71,7 @@
71
71
  "d3-format": "^3.1.0",
72
72
  "d3-scale": "^4.0.2",
73
73
  "h3-js": "^4.1.0",
74
+ "jsep": "^1.4.0",
74
75
  "quadbin": "^0.4.1-alpha.0"
75
76
  },
76
77
  "devDependencies": {
@@ -131,5 +132,6 @@
131
132
  "resolutions": {
132
133
  "@carto/api-client": "portal:./",
133
134
  "rollup": "^4.20.0"
134
- }
135
+ },
136
+ "stableVersion": "0.5.14"
135
137
  }
@@ -92,7 +92,7 @@ export const STYLE_LAYER_GROUPS: StyleLayerGroup[] = [
92
92
  ];
93
93
 
94
94
  export function applyLayerGroupFilters(
95
- style: any,
95
+ style: any, // this Maplibre/Mapbox style, we don't want to add a dependency on Maplibre
96
96
  visibleLayerGroups: Record<StyleLayerGroupSlug, boolean>
97
97
  ) {
98
98
  if (!Array.isArray(style?.layers)) {
@@ -1,4 +1,8 @@
1
- export {default as BASEMAP} from './basemap-styles.js';
1
+ export {
2
+ default as BASEMAP,
3
+ applyLayerGroupFilters as _applyLayerGroupFilters,
4
+ } from './basemap-styles.js';
5
+ export {getRasterTileLayerStyleProps as _getRasterTileLayerStyleProps} from './raster-layer.js';
2
6
  export {fetchMap} from './fetch-map.js';
3
7
  export type {FetchMapOptions, FetchMapResult} from './fetch-map.js';
4
8
  export type {
@@ -28,6 +28,7 @@ import {
28
28
  scaleIdentity,
29
29
  } from './utils.js';
30
30
  import type {
31
+ ColorRange,
31
32
  CustomMarkersRange,
32
33
  Dataset,
33
34
  MapLayerConfig,
@@ -38,6 +39,7 @@ import type {
38
39
  import type {ProviderType, SchemaField} from '../types.js';
39
40
  import {DEFAULT_AGGREGATION_EXP_ALIAS} from '../constants-internal.js';
40
41
  import {AggregationTypes} from '../constants.js';
42
+ import type {Attribute, TilejsonResult} from '../sources/types.js';
41
43
 
42
44
  export type D3Scale = {
43
45
  domain: (d?: any) => any[];
@@ -113,6 +115,13 @@ const sharedPropMap = {
113
115
  },
114
116
  };
115
117
 
118
+ const rasterPropsMap = {
119
+ isVisible: 'visible',
120
+ visConfig: {
121
+ opacity: 'opacity',
122
+ },
123
+ };
124
+
116
125
  const customMarkersPropsMap = {
117
126
  color: 'getIconColor',
118
127
  visConfig: {
@@ -174,6 +183,13 @@ export function getLayerProps(
174
183
  );
175
184
  }
176
185
 
186
+ if (type === 'raster') {
187
+ return {
188
+ propMap: rasterPropsMap,
189
+ defaultProps: {},
190
+ };
191
+ }
192
+
177
193
  let basePropMap: any = sharedPropMap;
178
194
  if (config.visConfig?.customMarkers) {
179
195
  basePropMap = mergePropMaps(basePropMap, customMarkersPropsMap);
@@ -195,18 +211,21 @@ export function getLayerProps(
195
211
  }
196
212
 
197
213
  function domainFromAttribute(
198
- attribute: any,
214
+ attribute: Attribute,
199
215
  scaleType: ScaleType,
200
216
  scaleLength: number
201
- ) {
217
+ ): number[] | string[] {
202
218
  if (scaleType === 'ordinal' || scaleType === 'point') {
219
+ if (!attribute.categories) {
220
+ return [0, 1];
221
+ }
203
222
  return attribute.categories
204
223
  .map((c: any) => c.category)
205
224
  .filter((c: any) => c !== undefined && c !== null);
206
225
  }
207
226
 
208
227
  if (scaleType === 'quantile' && attribute.quantiles) {
209
- return attribute.quantiles.global
228
+ return 'global' in attribute.quantiles
210
229
  ? attribute.quantiles.global[scaleLength]
211
230
  : attribute.quantiles[scaleLength];
212
231
  }
@@ -215,7 +234,7 @@ function domainFromAttribute(
215
234
  if (scaleType === 'log' && min === 0) {
216
235
  min = 1e-5;
217
236
  }
218
- return [min, attribute.max];
237
+ return [min ?? 0, attribute.max ?? 1];
219
238
  }
220
239
 
221
240
  function domainFromValues(values: any, scaleType: ScaleType) {
@@ -235,8 +254,8 @@ function domainFromValues(values: any, scaleType: ScaleType) {
235
254
  }
236
255
 
237
256
  function calculateDomain(
238
- data: any,
239
- name: any,
257
+ data: TilejsonResult,
258
+ name: string,
240
259
  scaleType: ScaleType,
241
260
  scaleLength?: number
242
261
  ) {
@@ -244,14 +263,16 @@ function calculateDomain(
244
263
  // Tileset data type
245
264
  const {attributes} = data.tilestats.layers[0];
246
265
  const attribute = attributes.find((a: any) => a.attribute === name);
247
- return domainFromAttribute(attribute, scaleType, scaleLength as number);
266
+ if (attribute) {
267
+ return domainFromAttribute(attribute, scaleType, scaleLength as number);
268
+ }
248
269
  }
249
270
 
250
271
  return [0, 1];
251
272
  }
252
273
 
253
274
  function normalizeAccessor(accessor: any, data: any) {
254
- if (data.features || data.tilestats) {
275
+ if (data.features || data.tilestats || data.raster_metadata) {
255
276
  return (object: any, info: any) => {
256
277
  if (object) {
257
278
  return accessor(object.properties || object.__source.object.properties);
@@ -299,7 +320,7 @@ export function getColorAccessor(
299
320
  scaleType: ScaleType,
300
321
  {aggregation, range}: {aggregation: string; range: any},
301
322
  opacity: number | undefined,
302
- data: any
323
+ data: TilejsonResult
303
324
  ): {accessor: any; scale: any} {
304
325
  const scale = calculateLayerScale(
305
326
  colorColumn || name,
@@ -321,14 +342,13 @@ export function getColorAccessor(
321
342
  return {accessor: normalizeAccessor(accessor, data), scale};
322
343
  }
323
344
 
324
- function calculateLayerScale(
325
- name: any,
345
+ export function calculateLayerScale(
346
+ name: string,
326
347
  scaleType: ScaleType,
327
- range: any,
328
- data: any
348
+ range: ColorRange,
349
+ data: TilejsonResult
329
350
  ) {
330
- const scale = SCALE_FUNCS[scaleType]();
331
- let domain: (string | number)[] = [];
351
+ let domain: string[] | number[] = [];
332
352
  let scaleColor: string[] = [];
333
353
 
334
354
  if (scaleType !== 'identity') {
@@ -336,7 +356,7 @@ function calculateLayerScale(
336
356
 
337
357
  if (Array.isArray(colorMap)) {
338
358
  colorMap.forEach(([value, color]) => {
339
- domain.push(value);
359
+ (domain as string[]).push(value);
340
360
  scaleColor.push(color);
341
361
  });
342
362
  } else {
@@ -349,9 +369,19 @@ function calculateLayerScale(
349
369
  }
350
370
  }
351
371
 
372
+ return createColorScale(scaleType, domain, scaleColor, UNKNOWN_COLOR);
373
+ }
374
+
375
+ export function createColorScale<T>(
376
+ scaleType: ScaleType,
377
+ domain: string[] | number[],
378
+ range: T[],
379
+ unknown: T
380
+ ) {
381
+ const scale = SCALE_FUNCS[scaleType]();
352
382
  scale.domain(domain);
353
- scale.range(scaleColor);
354
- scale.unknown!(UNKNOWN_COLOR);
383
+ scale.range(range);
384
+ scale.unknown!(unknown as any);
355
385
 
356
386
  return scale;
357
387
  }
@@ -428,7 +458,7 @@ export function getSizeAccessor(
428
458
  scaleType: ScaleType | undefined,
429
459
  aggregation: string | null | undefined,
430
460
  range: Iterable<Range> | null | undefined,
431
- data: any
461
+ data: TilejsonResult
432
462
  ): {accessor: any; scale: any} {
433
463
  const scale = scaleType ? SCALE_FUNCS[scaleType]() : identity;
434
464
  if (scaleType) {
@@ -32,6 +32,11 @@ import type {
32
32
  VisualChannelField,
33
33
  } from './types.js';
34
34
  import {isRemoteCalculationSupported} from './utils.js';
35
+ import {
36
+ getRasterTileLayerStylePropsRgb,
37
+ getRasterTileLayerStylePropsScaledBand,
38
+ } from './raster-layer.js';
39
+ import type {TilejsonResult} from '../sources/types.js';
35
40
 
36
41
  export type Scale = {
37
42
  field: VisualChannelField;
@@ -40,11 +45,18 @@ export type Scale = {
40
45
  type: ScaleType;
41
46
  };
42
47
 
48
+ export type ScaleKey =
49
+ | 'fillColor'
50
+ | 'pointRadius'
51
+ | 'lineColor'
52
+ | 'elevation'
53
+ | 'weight';
54
+
43
55
  export type LayerDescriptor = {
44
56
  type: LayerType;
45
57
  props: Record<string, any>;
46
58
  filters?: Filters;
47
- scales: Record<ScaleKey, Scale>;
59
+ scales: Partial<Record<ScaleKey, Scale>>;
48
60
  };
49
61
 
50
62
  export type ParseMapResult = {
@@ -68,13 +80,62 @@ export type ParseMapResult = {
68
80
  layers: LayerDescriptor[];
69
81
  };
70
82
 
83
+ export function getLayerDescriptor({
84
+ mapConfig,
85
+ layer,
86
+ dataset,
87
+ }: {
88
+ mapConfig: KeplerMapConfig;
89
+ layer: MapConfigLayer;
90
+ dataset: Dataset;
91
+ }) {
92
+ const {filters, visState} = mapConfig;
93
+ const {layerBlending, interactionConfig} = visState;
94
+ const {id, type, config, visualChannels} = layer;
95
+ const {data, id: datasetId} = dataset;
96
+
97
+ const {propMap, defaultProps} = getLayerProps(type, config, dataset);
98
+
99
+ const styleProps = createStyleProps(config, propMap);
100
+
101
+ const {channelProps, scales} = createChannelProps(
102
+ id,
103
+ type,
104
+ config,
105
+ visualChannels,
106
+ data,
107
+ dataset
108
+ );
109
+ const layerDescriptor: LayerDescriptor = {
110
+ type,
111
+ filters:
112
+ isEmptyObject(filters) || isRemoteCalculationSupported(dataset)
113
+ ? undefined
114
+ : filters[datasetId],
115
+ props: {
116
+ id,
117
+ data,
118
+ ...defaultProps,
119
+ ...createInteractionProps(interactionConfig),
120
+ ...styleProps,
121
+ ...channelProps,
122
+ ...createParametersProp(layerBlending, styleProps.parameters || {}), // Must come after style
123
+ ...createLoadOptions(data.accessToken),
124
+ },
125
+ scales,
126
+ };
127
+ return layerDescriptor;
128
+ }
129
+
71
130
  export function parseMap(json: any) {
72
131
  const {keplerMapConfig, datasets, token} = json;
73
132
  assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
74
- const config = keplerMapConfig.config as KeplerMapConfig;
75
- const {filters, mapState, mapStyle, popupSettings, legendSettings} = config;
76
- const {layers, layerBlending, interactionConfig} = config.visState;
133
+ const mapConfig = keplerMapConfig.config as KeplerMapConfig;
134
+ const {mapState, mapStyle, popupSettings, legendSettings, visState} =
135
+ mapConfig;
136
+ const {layers} = visState;
77
137
 
138
+ const layersReverse = [...layers].reverse();
78
139
  return {
79
140
  id: json.id,
80
141
  title: json.title,
@@ -87,57 +148,24 @@ export function parseMap(json: any) {
87
148
  popupSettings,
88
149
  legendSettings,
89
150
  token,
90
- layers: layers
91
- .reverse()
92
- .map(({id, type, config, visualChannels}: MapConfigLayer) => {
93
- try {
94
- const {dataId} = config;
95
- const dataset: Dataset | null = datasets.find(
96
- (d: any) => d.id === dataId
97
- );
98
- assert(dataset, `No dataset matching dataId: ${dataId}`);
99
- const {data} = dataset;
100
- assert(data, `No data loaded for dataId: ${dataId}`);
101
-
102
- const {propMap, defaultProps} = getLayerProps(type, config, dataset);
103
-
104
- const styleProps = createStyleProps(config, propMap);
105
-
106
- const {channelProps, scales} = createChannelProps(
107
- id,
108
- type,
109
- config,
110
- visualChannels,
111
- data,
112
- dataset
113
- );
114
- const layer: LayerDescriptor = {
115
- type,
116
- filters:
117
- isEmptyObject(filters) || isRemoteCalculationSupported(dataset)
118
- ? undefined
119
- : filters[dataId],
120
- props: {
121
- id,
122
- data,
123
- ...defaultProps,
124
- ...createInteractionProps(interactionConfig),
125
- ...styleProps,
126
- ...channelProps,
127
- ...createParametersProp(
128
- layerBlending,
129
- styleProps.parameters || {}
130
- ), // Must come after style
131
- ...createLoadOptions(token),
132
- },
133
- scales,
134
- };
135
- return layer;
136
- } catch (e: any) {
137
- console.error(e.message);
138
- return undefined;
139
- }
140
- }),
151
+ layers: layersReverse.map((layer: MapConfigLayer) => {
152
+ try {
153
+ const {dataId} = layer.config;
154
+ const dataset: Dataset | null = datasets.find(
155
+ (d: any) => d.id === dataId
156
+ );
157
+ assert(dataset, `No dataset matching dataId: ${dataId}`);
158
+ const layerDescriptor = getLayerDescriptor({
159
+ mapConfig,
160
+ layer,
161
+ dataset,
162
+ });
163
+ return layerDescriptor;
164
+ } catch (e: any) {
165
+ console.error(e.message);
166
+ return undefined;
167
+ }
168
+ }),
141
169
  };
142
170
  }
143
171
 
@@ -225,21 +253,17 @@ function domainAndRangeFromScale(
225
253
  };
226
254
  }
227
255
 
228
- export type ScaleKey =
229
- | 'fillColor'
230
- | 'pointRadius'
231
- | 'lineColor'
232
- | 'elevation'
233
- | 'weight';
234
-
235
256
  function createChannelProps(
236
257
  id: string,
237
258
  layerType: LayerType,
238
259
  config: MapLayerConfig,
239
260
  visualChannels: VisualChannels,
240
- data: any,
261
+ data: TilejsonResult,
241
262
  dataset: Dataset
242
- ): {channelProps: Record<string, any>; scales: Record<ScaleKey, Scale>} {
263
+ ): {
264
+ channelProps: Record<string, any>;
265
+ scales: Partial<Record<ScaleKey, Scale>>;
266
+ } {
243
267
  const {
244
268
  colorField,
245
269
  colorScale,
@@ -247,11 +271,52 @@ function createChannelProps(
247
271
  radiusScale,
248
272
  strokeColorField,
249
273
  strokeColorScale,
274
+ sizeField: strokeWidthField,
275
+ sizeScale: strokeWidthScale,
250
276
  weightField,
251
277
  } = visualChannels;
278
+ if (layerType === 'raster') {
279
+ const rasterMetadata = data.raster_metadata;
280
+ if (!rasterMetadata) {
281
+ return {
282
+ channelProps: {},
283
+ scales: {},
284
+ };
285
+ }
286
+ const rasterStyleType = config.visConfig.rasterStyleType;
287
+ if (rasterStyleType === 'Rgb') {
288
+ return {
289
+ channelProps: getRasterTileLayerStylePropsRgb({
290
+ layerConfig: config,
291
+ rasterMetadata,
292
+ visualChannels,
293
+ }),
294
+ scales: {},
295
+ };
296
+ } else {
297
+ return {
298
+ channelProps: getRasterTileLayerStylePropsScaledBand({
299
+ layerConfig: config,
300
+ visualChannels,
301
+ rasterMetadata,
302
+ }),
303
+ scales: {
304
+ ...(colorField && {
305
+ fillColor: {
306
+ field: colorField,
307
+ type: 'ordinal',
308
+ domain: [],
309
+ range: [],
310
+ },
311
+ }),
312
+ },
313
+ };
314
+ }
315
+ }
252
316
  const {heightField, heightScale} = visualChannels;
253
317
  const {textLabel, visConfig} = config;
254
318
  const result: Record<string, any> = {};
319
+ const updateTriggers: Record<string, any> = {};
255
320
 
256
321
  const scales: Record<string, Scale> = {};
257
322
 
@@ -265,7 +330,7 @@ function createChannelProps(
265
330
  data
266
331
  );
267
332
  result.getFillColor = accessor;
268
- scales.fillColor = {
333
+ scales.fillColor = updateTriggers.getFillColor = {
269
334
  field: colorField,
270
335
  type: colorScale!,
271
336
  ...domainAndRangeFromScale(scale),
@@ -288,6 +353,8 @@ function createChannelProps(
288
353
  return d.properties[aggregationExpAlias];
289
354
  };
290
355
 
356
+ updateTriggers.getWeight = aggregationExpAlias;
357
+
291
358
  result.getPointRadius = (d: any, info: any) => {
292
359
  return calculateClusterRadius(
293
360
  d.properties,
@@ -296,6 +363,10 @@ function createChannelProps(
296
363
  aggregationExpAlias
297
364
  );
298
365
  };
366
+ updateTriggers.getPointRadius = {
367
+ aggregationExpAlias,
368
+ radiusRange: visConfig.radiusRange,
369
+ };
299
370
 
300
371
  result.textCharacterSet = 'auto';
301
372
  result.textFontFamily = 'Inter, sans';
@@ -305,6 +376,8 @@ function createChannelProps(
305
376
  result.getText = (d: any) =>
306
377
  TEXT_NUMBER_FORMATTER.format(d.properties[aggregationExpAlias]);
307
378
 
379
+ updateTriggers.getText = aggregationExpAlias;
380
+
308
381
  result.getTextColor = config.textLabel[TEXT_LABEL_INDEX].color;
309
382
  result.textOutlineColor = [
310
383
  ...(config.textLabel[TEXT_LABEL_INDEX].outlineColor as number[]),
@@ -322,6 +395,11 @@ function createChannelProps(
322
395
  );
323
396
  return calculateClusterTextFontSize(radius);
324
397
  };
398
+
399
+ updateTriggers.getTextSize = {
400
+ aggregationExpAlias,
401
+ radiusRange: visConfig.radiusRange,
402
+ };
325
403
  }
326
404
 
327
405
  if (radiusField) {
@@ -333,7 +411,7 @@ function createChannelProps(
333
411
  data
334
412
  );
335
413
  result.getPointRadius = accessor;
336
- scales.pointRadius = {
414
+ scales.pointRadius = updateTriggers.getPointRadius = {
337
415
  field: radiusField,
338
416
  type: radiusScale || 'identity',
339
417
  ...domainAndRangeFromScale(scale),
@@ -353,12 +431,28 @@ function createChannelProps(
353
431
  data
354
432
  );
355
433
  result.getLineColor = accessor;
356
- scales.lineColor = {
434
+ scales.lineColor = updateTriggers.getLineColor = {
357
435
  field: strokeColorField,
358
436
  type: strokeColorScale!,
359
437
  ...domainAndRangeFromScale(scale),
360
438
  };
361
439
  }
440
+ if (strokeWidthField) {
441
+ const {accessor, scale} = getSizeAccessor(
442
+ strokeWidthField,
443
+ strokeWidthScale,
444
+ visConfig.sizeAggregation,
445
+ visConfig.sizeRange,
446
+ data
447
+ );
448
+ result.getLineWidth = accessor;
449
+ scales.lineWidth = updateTriggers.getLineWidth = {
450
+ field: strokeWidthField,
451
+ type: strokeWidthScale || 'identity',
452
+ ...domainAndRangeFromScale(scale),
453
+ };
454
+ }
455
+
362
456
  if (heightField && visConfig.enable3d) {
363
457
  const {accessor, scale} = getSizeAccessor(
364
458
  heightField,
@@ -368,7 +462,7 @@ function createChannelProps(
368
462
  data
369
463
  );
370
464
  result.getElevation = accessor;
371
- scales.elevation = {
465
+ scales.elevation = updateTriggers.getElevation = {
372
466
  field: heightField,
373
467
  type: heightScale || 'identity',
374
468
  ...domainAndRangeFromScale(scale),
@@ -384,9 +478,9 @@ function createChannelProps(
384
478
  data
385
479
  );
386
480
  result.getWeight = accessor;
387
- scales.weight = {
481
+ scales.weight = updateTriggers.getWeight = {
388
482
  field: weightField,
389
- type: 'identity',
483
+ type: 'identity' as ScaleType,
390
484
  ...domainAndRangeFromScale(scale),
391
485
  };
392
486
  }
@@ -407,6 +501,12 @@ function createChannelProps(
407
501
  {fallbackUrl: customMarkersUrl, maxIconSize, useMaskedIcons},
408
502
  data
409
503
  );
504
+ updateTriggers.getIcon = {
505
+ customMarkersUrl,
506
+ customMarkersRange,
507
+ maxIconSize,
508
+ useMaskedIcons,
509
+ };
410
510
  result._subLayerProps = {
411
511
  'points-icon': {
412
512
  loadOptions: {
@@ -424,10 +524,12 @@ function createChannelProps(
424
524
 
425
525
  if (getFillColor && useMaskedIcons) {
426
526
  result.getIconColor = getFillColor;
527
+ updateTriggers.getIconColor = updateTriggers.getFillColor;
427
528
  }
428
529
 
429
530
  if (getPointRadius) {
430
531
  result.getIconSize = getPointRadius;
532
+ updateTriggers.getIconSize = updateTriggers.getPointRadius;
431
533
  }
432
534
 
433
535
  if (visualChannels.rotationField) {
@@ -439,6 +541,7 @@ function createChannelProps(
439
541
  data
440
542
  );
441
543
  result.getIconAngle = negateAccessor(accessor);
544
+ updateTriggers.getIconAngle = updateTriggers.getRotationField;
442
545
  }
443
546
  } else if (layerType === 'tileset') {
444
547
  result.pointType = 'circle';
@@ -494,7 +597,13 @@ function createChannelProps(
494
597
  };
495
598
  }
496
599
 
497
- return {channelProps: result, scales};
600
+ return {
601
+ channelProps: {
602
+ ...result,
603
+ updateTriggers,
604
+ },
605
+ scales,
606
+ };
498
607
  }
499
608
 
500
609
  function createLoadOptions(accessToken: string) {