@carto/api-client 0.5.15 → 0.5.17
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 +11 -0
- package/build/api-client.cjs +1048 -154
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +184 -30
- package/build/api-client.d.ts +184 -30
- package/build/api-client.js +1034 -152
- package/build/api-client.js.map +1 -1
- package/build/worker-compat.js +1395 -1276
- package/build/worker-compat.js.map +1 -1
- package/build/worker.js +34 -0
- package/build/worker.js.map +1 -1
- package/package.json +3 -2
- package/src/fetch-map/basemap-styles.ts +1 -1
- package/src/fetch-map/index.ts +6 -1
- package/src/fetch-map/layer-map.ts +130 -40
- package/src/fetch-map/parse-map.ts +284 -165
- package/src/fetch-map/raster-layer.ts +536 -0
- package/src/fetch-map/types.ts +21 -7
- package/src/fetch-map/utils.ts +56 -0
- package/src/fetch-map/vec-expr-evaluator.ts +374 -0
- package/src/index.ts +7 -1
- package/src/models/model.ts +1 -0
- package/src/sources/types.ts +52 -11
- package/src/widget-sources/types.ts +25 -0
- package/src/widget-sources/widget-remote-source.ts +30 -0
- package/src/widget-sources/widget-source.ts +11 -0
- package/src/widget-sources/widget-tileset-source-impl.ts +49 -0
- package/src/widget-sources/widget-tileset-source.ts +13 -0
- package/src/workers/constants.ts +1 -0
|
@@ -0,0 +1,374 @@
|
|
|
1
|
+
import jsep from 'jsep';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Create vector expresion evaluator.
|
|
5
|
+
*
|
|
6
|
+
* Used to calculate vector expressions, such as `(band_1 * 3) + band_2/2`,
|
|
7
|
+
* where `band_1` and `band_2` are arrays or typed arrays.
|
|
8
|
+
*
|
|
9
|
+
* Note that all vector operations are element-wise, in paricular `band_1 * band_2`
|
|
10
|
+
* is not "mathematical" dot or cross product, but just element-wise multiplication.
|
|
11
|
+
*
|
|
12
|
+
* Based on:
|
|
13
|
+
* - Copyright (c) 2013 Stephen Oney, http://jsep.from.so/, MIT License
|
|
14
|
+
* - Copyright (c) 2023 Don McCurdy, https://github.com/donmccurdy/expression-eval, MIT License
|
|
15
|
+
*/
|
|
16
|
+
export function createVecExprEvaluator(
|
|
17
|
+
expression: string | jsep.Expression
|
|
18
|
+
): VecExprEvaluator | null {
|
|
19
|
+
try {
|
|
20
|
+
const parsed = compile(expression);
|
|
21
|
+
const evalFun = (context: Record<string, VecExprResult>) =>
|
|
22
|
+
evaluate(parsed, context);
|
|
23
|
+
evalFun.symbols = getSymbols(parsed);
|
|
24
|
+
return evalFun as VecExprEvaluator;
|
|
25
|
+
} catch {
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function evaluateVecExpr(
|
|
31
|
+
expression: string | jsep.Expression,
|
|
32
|
+
context: Record<string, VecExprResult>
|
|
33
|
+
) {
|
|
34
|
+
try {
|
|
35
|
+
return createVecExprEvaluator(expression)?.(context);
|
|
36
|
+
} catch {
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export enum ErrorCode {
|
|
42
|
+
InvalidSyntax,
|
|
43
|
+
UnknownIdentifier,
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export type ValidationResult = {
|
|
47
|
+
valid: boolean;
|
|
48
|
+
errorCode?: ErrorCode;
|
|
49
|
+
errorMessage?: string;
|
|
50
|
+
};
|
|
51
|
+
|
|
52
|
+
export function validateVecExprSyntax(
|
|
53
|
+
expression: string | jsep.Expression,
|
|
54
|
+
context: Record<string, unknown>
|
|
55
|
+
): ValidationResult {
|
|
56
|
+
let parsed: jsep.Expression;
|
|
57
|
+
try {
|
|
58
|
+
parsed = compile(expression);
|
|
59
|
+
} catch (e: any) {
|
|
60
|
+
return {
|
|
61
|
+
valid: false,
|
|
62
|
+
errorCode: ErrorCode.InvalidSyntax,
|
|
63
|
+
errorMessage: e && 'message' in e ? String(e.message) : String(e),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
return validate(parsed, context);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export type VecExprVecLike =
|
|
70
|
+
| number[]
|
|
71
|
+
| Float32Array
|
|
72
|
+
| Float64Array
|
|
73
|
+
| Uint8Array
|
|
74
|
+
| Int8Array
|
|
75
|
+
| Int32Array
|
|
76
|
+
| Uint32Array
|
|
77
|
+
| Uint16Array
|
|
78
|
+
| Int16Array;
|
|
79
|
+
|
|
80
|
+
export type VecExprResult = number | VecExprVecLike;
|
|
81
|
+
|
|
82
|
+
export type VecExprEvaluator = {
|
|
83
|
+
(context: object): VecExprResult;
|
|
84
|
+
|
|
85
|
+
symbols?: string[];
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
function createResultArray(
|
|
89
|
+
typeTemplate: VecExprVecLike,
|
|
90
|
+
length: number = typeTemplate.length
|
|
91
|
+
): number[] {
|
|
92
|
+
return new Array(length);
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
function isVecLike(a: unknown): a is number[] {
|
|
96
|
+
return Array.isArray(a) || ArrayBuffer.isView(a);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
const createBinopVec =
|
|
100
|
+
(scalarBinOp: (a: number, b: number) => number) =>
|
|
101
|
+
(left: number[], right: number[]) => {
|
|
102
|
+
const length = Math.min(left.length, right.length);
|
|
103
|
+
const r = createResultArray(left, length);
|
|
104
|
+
for (let i = 0; i < length; i++) {
|
|
105
|
+
r[i] = scalarBinOp(left[i], right[i]);
|
|
106
|
+
}
|
|
107
|
+
return r;
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const createBinopVecNum =
|
|
111
|
+
(scalarBinOp: (a: number, b: number) => number) =>
|
|
112
|
+
(left: number[], right: number) => {
|
|
113
|
+
const length = left.length;
|
|
114
|
+
const r = createResultArray(left, length);
|
|
115
|
+
for (let i = 0; i < length; i++) {
|
|
116
|
+
r[i] = scalarBinOp(left[i], right);
|
|
117
|
+
}
|
|
118
|
+
return r;
|
|
119
|
+
};
|
|
120
|
+
|
|
121
|
+
// number vec op
|
|
122
|
+
const createBinopNumVec =
|
|
123
|
+
(scalarBinOp: (a: number, b: number) => number) =>
|
|
124
|
+
(left: number, right: number[]) => {
|
|
125
|
+
const length = right.length;
|
|
126
|
+
const r = createResultArray(right, length);
|
|
127
|
+
for (let i = 0; i < length; i++) {
|
|
128
|
+
r[i] = scalarBinOp(left, right[i]);
|
|
129
|
+
}
|
|
130
|
+
return r;
|
|
131
|
+
};
|
|
132
|
+
|
|
133
|
+
const createUnopVec = (scalarUnop: (a: number) => number) => (a: number[]) => {
|
|
134
|
+
const length = a.length;
|
|
135
|
+
const r = createResultArray(a, length);
|
|
136
|
+
for (let i = 0; i < length; i++) {
|
|
137
|
+
r[i] = scalarUnop(a[i]);
|
|
138
|
+
}
|
|
139
|
+
return r;
|
|
140
|
+
};
|
|
141
|
+
|
|
142
|
+
function mapDictValues<V, NewV>(dict: Record<string, V>, fun: (v: V) => NewV) {
|
|
143
|
+
return Object.keys(dict).reduce(
|
|
144
|
+
(acc, key) => {
|
|
145
|
+
acc[key] = fun(dict[key]);
|
|
146
|
+
return acc;
|
|
147
|
+
},
|
|
148
|
+
{} as Record<string, NewV>
|
|
149
|
+
);
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
const binopsNum: Record<string, (a: number, b: number) => number> = {
|
|
153
|
+
'||': (a: number, b: number) => a || b,
|
|
154
|
+
'&&': (a: number, b: number) => a && b,
|
|
155
|
+
'|': (a: number, b: number) => a | b,
|
|
156
|
+
'^': (a: number, b: number) => a ^ b,
|
|
157
|
+
'&': (a: number, b: number) => a & b,
|
|
158
|
+
'==': (a: number, b: number) => Number(a == b),
|
|
159
|
+
'!=': (a: number, b: number) => Number(a != b),
|
|
160
|
+
'===': (a: number, b: number) => Number(a === b),
|
|
161
|
+
'!==': (a: number, b: number) => Number(a !== b),
|
|
162
|
+
'<': (a: number, b: number) => Number(a < b),
|
|
163
|
+
'>': (a: number, b: number) => Number(a > b),
|
|
164
|
+
'<=': (a: number, b: number) => Number(a <= b),
|
|
165
|
+
'>=': (a: number, b: number) => Number(a >= b),
|
|
166
|
+
'<<': (a: number, b: number) => a << b,
|
|
167
|
+
'>>': (a: number, b: number) => a >> b,
|
|
168
|
+
'>>>': (a: number, b: number) => a >>> b,
|
|
169
|
+
'+': (a: number, b: number) => a + b,
|
|
170
|
+
'-': (a: number, b: number) => a - b,
|
|
171
|
+
'*': (a: number, b: number) => a * b,
|
|
172
|
+
'/': (a: number, b: number) => a / b,
|
|
173
|
+
'%': (a: number, b: number) => a % b,
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
const unopsNum: Record<string, (a: number) => number> = {
|
|
177
|
+
'-': (a: number) => -a,
|
|
178
|
+
'+': (a: number) => +a,
|
|
179
|
+
'~': (a: number) => ~a,
|
|
180
|
+
'!': (a: number) => Number(!a),
|
|
181
|
+
};
|
|
182
|
+
|
|
183
|
+
const binopsVector = mapDictValues(binopsNum, createBinopVec) as Record<
|
|
184
|
+
string,
|
|
185
|
+
Binop
|
|
186
|
+
>;
|
|
187
|
+
const binopsNumVec = mapDictValues(binopsNum, createBinopNumVec) as Record<
|
|
188
|
+
string,
|
|
189
|
+
Binop
|
|
190
|
+
>;
|
|
191
|
+
const binopsVecNum = mapDictValues(binopsNum, createBinopVecNum) as Record<
|
|
192
|
+
string,
|
|
193
|
+
Binop
|
|
194
|
+
>;
|
|
195
|
+
|
|
196
|
+
const unopsVector = mapDictValues(unopsNum, createUnopVec) as Record<
|
|
197
|
+
string,
|
|
198
|
+
UnOp
|
|
199
|
+
>;
|
|
200
|
+
|
|
201
|
+
type UnOp = (a: VecExprResult) => VecExprResult;
|
|
202
|
+
type Binop = (a: VecExprResult, b: VecExprResult) => VecExprResult;
|
|
203
|
+
|
|
204
|
+
function getBinop(
|
|
205
|
+
operator: string,
|
|
206
|
+
left: VecExprResult,
|
|
207
|
+
right: VecExprResult
|
|
208
|
+
): Binop {
|
|
209
|
+
const isLeftVec = isVecLike(left);
|
|
210
|
+
const isRightVec = isVecLike(right);
|
|
211
|
+
if (isLeftVec && isRightVec) {
|
|
212
|
+
return binopsVector[operator];
|
|
213
|
+
} else if (isLeftVec) {
|
|
214
|
+
return binopsVecNum[operator];
|
|
215
|
+
} else if (isRightVec) {
|
|
216
|
+
return binopsNumVec[operator];
|
|
217
|
+
} else {
|
|
218
|
+
return binopsNum[operator] as Binop;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
type AnyExpression =
|
|
223
|
+
| jsep.ArrayExpression
|
|
224
|
+
| jsep.BinaryExpression
|
|
225
|
+
| jsep.MemberExpression
|
|
226
|
+
| jsep.CallExpression
|
|
227
|
+
| jsep.ConditionalExpression
|
|
228
|
+
| jsep.Identifier
|
|
229
|
+
| jsep.Literal
|
|
230
|
+
| jsep.ThisExpression
|
|
231
|
+
| jsep.UnaryExpression;
|
|
232
|
+
|
|
233
|
+
export function evaluate(
|
|
234
|
+
_node: jsep.Expression,
|
|
235
|
+
context: Record<string, VecExprResult>
|
|
236
|
+
): VecExprResult {
|
|
237
|
+
const node = _node as AnyExpression;
|
|
238
|
+
|
|
239
|
+
switch (node.type) {
|
|
240
|
+
case 'BinaryExpression': {
|
|
241
|
+
const left = evaluate(node.left, context);
|
|
242
|
+
const right = evaluate(node.right, context);
|
|
243
|
+
const binopFun = getBinop(node.operator, left, right);
|
|
244
|
+
|
|
245
|
+
return binopFun(left, right);
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
case 'ConditionalExpression': {
|
|
249
|
+
const val = evaluate(node.test, context);
|
|
250
|
+
if (isVecLike(val)) {
|
|
251
|
+
const length = val.length;
|
|
252
|
+
const consequentVal = evaluate(node.consequent, context);
|
|
253
|
+
const alternateVal = evaluate(node.alternate, context);
|
|
254
|
+
const r = createResultArray(val);
|
|
255
|
+
for (let i = 0; i < length; i++) {
|
|
256
|
+
const entryVal = val[i] ? consequentVal : alternateVal;
|
|
257
|
+
r[i] = isVecLike(entryVal)
|
|
258
|
+
? (entryVal[i] ?? NaN)
|
|
259
|
+
: (entryVal as number);
|
|
260
|
+
}
|
|
261
|
+
return r;
|
|
262
|
+
} else {
|
|
263
|
+
return val
|
|
264
|
+
? evaluate(node.consequent, context)
|
|
265
|
+
: evaluate(node.alternate, context);
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
case 'Identifier':
|
|
270
|
+
return context[node.name];
|
|
271
|
+
|
|
272
|
+
case 'Literal':
|
|
273
|
+
return node.value as number;
|
|
274
|
+
|
|
275
|
+
case 'UnaryExpression': {
|
|
276
|
+
const val = evaluate(node.argument, context);
|
|
277
|
+
const unopFun = isVecLike(val)
|
|
278
|
+
? unopsVector[node.operator]
|
|
279
|
+
: (unopsNum[node.operator] as UnOp);
|
|
280
|
+
return unopFun(val);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
default:
|
|
284
|
+
return undefined as unknown as VecExprResult;
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const validResult = {valid: true};
|
|
289
|
+
|
|
290
|
+
function visit(_node: jsep.Expression, visitor: (node: AnyExpression) => void) {
|
|
291
|
+
const node = _node as AnyExpression;
|
|
292
|
+
|
|
293
|
+
visitor(node);
|
|
294
|
+
switch (node.type) {
|
|
295
|
+
case 'BinaryExpression': {
|
|
296
|
+
visit(node.left, visitor);
|
|
297
|
+
visit(node.right, visitor);
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case 'ConditionalExpression': {
|
|
302
|
+
visit(node.test, visitor);
|
|
303
|
+
visit(node.consequent, visitor);
|
|
304
|
+
visit(node.alternate, visitor);
|
|
305
|
+
break;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
case 'UnaryExpression': {
|
|
309
|
+
visit(node.argument, visitor);
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
const supportedExpressionTypes = [
|
|
316
|
+
'BinaryExpression',
|
|
317
|
+
'UnaryExpression',
|
|
318
|
+
'ConditionalExpression',
|
|
319
|
+
'LogicalExpression',
|
|
320
|
+
'Identifier',
|
|
321
|
+
'Literal',
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
function validate(_node: jsep.Expression, context: object): ValidationResult {
|
|
325
|
+
const node = _node as AnyExpression;
|
|
326
|
+
|
|
327
|
+
const errors: ValidationResult[] = [];
|
|
328
|
+
|
|
329
|
+
visit(node, (node) => {
|
|
330
|
+
if (!supportedExpressionTypes.includes(node.type)) {
|
|
331
|
+
errors.push({
|
|
332
|
+
valid: false,
|
|
333
|
+
errorCode: ErrorCode.InvalidSyntax,
|
|
334
|
+
errorMessage: `Not allowed`,
|
|
335
|
+
});
|
|
336
|
+
return;
|
|
337
|
+
}
|
|
338
|
+
if (node.type === 'Identifier') {
|
|
339
|
+
if (!Object.prototype.hasOwnProperty.call(context, node.name)) {
|
|
340
|
+
return errors.push({
|
|
341
|
+
valid: false,
|
|
342
|
+
errorCode: ErrorCode.UnknownIdentifier,
|
|
343
|
+
errorMessage: `"${node.name}" not found`,
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
if (node.type === 'Literal') {
|
|
348
|
+
// we actually support only numbers
|
|
349
|
+
if (typeof node.value !== 'number') {
|
|
350
|
+
return errors.push({
|
|
351
|
+
valid: false,
|
|
352
|
+
errorCode: ErrorCode.InvalidSyntax,
|
|
353
|
+
errorMessage: `Only number literals are supported`,
|
|
354
|
+
});
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
});
|
|
358
|
+
return errors.length ? errors[0] : validResult;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
function getSymbols(node: jsep.Expression): string[] {
|
|
362
|
+
const symbols = new Set<string>();
|
|
363
|
+
|
|
364
|
+
visit(node, (node) => {
|
|
365
|
+
if (node.type === 'Identifier') {
|
|
366
|
+
symbols.add(node.name);
|
|
367
|
+
}
|
|
368
|
+
});
|
|
369
|
+
return Array.from(symbols);
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
export function compile(expression: string | jsep.Expression) {
|
|
373
|
+
return jsep(expression);
|
|
374
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,13 @@ export * from './client.js';
|
|
|
2
2
|
export * from './constants.js';
|
|
3
3
|
export * from './deck/index.js';
|
|
4
4
|
export * from './fetch-map/index.js';
|
|
5
|
-
export
|
|
5
|
+
export {
|
|
6
|
+
createVecExprEvaluator as _createVecExprEvaluator,
|
|
7
|
+
evaluateVecExpr as _evaluateVecExpr,
|
|
8
|
+
validateVecExprSyntax as _validateVecExprSyntax,
|
|
9
|
+
type VecExprResult as _VecExprResult,
|
|
10
|
+
ErrorCode as _ErrorCode,
|
|
11
|
+
} from './fetch-map/vec-expr-evaluator.js';
|
|
6
12
|
export * from './filters.js';
|
|
7
13
|
export * from './geo.js';
|
|
8
14
|
export * from './sources/index.js';
|
package/src/models/model.ts
CHANGED
package/src/sources/types.ts
CHANGED
|
@@ -292,14 +292,64 @@ export interface Tilestats {
|
|
|
292
292
|
|
|
293
293
|
export interface Layer {
|
|
294
294
|
layer: string;
|
|
295
|
+
/** Number of features in the layer. */
|
|
295
296
|
count: number;
|
|
297
|
+
|
|
298
|
+
/** Number of attributes in the layer. */
|
|
296
299
|
attributeCount: number;
|
|
297
300
|
attributes: Attribute[];
|
|
301
|
+
|
|
302
|
+
/** Type of geometry as in geojson geometry type (Point, LineString, Polygon, etc.) */
|
|
303
|
+
geometry?: string;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
export interface AttributeCategoryItem {
|
|
307
|
+
category: string;
|
|
308
|
+
frequency: number;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
/**
|
|
312
|
+
* Quantiles by number of buckets.
|
|
313
|
+
*
|
|
314
|
+
* Example:
|
|
315
|
+
* ```ts
|
|
316
|
+
* {
|
|
317
|
+
* // for 3 buckets, first 1/3 of items lies in range [min, 20], second 1/3 is in [20, 40], and last 1/3 is in [40, max]
|
|
318
|
+
* 3: [20, 40],
|
|
319
|
+
* 4: [20, 30, 50], for 4 buckets ...
|
|
320
|
+
* }
|
|
321
|
+
* ```
|
|
322
|
+
*/
|
|
323
|
+
export interface QuantileStats {
|
|
324
|
+
[bucketCount: number]: number[];
|
|
298
325
|
}
|
|
299
326
|
|
|
300
327
|
export interface Attribute {
|
|
301
|
-
|
|
328
|
+
/**
|
|
329
|
+
* String, Number, Timestamp, Boolean
|
|
330
|
+
*/
|
|
302
331
|
type: string;
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Attribute name.
|
|
335
|
+
*/
|
|
336
|
+
attribute: string;
|
|
337
|
+
|
|
338
|
+
// Stats for numeric attributes
|
|
339
|
+
min?: number;
|
|
340
|
+
max?: number;
|
|
341
|
+
sum?: number;
|
|
342
|
+
|
|
343
|
+
/** Quantiles by number of buckets */
|
|
344
|
+
quantiles?:
|
|
345
|
+
| {
|
|
346
|
+
// Quantile stats for numeric attributes in static spatial index tilesets are enclosed in extra global object
|
|
347
|
+
global: QuantileStats;
|
|
348
|
+
}
|
|
349
|
+
| QuantileStats;
|
|
350
|
+
|
|
351
|
+
// Stats for string/boolean attributes
|
|
352
|
+
categories?: AttributeCategoryItem[];
|
|
303
353
|
}
|
|
304
354
|
|
|
305
355
|
export interface VectorLayer {
|
|
@@ -325,17 +375,8 @@ export type RasterMetadataBandStats = {
|
|
|
325
375
|
|
|
326
376
|
/**
|
|
327
377
|
* Quantiles by number of buckets.
|
|
328
|
-
*
|
|
329
|
-
* Example:
|
|
330
|
-
* ```ts
|
|
331
|
-
* {
|
|
332
|
-
* // for 3 buckets, first 1/3 of items lies in range [min, 20], second 1/3 is in [20, 40], and last 1/3 is in [40, max]
|
|
333
|
-
* 3: [20, 40],
|
|
334
|
-
* 4: [20, 30, 50], for 4 buckets ...
|
|
335
|
-
* }
|
|
336
|
-
* ```
|
|
337
378
|
*/
|
|
338
|
-
quantiles?:
|
|
379
|
+
quantiles?: QuantileStats;
|
|
339
380
|
|
|
340
381
|
/**
|
|
341
382
|
* Top values by number of values.
|
|
@@ -207,6 +207,26 @@ export interface TimeSeriesRequestOptions extends BaseRequestOptions {
|
|
|
207
207
|
splitByCategoryValues?: string[];
|
|
208
208
|
}
|
|
209
209
|
|
|
210
|
+
/**
|
|
211
|
+
* Examples:
|
|
212
|
+
* * aggregations with array syntax
|
|
213
|
+
* * aggregations: [{column: 'pop_high', operation: 'sum', alias: 'high_pop'}, {column: 'pop_low', operation: 'avg'}]
|
|
214
|
+
* * aggregations with string syntax
|
|
215
|
+
* * aggregations: 'sum(pop_high) as high_pop, avg(pop_low) as avg_low'
|
|
216
|
+
*
|
|
217
|
+
* Options for {@link WidgetRemoteSource#getAggregations}.
|
|
218
|
+
*/
|
|
219
|
+
export interface AggregationsRequestOptions extends BaseRequestOptions {
|
|
220
|
+
/** Aggregations to compute. Can be an array of objects or a SQL string expression. */
|
|
221
|
+
aggregations:
|
|
222
|
+
| {
|
|
223
|
+
column: string;
|
|
224
|
+
operation: Exclude<AggregationType, 'custom'>;
|
|
225
|
+
alias: string;
|
|
226
|
+
}[]
|
|
227
|
+
| string;
|
|
228
|
+
}
|
|
229
|
+
|
|
210
230
|
/** @experimental */
|
|
211
231
|
export type ExtentRequestOptions = BaseRequestOptions;
|
|
212
232
|
|
|
@@ -264,5 +284,10 @@ export type TimeSeriesResponse = {
|
|
|
264
284
|
/** Response from {@link WidgetRemoteSource#getHistogram}. */
|
|
265
285
|
export type HistogramResponse = number[];
|
|
266
286
|
|
|
287
|
+
/** Response from {@link WidgetRemoteSource#getAggregations}. */
|
|
288
|
+
export type AggregationsResponse = {
|
|
289
|
+
rows: Record<string, number | string | null>[];
|
|
290
|
+
};
|
|
291
|
+
|
|
267
292
|
/** @experimental */
|
|
268
293
|
export type ExtentResponse = {bbox: BBox};
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import {executeModel, type ModelSource} from '../models/index.js';
|
|
2
2
|
import type {
|
|
3
|
+
AggregationsRequestOptions,
|
|
4
|
+
AggregationsResponse,
|
|
3
5
|
CategoryRequestOptions,
|
|
4
6
|
CategoryResponse,
|
|
5
7
|
ExtentRequestOptions,
|
|
@@ -394,6 +396,34 @@ export abstract class WidgetRemoteSource<
|
|
|
394
396
|
}));
|
|
395
397
|
}
|
|
396
398
|
|
|
399
|
+
async getAggregations(
|
|
400
|
+
options: AggregationsRequestOptions
|
|
401
|
+
): Promise<AggregationsResponse> {
|
|
402
|
+
const {
|
|
403
|
+
signal,
|
|
404
|
+
filters = this.props.filters,
|
|
405
|
+
filterOwner,
|
|
406
|
+
spatialFilter,
|
|
407
|
+
spatialFiltersMode,
|
|
408
|
+
aggregations,
|
|
409
|
+
} = options;
|
|
410
|
+
|
|
411
|
+
return executeModel({
|
|
412
|
+
model: 'aggregations',
|
|
413
|
+
source: {
|
|
414
|
+
...this.getModelSource(filters, filterOwner),
|
|
415
|
+
spatialFiltersMode,
|
|
416
|
+
spatialFilter,
|
|
417
|
+
},
|
|
418
|
+
params: {
|
|
419
|
+
aggregations,
|
|
420
|
+
},
|
|
421
|
+
opts: {signal, headers: this.props.headers},
|
|
422
|
+
}).then((res: AggregationsResponse) => ({
|
|
423
|
+
rows: res.rows.map((row) => normalizeObjectKeys(row)),
|
|
424
|
+
}));
|
|
425
|
+
}
|
|
426
|
+
|
|
397
427
|
/** @experimental */
|
|
398
428
|
async getExtent(options: ExtentRequestOptions = {}): Promise<ExtentResponse> {
|
|
399
429
|
const {signal, filters = this.props.filters, filterOwner} = options;
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AggregationsRequestOptions,
|
|
3
|
+
AggregationsResponse,
|
|
2
4
|
CategoryRequestOptions,
|
|
3
5
|
CategoryResponse,
|
|
4
6
|
ExtentRequestOptions,
|
|
@@ -127,6 +129,15 @@ export abstract class WidgetSource<
|
|
|
127
129
|
options: TimeSeriesRequestOptions
|
|
128
130
|
): Promise<TimeSeriesResponse>;
|
|
129
131
|
|
|
132
|
+
/**
|
|
133
|
+
* Returns multiple aggregated values computed over matching data. Suitable
|
|
134
|
+
* for aggregated statistics from pivoted tables, such as H3 tables with
|
|
135
|
+
* pre-computed aggregations across multiple columns.
|
|
136
|
+
*/
|
|
137
|
+
abstract getAggregations(
|
|
138
|
+
options: AggregationsRequestOptions
|
|
139
|
+
): Promise<AggregationsResponse>;
|
|
140
|
+
|
|
130
141
|
/** @experimental */
|
|
131
142
|
abstract getExtent(options?: ExtentRequestOptions): Promise<ExtentResponse>;
|
|
132
143
|
}
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
/* eslint-disable @typescript-eslint/require-await */
|
|
2
2
|
import type {
|
|
3
|
+
AggregationsRequestOptions,
|
|
4
|
+
AggregationsResponse,
|
|
3
5
|
CategoryRequestOptions,
|
|
4
6
|
CategoryResponse,
|
|
5
7
|
ExtentResponse,
|
|
@@ -148,6 +150,8 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
|
|
|
148
150
|
}
|
|
149
151
|
|
|
150
152
|
const targetOperation = aggregationFunctions[operation];
|
|
153
|
+
assert(targetOperation, `Unsupported aggregation operation: ${operation}`);
|
|
154
|
+
|
|
151
155
|
return {
|
|
152
156
|
value: targetOperation(filteredFeatures, column, joinOperation),
|
|
153
157
|
};
|
|
@@ -391,6 +395,51 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
|
|
|
391
395
|
};
|
|
392
396
|
}
|
|
393
397
|
|
|
398
|
+
async getAggregations({
|
|
399
|
+
aggregations,
|
|
400
|
+
filters,
|
|
401
|
+
filterOwner,
|
|
402
|
+
spatialFilter,
|
|
403
|
+
}: AggregationsRequestOptions): Promise<AggregationsResponse> {
|
|
404
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
405
|
+
spatialFilter,
|
|
406
|
+
filters,
|
|
407
|
+
filterOwner
|
|
408
|
+
);
|
|
409
|
+
|
|
410
|
+
if (!this._features.length) {
|
|
411
|
+
return {rows: []};
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// SQL aggregations require remote execution, and are not supported for tilesets.
|
|
415
|
+
assert(
|
|
416
|
+
typeof aggregations !== 'string',
|
|
417
|
+
'Unsupported tileset SQL aggregation'
|
|
418
|
+
);
|
|
419
|
+
|
|
420
|
+
// Handle array-based aggregations
|
|
421
|
+
const result: Record<string, number> = {};
|
|
422
|
+
const usedAliases = new Set<string>();
|
|
423
|
+
|
|
424
|
+
for (const {column, operation, alias} of aggregations) {
|
|
425
|
+
// Column is required except when operation is 'count'.
|
|
426
|
+
if ((column && column !== '*') || operation !== AggregationTypes.Count) {
|
|
427
|
+
assertColumn(this._features, column);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
const aliasKey = alias.toLowerCase();
|
|
431
|
+
assert(!usedAliases.has(aliasKey), `Duplicate alias: ${aliasKey}`);
|
|
432
|
+
usedAliases.add(aliasKey);
|
|
433
|
+
|
|
434
|
+
const targetOperation = aggregationFunctions[operation];
|
|
435
|
+
assert(targetOperation, `Unsupported operation: ${operation}`);
|
|
436
|
+
|
|
437
|
+
result[alias] = targetOperation(filteredFeatures, column);
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
return {rows: [result]};
|
|
441
|
+
}
|
|
442
|
+
|
|
394
443
|
/** @experimental */
|
|
395
444
|
async getExtent(): Promise<ExtentResponse> {
|
|
396
445
|
return Promise.reject(new Error('not implemented'));
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import type {
|
|
2
|
+
AggregationsRequestOptions,
|
|
3
|
+
AggregationsResponse,
|
|
2
4
|
CategoryRequestOptions,
|
|
3
5
|
CategoryResponse,
|
|
4
6
|
ExtentResponse,
|
|
@@ -326,6 +328,17 @@ export class WidgetTilesetSource<
|
|
|
326
328
|
return this._executeWorkerMethod(Method.GET_RANGE, [options], signal);
|
|
327
329
|
}
|
|
328
330
|
|
|
331
|
+
async getAggregations({
|
|
332
|
+
signal,
|
|
333
|
+
...options
|
|
334
|
+
}: AggregationsRequestOptions): Promise<AggregationsResponse> {
|
|
335
|
+
return this._executeWorkerMethod(
|
|
336
|
+
Method.GET_AGGREGATIONS,
|
|
337
|
+
[options],
|
|
338
|
+
signal
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
329
342
|
/** @experimental */
|
|
330
343
|
async getExtent(): Promise<ExtentResponse> {
|
|
331
344
|
return Promise.resolve({
|