vega2ol 1.1.0 → 1.1.2
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/LICENSE +29 -0
- package/dist/index.d.ts +1 -1
- package/dist/main.d.ts +3 -3
- package/dist/main.js +83 -52
- package/dist/tests/test-basic.js +141 -3
- package/package.json +3 -2
- package/dist/src/index.d.ts +0 -4
- package/dist/src/index.js +0 -4
- package/dist/src/main.d.ts +0 -25
- package/dist/src/main.js +0 -240
package/LICENSE
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
BSD 3-Clause License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026, Vega2ol Contributors
|
|
4
|
+
All rights reserved.
|
|
5
|
+
|
|
6
|
+
Redistribution and use in source and binary forms, with or without
|
|
7
|
+
modification, are permitted provided that the following conditions are met:
|
|
8
|
+
|
|
9
|
+
1. Redistributions of source code must retain the above copyright notice, this
|
|
10
|
+
list of conditions and the following disclaimer.
|
|
11
|
+
|
|
12
|
+
2. Redistributions in binary form must reproduce the above copyright notice,
|
|
13
|
+
this list of conditions and the following disclaimer in the documentation
|
|
14
|
+
and/or other materials provided with the distribution.
|
|
15
|
+
|
|
16
|
+
3. Neither the name of the copyright holder nor the names of its
|
|
17
|
+
contributors may be used to endorse or promote products derived from
|
|
18
|
+
this software without specific prior written permission.
|
|
19
|
+
|
|
20
|
+
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
|
|
21
|
+
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
|
|
22
|
+
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
23
|
+
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
24
|
+
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
25
|
+
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
26
|
+
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
27
|
+
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
28
|
+
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
29
|
+
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
package/dist/index.d.ts
CHANGED
package/dist/main.d.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import type { ExpressionValue } from "ol/expr/expression.js";
|
|
1
2
|
/**
|
|
2
3
|
* OpenLayers expression types
|
|
3
4
|
* OL expressions are arrays: ['operator', arg1, arg2, ...] or ['case', condition, value, ...]
|
|
4
5
|
*/
|
|
5
|
-
type OLExpression = any[] | string | number | boolean | null;
|
|
6
6
|
/**
|
|
7
7
|
* Vega AST node types (simplified)
|
|
8
8
|
*/
|
|
@@ -21,5 +21,5 @@ export declare class Vega2OLError extends Error {
|
|
|
21
21
|
* @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
|
|
22
22
|
* @returns OpenLayers expression array
|
|
23
23
|
*/
|
|
24
|
-
export declare function vega2ol(vegaExpressionStr: string):
|
|
25
|
-
export { OLExpression, VegaNode };
|
|
24
|
+
export declare function vega2ol(vegaExpressionStr: string): ExpressionValue;
|
|
25
|
+
export { ExpressionValue as OLExpression, VegaNode };
|
package/dist/main.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { parseExpression } from "vega-expression";
|
|
1
2
|
/**
|
|
2
3
|
* Operator mapping from Vega operators to OpenLayers operators
|
|
3
4
|
*/
|
|
@@ -18,8 +19,21 @@ const OPERATOR_MAPPING = {
|
|
|
18
19
|
"*": "*",
|
|
19
20
|
"/": "/",
|
|
20
21
|
"%": "%",
|
|
21
|
-
|
|
22
|
-
|
|
22
|
+
};
|
|
23
|
+
/**
|
|
24
|
+
* Constants mapping from Vega constants to OpenLayers constants
|
|
25
|
+
*/
|
|
26
|
+
const CONSTANTS_MAPPING = {
|
|
27
|
+
PI: Math.PI,
|
|
28
|
+
E: Math.E,
|
|
29
|
+
LN2: Math.LN2,
|
|
30
|
+
LN10: Math.LN10,
|
|
31
|
+
LOG2E: Math.LOG2E,
|
|
32
|
+
LOG10E: Math.LOG10E,
|
|
33
|
+
SQRT1_2: Math.SQRT1_2,
|
|
34
|
+
SQRT2: Math.SQRT2,
|
|
35
|
+
MIN_VALUE: Number.MIN_VALUE,
|
|
36
|
+
MAX_VALUE: Number.MAX_VALUE,
|
|
23
37
|
};
|
|
24
38
|
/**
|
|
25
39
|
* Function mapping from Vega functions to OpenLayers functions
|
|
@@ -31,30 +45,12 @@ const FUNCTION_MAPPING = {
|
|
|
31
45
|
ceil: "ceil",
|
|
32
46
|
round: "round",
|
|
33
47
|
sqrt: "sqrt",
|
|
34
|
-
pow: "
|
|
35
|
-
log: "log",
|
|
36
|
-
exp: "exp",
|
|
48
|
+
pow: "^",
|
|
37
49
|
sin: "sin",
|
|
38
50
|
cos: "cos",
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
upper: "toUpperCase",
|
|
43
|
-
lower: "toLowerCase",
|
|
44
|
-
substring: "substring",
|
|
45
|
-
// Array functions
|
|
46
|
-
length: "length",
|
|
47
|
-
indexof: "indexOf",
|
|
48
|
-
slice: "slice",
|
|
49
|
-
// Type functions
|
|
50
|
-
type: "type",
|
|
51
|
-
isvalid: "isValid",
|
|
52
|
-
isnumber: "isNumber",
|
|
53
|
-
isstring: "isString",
|
|
54
|
-
isboolean: "isBoolean",
|
|
55
|
-
// Aggregation functions
|
|
56
|
-
min: "min",
|
|
57
|
-
max: "max",
|
|
51
|
+
atan: "atan",
|
|
52
|
+
atan2: "atan",
|
|
53
|
+
clamp: "clamp",
|
|
58
54
|
};
|
|
59
55
|
/**
|
|
60
56
|
* Custom error class for Vega2OL transpilation errors
|
|
@@ -73,6 +69,20 @@ class VegaToOLVisitor {
|
|
|
73
69
|
constructor() {
|
|
74
70
|
this.scope = {};
|
|
75
71
|
}
|
|
72
|
+
isNegativeOne(node) {
|
|
73
|
+
// Case 1: Literal -1
|
|
74
|
+
if (node.type === "Literal" && node.value === -1) {
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
// Case 2: UnaryExpression: -1
|
|
78
|
+
if (node.type === "UnaryExpression" &&
|
|
79
|
+
node.operator === "-" &&
|
|
80
|
+
node.argument?.type === "Literal" &&
|
|
81
|
+
node.argument.value === 1) {
|
|
82
|
+
return true;
|
|
83
|
+
}
|
|
84
|
+
return false;
|
|
85
|
+
}
|
|
76
86
|
visit(node) {
|
|
77
87
|
if (!node || !node.type) {
|
|
78
88
|
throw new Vega2OLError("Invalid AST node");
|
|
@@ -112,21 +122,42 @@ class VegaToOLVisitor {
|
|
|
112
122
|
}
|
|
113
123
|
throw new Vega2OLError(`Unsupported member access`);
|
|
114
124
|
}
|
|
125
|
+
isIndexOfCall(node) {
|
|
126
|
+
return (node.type === "CallExpression" &&
|
|
127
|
+
node.callee?.type === "Identifier" &&
|
|
128
|
+
node.callee.name.toLowerCase() === "indexof");
|
|
129
|
+
}
|
|
115
130
|
// Binary operations: a + b, a > b, etc.
|
|
116
131
|
visitBinaryExpression(node) {
|
|
117
132
|
const { operator, left, right } = node;
|
|
133
|
+
// Special handling for indexOf comparisons
|
|
134
|
+
if (operator === "!=" || operator === "==") {
|
|
135
|
+
let call = null;
|
|
136
|
+
if (this.isIndexOfCall(left) && this.isNegativeOne(right)) {
|
|
137
|
+
call = left;
|
|
138
|
+
}
|
|
139
|
+
else if (this.isIndexOfCall(right) && this.isNegativeOne(left)) {
|
|
140
|
+
call = right;
|
|
141
|
+
}
|
|
142
|
+
if (call) {
|
|
143
|
+
const [arrayArg, valueArg] = call.arguments || [];
|
|
144
|
+
if (arrayArg && valueArg) {
|
|
145
|
+
const arrayOL = this.visit(arrayArg);
|
|
146
|
+
const valueOL = this.visit(valueArg);
|
|
147
|
+
const inExpr = ["in", valueOL, arrayOL];
|
|
148
|
+
// indexof(...) != -1 → is in
|
|
149
|
+
if (operator === "!=") {
|
|
150
|
+
return inExpr;
|
|
151
|
+
}
|
|
152
|
+
// indexof(...) == -1 → is NOT in
|
|
153
|
+
if (operator === "==") {
|
|
154
|
+
return ["!", inExpr];
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
118
159
|
const leftOL = this.visit(left);
|
|
119
160
|
const rightOL = this.visit(right);
|
|
120
|
-
// Handle special cases
|
|
121
|
-
if (operator === "in") {
|
|
122
|
-
return ["in", leftOL, rightOL];
|
|
123
|
-
}
|
|
124
|
-
if (operator === "&&") {
|
|
125
|
-
return ["all", leftOL, rightOL];
|
|
126
|
-
}
|
|
127
|
-
if (operator === "||") {
|
|
128
|
-
return ["any", leftOL, rightOL];
|
|
129
|
-
}
|
|
130
161
|
const olOp = OPERATOR_MAPPING[operator];
|
|
131
162
|
if (!olOp) {
|
|
132
163
|
throw new Vega2OLError(`Unsupported binary operator: ${operator}`);
|
|
@@ -171,6 +202,10 @@ class VegaToOLVisitor {
|
|
|
171
202
|
else {
|
|
172
203
|
throw new Vega2OLError(`Unsupported function call`);
|
|
173
204
|
}
|
|
205
|
+
if (this.isIndexOfCall(node)) {
|
|
206
|
+
throw new Vega2OLError(`indexof is only supported in the pattern 'indexof(array, value) != -1' (or '== -1'), ` +
|
|
207
|
+
`it cannot be used as a standalone expression in OpenLayers`);
|
|
208
|
+
}
|
|
174
209
|
const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
|
|
175
210
|
if (!olFunc) {
|
|
176
211
|
throw new Vega2OLError(`Unsupported function: ${funcName}`);
|
|
@@ -183,6 +218,17 @@ class VegaToOLVisitor {
|
|
|
183
218
|
const { elements } = node;
|
|
184
219
|
return elements.map((elem) => this.visit(elem));
|
|
185
220
|
}
|
|
221
|
+
// Logical expressions
|
|
222
|
+
visitLogicalExpression(node) {
|
|
223
|
+
const { operator, left, right } = node;
|
|
224
|
+
const leftOL = this.visit(left);
|
|
225
|
+
const rightOL = this.visit(right);
|
|
226
|
+
const olOp = OPERATOR_MAPPING[operator];
|
|
227
|
+
if (!olOp) {
|
|
228
|
+
throw new Vega2OLError(`Unsupported logical operator: ${operator}`);
|
|
229
|
+
}
|
|
230
|
+
return [olOp, leftOL, rightOL];
|
|
231
|
+
}
|
|
186
232
|
// Identifier/Variable: x, PI, etc.
|
|
187
233
|
visitIdentifier(node) {
|
|
188
234
|
const { name } = node;
|
|
@@ -190,21 +236,8 @@ class VegaToOLVisitor {
|
|
|
190
236
|
if (name in this.scope) {
|
|
191
237
|
return this.scope[name];
|
|
192
238
|
}
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
PI: Math.PI,
|
|
196
|
-
E: Math.E,
|
|
197
|
-
LN2: Math.LN2,
|
|
198
|
-
LN10: Math.LN10,
|
|
199
|
-
LOG2E: Math.LOG2E,
|
|
200
|
-
LOG10E: Math.LOG10E,
|
|
201
|
-
SQRT1_2: Math.SQRT1_2,
|
|
202
|
-
SQRT2: Math.SQRT2,
|
|
203
|
-
MIN_VALUE: Number.MIN_VALUE,
|
|
204
|
-
MAX_VALUE: Number.MAX_VALUE,
|
|
205
|
-
};
|
|
206
|
-
if (name in vegaConstants) {
|
|
207
|
-
return vegaConstants[name];
|
|
239
|
+
if (name in CONSTANTS_MAPPING) {
|
|
240
|
+
return CONSTANTS_MAPPING[name];
|
|
208
241
|
}
|
|
209
242
|
// Return as string (might be a function or field)
|
|
210
243
|
return name;
|
|
@@ -215,13 +248,11 @@ class VegaToOLVisitor {
|
|
|
215
248
|
* @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
|
|
216
249
|
* @returns OpenLayers expression array
|
|
217
250
|
*/
|
|
218
|
-
export
|
|
251
|
+
export function vega2ol(vegaExpressionStr) {
|
|
219
252
|
if (typeof vegaExpressionStr !== "string") {
|
|
220
253
|
throw new Vega2OLError("Input must be a string");
|
|
221
254
|
}
|
|
222
255
|
try {
|
|
223
|
-
// Dynamically import parseExpression to handle ESM
|
|
224
|
-
const { parseExpression } = await import("vega-expression");
|
|
225
256
|
// Parse Vega expression to AST
|
|
226
257
|
const ast = parseExpression(vegaExpressionStr);
|
|
227
258
|
// Visit the AST and convert to OL expression
|
package/dist/tests/test-basic.js
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { vega2ol, Vega2OLError } from "../
|
|
1
|
+
import { vega2ol, Vega2OLError } from "../main.js";
|
|
2
2
|
// Simple assertion helper
|
|
3
3
|
function assertEqual(actual, expected, message) {
|
|
4
4
|
const actualStr = JSON.stringify(actual);
|
|
@@ -14,6 +14,30 @@ function assertEqual(actual, expected, message) {
|
|
|
14
14
|
return false;
|
|
15
15
|
}
|
|
16
16
|
}
|
|
17
|
+
// Assert that vega2ol throws a Vega2OLError for the given expression
|
|
18
|
+
async function assertThrows(expr, message, messageContains) {
|
|
19
|
+
try {
|
|
20
|
+
const result = await vega2ol(expr);
|
|
21
|
+
console.log(` ❌ ${message}`);
|
|
22
|
+
console.log(` Expected a Vega2OLError, but got result: ${JSON.stringify(result)}`);
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
catch (error) {
|
|
26
|
+
if (!(error instanceof Vega2OLError)) {
|
|
27
|
+
console.log(` ❌ ${message}`);
|
|
28
|
+
console.log(` Expected Vega2OLError, got: ${error.constructor.name}: ${error.message}`);
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
if (messageContains && !error.message.includes(messageContains)) {
|
|
32
|
+
console.log(` ❌ ${message}`);
|
|
33
|
+
console.log(` Error message did not contain "${messageContains}"`);
|
|
34
|
+
console.log(` Got message: ${error.message}`);
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
console.log(` ✅ ${message}`);
|
|
38
|
+
return true;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
17
41
|
// Run tests
|
|
18
42
|
async function runTests() {
|
|
19
43
|
console.log("🚀 Testing vega2ol transpiler\n");
|
|
@@ -106,7 +130,43 @@ async function runTests() {
|
|
|
106
130
|
passed++;
|
|
107
131
|
else
|
|
108
132
|
failed++;
|
|
109
|
-
if (assertEqual(await vega2ol("
|
|
133
|
+
if (assertEqual(await vega2ol("ceil(datum.value)"), ["ceil", ["get", "value"]], "ceil()"))
|
|
134
|
+
passed++;
|
|
135
|
+
else
|
|
136
|
+
failed++;
|
|
137
|
+
if (assertEqual(await vega2ol("round(datum.value)"), ["round", ["get", "value"]], "round()"))
|
|
138
|
+
passed++;
|
|
139
|
+
else
|
|
140
|
+
failed++;
|
|
141
|
+
if (assertEqual(await vega2ol("abs(datum.value)"), ["abs", ["get", "value"]], "abs()"))
|
|
142
|
+
passed++;
|
|
143
|
+
else
|
|
144
|
+
failed++;
|
|
145
|
+
if (assertEqual(await vega2ol("sqrt(datum.value)"), ["sqrt", ["get", "value"]], "sqrt()"))
|
|
146
|
+
passed++;
|
|
147
|
+
else
|
|
148
|
+
failed++;
|
|
149
|
+
if (assertEqual(await vega2ol("sin(datum.value)"), ["sin", ["get", "value"]], "sin()"))
|
|
150
|
+
passed++;
|
|
151
|
+
else
|
|
152
|
+
failed++;
|
|
153
|
+
if (assertEqual(await vega2ol("cos(datum.value)"), ["cos", ["get", "value"]], "cos()"))
|
|
154
|
+
passed++;
|
|
155
|
+
else
|
|
156
|
+
failed++;
|
|
157
|
+
if (assertEqual(await vega2ol("pow(datum.base, 2)"), ["^", ["get", "base"], 2], "pow() maps to OL's '^' operator, not a 'pow' function"))
|
|
158
|
+
passed++;
|
|
159
|
+
else
|
|
160
|
+
failed++;
|
|
161
|
+
if (assertEqual(await vega2ol("atan(datum.value)"), ["atan", ["get", "value"]], "atan() single-arg form"))
|
|
162
|
+
passed++;
|
|
163
|
+
else
|
|
164
|
+
failed++;
|
|
165
|
+
if (assertEqual(await vega2ol("atan2(datum.x, datum.y)"), ["atan", ["get", "x"], ["get", "y"]], "atan2() two-arg form"))
|
|
166
|
+
passed++;
|
|
167
|
+
else
|
|
168
|
+
failed++;
|
|
169
|
+
if (assertEqual(await vega2ol("clamp(datum.value, 0, 100)"), ["clamp", ["get", "value"], 0, 100], "clamp() preserves (value, min, max) arg order"))
|
|
110
170
|
passed++;
|
|
111
171
|
else
|
|
112
172
|
failed++;
|
|
@@ -133,7 +193,85 @@ async function runTests() {
|
|
|
133
193
|
passed++;
|
|
134
194
|
else
|
|
135
195
|
failed++;
|
|
136
|
-
// Test 13:
|
|
196
|
+
// Test 13: Binary expressions
|
|
197
|
+
console.log("\n📋 Binary Expressions");
|
|
198
|
+
if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) != -1"), ["in", ["get", "country"], ["USA", "India"]], "indexOf with != -1 (is in)"))
|
|
199
|
+
passed++;
|
|
200
|
+
else
|
|
201
|
+
failed++;
|
|
202
|
+
if (assertEqual(await vega2ol("-1 != indexof(['USA', 'India'], datum.country)"), ["in", ["get", "country"], ["USA", "India"]], "reverse indexOf with != -1 (is in)"))
|
|
203
|
+
passed++;
|
|
204
|
+
else
|
|
205
|
+
failed++;
|
|
206
|
+
if (assertEqual(await vega2ol("indexof(['hot', 'warm'], datum.temperature) != -1 ? 'red' : 'blue'"), [
|
|
207
|
+
"case",
|
|
208
|
+
["in", ["get", "temperature"], ["hot", "warm"]],
|
|
209
|
+
"red",
|
|
210
|
+
"blue",
|
|
211
|
+
], "indexOf in ternary (if in)"))
|
|
212
|
+
passed++;
|
|
213
|
+
else
|
|
214
|
+
failed++;
|
|
215
|
+
if (assertEqual(await vega2ol("indexof(['USA', 'India'], datum.country) == -1"), ["!", ["in", ["get", "country"], ["USA", "India"]]], "indexOf with == -1 (is not in)"))
|
|
216
|
+
passed++;
|
|
217
|
+
else
|
|
218
|
+
failed++;
|
|
219
|
+
if (assertEqual(await vega2ol("-1 == indexof(['USA', 'India'], datum.country)"), ["!", ["in", ["get", "country"], ["USA", "India"]]], "reverse indexOf with == -1 (is not in)"))
|
|
220
|
+
passed++;
|
|
221
|
+
else
|
|
222
|
+
failed++;
|
|
223
|
+
// Test 14: Unsupported / unknown function errors
|
|
224
|
+
console.log("\n📋 Errors: Vega Functions With No OL Equivalent");
|
|
225
|
+
if (await assertThrows("upper(datum.name)", "upper() throws (no OL string case operator)", "Unsupported function"))
|
|
226
|
+
passed++;
|
|
227
|
+
else
|
|
228
|
+
failed++;
|
|
229
|
+
if (await assertThrows("lower(datum.name)", "lower() throws (no OL string case operator)", "Unsupported function"))
|
|
230
|
+
passed++;
|
|
231
|
+
else
|
|
232
|
+
failed++;
|
|
233
|
+
if (await assertThrows("toString(datum.value)", "toString() throws (no OL type coercion operator)", "Unsupported function"))
|
|
234
|
+
passed++;
|
|
235
|
+
else
|
|
236
|
+
failed++;
|
|
237
|
+
if (await assertThrows("isValid(datum.value)", "isValid() throws (no OL type-checking operator)", "Unsupported function"))
|
|
238
|
+
passed++;
|
|
239
|
+
else
|
|
240
|
+
failed++;
|
|
241
|
+
if (await assertThrows("length(datum.items)", "length() throws (no OL length operator)", "Unsupported function"))
|
|
242
|
+
passed++;
|
|
243
|
+
else
|
|
244
|
+
failed++;
|
|
245
|
+
if (await assertThrows("format(datum.value, ',.2f')", "format() throws (no OL number-formatting operator)", "Unsupported function"))
|
|
246
|
+
passed++;
|
|
247
|
+
else
|
|
248
|
+
failed++;
|
|
249
|
+
if (await assertThrows("now()", "now() throws (no OL date/time operators)", "Unsupported function"))
|
|
250
|
+
passed++;
|
|
251
|
+
else
|
|
252
|
+
failed++;
|
|
253
|
+
if (await assertThrows("min(datum.a, datum.b)", "min() throws (no OL aggregate min/max operator)", "Unsupported function"))
|
|
254
|
+
passed++;
|
|
255
|
+
else
|
|
256
|
+
failed++;
|
|
257
|
+
if (await assertThrows("tan(datum.value)", "tan() throws (OL only supports sin/cos/atan)", "Unsupported function"))
|
|
258
|
+
passed++;
|
|
259
|
+
else
|
|
260
|
+
failed++;
|
|
261
|
+
if (await assertThrows("indexof(datum.items, 'x')", "bare indexof() outside the != -1 / == -1 pattern throws a specific error", "indexof is only supported in the pattern"))
|
|
262
|
+
passed++;
|
|
263
|
+
else
|
|
264
|
+
failed++;
|
|
265
|
+
console.log("\n📋 Errors: Names That Don't Exist In Vega Or OL");
|
|
266
|
+
if (await assertThrows("totallyMadeUpFunction(datum.value)", "Nonexistent function name throws", "Unsupported function"))
|
|
267
|
+
passed++;
|
|
268
|
+
else
|
|
269
|
+
failed++;
|
|
270
|
+
if (await assertThrows("fooBarBaz(1, 2, 3)", "Another nonexistent function name throws", "Unsupported function"))
|
|
271
|
+
passed++;
|
|
272
|
+
else
|
|
273
|
+
failed++;
|
|
274
|
+
// Test 15: Error handling
|
|
137
275
|
console.log("\n📋 Error Handling");
|
|
138
276
|
try {
|
|
139
277
|
await vega2ol(null);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "vega2ol",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.2",
|
|
4
4
|
"description": "Vega expression to Open-Layers expression transpiler",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"vega",
|
|
@@ -31,7 +31,8 @@
|
|
|
31
31
|
"scripts": {
|
|
32
32
|
"build": "tsc",
|
|
33
33
|
"test": "node dist/tests/test-basic.js",
|
|
34
|
-
"dev": "tsc --watch"
|
|
34
|
+
"dev": "tsc --watch",
|
|
35
|
+
"clean": "rm -rf dist"
|
|
35
36
|
},
|
|
36
37
|
"dependencies": {
|
|
37
38
|
"vega-expression": "^6.1.0"
|
package/dist/src/index.d.ts
DELETED
package/dist/src/index.js
DELETED
package/dist/src/main.d.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import type { ExpressionValue } from "ol/expr/expression.js";
|
|
2
|
-
/**
|
|
3
|
-
* OpenLayers expression types
|
|
4
|
-
* OL expressions are arrays: ['operator', arg1, arg2, ...] or ['case', condition, value, ...]
|
|
5
|
-
*/
|
|
6
|
-
/**
|
|
7
|
-
* Vega AST node types (simplified)
|
|
8
|
-
*/
|
|
9
|
-
interface VegaNode {
|
|
10
|
-
type: string;
|
|
11
|
-
[key: string]: any;
|
|
12
|
-
}
|
|
13
|
-
/**
|
|
14
|
-
* Custom error class for Vega2OL transpilation errors
|
|
15
|
-
*/
|
|
16
|
-
export declare class Vega2OLError extends Error {
|
|
17
|
-
constructor(message: string);
|
|
18
|
-
}
|
|
19
|
-
/**
|
|
20
|
-
* Main transpiler function
|
|
21
|
-
* @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
|
|
22
|
-
* @returns OpenLayers expression array
|
|
23
|
-
*/
|
|
24
|
-
export declare function vega2ol(vegaExpressionStr: string): Promise<ExpressionValue>;
|
|
25
|
-
export { ExpressionValue as OLExpression, VegaNode };
|
package/dist/src/main.js
DELETED
|
@@ -1,240 +0,0 @@
|
|
|
1
|
-
import { parseExpression } from "vega-expression";
|
|
2
|
-
/**
|
|
3
|
-
* Operator mapping from Vega operators to OpenLayers operators
|
|
4
|
-
*/
|
|
5
|
-
const OPERATOR_MAPPING = {
|
|
6
|
-
// Comparison operators
|
|
7
|
-
"==": "==",
|
|
8
|
-
"!=": "!=",
|
|
9
|
-
"<": "<",
|
|
10
|
-
"<=": "<=",
|
|
11
|
-
">": ">",
|
|
12
|
-
">=": ">=",
|
|
13
|
-
// Logical operators
|
|
14
|
-
"&&": "all",
|
|
15
|
-
"||": "any",
|
|
16
|
-
// Arithmetic operators
|
|
17
|
-
"+": "+",
|
|
18
|
-
"-": "-",
|
|
19
|
-
"*": "*",
|
|
20
|
-
"/": "/",
|
|
21
|
-
"%": "%",
|
|
22
|
-
// Other
|
|
23
|
-
"**": "pow",
|
|
24
|
-
};
|
|
25
|
-
/**
|
|
26
|
-
* Constants mapping from Vega constants to OpenLayers constants
|
|
27
|
-
*/
|
|
28
|
-
const CONSTANTS_MAPPING = {
|
|
29
|
-
PI: Math.PI,
|
|
30
|
-
E: Math.E,
|
|
31
|
-
LN2: Math.LN2,
|
|
32
|
-
LN10: Math.LN10,
|
|
33
|
-
LOG2E: Math.LOG2E,
|
|
34
|
-
LOG10E: Math.LOG10E,
|
|
35
|
-
SQRT1_2: Math.SQRT1_2,
|
|
36
|
-
SQRT2: Math.SQRT2,
|
|
37
|
-
MIN_VALUE: Number.MIN_VALUE,
|
|
38
|
-
MAX_VALUE: Number.MAX_VALUE,
|
|
39
|
-
};
|
|
40
|
-
/**
|
|
41
|
-
* Function mapping from Vega functions to OpenLayers functions
|
|
42
|
-
*/
|
|
43
|
-
const FUNCTION_MAPPING = {
|
|
44
|
-
// Math functions
|
|
45
|
-
abs: "abs",
|
|
46
|
-
floor: "floor",
|
|
47
|
-
ceil: "ceil",
|
|
48
|
-
round: "round",
|
|
49
|
-
sqrt: "sqrt",
|
|
50
|
-
pow: "pow",
|
|
51
|
-
log: "log",
|
|
52
|
-
exp: "exp",
|
|
53
|
-
sin: "sin",
|
|
54
|
-
cos: "cos",
|
|
55
|
-
tan: "tan",
|
|
56
|
-
// String functions
|
|
57
|
-
tostring: "toString",
|
|
58
|
-
upper: "toUpperCase",
|
|
59
|
-
lower: "toLowerCase",
|
|
60
|
-
substring: "substring",
|
|
61
|
-
// Array functions
|
|
62
|
-
length: "length",
|
|
63
|
-
indexof: "indexOf",
|
|
64
|
-
slice: "slice",
|
|
65
|
-
// Type functions
|
|
66
|
-
type: "type",
|
|
67
|
-
isvalid: "isValid",
|
|
68
|
-
isnumber: "isNumber",
|
|
69
|
-
isstring: "isString",
|
|
70
|
-
isboolean: "isBoolean",
|
|
71
|
-
// Aggregation functions
|
|
72
|
-
min: "min",
|
|
73
|
-
max: "max",
|
|
74
|
-
};
|
|
75
|
-
/**
|
|
76
|
-
* Custom error class for Vega2OL transpilation errors
|
|
77
|
-
*/
|
|
78
|
-
export class Vega2OLError extends Error {
|
|
79
|
-
constructor(message) {
|
|
80
|
-
const fullMessage = `${message}, note that only a subset of Vega is supported`;
|
|
81
|
-
super(fullMessage);
|
|
82
|
-
this.name = "Vega2OLError";
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
/**
|
|
86
|
-
* Visitor class to convert Vega AST to OpenLayers expressions
|
|
87
|
-
*/
|
|
88
|
-
class VegaToOLVisitor {
|
|
89
|
-
constructor() {
|
|
90
|
-
this.scope = {};
|
|
91
|
-
}
|
|
92
|
-
visit(node) {
|
|
93
|
-
if (!node || !node.type) {
|
|
94
|
-
throw new Vega2OLError("Invalid AST node");
|
|
95
|
-
}
|
|
96
|
-
const method = `visit${node.type}`;
|
|
97
|
-
if (typeof this[method] === "function") {
|
|
98
|
-
return this[method](node);
|
|
99
|
-
}
|
|
100
|
-
throw new Vega2OLError(`Unsupported ${node.type} node`);
|
|
101
|
-
}
|
|
102
|
-
// Literal values
|
|
103
|
-
visitLiteral(node) {
|
|
104
|
-
const { value } = node;
|
|
105
|
-
if (value === null)
|
|
106
|
-
return null;
|
|
107
|
-
if (typeof value === "boolean")
|
|
108
|
-
return value;
|
|
109
|
-
if (typeof value === "number")
|
|
110
|
-
return value;
|
|
111
|
-
if (typeof value === "string")
|
|
112
|
-
return value;
|
|
113
|
-
throw new Vega2OLError(`Unsupported literal type: ${typeof value}`);
|
|
114
|
-
}
|
|
115
|
-
// Member access: datum.value, datum['value']
|
|
116
|
-
visitMemberExpression(node) {
|
|
117
|
-
const { object, property } = node;
|
|
118
|
-
if (object.type === "Identifier" && object.name === "datum") {
|
|
119
|
-
if (typeof property === "string") {
|
|
120
|
-
return ["get", property];
|
|
121
|
-
}
|
|
122
|
-
// For computed properties - if property is an Identifier with 'name'
|
|
123
|
-
if (property.type === "Identifier" && property.name) {
|
|
124
|
-
return ["get", property.name];
|
|
125
|
-
}
|
|
126
|
-
// For computed properties like datum[variable]
|
|
127
|
-
return ["get", this.visit(property)];
|
|
128
|
-
}
|
|
129
|
-
throw new Vega2OLError(`Unsupported member access`);
|
|
130
|
-
}
|
|
131
|
-
// Binary operations: a + b, a > b, etc.
|
|
132
|
-
visitBinaryExpression(node) {
|
|
133
|
-
const { operator, left, right } = node;
|
|
134
|
-
const leftOL = this.visit(left);
|
|
135
|
-
const rightOL = this.visit(right);
|
|
136
|
-
const olOp = OPERATOR_MAPPING[operator];
|
|
137
|
-
if (!olOp) {
|
|
138
|
-
throw new Vega2OLError(`Unsupported binary operator: ${operator}`);
|
|
139
|
-
}
|
|
140
|
-
return [olOp, leftOL, rightOL];
|
|
141
|
-
}
|
|
142
|
-
// Unary operations: !x, -x, +x
|
|
143
|
-
visitUnaryExpression(node) {
|
|
144
|
-
const { operator, argument } = node;
|
|
145
|
-
const argOL = this.visit(argument);
|
|
146
|
-
if (operator === "!") {
|
|
147
|
-
return ["!", argOL];
|
|
148
|
-
}
|
|
149
|
-
if (operator === "-") {
|
|
150
|
-
return ["*", argOL, -1];
|
|
151
|
-
}
|
|
152
|
-
if (operator === "+") {
|
|
153
|
-
return argOL;
|
|
154
|
-
}
|
|
155
|
-
throw new Vega2OLError(`Unsupported unary operator: ${operator}`);
|
|
156
|
-
}
|
|
157
|
-
// Conditional: test ? consequent : alternate
|
|
158
|
-
visitConditionalExpression(node) {
|
|
159
|
-
const { test, consequent, alternate } = node;
|
|
160
|
-
const testOL = this.visit(test);
|
|
161
|
-
const consequentOL = this.visit(consequent);
|
|
162
|
-
const alternateOL = this.visit(alternate);
|
|
163
|
-
// OL 'case' expression: ['case', condition1, value1, condition2, value2, ..., defaultValue]
|
|
164
|
-
return ["case", testOL, consequentOL, alternateOL];
|
|
165
|
-
}
|
|
166
|
-
// Function calls: floor(x), ceil(y), etc.
|
|
167
|
-
visitCallExpression(node) {
|
|
168
|
-
const { callee, arguments: args } = node;
|
|
169
|
-
let funcName;
|
|
170
|
-
if (callee.type === "Identifier") {
|
|
171
|
-
funcName = callee.name;
|
|
172
|
-
}
|
|
173
|
-
else if (callee.type === "MemberExpression" &&
|
|
174
|
-
callee.object.name === "Math") {
|
|
175
|
-
funcName = callee.property;
|
|
176
|
-
}
|
|
177
|
-
else {
|
|
178
|
-
throw new Vega2OLError(`Unsupported function call`);
|
|
179
|
-
}
|
|
180
|
-
const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
|
|
181
|
-
if (!olFunc) {
|
|
182
|
-
throw new Vega2OLError(`Unsupported function: ${funcName}`);
|
|
183
|
-
}
|
|
184
|
-
const olArgs = args.map((arg) => this.visit(arg));
|
|
185
|
-
return [olFunc, ...olArgs];
|
|
186
|
-
}
|
|
187
|
-
// Array literal: [1, 2, 3]
|
|
188
|
-
visitArrayExpression(node) {
|
|
189
|
-
const { elements } = node;
|
|
190
|
-
return elements.map((elem) => this.visit(elem));
|
|
191
|
-
}
|
|
192
|
-
// Logical expressions
|
|
193
|
-
visitLogicalExpression(node) {
|
|
194
|
-
const { operator, left, right } = node;
|
|
195
|
-
const leftOL = this.visit(left);
|
|
196
|
-
const rightOL = this.visit(right);
|
|
197
|
-
const olOp = OPERATOR_MAPPING[operator];
|
|
198
|
-
if (!olOp) {
|
|
199
|
-
throw new Vega2OLError(`Unsupported logical operator: ${operator}`);
|
|
200
|
-
}
|
|
201
|
-
return [olOp, leftOL, rightOL];
|
|
202
|
-
}
|
|
203
|
-
// Identifier/Variable: x, PI, etc.
|
|
204
|
-
visitIdentifier(node) {
|
|
205
|
-
const { name } = node;
|
|
206
|
-
// Check if it's in the current scope
|
|
207
|
-
if (name in this.scope) {
|
|
208
|
-
return this.scope[name];
|
|
209
|
-
}
|
|
210
|
-
if (name in CONSTANTS_MAPPING) {
|
|
211
|
-
return CONSTANTS_MAPPING[name];
|
|
212
|
-
}
|
|
213
|
-
// Return as string (might be a function or field)
|
|
214
|
-
return name;
|
|
215
|
-
}
|
|
216
|
-
}
|
|
217
|
-
/**
|
|
218
|
-
* Main transpiler function
|
|
219
|
-
* @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
|
|
220
|
-
* @returns OpenLayers expression array
|
|
221
|
-
*/
|
|
222
|
-
export async function vega2ol(vegaExpressionStr) {
|
|
223
|
-
if (typeof vegaExpressionStr !== "string") {
|
|
224
|
-
throw new Vega2OLError("Input must be a string");
|
|
225
|
-
}
|
|
226
|
-
try {
|
|
227
|
-
// Parse Vega expression to AST
|
|
228
|
-
const ast = parseExpression(vegaExpressionStr);
|
|
229
|
-
// Visit the AST and convert to OL expression
|
|
230
|
-
const visitor = new VegaToOLVisitor();
|
|
231
|
-
const olExpression = visitor.visit(ast);
|
|
232
|
-
return olExpression;
|
|
233
|
-
}
|
|
234
|
-
catch (error) {
|
|
235
|
-
if (error instanceof Vega2OLError) {
|
|
236
|
-
throw error;
|
|
237
|
-
}
|
|
238
|
-
throw new Vega2OLError(`Failed to parse expression: ${error.message}`);
|
|
239
|
-
}
|
|
240
|
-
}
|