@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.
- package/CHANGELOG.md +30 -1
- package/build/api-client.cjs +9849 -2868
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +510 -171
- package/build/api-client.d.ts +510 -171
- package/build/api-client.js +9558 -2627
- package/build/api-client.js.map +1 -1
- package/build/worker.js +122 -469
- package/build/worker.js.map +1 -1
- package/package.json +36 -22
- package/src/api/query.ts +2 -1
- package/src/constants-internal.ts +10 -0
- package/src/constants.ts +5 -1
- package/src/fetch-map/basemap-styles.ts +159 -0
- package/src/fetch-map/basemap.ts +120 -0
- package/src/fetch-map/fetch-map.ts +331 -0
- package/src/fetch-map/index.ts +13 -0
- package/src/fetch-map/layer-map.ts +461 -0
- package/src/fetch-map/parse-map.ts +425 -0
- package/src/fetch-map/source.ts +233 -0
- package/src/fetch-map/types.ts +268 -0
- package/src/fetch-map/utils.ts +69 -0
- package/src/filters/tileFeatures.ts +26 -10
- package/src/filters/tileFeaturesRaster.ts +122 -0
- package/src/index.ts +1 -0
- package/src/models/model.ts +0 -7
- package/src/sources/base-source.ts +4 -2
- package/src/sources/h3-tileset-source.ts +1 -1
- package/src/sources/quadbin-tileset-source.ts +1 -1
- package/src/sources/raster-source.ts +18 -5
- package/src/sources/types.ts +14 -7
- package/src/sources/vector-tileset-source.ts +1 -1
- package/src/spatial-index.ts +3 -84
- package/src/types.ts +16 -2
- package/src/widget-sources/index.ts +1 -0
- package/src/widget-sources/types.ts +0 -2
- package/src/widget-sources/widget-raster-source.ts +14 -0
- package/src/widget-sources/widget-remote-source.ts +8 -76
- package/src/widget-sources/widget-source.ts +6 -24
- package/src/widget-sources/widget-tileset-source-impl.ts +13 -16
- package/src/widget-sources/widget-tileset-source.ts +20 -7
|
@@ -0,0 +1,425 @@
|
|
|
1
|
+
import type {ColorParameters} from '@luma.gl/core';
|
|
2
|
+
import {
|
|
3
|
+
AGGREGATION,
|
|
4
|
+
getLayerProps,
|
|
5
|
+
getColorAccessor,
|
|
6
|
+
getColorValueAccessor,
|
|
7
|
+
getSizeAccessor,
|
|
8
|
+
getTextAccessor,
|
|
9
|
+
OPACITY_MAP,
|
|
10
|
+
opacityToAlpha,
|
|
11
|
+
getIconUrlAccessor,
|
|
12
|
+
negateAccessor,
|
|
13
|
+
getMaxMarkerSize,
|
|
14
|
+
LayerType,
|
|
15
|
+
} from './layer-map.js';
|
|
16
|
+
|
|
17
|
+
import {assert, isEmptyObject} from '../utils.js';
|
|
18
|
+
import {Filters} from '../types.js';
|
|
19
|
+
import {
|
|
20
|
+
KeplerMapConfig,
|
|
21
|
+
MapLayerConfig,
|
|
22
|
+
VisualChannels,
|
|
23
|
+
VisConfig,
|
|
24
|
+
MapConfigLayer,
|
|
25
|
+
Dataset,
|
|
26
|
+
} from './types.js';
|
|
27
|
+
import {isRemoteCalculationSupported} from './utils.js';
|
|
28
|
+
|
|
29
|
+
export type LayerDescriptor = {
|
|
30
|
+
type: LayerType;
|
|
31
|
+
props: Record<string, any>;
|
|
32
|
+
filters?: Filters;
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
export type ParseMapResult = {
|
|
36
|
+
/** Map id. */
|
|
37
|
+
id: string;
|
|
38
|
+
|
|
39
|
+
/** Title of map. */
|
|
40
|
+
title: string;
|
|
41
|
+
|
|
42
|
+
/** Description of map. */
|
|
43
|
+
description?: string;
|
|
44
|
+
createdAt: string;
|
|
45
|
+
updatedAt: string;
|
|
46
|
+
initialViewState: any;
|
|
47
|
+
|
|
48
|
+
/** @deprecated Use `basemap`. */
|
|
49
|
+
mapStyle: any;
|
|
50
|
+
popupSettings: any;
|
|
51
|
+
token: string;
|
|
52
|
+
|
|
53
|
+
layers: LayerDescriptor[];
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
export function parseMap(json: any) {
|
|
57
|
+
const {keplerMapConfig, datasets, token} = json;
|
|
58
|
+
assert(keplerMapConfig.version === 'v1', 'Only support Kepler v1');
|
|
59
|
+
const config = keplerMapConfig.config as KeplerMapConfig;
|
|
60
|
+
const {filters, mapState, mapStyle, popupSettings} = config;
|
|
61
|
+
const {layers, layerBlending, interactionConfig} = config.visState;
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
id: json.id,
|
|
65
|
+
title: json.title,
|
|
66
|
+
description: json.description,
|
|
67
|
+
createdAt: json.createdAt,
|
|
68
|
+
updatedAt: json.updatedAt,
|
|
69
|
+
initialViewState: mapState,
|
|
70
|
+
/** @deprecated Use `basemap`. */
|
|
71
|
+
mapStyle,
|
|
72
|
+
popupSettings,
|
|
73
|
+
token,
|
|
74
|
+
layers: layers
|
|
75
|
+
.reverse()
|
|
76
|
+
.map(({id, type, config, visualChannels}: MapConfigLayer) => {
|
|
77
|
+
try {
|
|
78
|
+
const {dataId} = config;
|
|
79
|
+
const dataset: Dataset | null = datasets.find(
|
|
80
|
+
(d: any) => d.id === dataId
|
|
81
|
+
);
|
|
82
|
+
assert(dataset, `No dataset matching dataId: ${dataId}`);
|
|
83
|
+
const {data} = dataset;
|
|
84
|
+
assert(data, `No data loaded for dataId: ${dataId}`);
|
|
85
|
+
|
|
86
|
+
const {propMap, defaultProps} = getLayerProps(type, config, dataset);
|
|
87
|
+
|
|
88
|
+
const styleProps = createStyleProps(config, propMap);
|
|
89
|
+
|
|
90
|
+
const layer: LayerDescriptor = {
|
|
91
|
+
type,
|
|
92
|
+
filters:
|
|
93
|
+
isEmptyObject(filters) || isRemoteCalculationSupported(dataset)
|
|
94
|
+
? undefined
|
|
95
|
+
: filters[dataId],
|
|
96
|
+
props: {
|
|
97
|
+
id,
|
|
98
|
+
data,
|
|
99
|
+
...defaultProps,
|
|
100
|
+
...createInteractionProps(interactionConfig),
|
|
101
|
+
...styleProps,
|
|
102
|
+
...createChannelProps(id, type, config, visualChannels, data), // Must come after style
|
|
103
|
+
...createParametersProp(
|
|
104
|
+
layerBlending,
|
|
105
|
+
styleProps.parameters || {}
|
|
106
|
+
), // Must come after style
|
|
107
|
+
...createLoadOptions(token),
|
|
108
|
+
},
|
|
109
|
+
};
|
|
110
|
+
return layer;
|
|
111
|
+
} catch (e: any) {
|
|
112
|
+
console.error(e.message);
|
|
113
|
+
return undefined;
|
|
114
|
+
}
|
|
115
|
+
}),
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function createParametersProp(
|
|
120
|
+
layerBlending: string,
|
|
121
|
+
parameters: ColorParameters
|
|
122
|
+
) {
|
|
123
|
+
if (layerBlending === 'additive') {
|
|
124
|
+
parameters.blendColorSrcFactor = parameters.blendAlphaSrcFactor =
|
|
125
|
+
'src-alpha';
|
|
126
|
+
parameters.blendColorDstFactor = parameters.blendAlphaDstFactor =
|
|
127
|
+
'dst-alpha';
|
|
128
|
+
parameters.blendColorOperation = parameters.blendAlphaOperation = 'add';
|
|
129
|
+
} else if (layerBlending === 'subtractive') {
|
|
130
|
+
parameters.blendColorSrcFactor = 'one';
|
|
131
|
+
parameters.blendColorDstFactor = 'one-minus-dst-color';
|
|
132
|
+
parameters.blendAlphaSrcFactor = 'src-alpha';
|
|
133
|
+
parameters.blendAlphaDstFactor = 'dst-alpha';
|
|
134
|
+
parameters.blendColorOperation = 'subtract';
|
|
135
|
+
parameters.blendAlphaOperation = 'add';
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return Object.keys(parameters).length ? {parameters} : {};
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
function createInteractionProps(interactionConfig: any) {
|
|
142
|
+
const pickable = interactionConfig && interactionConfig.tooltip.enabled;
|
|
143
|
+
return {
|
|
144
|
+
autoHighlight: pickable,
|
|
145
|
+
pickable,
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
function mapProps(source: any, target: any, mapping: any) {
|
|
150
|
+
for (const sourceKey in mapping) {
|
|
151
|
+
const sourceValue = source[sourceKey];
|
|
152
|
+
const targetKey = mapping[sourceKey];
|
|
153
|
+
if (sourceValue === undefined) {
|
|
154
|
+
continue;
|
|
155
|
+
}
|
|
156
|
+
if (typeof targetKey === 'string') {
|
|
157
|
+
target[targetKey] = sourceValue;
|
|
158
|
+
} else if (typeof targetKey === 'function') {
|
|
159
|
+
const [key, value] = Object.entries(targetKey(sourceValue))[0];
|
|
160
|
+
target[key] = value;
|
|
161
|
+
} else if (typeof targetKey === 'object') {
|
|
162
|
+
// Nested definition, recurse down one level (also handles arrays)
|
|
163
|
+
mapProps(sourceValue, target, targetKey);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
function createStyleProps(config: MapLayerConfig, mapping: any) {
|
|
169
|
+
const result: Record<string, any> = {};
|
|
170
|
+
mapProps(config, result, mapping);
|
|
171
|
+
|
|
172
|
+
// Kepler format sometimes omits strokeColor. TODO: remove once we can rely on
|
|
173
|
+
// `strokeColor` always being set when `stroke: true`.
|
|
174
|
+
if (result.stroked && !result.getLineColor) {
|
|
175
|
+
result.getLineColor = result.getFillColor;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
for (const colorAccessor in OPACITY_MAP) {
|
|
179
|
+
if (Array.isArray(result[colorAccessor])) {
|
|
180
|
+
const color = [...result[colorAccessor]];
|
|
181
|
+
const opacityKey = OPACITY_MAP[colorAccessor];
|
|
182
|
+
const opacity = config.visConfig[opacityKey as keyof VisConfig];
|
|
183
|
+
color[3] = opacityToAlpha(opacity);
|
|
184
|
+
result[colorAccessor] = color;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
result.highlightColor = config.visConfig.enable3d
|
|
189
|
+
? [255, 255, 255, 60]
|
|
190
|
+
: [252, 242, 26, 255];
|
|
191
|
+
return result;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
function createChannelProps(
|
|
195
|
+
id: string,
|
|
196
|
+
type: string,
|
|
197
|
+
config: MapLayerConfig,
|
|
198
|
+
visualChannels: VisualChannels,
|
|
199
|
+
data: any
|
|
200
|
+
) {
|
|
201
|
+
const {
|
|
202
|
+
colorField,
|
|
203
|
+
colorScale,
|
|
204
|
+
radiusField,
|
|
205
|
+
radiusScale,
|
|
206
|
+
sizeField,
|
|
207
|
+
sizeScale,
|
|
208
|
+
strokeColorField,
|
|
209
|
+
strokeColorScale,
|
|
210
|
+
weightField,
|
|
211
|
+
} = visualChannels;
|
|
212
|
+
let {heightField, heightScale} = visualChannels;
|
|
213
|
+
if (type === 'hexagonId') {
|
|
214
|
+
heightField = sizeField;
|
|
215
|
+
heightScale = sizeScale;
|
|
216
|
+
}
|
|
217
|
+
const {textLabel, visConfig} = config;
|
|
218
|
+
const result: Record<string, any> = {};
|
|
219
|
+
|
|
220
|
+
if (type === 'grid' || type === 'hexagon') {
|
|
221
|
+
result.colorScaleType = colorScale;
|
|
222
|
+
if (colorField) {
|
|
223
|
+
const {colorAggregation} = config.visConfig;
|
|
224
|
+
if (!AGGREGATION[colorAggregation]) {
|
|
225
|
+
result.getColorValue = getColorValueAccessor(
|
|
226
|
+
colorField,
|
|
227
|
+
colorAggregation,
|
|
228
|
+
data
|
|
229
|
+
);
|
|
230
|
+
} else {
|
|
231
|
+
result.getColorWeight = (d: any) => d[colorField.name];
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
} else if (colorField) {
|
|
235
|
+
const {colorAggregation: aggregation, colorRange: range} = visConfig;
|
|
236
|
+
result.getFillColor = getColorAccessor(
|
|
237
|
+
colorField,
|
|
238
|
+
// @ts-ignore
|
|
239
|
+
colorScale,
|
|
240
|
+
{aggregation, range},
|
|
241
|
+
visConfig.opacity,
|
|
242
|
+
data
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (type === 'point') {
|
|
247
|
+
const altitude = config.columns?.altitude;
|
|
248
|
+
if (altitude) {
|
|
249
|
+
result.dataTransform = (data: any) => {
|
|
250
|
+
data.features.forEach(
|
|
251
|
+
({geometry, properties}: {geometry: any; properties: any}) => {
|
|
252
|
+
const {type, coordinates} = geometry;
|
|
253
|
+
if (type === 'Point') {
|
|
254
|
+
coordinates[2] = properties[altitude];
|
|
255
|
+
}
|
|
256
|
+
}
|
|
257
|
+
);
|
|
258
|
+
return data;
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
if (radiusField || sizeField) {
|
|
264
|
+
result.getPointRadius = getSizeAccessor(
|
|
265
|
+
// @ts-ignore
|
|
266
|
+
radiusField || sizeField,
|
|
267
|
+
// @ts-ignore
|
|
268
|
+
radiusScale || sizeScale,
|
|
269
|
+
visConfig.sizeAggregation,
|
|
270
|
+
visConfig.radiusRange || visConfig.sizeRange,
|
|
271
|
+
data
|
|
272
|
+
);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
if (strokeColorField) {
|
|
276
|
+
const fallbackOpacity = type === 'point' ? visConfig.opacity : 1;
|
|
277
|
+
const opacity =
|
|
278
|
+
visConfig.strokeOpacity !== undefined
|
|
279
|
+
? visConfig.strokeOpacity
|
|
280
|
+
: fallbackOpacity;
|
|
281
|
+
const {strokeColorAggregation: aggregation, strokeColorRange: range} =
|
|
282
|
+
visConfig;
|
|
283
|
+
result.getLineColor = getColorAccessor(
|
|
284
|
+
strokeColorField,
|
|
285
|
+
// @ts-ignore
|
|
286
|
+
strokeColorScale,
|
|
287
|
+
// @ts-ignore
|
|
288
|
+
{aggregation, range},
|
|
289
|
+
opacity,
|
|
290
|
+
data
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
if (heightField && visConfig.enable3d) {
|
|
294
|
+
result.getElevation = getSizeAccessor(
|
|
295
|
+
heightField,
|
|
296
|
+
// @ts-ignore
|
|
297
|
+
heightScale,
|
|
298
|
+
visConfig.heightAggregation,
|
|
299
|
+
visConfig.heightRange || visConfig.sizeRange,
|
|
300
|
+
data
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (weightField) {
|
|
305
|
+
result.getWeight = getSizeAccessor(
|
|
306
|
+
weightField,
|
|
307
|
+
undefined,
|
|
308
|
+
visConfig.weightAggregation,
|
|
309
|
+
undefined,
|
|
310
|
+
data
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
if (visConfig.customMarkers) {
|
|
315
|
+
const maxIconSize = getMaxMarkerSize(visConfig, visualChannels);
|
|
316
|
+
const {getPointRadius, getFillColor} = result;
|
|
317
|
+
const {
|
|
318
|
+
customMarkersUrl,
|
|
319
|
+
customMarkersRange,
|
|
320
|
+
filled: useMaskedIcons,
|
|
321
|
+
} = visConfig;
|
|
322
|
+
|
|
323
|
+
result.pointType = 'icon';
|
|
324
|
+
result.getIcon = getIconUrlAccessor(
|
|
325
|
+
visualChannels.customMarkersField,
|
|
326
|
+
customMarkersRange,
|
|
327
|
+
{fallbackUrl: customMarkersUrl, maxIconSize, useMaskedIcons},
|
|
328
|
+
data
|
|
329
|
+
);
|
|
330
|
+
result._subLayerProps = {
|
|
331
|
+
'points-icon': {
|
|
332
|
+
loadOptions: {
|
|
333
|
+
image: {
|
|
334
|
+
type: 'imagebitmap',
|
|
335
|
+
},
|
|
336
|
+
imagebitmap: {
|
|
337
|
+
resizeWidth: maxIconSize,
|
|
338
|
+
resizeHeight: maxIconSize,
|
|
339
|
+
resizeQuality: 'high',
|
|
340
|
+
},
|
|
341
|
+
},
|
|
342
|
+
},
|
|
343
|
+
};
|
|
344
|
+
|
|
345
|
+
if (getFillColor && useMaskedIcons) {
|
|
346
|
+
result.getIconColor = getFillColor;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
if (getPointRadius) {
|
|
350
|
+
result.getIconSize = getPointRadius;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
if (visualChannels.rotationField) {
|
|
354
|
+
result.getIconAngle = negateAccessor(
|
|
355
|
+
getSizeAccessor(
|
|
356
|
+
visualChannels.rotationField,
|
|
357
|
+
undefined,
|
|
358
|
+
null,
|
|
359
|
+
undefined,
|
|
360
|
+
data
|
|
361
|
+
)
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
} else if (type === 'point' || type === 'tileset') {
|
|
365
|
+
result.pointType = 'circle';
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
if (textLabel && textLabel.length && textLabel[0].field) {
|
|
369
|
+
const [mainLabel, secondaryLabel] = textLabel;
|
|
370
|
+
const collisionGroup = id;
|
|
371
|
+
|
|
372
|
+
({
|
|
373
|
+
alignment: result.getTextAlignmentBaseline,
|
|
374
|
+
anchor: result.getTextAnchor,
|
|
375
|
+
color: result.getTextColor,
|
|
376
|
+
outlineColor: result.textOutlineColor,
|
|
377
|
+
size: result.textSizeScale,
|
|
378
|
+
} = mainLabel);
|
|
379
|
+
const {
|
|
380
|
+
color: getSecondaryColor,
|
|
381
|
+
field: secondaryField,
|
|
382
|
+
outlineColor: secondaryOutlineColor,
|
|
383
|
+
size: secondarySizeScale,
|
|
384
|
+
} = secondaryLabel || {};
|
|
385
|
+
|
|
386
|
+
result.getText = mainLabel.field && getTextAccessor(mainLabel.field, data);
|
|
387
|
+
const getSecondaryText =
|
|
388
|
+
secondaryField && getTextAccessor(secondaryField, data);
|
|
389
|
+
|
|
390
|
+
result.pointType = `${result.pointType}+text`;
|
|
391
|
+
result.textCharacterSet = 'auto';
|
|
392
|
+
result.textFontFamily = 'Inter, sans';
|
|
393
|
+
result.textFontSettings = {sdf: true};
|
|
394
|
+
result.textFontWeight = 600;
|
|
395
|
+
result.textOutlineWidth = 3;
|
|
396
|
+
|
|
397
|
+
result._subLayerProps = {
|
|
398
|
+
...result._subLayerProps,
|
|
399
|
+
'points-text': {
|
|
400
|
+
collisionEnabled: true,
|
|
401
|
+
collisionGroup,
|
|
402
|
+
|
|
403
|
+
// getPointRadius already has radiusScale baked in, so only pass one or the other
|
|
404
|
+
...(result.getPointRadius
|
|
405
|
+
? {getRadius: result.getPointRadius}
|
|
406
|
+
: {radiusScale: visConfig.radius}),
|
|
407
|
+
|
|
408
|
+
...(secondaryField && {
|
|
409
|
+
getSecondaryText,
|
|
410
|
+
getSecondaryColor,
|
|
411
|
+
secondarySizeScale,
|
|
412
|
+
secondaryOutlineColor,
|
|
413
|
+
}),
|
|
414
|
+
},
|
|
415
|
+
};
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
return result;
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function createLoadOptions(accessToken: string) {
|
|
422
|
+
return {
|
|
423
|
+
loadOptions: {fetch: {headers: {Authorization: `Bearer ${accessToken}`}}},
|
|
424
|
+
};
|
|
425
|
+
}
|
|
@@ -0,0 +1,233 @@
|
|
|
1
|
+
import {Dataset} from './types.js';
|
|
2
|
+
import {SpatialIndex, SpatialIndexColumn} from '../constants.js';
|
|
3
|
+
import {
|
|
4
|
+
QuerySourceOptions,
|
|
5
|
+
TableSourceOptions,
|
|
6
|
+
TilejsonResult,
|
|
7
|
+
TileResolution,
|
|
8
|
+
} from '../sources/types.js';
|
|
9
|
+
import {
|
|
10
|
+
DEFAULT_AGGREGATION_EXP,
|
|
11
|
+
DEFAULT_AGGREGATION_RES_LEVEL_H3,
|
|
12
|
+
DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN,
|
|
13
|
+
DEFAULT_TILE_RESOLUTION,
|
|
14
|
+
REDUCED_QUERIES_TILE_RESOLUTION,
|
|
15
|
+
} from '../constants-internal.js';
|
|
16
|
+
import {
|
|
17
|
+
h3QuerySource,
|
|
18
|
+
H3QuerySourceOptions,
|
|
19
|
+
h3TableSource,
|
|
20
|
+
H3TableSourceOptions,
|
|
21
|
+
quadbinQuerySource,
|
|
22
|
+
QuadbinQuerySourceOptions,
|
|
23
|
+
quadbinTableSource,
|
|
24
|
+
QuadbinTableSourceOptions,
|
|
25
|
+
rasterSource,
|
|
26
|
+
vectorQuerySource,
|
|
27
|
+
VectorQuerySourceOptions,
|
|
28
|
+
vectorTableSource,
|
|
29
|
+
VectorTableSourceOptions,
|
|
30
|
+
vectorTilesetSource,
|
|
31
|
+
VectorTilesetSourceOptions,
|
|
32
|
+
} from '../sources/index.js';
|
|
33
|
+
import {Filter} from '../types.js';
|
|
34
|
+
|
|
35
|
+
type FetchDatasetOptions = {
|
|
36
|
+
accessToken: string;
|
|
37
|
+
apiBaseUrl: string;
|
|
38
|
+
connection: string;
|
|
39
|
+
headers?: Record<string, string>;
|
|
40
|
+
localCache?: {
|
|
41
|
+
cacheControl: 'no-cache'[];
|
|
42
|
+
};
|
|
43
|
+
maxLengthURL?: number;
|
|
44
|
+
};
|
|
45
|
+
|
|
46
|
+
type FetchDataset = {
|
|
47
|
+
dataset: Dataset;
|
|
48
|
+
filters?: Filter;
|
|
49
|
+
options: FetchDatasetOptions;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
// Copy of getCartoSource from cloud-native:
|
|
53
|
+
// https://github.com/CartoDB/cloud-native/blob/main/workspace-www/src/features/common/utils/cartoDeckGL.ts#L79
|
|
54
|
+
export function configureSource({
|
|
55
|
+
dataset,
|
|
56
|
+
filters,
|
|
57
|
+
options,
|
|
58
|
+
}: FetchDataset): Promise<TilejsonResult> {
|
|
59
|
+
const {
|
|
60
|
+
geoColumn,
|
|
61
|
+
columns,
|
|
62
|
+
type,
|
|
63
|
+
source,
|
|
64
|
+
queryParameters,
|
|
65
|
+
aggregationExp,
|
|
66
|
+
aggregationResLevel: originalAggregationResLevel,
|
|
67
|
+
} = dataset;
|
|
68
|
+
const sourceOptions = getSourceOptions(options);
|
|
69
|
+
const spatialDataColumn = getColumnNameFromGeoColumn(geoColumn) || undefined;
|
|
70
|
+
const spatialIndex = geoColumn
|
|
71
|
+
? getSpatialIndexFromGeoColumn(geoColumn)
|
|
72
|
+
: undefined;
|
|
73
|
+
const tileResolution = getDynamicTileResolution(spatialIndex);
|
|
74
|
+
const isH3 = spatialIndex === SpatialIndex.H3;
|
|
75
|
+
const isQuadbin = spatialIndex === SpatialIndex.QUADBIN;
|
|
76
|
+
let aggregationResLevel = originalAggregationResLevel;
|
|
77
|
+
|
|
78
|
+
if (typeof originalAggregationResLevel !== 'number' && isH3) {
|
|
79
|
+
aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_H3;
|
|
80
|
+
} else if (typeof originalAggregationResLevel !== 'number' && isQuadbin) {
|
|
81
|
+
aggregationResLevel = DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const spatialIndexOptions = {
|
|
85
|
+
aggregationExp: !aggregationExp ? DEFAULT_AGGREGATION_EXP : aggregationExp,
|
|
86
|
+
aggregationResLevel: scaleAggregationResLevel(
|
|
87
|
+
aggregationResLevel,
|
|
88
|
+
tileResolution
|
|
89
|
+
),
|
|
90
|
+
spatialDataColumn,
|
|
91
|
+
...(filters && {filters}),
|
|
92
|
+
} as H3QuerySourceOptions;
|
|
93
|
+
const tilesetOptions = {
|
|
94
|
+
...sourceOptions,
|
|
95
|
+
tableName: source,
|
|
96
|
+
} as VectorTilesetSourceOptions;
|
|
97
|
+
const tableOptions = {
|
|
98
|
+
...sourceOptions,
|
|
99
|
+
tableName: source,
|
|
100
|
+
tileResolution,
|
|
101
|
+
} as TableSourceOptions;
|
|
102
|
+
const queryOptions = {
|
|
103
|
+
...sourceOptions,
|
|
104
|
+
sqlQuery: source,
|
|
105
|
+
tileResolution,
|
|
106
|
+
...(queryParameters && {queryParameters}),
|
|
107
|
+
} as QuerySourceOptions;
|
|
108
|
+
const vectorOptions = {
|
|
109
|
+
spatialDataColumn,
|
|
110
|
+
...(columns && {columns}),
|
|
111
|
+
...(filters && {filters}),
|
|
112
|
+
...(aggregationExp && {aggregationExp}),
|
|
113
|
+
} as VectorTableSourceOptions;
|
|
114
|
+
|
|
115
|
+
if (type === 'raster') {
|
|
116
|
+
return rasterSource({
|
|
117
|
+
...sourceOptions,
|
|
118
|
+
tableName: source,
|
|
119
|
+
...(filters && {filters: filters as any}),
|
|
120
|
+
});
|
|
121
|
+
}
|
|
122
|
+
if (type === 'tileset') {
|
|
123
|
+
return vectorTilesetSource({...tilesetOptions});
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
if (type === 'table') {
|
|
127
|
+
if (isH3) {
|
|
128
|
+
return h3TableSource({
|
|
129
|
+
...(tableOptions as H3TableSourceOptions),
|
|
130
|
+
...spatialIndexOptions,
|
|
131
|
+
});
|
|
132
|
+
} else if (isQuadbin) {
|
|
133
|
+
return quadbinTableSource({
|
|
134
|
+
...(tableOptions as QuadbinTableSourceOptions),
|
|
135
|
+
...spatialIndexOptions,
|
|
136
|
+
});
|
|
137
|
+
} else {
|
|
138
|
+
return vectorTableSource({
|
|
139
|
+
...(tableOptions as VectorTableSourceOptions),
|
|
140
|
+
...vectorOptions,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
if (type === 'query') {
|
|
146
|
+
if (isH3) {
|
|
147
|
+
return h3QuerySource({
|
|
148
|
+
...(queryOptions as H3QuerySourceOptions),
|
|
149
|
+
...spatialIndexOptions,
|
|
150
|
+
});
|
|
151
|
+
} else if (isQuadbin) {
|
|
152
|
+
return quadbinQuerySource({
|
|
153
|
+
...(queryOptions as QuadbinQuerySourceOptions),
|
|
154
|
+
...spatialIndexOptions,
|
|
155
|
+
});
|
|
156
|
+
} else {
|
|
157
|
+
return vectorQuerySource({
|
|
158
|
+
...(queryOptions as VectorQuerySourceOptions),
|
|
159
|
+
...vectorOptions,
|
|
160
|
+
});
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
throw new Error(`Invalid source type: ${type}`);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function getSourceOptions({
|
|
167
|
+
accessToken,
|
|
168
|
+
apiBaseUrl,
|
|
169
|
+
connection,
|
|
170
|
+
headers,
|
|
171
|
+
maxLengthURL,
|
|
172
|
+
}: FetchDatasetOptions) {
|
|
173
|
+
return {
|
|
174
|
+
accessToken,
|
|
175
|
+
connectionName: connection,
|
|
176
|
+
apiBaseUrl,
|
|
177
|
+
headers,
|
|
178
|
+
maxLengthURL,
|
|
179
|
+
...(headers?.['Cache-Control']?.includes('no-cache') && {
|
|
180
|
+
localCache: {
|
|
181
|
+
cacheControl: ['no-cache'] as 'no-cache'[],
|
|
182
|
+
},
|
|
183
|
+
}),
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* Returns default tile resolution for dynamic tilesets, based on layer configuration
|
|
189
|
+
* Result is not applicable for static tilesets, for which tile resolution is defined
|
|
190
|
+
* by tilejson.
|
|
191
|
+
*/
|
|
192
|
+
function getDynamicTileResolution(
|
|
193
|
+
spatialIndex?: SpatialIndex | null
|
|
194
|
+
): TileResolution {
|
|
195
|
+
// TODO: Support increased tile size and resolution for dynamic H3 spatial indexes.
|
|
196
|
+
if (spatialIndex !== SpatialIndex.H3) {
|
|
197
|
+
return REDUCED_QUERIES_TILE_RESOLUTION;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
return DEFAULT_TILE_RESOLUTION;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* State of `aggregationResLevel` in the UI and backend config is based on an assumption of
|
|
205
|
+
* 512x512px tiles. Because we may change tile resolution for performance goals, the
|
|
206
|
+
* `aggregationResLevel` passed to the deck.gl layer must be scaled with tile resolution.
|
|
207
|
+
*/
|
|
208
|
+
function scaleAggregationResLevel(
|
|
209
|
+
aggregationResLevel: number,
|
|
210
|
+
tileResolution: number
|
|
211
|
+
): number | undefined {
|
|
212
|
+
if (typeof aggregationResLevel !== 'number') return;
|
|
213
|
+
return aggregationResLevel - Math.log2(0.5 / tileResolution);
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function getColumnNameFromGeoColumn(geoColumn: string | null | undefined) {
|
|
217
|
+
if (!geoColumn) {
|
|
218
|
+
return geoColumn;
|
|
219
|
+
}
|
|
220
|
+
const parts = geoColumn.split(':');
|
|
221
|
+
return parts.length === 1 ? parts[0] : parts.length === 2 ? parts[1] : null;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
function getSpatialIndexFromGeoColumn(geoColumn: string) {
|
|
225
|
+
const spatialIndexToSearch = geoColumn.split(':')[0];
|
|
226
|
+
|
|
227
|
+
for (const index of Object.values(SpatialIndex)) {
|
|
228
|
+
if (SpatialIndexColumn[index].includes(spatialIndexToSearch)) {
|
|
229
|
+
return index;
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
return null;
|
|
233
|
+
}
|