@carto/api-client 0.5.0-alpha.13 → 0.5.0-alpha.15

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