@deck.gl/carto 9.4.0-beta.1 → 9.4.0-beta.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.
@@ -1,498 +0,0 @@
1
- // deck.gl
2
- // SPDX-License-Identifier: MIT
3
- // Copyright (c) vis.gl contributors
4
-
5
- import {deviation, extent, groupSort, median, variance} from 'd3-array';
6
- import {rgb} from 'd3-color';
7
- import {
8
- scaleLinear,
9
- scaleOrdinal,
10
- scaleLog,
11
- scalePoint,
12
- scaleQuantile,
13
- scaleQuantize,
14
- scaleSqrt,
15
- scaleThreshold
16
- } from 'd3-scale';
17
- import {format as d3Format} from 'd3-format';
18
- import moment from 'moment-timezone';
19
-
20
- import {Accessor, Layer, _ConstructorOf as ConstructorOf} from '@deck.gl/core';
21
- import {GridLayer, HeatmapLayer, HexagonLayer} from '@deck.gl/aggregation-layers';
22
- import {GeoJsonLayer} from '@deck.gl/layers';
23
- import {H3HexagonLayer} from '@deck.gl/geo-layers';
24
-
25
- import ClusterTileLayer from '../layers/cluster-tile-layer';
26
- import H3TileLayer from '../layers/h3-tile-layer';
27
- import QuadbinTileLayer from '../layers/quadbin-tile-layer';
28
- import RasterTileLayer from '../layers/raster-tile-layer';
29
- import VectorTileLayer from '../layers/vector-tile-layer';
30
- import {assert, createBinaryProxy, scaleIdentity} from '../utils';
31
- import {
32
- CustomMarkersRange,
33
- MapDataset,
34
- MapTextSubLayerConfig,
35
- VisConfig,
36
- VisualChannelField,
37
- VisualChannels
38
- } from './types';
39
- import HeatmapTileLayer from '../layers/heatmap-tile-layer';
40
-
41
- const SCALE_FUNCS = {
42
- linear: scaleLinear,
43
- ordinal: scaleOrdinal,
44
- log: scaleLog,
45
- point: scalePoint,
46
- quantile: scaleQuantile,
47
- quantize: scaleQuantize,
48
- sqrt: scaleSqrt,
49
- custom: scaleThreshold,
50
- identity: scaleIdentity
51
- };
52
- export type SCALE_TYPE = keyof typeof SCALE_FUNCS;
53
-
54
- type TileLayerType =
55
- | 'clusterTile'
56
- | 'h3'
57
- | 'heatmapTile'
58
- | 'mvt'
59
- | 'quadbin'
60
- | 'raster'
61
- | 'tileset';
62
- type DocumentLayerType = 'geojson' | 'grid' | 'heatmap' | 'hexagon' | 'hexagonId' | 'point';
63
- type LayerType = TileLayerType | DocumentLayerType;
64
-
65
- function identity<T>(v: T): T {
66
- return v;
67
- }
68
-
69
- const UNKNOWN_COLOR = '#868d91';
70
-
71
- export const AGGREGATION = {
72
- average: 'MEAN',
73
- maximum: 'MAX',
74
- minimum: 'MIN',
75
- sum: 'SUM'
76
- };
77
-
78
- export const OPACITY_MAP = {
79
- getFillColor: 'opacity',
80
- getLineColor: 'strokeOpacity',
81
- getTextColor: 'opacity'
82
- };
83
-
84
- const AGGREGATION_FUNC = {
85
- 'count unique': (values, accessor) => groupSort(values, v => v.length, accessor).length,
86
- median,
87
- // Unfortunately mode() is only available in d3-array@3+ which is ESM only
88
- mode: (values, accessor) => groupSort(values, v => v.length, accessor).pop(),
89
- stddev: deviation,
90
- variance
91
- };
92
-
93
- const TILE_LAYER_TYPE_TO_LAYER: Record<TileLayerType, ConstructorOf<Layer>> = {
94
- clusterTile: ClusterTileLayer,
95
- h3: H3TileLayer,
96
- heatmapTile: HeatmapTileLayer,
97
- mvt: VectorTileLayer,
98
- quadbin: QuadbinTileLayer,
99
- raster: RasterTileLayer,
100
- tileset: VectorTileLayer
101
- };
102
-
103
- const hexToRGBA = c => {
104
- const {r, g, b, opacity} = rgb(c);
105
- return [r, g, b, 255 * opacity];
106
- };
107
-
108
- // Kepler prop value -> Deck.gl prop value
109
- // Supports nested definitions, and function transforms:
110
- // {keplerProp: 'deckProp'} is equivalent to:
111
- // {keplerProp: x => ({deckProp: x})}
112
- const sharedPropMap = {
113
- // Apply the value of Kepler `color` prop to the deck `getFillColor` prop
114
- color: 'getFillColor',
115
- isVisible: 'visible',
116
- label: 'cartoLabel',
117
- textLabel: {
118
- alignment: 'getTextAlignmentBaseline',
119
- anchor: 'getTextAnchor',
120
- // Apply the value of Kepler `textLabel.color` prop to the deck `getTextColor` prop
121
- color: 'getTextColor',
122
- size: 'getTextSize'
123
- },
124
- visConfig: {
125
- enable3d: 'extruded',
126
- elevationScale: 'elevationScale',
127
- filled: 'filled',
128
- strokeColor: 'getLineColor',
129
- stroked: 'stroked',
130
- thickness: 'getLineWidth',
131
- radius: 'getPointRadius',
132
- wireframe: 'wireframe'
133
- }
134
- };
135
-
136
- const customMarkersPropsMap = {
137
- color: 'getIconColor',
138
- visConfig: {
139
- radius: 'getIconSize'
140
- }
141
- };
142
-
143
- const heatmapTilePropsMap = {
144
- visConfig: {
145
- colorRange: x => ({colorRange: x.colors.map(hexToRGBA)}),
146
- radius: 'radiusPixels'
147
- }
148
- };
149
-
150
- const aggregationVisConfig = {
151
- colorAggregation: x => ({colorAggregation: AGGREGATION[x] || AGGREGATION.sum}),
152
- colorRange: x => ({colorRange: x.colors.map(hexToRGBA)}),
153
- coverage: 'coverage',
154
- elevationPercentile: ['elevationLowerPercentile', 'elevationUpperPercentile'],
155
- percentile: ['lowerPercentile', 'upperPercentile']
156
- };
157
-
158
- const defaultProps = {
159
- lineMiterLimit: 2,
160
- lineWidthUnits: 'pixels',
161
- pointRadiusUnits: 'pixels',
162
- rounded: true,
163
- wrapLongitude: false
164
- };
165
-
166
- function mergePropMaps(a: Record<string, any> = {}, b: Record<string, any> = {}) {
167
- return {...a, ...b, visConfig: {...a.visConfig, ...b.visConfig}};
168
- }
169
-
170
- export function getLayer(
171
- type: LayerType,
172
- config: MapTextSubLayerConfig,
173
- dataset: MapDataset
174
- ): {Layer: ConstructorOf<Layer>; propMap: any; defaultProps: any} {
175
- let basePropMap: any = sharedPropMap;
176
-
177
- if (config.visConfig?.customMarkers) {
178
- basePropMap = mergePropMaps(basePropMap, customMarkersPropsMap);
179
- }
180
- if (type === 'heatmapTile') {
181
- basePropMap = mergePropMaps(basePropMap, heatmapTilePropsMap);
182
- }
183
- if (TILE_LAYER_TYPE_TO_LAYER[type]) {
184
- return getTileLayer(dataset, basePropMap, type);
185
- }
186
-
187
- const geoColumn = dataset?.geoColumn;
188
- const getPosition = d => d[geoColumn].coordinates;
189
-
190
- const hexagonId = config.columns?.hex_id;
191
-
192
- const layerTypeDefs: Record<
193
- DocumentLayerType,
194
- {Layer: ConstructorOf<Layer>; propMap?: any; defaultProps?: any}
195
- > = {
196
- point: {
197
- Layer: GeoJsonLayer,
198
- propMap: {
199
- columns: {
200
- altitude: x => ({parameters: {depthWriteEnabled: Boolean(x)}})
201
- },
202
- visConfig: {outline: 'stroked'}
203
- }
204
- },
205
- geojson: {
206
- Layer: GeoJsonLayer
207
- },
208
- grid: {
209
- Layer: GridLayer,
210
- propMap: {visConfig: {...aggregationVisConfig, worldUnitSize: x => ({cellSize: 1000 * x})}},
211
- defaultProps: {getPosition}
212
- },
213
- heatmap: {
214
- Layer: HeatmapLayer,
215
- propMap: {visConfig: {...aggregationVisConfig, radius: 'radiusPixels'}},
216
- defaultProps: {getPosition}
217
- },
218
- hexagon: {
219
- Layer: HexagonLayer,
220
- propMap: {visConfig: {...aggregationVisConfig, worldUnitSize: x => ({radius: 1000 * x})}},
221
- defaultProps: {getPosition}
222
- },
223
- hexagonId: {
224
- Layer: H3HexagonLayer,
225
- propMap: {visConfig: {coverage: 'coverage'}},
226
- defaultProps: {getHexagon: d => d[hexagonId], stroked: false}
227
- }
228
- };
229
-
230
- const layer = layerTypeDefs[type];
231
-
232
- assert(layer, `Unsupported layer type: ${type}`);
233
- return {
234
- ...layer,
235
- propMap: mergePropMaps(basePropMap, layer.propMap),
236
- defaultProps: {...defaultProps, ...layer.defaultProps}
237
- };
238
- }
239
-
240
- function getTileLayer(dataset: MapDataset, basePropMap, type: LayerType) {
241
- const {aggregationExp, aggregationResLevel} = dataset;
242
-
243
- return {
244
- Layer: TILE_LAYER_TYPE_TO_LAYER[type] || VectorTileLayer,
245
- propMap: basePropMap,
246
- defaultProps: {
247
- ...defaultProps,
248
- ...(aggregationExp && {aggregationExp}),
249
- ...(aggregationResLevel && {aggregationResLevel}),
250
- uniqueIdProperty: 'geoid'
251
- }
252
- };
253
- }
254
-
255
- function domainFromAttribute(attribute, scaleType: SCALE_TYPE, scaleLength: number) {
256
- if (scaleType === 'ordinal' || scaleType === 'point') {
257
- return attribute.categories.map(c => c.category).filter(c => c !== undefined && c !== null);
258
- }
259
-
260
- if (scaleType === 'quantile' && attribute.quantiles) {
261
- return attribute.quantiles.global
262
- ? attribute.quantiles.global[scaleLength]
263
- : attribute.quantiles[scaleLength];
264
- }
265
-
266
- let {min} = attribute;
267
- if (scaleType === 'log' && min === 0) {
268
- min = 1e-5;
269
- }
270
- return [min, attribute.max];
271
- }
272
-
273
- function domainFromValues(values, scaleType: SCALE_TYPE) {
274
- if (scaleType === 'ordinal' || scaleType === 'point') {
275
- return groupSort(
276
- values,
277
- g => -g.length,
278
- d => d
279
- );
280
- } else if (scaleType === 'quantile') {
281
- return values.sort((a, b) => a - b);
282
- } else if (scaleType === 'log') {
283
- const [d0, d1] = extent(values as number[]);
284
- return [d0 === 0 ? 1e-5 : d0, d1];
285
- }
286
- return extent(values);
287
- }
288
-
289
- function calculateDomain(data, name, scaleType, scaleLength?) {
290
- if (data.tilestats) {
291
- // Tileset data type
292
- const {attributes} = data.tilestats.layers[0];
293
- const attribute = attributes.find(a => a.attribute === name);
294
- return domainFromAttribute(attribute, scaleType, scaleLength);
295
- } else if (data.features) {
296
- // GeoJSON data type
297
- const values = data.features.map(({properties}) => properties[name]);
298
- return domainFromValues(values, scaleType);
299
- } else if (Array.isArray(data) && data[0][name] !== undefined) {
300
- // JSON data type
301
- const values = data.map(properties => properties[name]);
302
- return domainFromValues(values, scaleType);
303
- }
304
-
305
- return [0, 1];
306
- }
307
-
308
- function normalizeAccessor(accessor, data) {
309
- if (data.features || data.tilestats) {
310
- return (object, info) => {
311
- if (object) {
312
- return accessor(object.properties || object.__source.object.properties);
313
- }
314
-
315
- const {data, index} = info;
316
- const proxy = createBinaryProxy(data, index);
317
- return accessor(proxy);
318
- };
319
- }
320
- return accessor;
321
- }
322
-
323
- export function opacityToAlpha(opacity?: number) {
324
- return opacity !== undefined ? Math.round(255 * Math.pow(opacity, 1 / 2.2)) : 255;
325
- }
326
-
327
- function getAccessorKeys(name: string, aggregation?: string | undefined): string[] {
328
- let keys = [name];
329
- if (aggregation) {
330
- // Snowflake will capitalized the keys, need to check lower and upper case version
331
- keys = keys.concat([aggregation, aggregation.toUpperCase()].map(a => `${name}_${a}`));
332
- }
333
- return keys;
334
- }
335
-
336
- function findAccessorKey(keys: string[], properties): string[] {
337
- for (const key of keys) {
338
- if (key in properties) {
339
- return [key];
340
- }
341
- }
342
-
343
- throw new Error(`Could not find property for any accessor key: ${keys}`);
344
- }
345
-
346
- export function getColorValueAccessor({name}, colorAggregation, data: any) {
347
- const aggregator = AGGREGATION_FUNC[colorAggregation];
348
- const accessor = values => aggregator(values, p => p[name]);
349
- return normalizeAccessor(accessor, data);
350
- }
351
-
352
- export function getColorAccessor(
353
- {name, colorColumn}: VisualChannelField,
354
- scaleType: SCALE_TYPE,
355
- {aggregation, range},
356
- opacity: number | undefined,
357
- data: any
358
- ) {
359
- const scale = calculateLayerScale(colorColumn || name, scaleType, range, data);
360
- const alpha = opacityToAlpha(opacity);
361
-
362
- let accessorKeys = getAccessorKeys(name, aggregation);
363
- const accessor = properties => {
364
- if (!(accessorKeys[0] in properties)) {
365
- accessorKeys = findAccessorKey(accessorKeys, properties);
366
- }
367
- const propertyValue = properties[accessorKeys[0]];
368
- const {r, g, b} = rgb(scale(propertyValue));
369
- return [r, g, b, propertyValue === null ? 0 : alpha];
370
- };
371
- return normalizeAccessor(accessor, data);
372
- }
373
-
374
- function calculateLayerScale(name, scaleType, range, data) {
375
- const scale = SCALE_FUNCS[scaleType]();
376
- let domain: (string | number)[] = [];
377
- let scaleColor: string[] = [];
378
-
379
- if (scaleType !== 'identity') {
380
- const {colorMap, colors} = range;
381
-
382
- if (Array.isArray(colorMap)) {
383
- colorMap.forEach(([value, color]) => {
384
- domain.push(value);
385
- scaleColor.push(color);
386
- });
387
- } else {
388
- domain = calculateDomain(data, name, scaleType, colors.length);
389
- scaleColor = colors;
390
- }
391
-
392
- if (scaleType === 'ordinal') {
393
- domain = domain.slice(0, scaleColor.length);
394
- }
395
- }
396
-
397
- scale.domain(domain);
398
- scale.range(scaleColor);
399
- scale.unknown(UNKNOWN_COLOR);
400
-
401
- return scale;
402
- }
403
-
404
- const FALLBACK_ICON =
405
- 'data:image/svg+xml;charset=utf-8;base64,PHN2ZyB2aWV3Qm94PSIwIDAgMTAwIDEwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4NCiAgPGNpcmNsZSBjeD0iNTAiIGN5PSI1MCIgcj0iNTAiLz4NCjwvc3ZnPg==';
406
-
407
- export function getIconUrlAccessor(
408
- field: VisualChannelField | null | undefined,
409
- range: CustomMarkersRange | null | undefined,
410
- {fallbackUrl, maxIconSize, useMaskedIcons},
411
- data: any
412
- ) {
413
- const urlToUnpackedIcon = (url: string) => ({
414
- id: `${url}@@${maxIconSize}`,
415
- url,
416
- width: maxIconSize,
417
- height: maxIconSize,
418
- mask: useMaskedIcons
419
- });
420
- let unknownValue = fallbackUrl || FALLBACK_ICON;
421
-
422
- if (range?.othersMarker) {
423
- unknownValue = range.othersMarker;
424
- }
425
-
426
- const unknownIcon = urlToUnpackedIcon(unknownValue);
427
- if (!range || !field) {
428
- return () => unknownIcon;
429
- }
430
-
431
- const mapping: Record<string, any> = {};
432
- for (const {value, markerUrl} of range.markerMap) {
433
- if (markerUrl) {
434
- mapping[value] = urlToUnpackedIcon(markerUrl);
435
- }
436
- }
437
-
438
- const accessor = properties => {
439
- const propertyValue = properties[field.name];
440
- return mapping[propertyValue] || unknownIcon;
441
- };
442
- return normalizeAccessor(accessor, data);
443
- }
444
-
445
- export function getMaxMarkerSize(visConfig: VisConfig, visualChannels: VisualChannels): number {
446
- const {radiusRange, radius} = visConfig;
447
- const {radiusField, sizeField} = visualChannels;
448
- const field = radiusField || sizeField;
449
- return Math.ceil(radiusRange && field ? radiusRange[1] : radius);
450
- }
451
-
452
- export function negateAccessor<T>(accessor: Accessor<T, number>): Accessor<T, number> {
453
- return typeof accessor === 'function' ? (d, i) => -accessor(d, i) : -accessor;
454
- }
455
-
456
- export function getSizeAccessor(
457
- {name},
458
- scaleType: SCALE_TYPE | undefined,
459
- aggregation,
460
- range: Iterable<Range> | undefined,
461
- data: any
462
- ) {
463
- const scale = scaleType ? SCALE_FUNCS[scaleType as any]() : identity;
464
- if (scaleType) {
465
- if (aggregation !== 'count') {
466
- scale.domain(calculateDomain(data, name, scaleType));
467
- }
468
- scale.range(range);
469
- }
470
-
471
- let accessorKeys = getAccessorKeys(name, aggregation);
472
- const accessor = properties => {
473
- if (!(accessorKeys[0] in properties)) {
474
- accessorKeys = findAccessorKey(accessorKeys, properties);
475
- }
476
- const propertyValue = properties[accessorKeys[0]];
477
- return scale(propertyValue);
478
- };
479
- return normalizeAccessor(accessor, data);
480
- }
481
-
482
- const FORMATS: Record<string, (value: any) => string> = {
483
- date: s => moment.utc(s).format('MM/DD/YY HH:mm:ssa'),
484
- integer: d3Format('i'),
485
- float: d3Format('.5f'),
486
- timestamp: s => moment.utc(s).format('X'),
487
- default: String
488
- };
489
-
490
- export function getTextAccessor({name, type}: VisualChannelField, data) {
491
- const format = FORMATS[type] || FORMATS.default;
492
- const accessor = properties => {
493
- return format(properties[name]);
494
- };
495
- return normalizeAccessor(accessor, data);
496
- }
497
-
498
- export {domainFromValues as _domainFromValues};