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

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.15-alpha.raster-1",
11
+ "version": "0.5.15-alpha.raster-3",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -15,3 +15,4 @@ export type {
15
15
  export * from './basemap.js';
16
16
  export * from './layer-map.js';
17
17
  export * from './parse-map.js';
18
+ export {getLog10ScaleSteps as _getLog10ScaleSteps} from './utils.js';
@@ -25,6 +25,7 @@ import {
25
25
  createBinaryProxy,
26
26
  formatDate,
27
27
  formatTimestamp,
28
+ getLog10ScaleSteps,
28
29
  scaleIdentity,
29
30
  } from './utils.js';
30
31
  import type {
@@ -74,7 +75,21 @@ function identity<T>(v: T): T {
74
75
  return v;
75
76
  }
76
77
 
78
+ const hexToRGB = (c: any) => {
79
+ const {r, g, b} = rgb(c);
80
+ return [r, g, b];
81
+ };
82
+
83
+ const rgbToHex = (c: number[]) => {
84
+ const [r, g, b] = c;
85
+ const rStr = r.toString(16).padStart(2, '0');
86
+ const gStr = g.toString(16).padStart(2, '0');
87
+ const bStr = b.toString(16).padStart(2, '0');
88
+ return `#${rStr}${gStr}${bStr}`.toUpperCase();
89
+ };
90
+
77
91
  const UNKNOWN_COLOR = '#868d91';
92
+ const UNKNOWN_COLOR_RGB = hexToRGB(UNKNOWN_COLOR);
78
93
 
79
94
  export const OPACITY_MAP: Record<string, string> = {
80
95
  getFillColor: 'opacity',
@@ -318,11 +333,16 @@ function findAccessorKey(keys: string[], properties: any): string[] {
318
333
  export function getColorAccessor(
319
334
  {name, colorColumn}: VisualChannelField,
320
335
  scaleType: ScaleType,
321
- {aggregation, range}: {aggregation: string; range: any},
336
+ {aggregation, range}: {aggregation?: string; range: ColorRange},
322
337
  opacity: number | undefined,
323
338
  data: TilejsonResult
324
- ): {accessor: any; scale: any} {
325
- const scale = calculateLayerScale(
339
+ ): {
340
+ accessor: any;
341
+ domain: number[] | string[];
342
+ scaleDomain: number[] | string[];
343
+ range: string[];
344
+ } {
345
+ const {scale, domain} = calculateLayerScale(
326
346
  colorColumn || name,
327
347
  scaleType,
328
348
  range,
@@ -336,10 +356,15 @@ export function getColorAccessor(
336
356
  accessorKeys = findAccessorKey(accessorKeys, properties);
337
357
  }
338
358
  const propertyValue = properties[accessorKeys[0]];
339
- const {r, g, b} = rgb(scale(propertyValue));
359
+ const [r, g, b] = scale(propertyValue);
340
360
  return [r, g, b, propertyValue === null ? 0 : alpha];
341
361
  };
342
- return {accessor: normalizeAccessor(accessor, data), scale};
362
+ return {
363
+ accessor: normalizeAccessor(accessor, data),
364
+ scaleDomain: scale.domain(),
365
+ domain,
366
+ range: scale.range().map(rgbToHex),
367
+ };
343
368
  }
344
369
 
345
370
  export function calculateLayerScale(
@@ -347,29 +372,48 @@ export function calculateLayerScale(
347
372
  scaleType: ScaleType,
348
373
  range: ColorRange,
349
374
  data: TilejsonResult
350
- ) {
375
+ ): {scale: D3Scale; domain: string[] | number[]} {
351
376
  let domain: string[] | number[] = [];
377
+ let scaleDomain: number[] | undefined;
352
378
  let scaleColor: string[] = [];
379
+ const {colors} = range;
353
380
 
354
- if (scaleType !== 'identity') {
355
- const {colorMap, colors} = range;
381
+ if (scaleType === 'custom') {
382
+ if (range.uiCustomScaleType === 'logarithmic') {
383
+ domain = calculateDomain(data, name, scaleType, colors.length);
384
+ const [min, max] = domain as number[];
385
+ scaleDomain = getLog10ScaleSteps({
386
+ min,
387
+ max,
388
+ steps: colors.length,
389
+ });
356
390
 
357
- if (Array.isArray(colorMap)) {
391
+ scaleColor = colors;
392
+ } else if (range.colorMap) {
393
+ const {colorMap} = range;
358
394
  colorMap.forEach(([value, color]) => {
359
395
  (domain as string[]).push(value);
360
396
  scaleColor.push(color);
361
397
  });
362
- } else {
363
- domain = calculateDomain(data, name, scaleType, colors.length);
364
- scaleColor = colors;
365
398
  }
399
+ } else if (scaleType !== 'identity') {
400
+ domain = calculateDomain(data, name, scaleType, colors.length);
401
+ scaleColor = colors;
366
402
 
367
403
  if (scaleType === 'ordinal') {
368
404
  domain = domain.slice(0, scaleColor.length);
369
405
  }
370
406
  }
371
407
 
372
- return createColorScale(scaleType, domain, scaleColor, UNKNOWN_COLOR);
408
+ return {
409
+ scale: createColorScale(
410
+ scaleType,
411
+ scaleDomain || domain,
412
+ scaleColor.map(hexToRGB),
413
+ UNKNOWN_COLOR_RGB
414
+ ),
415
+ domain,
416
+ };
373
417
  }
374
418
 
375
419
  export function createColorScale<T>(
@@ -457,13 +501,22 @@ export function getSizeAccessor(
457
501
  {name}: VisualChannelField,
458
502
  scaleType: ScaleType | undefined,
459
503
  aggregation: string | null | undefined,
460
- range: Iterable<Range> | null | undefined,
504
+ range: number[] | undefined,
461
505
  data: TilejsonResult
462
- ): {accessor: any; scale: any} {
506
+ ): {
507
+ accessor: any;
508
+ domain: number[];
509
+ scaleDomain: number[];
510
+ range: number[] | undefined;
511
+ } {
463
512
  const scale = scaleType ? SCALE_FUNCS[scaleType]() : identity;
464
- if (scaleType) {
513
+ let domain: number[] = [];
514
+ if (scaleType && range) {
465
515
  if (aggregation !== AggregationTypes.Count) {
466
- (scale as D3Scale).domain(calculateDomain(data, name, scaleType));
516
+ domain = calculateDomain(data, name, scaleType) as number[];
517
+ (scale as D3Scale).domain(domain);
518
+ } else {
519
+ domain = (scale as D3Scale).domain();
467
520
  }
468
521
  (scale as D3Scale).range(range);
469
522
  }
@@ -476,7 +529,12 @@ export function getSizeAccessor(
476
529
  const propertyValue = properties[accessorKeys[0]];
477
530
  return scale(propertyValue);
478
531
  };
479
- return {accessor: normalizeAccessor(accessor, data), scale};
532
+ return {
533
+ accessor: normalizeAccessor(accessor, data),
534
+ domain,
535
+ scaleDomain: domain,
536
+ range,
537
+ };
480
538
  }
481
539
 
482
540
  const FORMATS: Record<string, (value: any) => string> = {
@@ -16,7 +16,6 @@ import {
16
16
  TEXT_NUMBER_FORMATTER,
17
17
  TEXT_LABEL_INDEX,
18
18
  TEXT_OUTLINE_OPACITY,
19
- type D3Scale,
20
19
  type ScaleType,
21
20
  } from './layer-map.js';
22
21
 
@@ -39,10 +38,15 @@ import {
39
38
  import type {TilejsonResult} from '../sources/types.js';
40
39
 
41
40
  export type Scale = {
41
+ type: ScaleType;
42
42
  field: VisualChannelField;
43
+
44
+ /** Natural domain of the scale, as defined by the data */
43
45
  domain: string[] | number[];
44
- range: string[] | number[];
45
- type: ScaleType;
46
+
47
+ /** Domain of the user to construct d3 scale */
48
+ scaleDomain?: string[] | number[];
49
+ range?: string[] | number[];
46
50
  };
47
51
 
48
52
  export type ScaleKey =
@@ -52,11 +56,13 @@ export type ScaleKey =
52
56
  | 'elevation'
53
57
  | 'weight';
54
58
 
59
+ export type Scales = Partial<Record<ScaleKey, Scale>>;
60
+
55
61
  export type LayerDescriptor = {
56
62
  type: LayerType;
57
63
  props: Record<string, any>;
58
64
  filters?: Filters;
59
- scales: Partial<Record<ScaleKey, Scale>>;
65
+ scales: Scales;
60
66
  };
61
67
 
62
68
  export type ParseMapResult = {
@@ -232,7 +238,7 @@ function createStyleProps(config: MapLayerConfig, mapping: any) {
232
238
  if (Array.isArray(result[colorAccessor])) {
233
239
  const color = [...result[colorAccessor]];
234
240
  const opacityKey = OPACITY_MAP[colorAccessor];
235
- const opacity = config.visConfig[opacityKey as keyof VisConfig];
241
+ const opacity = config.visConfig[opacityKey as keyof VisConfig] as number;
236
242
  color[3] = opacityToAlpha(opacity);
237
243
  result[colorAccessor] = color;
238
244
  }
@@ -244,15 +250,6 @@ function createStyleProps(config: MapLayerConfig, mapping: any) {
244
250
  return result;
245
251
  }
246
252
 
247
- function domainAndRangeFromScale(
248
- scale: D3Scale
249
- ): Pick<Scale, 'domain' | 'range'> {
250
- return {
251
- domain: scale.domain(),
252
- range: scale.range(),
253
- };
254
- }
255
-
256
253
  function createChannelProps(
257
254
  id: string,
258
255
  layerType: LayerType,
@@ -264,17 +261,6 @@ function createChannelProps(
264
261
  channelProps: Record<string, any>;
265
262
  scales: Partial<Record<ScaleKey, Scale>>;
266
263
  } {
267
- const {
268
- colorField,
269
- colorScale,
270
- radiusField,
271
- radiusScale,
272
- strokeColorField,
273
- strokeColorScale,
274
- sizeField: strokeWidthField,
275
- sizeScale: strokeWidthScale,
276
- weightField,
277
- } = visualChannels;
278
264
  if (layerType === 'raster') {
279
265
  const rasterMetadata = data.raster_metadata;
280
266
  if (!rasterMetadata) {
@@ -291,7 +277,7 @@ function createChannelProps(
291
277
  rasterMetadata,
292
278
  visualChannels,
293
279
  }),
294
- scales: {},
280
+ scales: {}, // TODO
295
281
  };
296
282
  } else {
297
283
  return {
@@ -301,42 +287,38 @@ function createChannelProps(
301
287
  rasterMetadata,
302
288
  }),
303
289
  scales: {
304
- ...(colorField && {
305
- fillColor: {
306
- field: colorField,
307
- type: 'ordinal',
308
- domain: [],
309
- range: [],
310
- },
311
- }),
290
+ // TODO
312
291
  },
313
292
  };
314
293
  }
315
294
  }
316
- const {heightField, heightScale} = visualChannels;
317
295
  const {textLabel, visConfig} = config;
318
296
  const result: Record<string, any> = {};
319
297
  const updateTriggers: Record<string, any> = {};
320
298
 
321
299
  const scales: Record<string, Scale> = {};
322
300
 
323
- if (colorField) {
324
- const {colorAggregation: aggregation, colorRange: range} = visConfig;
325
- const {accessor, scale} = getColorAccessor(
326
- colorField,
327
- colorScale!,
328
- {aggregation, range},
329
- visConfig.opacity,
330
- data
331
- );
332
- result.getFillColor = accessor;
333
- scales.fillColor = updateTriggers.getFillColor = {
334
- field: colorField,
335
- type: colorScale!,
336
- ...domainAndRangeFromScale(scale),
337
- };
338
- } else if (visConfig.filled) {
339
- scales.fillColor = {} as any;
301
+ // fill color
302
+ {
303
+ const {colorField, colorScale} = visualChannels;
304
+ const {colorRange, colorAggregation} = visConfig;
305
+ if (colorField && colorScale && colorRange) {
306
+ const {accessor, ...scaleProps} = getColorAccessor(
307
+ colorField,
308
+ colorScale,
309
+ {aggregation: colorAggregation, range: colorRange},
310
+ visConfig.opacity,
311
+ data
312
+ );
313
+ result.getFillColor = accessor;
314
+ scales.fillColor = updateTriggers.getFillColor = {
315
+ field: colorField,
316
+ type: colorScale,
317
+ ...scaleProps,
318
+ };
319
+ } else {
320
+ scales.fillColor = {} as any;
321
+ }
340
322
  }
341
323
 
342
324
  if (layerType === 'clusterTile') {
@@ -402,87 +384,115 @@ function createChannelProps(
402
384
  };
403
385
  }
404
386
 
405
- if (radiusField) {
406
- const {accessor, scale} = getSizeAccessor(
407
- radiusField,
408
- radiusScale,
409
- visConfig.sizeAggregation,
410
- visConfig.radiusRange || visConfig.sizeRange,
411
- data
412
- );
413
- result.getPointRadius = accessor;
414
- scales.pointRadius = updateTriggers.getPointRadius = {
415
- field: radiusField,
416
- type: radiusScale || 'identity',
417
- ...domainAndRangeFromScale(scale),
418
- };
387
+ // point radius
388
+ {
389
+ const radiusRange = visConfig.radiusRange;
390
+ const {radiusField, radiusScale} = visualChannels;
391
+ if (radiusField && radiusRange && radiusScale) {
392
+ const {accessor, ...scaleProps} = getSizeAccessor(
393
+ radiusField,
394
+ radiusScale,
395
+ visConfig.sizeAggregation,
396
+ radiusRange,
397
+ data
398
+ );
399
+ result.getPointRadius = accessor;
400
+ scales.pointRadius = updateTriggers.getPointRadius = {
401
+ field: radiusField,
402
+ type: radiusScale,
403
+ ...scaleProps,
404
+ };
405
+ }
419
406
  }
420
407
 
421
- if (strokeColorField) {
422
- const opacity =
423
- visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
424
- const {strokeColorAggregation: aggregation, strokeColorRange: range} =
425
- visConfig;
426
- const {accessor, scale} = getColorAccessor(
427
- strokeColorField,
428
- strokeColorScale!,
429
- {aggregation, range},
430
- opacity,
431
- data
432
- );
433
- result.getLineColor = accessor;
434
- scales.lineColor = updateTriggers.getLineColor = {
435
- field: strokeColorField,
436
- type: strokeColorScale!,
437
- ...domainAndRangeFromScale(scale),
438
- };
408
+ // stroke/ouline color
409
+ {
410
+ const strokeColorRange = visConfig.strokeColorRange;
411
+ const {strokeColorScale, strokeColorField} = visualChannels;
412
+ if (strokeColorField && strokeColorRange && strokeColorScale) {
413
+ const {strokeColorAggregation: aggregation} = visConfig;
414
+ const opacity =
415
+ visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
416
+
417
+ const {accessor, ...scaleProps} = getColorAccessor(
418
+ strokeColorField,
419
+ strokeColorScale,
420
+ {aggregation, range: strokeColorRange},
421
+ opacity,
422
+ data
423
+ );
424
+ result.getLineColor = accessor;
425
+ scales.lineColor = updateTriggers.getLineColor = {
426
+ field: strokeColorField,
427
+ type: strokeColorScale,
428
+ ...scaleProps,
429
+ };
430
+ }
439
431
  }
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
- };
432
+
433
+ // stroke/line width
434
+ {
435
+ const {sizeField: strokeWidthField, sizeScale: strokeWidthScale} =
436
+ visualChannels;
437
+ const {sizeRange, sizeAggregation} = visConfig;
438
+
439
+ if (strokeWidthField && sizeRange) {
440
+ const {accessor, ...scaleProps} = getSizeAccessor(
441
+ strokeWidthField,
442
+ strokeWidthScale,
443
+ sizeAggregation,
444
+ sizeRange,
445
+ data
446
+ );
447
+ result.getLineWidth = accessor;
448
+ scales.lineWidth = updateTriggers.getLineWidth = {
449
+ field: strokeWidthField,
450
+ type: strokeWidthScale || 'identity',
451
+ ...scaleProps,
452
+ };
453
+ }
454
454
  }
455
455
 
456
- if (heightField && visConfig.enable3d) {
457
- const {accessor, scale} = getSizeAccessor(
458
- heightField,
459
- heightScale,
460
- visConfig.heightAggregation,
461
- visConfig.heightRange || visConfig.sizeRange,
462
- data
463
- );
464
- result.getElevation = accessor;
465
- scales.elevation = updateTriggers.getElevation = {
466
- field: heightField,
467
- type: heightScale || 'identity',
468
- ...domainAndRangeFromScale(scale),
469
- };
456
+ // height / elevation
457
+ {
458
+ const {enable3d, heightRange} = visConfig;
459
+ const {heightField, heightScale} = visualChannels;
460
+ if (heightField && heightRange && enable3d) {
461
+ const {accessor, ...scaleProps} = getSizeAccessor(
462
+ heightField,
463
+ heightScale,
464
+ visConfig.heightAggregation,
465
+ heightRange,
466
+ data
467
+ );
468
+ result.getElevation = accessor;
469
+ scales.elevation = updateTriggers.getElevation = {
470
+ field: heightField,
471
+ type: heightScale || 'identity',
472
+ ...scaleProps,
473
+ };
474
+ }
470
475
  }
471
476
 
472
- if (weightField) {
473
- const {accessor, scale} = getSizeAccessor(
474
- weightField,
475
- undefined,
476
- visConfig.weightAggregation,
477
- undefined,
478
- data
479
- );
480
- result.getWeight = accessor;
481
- scales.weight = updateTriggers.getWeight = {
482
- field: weightField,
483
- type: 'identity' as ScaleType,
484
- ...domainAndRangeFromScale(scale),
485
- };
477
+ // weight
478
+ {
479
+ const {weightField} = visualChannels;
480
+ const {weightAggregation} = visConfig;
481
+ if (weightField && weightAggregation) {
482
+ const {accessor, ...scaleProps} = getSizeAccessor(
483
+ weightField,
484
+ undefined,
485
+ weightAggregation,
486
+ undefined,
487
+ data
488
+ );
489
+ result.getWeight = accessor;
490
+ scales.weight = updateTriggers.getWeight = {
491
+ field: weightField,
492
+ type: 'identity' as ScaleType,
493
+ ...scaleProps,
494
+ };
495
+ }
486
496
  }
487
497
 
488
498
  if (visConfig.customMarkers) {
@@ -13,6 +13,7 @@ import type {
13
13
  VisualChannels,
14
14
  } from './types.js';
15
15
  import {createColorScale, type ScaleType} from './layer-map.js';
16
+ import {getLog10ScaleSteps} from './utils.js';
16
17
 
17
18
  const UNKNOWN_COLOR = [134, 141, 145];
18
19
 
@@ -377,10 +378,11 @@ export function domainFromRasterMetadataBand(
377
378
  }
378
379
  if (scaleType === 'custom') {
379
380
  if (colorRange.uiCustomScaleType === 'logarithmic') {
380
- if (colorRange.colorMap) {
381
- return colorRange.colorMap?.map(([value]) => value) || [];
382
- }
383
- return [band.stats.min, band.stats.max];
381
+ return getLog10ScaleSteps({
382
+ min: band.stats.min,
383
+ max: band.stats.max,
384
+ steps: colorRange.colors.length,
385
+ });
384
386
  } else {
385
387
  // actually custom, read colorMap
386
388
  return colorRange.colorMap?.map(([value]) => value) || [];
@@ -63,7 +63,7 @@ export type VisConfig = {
63
63
  opacity?: number;
64
64
  enable3d?: boolean;
65
65
 
66
- colorAggregation?: any;
66
+ colorAggregation?: string;
67
67
  colorRange: ColorRange;
68
68
 
69
69
  customMarkers?: boolean;
@@ -73,17 +73,17 @@ export type VisConfig = {
73
73
  radius: number;
74
74
  radiusRange?: number[];
75
75
 
76
- sizeAggregation?: any;
77
- sizeRange?: any;
76
+ sizeAggregation?: string;
77
+ sizeRange?: number[];
78
78
 
79
- strokeColorAggregation?: any;
79
+ strokeColorAggregation?: string;
80
80
  strokeOpacity?: number;
81
81
  strokeColorRange?: ColorRange;
82
82
 
83
- heightRange?: any;
84
- heightAggregation?: any;
83
+ heightRange?: number[];
84
+ heightAggregation?: string;
85
85
 
86
- weightAggregation?: any;
86
+ weightAggregation?: string;
87
87
 
88
88
  // type = clusterTile
89
89
  clusterLevel?: number;
@@ -85,3 +85,59 @@ export function formatDate(value: string | number | Date): string {
85
85
  export function formatTimestamp(value: string | number | Date): string {
86
86
  return String(Math.floor(new Date(value).getTime() / 1000));
87
87
  }
88
+
89
+ function roundedPow10(exp: number) {
90
+ // Math.pow(10, less than 4) generates "0.0...009999999999999999" instead of "0.0...01"
91
+ // round it ...
92
+ const raw = Math.pow(10, exp);
93
+ if (exp < 0) {
94
+ const shift = Math.pow(10, -exp);
95
+ return Math.round(raw * shift) / shift;
96
+ }
97
+ return raw;
98
+ }
99
+
100
+ /**
101
+ * Create domain for D3 threshold scale with logarithmic steps.
102
+ *
103
+ * If min is 0, it starts with max and goes down to fill color scale.
104
+ * If max is Infinity, it starts with 10 and goes up to fill color scale.
105
+ * Othersise it starts on first power of 10 that is greater than min.
106
+ *
107
+ * Generates `steps-1` entries, as this is what d3 threshold scale expects
108
+ *
109
+ * @see https://d3js.org/d3-scale/threshold
110
+ */
111
+ export function getLog10ScaleSteps({
112
+ min,
113
+ max,
114
+ steps,
115
+ }: {
116
+ min: number;
117
+ max: number;
118
+ steps: number;
119
+ }): number[] {
120
+ if (min === 0) {
121
+ if (max === Infinity) {
122
+ // count aggregations have [0, Infinity]
123
+ // that will yield [10, 100, 1000, ...]
124
+ return [...Array(steps - 1)].map((_v, i) => roundedPow10(i + 1));
125
+ }
126
+ // if stats.min = 0, we only can attempt to start from max and decrease powers until
127
+ // we use all color buckets ...
128
+ const maxLog = Math.log10(max);
129
+ const endExponent = Math.ceil(maxLog);
130
+ const startExponent = endExponent - steps + 1;
131
+ return [...Array(steps - 1)].map((_v, i) =>
132
+ roundedPow10(startExponent + i)
133
+ );
134
+ } else {
135
+ const minLog = Math.log10(min);
136
+ const startExponent =
137
+ Math.ceil(minLog) === minLog ? minLog + 1 : Math.ceil(minLog);
138
+
139
+ return [...Array(steps - 1)].map((_v, i) =>
140
+ roundedPow10(startExponent + i)
141
+ );
142
+ }
143
+ }
@@ -66,18 +66,6 @@ export function validateVecExprSyntax(
66
66
  return validate(parsed, context);
67
67
  }
68
68
 
69
- export function createValidationContext(
70
- validSymbols: string[]
71
- ): Record<string, unknown> {
72
- return validSymbols.reduce(
73
- (acc, symbol) => {
74
- acc[symbol] = 1;
75
- return acc;
76
- },
77
- {} as Record<string, unknown>
78
- );
79
- }
80
-
81
69
  export type VecExprVecLike =
82
70
  | number[]
83
71
  | Float32Array