@carto/api-client 0.5.15-alpha.raster-5 → 0.5.16

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.
@@ -1,536 +0,0 @@
1
- import type {RasterMetadata, RasterMetadataBand} from '../sources/types.js';
2
-
3
- import {
4
- createVecExprEvaluator,
5
- type VecExprResult,
6
- type VecExprVecLike,
7
- } from './vec-expr-evaluator.js';
8
- import type {
9
- ColorBand,
10
- ColorRange,
11
- MapLayerConfig,
12
- RasterLayerConfigColorBand,
13
- VisualChannels,
14
- } from './types.js';
15
- import {createColorScale, type ScaleType} from './layer-map.js';
16
- import {getLog10ScaleSteps} from './utils.js';
17
-
18
- const UNKNOWN_COLOR = [134, 141, 145];
19
-
20
- const RASTER_COLOR_BANDS = ['red', 'green', 'blue'] as const;
21
-
22
- function getHasDataPredicate(noData: number | string | undefined) {
23
- if (noData === 'nan') {
24
- return (v: number) => !isNaN(v);
25
- }
26
- if (typeof noData === 'string') {
27
- noData = parseFloat(noData);
28
- }
29
- if (typeof noData === 'number') {
30
- return (v: number) => v !== noData && !isNaN(v);
31
- }
32
-
33
- return () => true;
34
- }
35
-
36
- // this is data as seen in RasterLayer
37
- type RasterLayerData = {
38
- blockSize: number;
39
- cells: BinaryDataCells;
40
- };
41
-
42
- // this is data as seen in RasterColumnLayer - the one that actually renders pixels
43
- // (binary data is just wrapped)
44
- type RasterColumnLayerData = {
45
- length: number; // number of pixels
46
- data: RasterLayerData;
47
-
48
- // used to store buffers directly to be used by deck.gl, so we can skip accessors
49
- attributes?: Record<string, ArrayLike<number>>;
50
-
51
- // temporary storage for expression results filled in dataTransform
52
- expressionEvalContext?: Record<string, ArrayLike<number>>;
53
- customExpressionResults?: Record<string, VecExprResult>;
54
- };
55
-
56
- type BinaryDataCells = {
57
- numericProps: Record<string, {value: VecExprVecLike}>;
58
- };
59
-
60
- function createRasterColumnLayerDataTransform(
61
- transform: (dataWrapped: RasterColumnLayerData) => RasterColumnLayerData
62
- ) {
63
- return (data: RasterLayerData | RasterColumnLayerData) => {
64
- if (!data || !('data' in data) || !data?.data?.cells?.numericProps) {
65
- // we're in RasterLayer, or we've got invalid data
66
- return data as RasterLayerData;
67
- }
68
- // we only transform data when in RasterColumnLayer
69
- return transform(data);
70
- };
71
- }
72
-
73
- function createEvaluationContext(
74
- numericProps: Record<string, {value: VecExprVecLike}>,
75
- noData: number | string | undefined
76
- ) {
77
- // Optimization note
78
- // Seems like Array.from(256k+typed array takes even 15ms), so _we_ can afford to copy the array if we really don't need it to
79
- // only copy values to array only if we really see nodata in all bands
80
- const hasData = getHasDataPredicate(noData);
81
- const bands = Object.entries(numericProps).map(([bandName, {value}]) => ({
82
- bandName,
83
- values: value,
84
- copied: false,
85
- }));
86
-
87
- const length = bands[0].values.length;
88
-
89
- for (let i = 0; i < length; i++) {
90
- let hasSomeData = false;
91
- for (let j = 0; j < bands.length; j++) {
92
- hasSomeData = hasSomeData || hasData(bands[j].values[i]);
93
- }
94
- if (!hasSomeData) {
95
- for (let j = 0; j < bands.length; j++) {
96
- if (!bands[j].copied) {
97
- bands[j].copied = true;
98
- bands[j].values = Array.from(bands[j].values);
99
- }
100
- bands[j].values[i] = NaN;
101
- }
102
- }
103
- }
104
-
105
- const context = bands.reduce(
106
- (agg, {bandName, values}) => {
107
- agg[bandName] = values;
108
- return agg;
109
- },
110
- {} as Record<string, ArrayLike<number>>
111
- );
112
- return context;
113
- }
114
-
115
- function createExprDataTransform({
116
- colorBand,
117
- rasterMetadata,
118
- usedSymbols,
119
- }: {
120
- colorBand: RasterLayerConfigColorBand | null | undefined;
121
- rasterMetadata: RasterMetadata;
122
- usedSymbols: string[];
123
- }) {
124
- if (!colorBand || !colorBand.type || colorBand.type === 'none') {
125
- return undefined;
126
- }
127
-
128
- const expr = colorBand?.type === 'expression' ? colorBand.value : undefined;
129
- const vecExprEvaluator = expr ? createVecExprEvaluator(expr) : undefined;
130
-
131
- const dataTransform = createRasterColumnLayerDataTransform(
132
- (dataWrapped: RasterColumnLayerData) => {
133
- const data = dataWrapped.data;
134
- if (expr) {
135
- const cachedResult = dataWrapped.customExpressionResults?.[expr];
136
- if (cachedResult) {
137
- return dataWrapped;
138
- }
139
- }
140
-
141
- let context = dataWrapped.expressionEvalContext;
142
- if (!context) {
143
- const usedNumericProps = usedSymbols.reduce(
144
- (acc, symbol) => {
145
- acc[symbol] = data.cells.numericProps[symbol];
146
- return acc;
147
- },
148
- {} as Record<string, {value: VecExprVecLike}>
149
- );
150
- context = createEvaluationContext(
151
- usedNumericProps,
152
- rasterMetadata.nodata
153
- );
154
- dataWrapped = {
155
- ...dataWrapped,
156
- expressionEvalContext: context,
157
- };
158
- }
159
-
160
- if (!vecExprEvaluator || !expr) return dataWrapped;
161
-
162
- const evalResult = vecExprEvaluator(context);
163
- return {
164
- ...dataWrapped,
165
- customExpressionResults: {
166
- ...dataWrapped.customExpressionResults,
167
- [expr]: evalResult,
168
- },
169
- };
170
- }
171
- );
172
- return dataTransform;
173
- }
174
-
175
- function combineDataTransforms<T>(
176
- dataTransforms: (((data: T) => T) | undefined)[]
177
- ): ((data: T) => T) | undefined {
178
- const actualTransforms = dataTransforms.filter((v) => v) as ((
179
- data: T
180
- ) => T)[];
181
-
182
- if (actualTransforms.length === 0) return undefined;
183
- if (actualTransforms.length === 1) return actualTransforms[0];
184
-
185
- return (data: T) =>
186
- actualTransforms.reduce(
187
- (aggData, transformFun) => transformFun(aggData),
188
- data
189
- );
190
- }
191
-
192
- function createRgbToColorBufferDataTransform({
193
- bandDefs,
194
- attribute,
195
- }: {
196
- bandDefs: Partial<Record<ColorBand, RasterLayerConfigColorBand>>;
197
- attribute: string;
198
- }) {
199
- return createRasterColumnLayerDataTransform(
200
- (dataWrapped: RasterColumnLayerData) => {
201
- const length = dataWrapped.length;
202
-
203
- const getBandBufferOrValue = (colorBand?: RasterLayerConfigColorBand) => {
204
- if (colorBand?.type === 'expression') {
205
- return dataWrapped.customExpressionResults?.[colorBand.value];
206
- }
207
- if (colorBand?.type === 'band') {
208
- // we could use original band, but this one is already cleared from nodata if needed
209
- return dataWrapped.expressionEvalContext?.[colorBand.value];
210
- }
211
- return 0;
212
- };
213
-
214
- const red = getBandBufferOrValue(bandDefs.red);
215
- const green = getBandBufferOrValue(bandDefs.green);
216
- const blue = getBandBufferOrValue(bandDefs.blue);
217
-
218
- const colorBuffer = new Uint8Array(length * 4);
219
- for (
220
- let inputIndex = 0, outputIndex = 0;
221
- inputIndex < length;
222
- inputIndex++, outputIndex += 4
223
- ) {
224
- const redRaw =
225
- typeof red === 'number' ? red : red ? red[inputIndex] : NaN;
226
- const greenRaw =
227
- typeof green === 'number' ? green : green ? green[inputIndex] : NaN;
228
- const blueRaw =
229
- typeof blue === 'number' ? blue : blue ? blue[inputIndex] : NaN;
230
-
231
- if (isNaN(redRaw) && isNaN(greenRaw) && isNaN(blueRaw)) {
232
- // skip pixel
233
- bufferSetRgba(colorBuffer, outputIndex, 0, 0, 0, 0);
234
- } else {
235
- bufferSetRgba(
236
- colorBuffer,
237
- outputIndex,
238
- redRaw,
239
- greenRaw,
240
- blueRaw,
241
- 255
242
- );
243
- }
244
- }
245
-
246
- // clear cached buffers
247
- // This transform is applied last -after expression & band evaluators which store and
248
- // cache values for _this_ transform.
249
- // Now, _assuming_ this is last transform we can clear those buffers.
250
- dataWrapped.customExpressionResults = undefined;
251
- dataWrapped.expressionEvalContext = undefined;
252
-
253
- return {
254
- ...dataWrapped,
255
- attributes: {
256
- [attribute]: colorBuffer,
257
- },
258
- };
259
- }
260
- );
261
- }
262
-
263
- function getUsedSymbols(colorBands: RasterLayerConfigColorBand[]) {
264
- return Array.from(
265
- colorBands.reduce((symbols, band) => {
266
- if (band.type === 'expression') {
267
- const expressionSymbols =
268
- createVecExprEvaluator(band.value)?.symbols || [];
269
- expressionSymbols.forEach((symbol) => symbols.add(symbol));
270
- }
271
- if (band.type === 'band') {
272
- symbols.add(band.value);
273
- }
274
- return symbols;
275
- }, new Set<string>())
276
- );
277
- }
278
-
279
- export function getRasterTileLayerStylePropsRgb({
280
- layerConfig,
281
- rasterMetadata,
282
- visualChannels,
283
- }: {
284
- layerConfig: MapLayerConfig;
285
- rasterMetadata: RasterMetadata;
286
- visualChannels: VisualChannels;
287
- }) {
288
- const {visConfig} = layerConfig;
289
- const {colorBands} = visConfig;
290
-
291
- const bandDefs = {
292
- red: colorBands?.find((band) => band.band === 'red'),
293
- green: colorBands?.find((band) => band.band === 'green'),
294
- blue: colorBands?.find((band) => band.band === 'blue'),
295
- };
296
-
297
- const rgbToInstanceFillColorsDataTransform =
298
- createRgbToColorBufferDataTransform({
299
- bandDefs,
300
- attribute: 'instanceFillColors',
301
- });
302
-
303
- const usedSymbols = colorBands ? getUsedSymbols(colorBands) : [];
304
- const bandTransforms = RASTER_COLOR_BANDS.map((band) =>
305
- createExprDataTransform({
306
- colorBand: bandDefs[band],
307
- rasterMetadata,
308
- usedSymbols,
309
- })
310
- );
311
- const combinedDataTransform = combineDataTransforms([
312
- ...bandTransforms,
313
- rgbToInstanceFillColorsDataTransform,
314
- ]);
315
-
316
- return {
317
- dataTransform: combinedDataTransform as () => any,
318
- updateTriggers: getRasterTileLayerUpdateTriggers({
319
- layerConfig,
320
- visualChannels,
321
- }),
322
- };
323
- }
324
-
325
- function createBandColorScaleDataTransform({
326
- bandName,
327
- scaleFun,
328
- nodata,
329
- attribute,
330
- }: {
331
- bandName: string;
332
- scaleFun: (v: number) => number[];
333
- nodata: number | string | undefined;
334
- attribute: string;
335
- }) {
336
- const hasData = getHasDataPredicate(nodata);
337
-
338
- return createRasterColumnLayerDataTransform(
339
- (dataWrapped: RasterColumnLayerData) => {
340
- const length = dataWrapped.length;
341
- const bandBuffer = dataWrapped.data.cells.numericProps[bandName].value;
342
- const colorBuffer = new Uint8Array(length * 4);
343
-
344
- for (let i = 0; i < length; i++) {
345
- const rawValue = bandBuffer[i];
346
- if (!hasData(rawValue)) {
347
- // skip pixel
348
- bufferSetRgba(colorBuffer, i * 4, 0, 0, 0, 0);
349
- } else {
350
- const colorRgb = scaleFun(rawValue);
351
- bufferSetRgba(
352
- colorBuffer,
353
- i * 4,
354
- colorRgb[0],
355
- colorRgb[1],
356
- colorRgb[2],
357
- 255
358
- );
359
- }
360
- }
361
- return {
362
- ...dataWrapped,
363
- attributes: {
364
- [attribute]: colorBuffer,
365
- },
366
- };
367
- }
368
- );
369
- }
370
-
371
- export function domainFromRasterMetadataBand(
372
- band: RasterMetadataBand,
373
- scaleType: ScaleType,
374
- colorRange: ColorRange
375
- ) {
376
- if (scaleType === 'ordinal') {
377
- return colorRange.colorMap?.map(([value]) => value) || [];
378
- }
379
- if (scaleType === 'custom') {
380
- if (colorRange.uiCustomScaleType === 'logarithmic') {
381
- return getLog10ScaleSteps({
382
- min: band.stats.min,
383
- max: band.stats.max,
384
- steps: colorRange.colors.length,
385
- });
386
- } else {
387
- // actually custom, read colorMap
388
- return colorRange.colorMap?.map(([value]) => value) || [];
389
- }
390
- }
391
- const scaleLength = colorRange.colors.length;
392
- if (scaleType === 'quantile') {
393
- const quantiles = band.stats.quantiles?.[scaleLength];
394
- if (!quantiles) {
395
- return [0, 1];
396
- }
397
- return [band.stats.min, ...quantiles, band.stats.max];
398
- }
399
- return [band.stats.min, band.stats.max];
400
- }
401
-
402
- /**
403
- * Get RasterLayerStyle props for ColorRange and UniqueValues modes
404
- *
405
- * Effectively, applies selected color scale applied to one band.
406
- */
407
- export function getRasterTileLayerStylePropsScaledBand({
408
- layerConfig,
409
- rasterMetadata,
410
- visualChannels,
411
- }: {
412
- layerConfig: MapLayerConfig;
413
- visualChannels: VisualChannels;
414
- rasterMetadata: RasterMetadata;
415
- }) {
416
- const {visConfig} = layerConfig;
417
- const {colorField} = visualChannels;
418
- const {rasterStyleType} = visConfig;
419
-
420
- const colorRange =
421
- rasterStyleType === 'ColorRange'
422
- ? visConfig.colorRange
423
- : visConfig.uniqueValuesColorRange;
424
- const scaleType =
425
- rasterStyleType === 'ColorRange' ? visualChannels.colorScale : 'ordinal';
426
- const bandInfo = rasterMetadata.bands.find(
427
- (band) => band.name === colorField?.name
428
- );
429
-
430
- if (!colorField?.name || !scaleType || !colorRange || !bandInfo) {
431
- return {};
432
- }
433
-
434
- const domain = domainFromRasterMetadataBand(bandInfo, scaleType, colorRange);
435
-
436
- const scaleFun = createColorScale(
437
- scaleType,
438
- domain,
439
- colorRange.colors.map(hexToRGB),
440
- UNKNOWN_COLOR
441
- );
442
-
443
- const bandColorScaleDataTransform = createBandColorScaleDataTransform({
444
- bandName: bandInfo.name,
445
- scaleFun,
446
- nodata: bandInfo?.nodata ?? rasterMetadata.nodata,
447
- attribute: 'instanceFillColors',
448
- });
449
-
450
- return {
451
- dataTransform: bandColorScaleDataTransform as () => any,
452
- updateTriggers: getRasterTileLayerUpdateTriggers({
453
- layerConfig,
454
- visualChannels,
455
- }),
456
- };
457
- }
458
-
459
- export function getRasterTileLayerStyleProps({
460
- layerConfig,
461
- visualChannels,
462
- rasterMetadata,
463
- }: {
464
- layerConfig: MapLayerConfig;
465
- visualChannels: VisualChannels;
466
- rasterMetadata: RasterMetadata;
467
- }) {
468
- const {visConfig} = layerConfig;
469
- const {rasterStyleType} = visConfig;
470
-
471
- if (rasterStyleType === 'Rgb') {
472
- return getRasterTileLayerStylePropsRgb({
473
- layerConfig,
474
- rasterMetadata,
475
- visualChannels,
476
- });
477
- } else {
478
- return getRasterTileLayerStylePropsScaledBand({
479
- layerConfig,
480
- rasterMetadata,
481
- visualChannels,
482
- });
483
- }
484
- }
485
-
486
- export function getRasterTileLayerUpdateTriggers({
487
- layerConfig,
488
- visualChannels,
489
- }: {
490
- layerConfig: MapLayerConfig;
491
- visualChannels: VisualChannels;
492
- }) {
493
- const {visConfig} = layerConfig;
494
- const {rasterStyleType} = visConfig;
495
- const getFillColorUpdateTriggers: Record<string, unknown> = {
496
- rasterStyleType,
497
- };
498
- if (rasterStyleType === 'ColorRange') {
499
- getFillColorUpdateTriggers.colorRange = visConfig.colorRange?.colors;
500
- getFillColorUpdateTriggers.colorMap = visConfig.colorRange?.colorMap;
501
- getFillColorUpdateTriggers.colorScale = visualChannels.colorScale;
502
- getFillColorUpdateTriggers.colorFieldId = visualChannels.colorField?.name;
503
- } else if (rasterStyleType === 'UniqueValues') {
504
- getFillColorUpdateTriggers.colorMap =
505
- visConfig.uniqueValuesColorRange?.colorMap;
506
- getFillColorUpdateTriggers.colorFieldId = visualChannels.colorField?.name;
507
- } else if (rasterStyleType === 'Rgb') {
508
- getFillColorUpdateTriggers.colorBands = visConfig.colorBands;
509
- }
510
-
511
- return {
512
- getFillColor: getFillColorUpdateTriggers,
513
- };
514
- }
515
-
516
- function bufferSetRgba(
517
- target: VecExprVecLike,
518
- index: number,
519
- r: number,
520
- g: number,
521
- b: number,
522
- a: number
523
- ) {
524
- target[index + 0] = r;
525
- target[index + 1] = g;
526
- target[index + 2] = b;
527
- target[index + 3] = a;
528
- }
529
-
530
- function hexToRGB(hexColor: string): [number, number, number] {
531
- const r = parseInt(hexColor.slice(1, 3), 16);
532
- const g = parseInt(hexColor.slice(3, 5), 16);
533
- const b = parseInt(hexColor.slice(5, 7), 16);
534
-
535
- return [r, g, b];
536
- }