@carto/api-client 0.5.16 → 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.
@@ -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 type {LayerDescriptor, LayerType} from './fetch-map/index.js';
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';
@@ -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
- attribute: string;
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?: Record<number, number[]>;
379
+ quantiles?: QuantileStats;
339
380
 
340
381
  /**
341
382
  * Top values by number of values.