vega2ol 1.0.0

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/README.md ADDED
@@ -0,0 +1,154 @@
1
+ # vega2ol
2
+
3
+ A TypeScript library to transpile [Vega expressions](https://vega.github.io/vega/docs/expressions/) to [OpenLayers](https://openlayers.org/) expressions.
4
+
5
+ ## Overview
6
+
7
+ `vega2ol` automatically converts Vega expression syntax to OpenLayers array-based expression format. This enables seamless integration between Vega-Lite/Vega visualization specifications and OpenLayers map styling.
8
+
9
+ ### Example
10
+
11
+ **Vega Expression:**
12
+ ```javascript
13
+ datum.value > 100 ? 'red' : 'blue'
14
+ ```
15
+
16
+ **OpenLayers Expression:**
17
+ ```javascript
18
+ ['case', ['>', ['get', 'value'], 100], 'red', 'blue']
19
+ ```
20
+
21
+ ## Installation
22
+
23
+ ```bash
24
+ npm install vega2ol
25
+ ```
26
+
27
+ ## Quick Start
28
+
29
+ ```typescript
30
+ import { vega2ol } from 'vega2ol';
31
+
32
+ // Simple conditional styling
33
+ const olExpr = vega2ol("datum.population > 1000000 ? '#FF0000' : '#0000FF'");
34
+ // Returns: ['case', ['>', ['get', 'population'], 1000000], '#FF0000', '#0000FF']
35
+
36
+ // Use in OpenLayers style
37
+ const style = {
38
+ 'fill-color': olExpr
39
+ };
40
+ ```
41
+
42
+ ## Supported Features
43
+
44
+ ### Literals
45
+ - Numbers: `42`, `3.14`, `-10`
46
+ - Strings: `'hello'`, `"world"`
47
+ - Booleans: `true`, `false`
48
+ - Null: `null`
49
+
50
+ ### Member Access
51
+ - `datum.fieldName` → `['get', 'fieldName']`
52
+ - Works with any data field
53
+
54
+ ### Operators
55
+
56
+ **Comparison:**
57
+ - `==`, `!=`, `<`, `<=`, `>`, `>=`
58
+
59
+ **Logical:**
60
+ - `&&` (AND) → maps to `all`
61
+ - `||` (OR) → maps to `any`
62
+ - `!` (NOT) → maps to `!`
63
+
64
+ **Arithmetic:**
65
+ - `+`, `-`, `*`, `/`, `%`
66
+
67
+ ### Conditional Expressions
68
+ - Ternary operator: `condition ? true_value : false_value`
69
+ - Nested ternaries supported for multiple conditions
70
+
71
+ ### Functions
72
+
73
+ **Math Functions:**
74
+ - `floor()`, `ceil()`, `round()`, `abs()`, `sqrt()`
75
+ - `pow()`, `log()`, `exp()`, `sin()`, `cos()`, `tan()`
76
+ - `min()`, `max()`
77
+
78
+ **String Functions:**
79
+ - `tostring()`, `upper()`, `lower()`, `substring()`
80
+
81
+ **Array Functions:**
82
+ - `length()`, `indexof()`, `slice()`
83
+
84
+ **Type Functions:**
85
+ - `type()`, `isvalid()`, `isnumber()`, `isstring()`, `isboolean()`
86
+
87
+ ### Vega Constants
88
+ - Math: `PI`, `E`, `LN2`, `LN10`, `LOG2E`, `LOG10E`, `SQRT1_2`, `SQRT2`
89
+ - Number: `MIN_VALUE`, `MAX_VALUE`
90
+
91
+ ## Development
92
+
93
+ ### Setup
94
+
95
+ ```bash
96
+ # Install dependencies
97
+ npm install
98
+
99
+ # Build
100
+ npm run build
101
+ ```
102
+
103
+ ### Testing
104
+
105
+ ```bash
106
+ # Run tests
107
+ npm test
108
+ ```
109
+
110
+ ### Architecture
111
+
112
+ The transpiler uses a **visitor pattern** similar to AST walkers:
113
+
114
+ 1. **Parser**: Uses `vega-expression` to parse Vega expressions into an Abstract Syntax Tree (AST)
115
+ 2. **Visitor**: `VegaToOLVisitor` class walks the AST and converts each node type to OpenLayers format
116
+ 3. **Output**: Returns OpenLayers expression array
117
+
118
+ ## Examples
119
+
120
+ ### Color Mapping Based on Value
121
+
122
+ ```typescript
123
+ const vegaExpr = `
124
+ datum.value > 100 ? 'red' :
125
+ datum.value > 50 ? 'yellow' :
126
+ 'green'
127
+ `;
128
+ const olExpr = vega2ol(vegaExpr);
129
+ // Use with OpenLayers style
130
+ style['fill-color'] = olExpr;
131
+ ```
132
+
133
+ ### Opacity Based on Category
134
+
135
+ ```typescript
136
+ const vegaExpr = "datum.category == 'important' ? 1 : 0.5";
137
+ const olExpr = vega2ol(vegaExpr);
138
+ style['fill-opacity'] = olExpr;
139
+ ```
140
+
141
+ ### Complex Conditions
142
+
143
+ ```typescript
144
+ const vegaExpr = `
145
+ (datum.x > 0 && datum.y < 100) ? 'inside' : 'outside'
146
+ `;
147
+ const olExpr = vega2ol(vegaExpr);
148
+ ```
149
+
150
+ ## Resources
151
+
152
+ - [Vega Expression Documentation](https://vega.github.io/vega/docs/expressions/)
153
+ - [Vega Expression Parser](https://github.com/vega/vega/tree/main/packages/vega-expression)
154
+ - [OpenLayers Documentation](https://openlayers.org/)
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError, OLExpression, VegaNode } from './main.js';
package/dist/index.js ADDED
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError } from './main.js';
package/dist/main.d.ts ADDED
@@ -0,0 +1,25 @@
1
+ /**
2
+ * OpenLayers expression types
3
+ * OL expressions are arrays: ['operator', arg1, arg2, ...] or ['case', condition, value, ...]
4
+ */
5
+ type OLExpression = any[] | string | number | boolean | null;
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<OLExpression>;
25
+ export { OLExpression, VegaNode };
package/dist/main.js ADDED
@@ -0,0 +1,238 @@
1
+ /**
2
+ * Operator mapping from Vega operators to OpenLayers operators
3
+ */
4
+ const OPERATOR_MAPPING = {
5
+ // Comparison operators
6
+ "==": "==",
7
+ "!=": "!=",
8
+ "<": "<",
9
+ "<=": "<=",
10
+ ">": ">",
11
+ ">=": ">=",
12
+ // Logical operators
13
+ "&&": "all",
14
+ "||": "any",
15
+ // Arithmetic operators
16
+ "+": "+",
17
+ "-": "-",
18
+ "*": "*",
19
+ "/": "/",
20
+ "%": "%",
21
+ // Other
22
+ "**": "pow",
23
+ };
24
+ /**
25
+ * Function mapping from Vega functions to OpenLayers functions
26
+ */
27
+ const FUNCTION_MAPPING = {
28
+ // Math functions
29
+ abs: "abs",
30
+ floor: "floor",
31
+ ceil: "ceil",
32
+ round: "round",
33
+ sqrt: "sqrt",
34
+ pow: "pow",
35
+ log: "log",
36
+ exp: "exp",
37
+ sin: "sin",
38
+ cos: "cos",
39
+ tan: "tan",
40
+ // String functions
41
+ tostring: "toString",
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",
58
+ };
59
+ /**
60
+ * Custom error class for Vega2OL transpilation errors
61
+ */
62
+ export class Vega2OLError extends Error {
63
+ constructor(message) {
64
+ const fullMessage = `${message}, note that only a subset of Vega is supported`;
65
+ super(fullMessage);
66
+ this.name = "Vega2OLError";
67
+ }
68
+ }
69
+ /**
70
+ * Visitor class to convert Vega AST to OpenLayers expressions
71
+ */
72
+ class VegaToOLVisitor {
73
+ constructor() {
74
+ this.scope = {};
75
+ }
76
+ visit(node) {
77
+ if (!node || !node.type) {
78
+ throw new Vega2OLError("Invalid AST node");
79
+ }
80
+ const method = `visit${node.type}`;
81
+ if (typeof this[method] === "function") {
82
+ return this[method](node);
83
+ }
84
+ throw new Vega2OLError(`Unsupported ${node.type} node`);
85
+ }
86
+ // Literal values
87
+ visitLiteral(node) {
88
+ const { value } = node;
89
+ if (value === null)
90
+ return null;
91
+ if (typeof value === "boolean")
92
+ return value;
93
+ if (typeof value === "number")
94
+ return value;
95
+ if (typeof value === "string")
96
+ return value;
97
+ throw new Vega2OLError(`Unsupported literal type: ${typeof value}`);
98
+ }
99
+ // Member access: datum.value, datum['value']
100
+ visitMemberExpression(node) {
101
+ const { object, property } = node;
102
+ if (object.type === "Identifier" && object.name === "datum") {
103
+ if (typeof property === "string") {
104
+ return ["get", property];
105
+ }
106
+ // For computed properties - if property is an Identifier with 'name'
107
+ if (property.type === "Identifier" && property.name) {
108
+ return ["get", property.name];
109
+ }
110
+ // For computed properties like datum[variable]
111
+ return ["get", this.visit(property)];
112
+ }
113
+ throw new Vega2OLError(`Unsupported member access`);
114
+ }
115
+ // Binary operations: a + b, a > b, etc.
116
+ visitBinaryExpression(node) {
117
+ const { operator, left, right } = node;
118
+ const leftOL = this.visit(left);
119
+ 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
+ const olOp = OPERATOR_MAPPING[operator];
131
+ if (!olOp) {
132
+ throw new Vega2OLError(`Unsupported binary operator: ${operator}`);
133
+ }
134
+ return [olOp, leftOL, rightOL];
135
+ }
136
+ // Unary operations: !x, -x, +x
137
+ visitUnaryExpression(node) {
138
+ const { operator, argument } = node;
139
+ const argOL = this.visit(argument);
140
+ if (operator === "!") {
141
+ return ["!", argOL];
142
+ }
143
+ if (operator === "-") {
144
+ return ["*", argOL, -1];
145
+ }
146
+ if (operator === "+") {
147
+ return argOL;
148
+ }
149
+ throw new Vega2OLError(`Unsupported unary operator: ${operator}`);
150
+ }
151
+ // Conditional: test ? consequent : alternate
152
+ visitConditionalExpression(node) {
153
+ const { test, consequent, alternate } = node;
154
+ const testOL = this.visit(test);
155
+ const consequentOL = this.visit(consequent);
156
+ const alternateOL = this.visit(alternate);
157
+ // OL 'case' expression: ['case', condition1, value1, condition2, value2, ..., defaultValue]
158
+ return ["case", testOL, consequentOL, alternateOL];
159
+ }
160
+ // Function calls: floor(x), ceil(y), etc.
161
+ visitCallExpression(node) {
162
+ const { callee, arguments: args } = node;
163
+ let funcName;
164
+ if (callee.type === "Identifier") {
165
+ funcName = callee.name;
166
+ }
167
+ else if (callee.type === "MemberExpression" &&
168
+ callee.object.name === "Math") {
169
+ funcName = callee.property;
170
+ }
171
+ else {
172
+ throw new Vega2OLError(`Unsupported function call`);
173
+ }
174
+ const olFunc = FUNCTION_MAPPING[funcName.toLowerCase()];
175
+ if (!olFunc) {
176
+ throw new Vega2OLError(`Unsupported function: ${funcName}`);
177
+ }
178
+ const olArgs = args.map((arg) => this.visit(arg));
179
+ return [olFunc, ...olArgs];
180
+ }
181
+ // Array literal: [1, 2, 3]
182
+ visitArrayExpression(node) {
183
+ const { elements } = node;
184
+ return elements.map((elem) => this.visit(elem));
185
+ }
186
+ // Identifier/Variable: x, PI, etc.
187
+ visitIdentifier(node) {
188
+ const { name } = node;
189
+ // Check if it's in the current scope
190
+ if (name in this.scope) {
191
+ return this.scope[name];
192
+ }
193
+ // Vega constants
194
+ const vegaConstants = {
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];
208
+ }
209
+ // Return as string (might be a function or field)
210
+ return name;
211
+ }
212
+ }
213
+ /**
214
+ * Main transpiler function
215
+ * @param vegaExpressionStr - A Vega expression string (e.g., "datum.value > 100 ? 'red' : 'blue'")
216
+ * @returns OpenLayers expression array
217
+ */
218
+ export async function vega2ol(vegaExpressionStr) {
219
+ if (typeof vegaExpressionStr !== "string") {
220
+ throw new Vega2OLError("Input must be a string");
221
+ }
222
+ try {
223
+ // Dynamically import parseExpression to handle ESM
224
+ const { parseExpression } = await import("vega-expression");
225
+ // Parse Vega expression to AST
226
+ const ast = parseExpression(vegaExpressionStr);
227
+ // Visit the AST and convert to OL expression
228
+ const visitor = new VegaToOLVisitor();
229
+ const olExpression = visitor.visit(ast);
230
+ return olExpression;
231
+ }
232
+ catch (error) {
233
+ if (error instanceof Vega2OLError) {
234
+ throw error;
235
+ }
236
+ throw new Vega2OLError(`Failed to parse expression: ${error.message}`);
237
+ }
238
+ }
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError, VegaNode } from './main.js';
@@ -0,0 +1,4 @@
1
+ /**
2
+ * vega2ol - Vega expression to OpenLayers expression transpiler
3
+ */
4
+ export { vega2ol, Vega2OLError } from './main.js';
@@ -0,0 +1,25 @@
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 };
@@ -0,0 +1,240 @@
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
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,165 @@
1
+ import { vega2ol, Vega2OLError } from "../src/main.js";
2
+ // Simple assertion helper
3
+ function assertEqual(actual, expected, message) {
4
+ const actualStr = JSON.stringify(actual);
5
+ const expectedStr = JSON.stringify(expected);
6
+ if (actualStr === expectedStr) {
7
+ console.log(` āœ… ${message}`);
8
+ return true;
9
+ }
10
+ else {
11
+ console.log(` āŒ ${message}`);
12
+ console.log(` Expected: ${expectedStr}`);
13
+ console.log(` Got: ${actualStr}`);
14
+ return false;
15
+ }
16
+ }
17
+ // Run tests
18
+ async function runTests() {
19
+ console.log("šŸš€ Testing vega2ol transpiler\n");
20
+ let passed = 0;
21
+ let failed = 0;
22
+ try {
23
+ // Test 1: Literal values
24
+ console.log("šŸ“‹ Literal Values");
25
+ if (assertEqual(await vega2ol("42"), 42, "Number literal"))
26
+ passed++;
27
+ else
28
+ failed++;
29
+ if (assertEqual(await vega2ol("'hello'"), "hello", "String literal"))
30
+ passed++;
31
+ else
32
+ failed++;
33
+ if (assertEqual(await vega2ol("true"), true, "Boolean literal"))
34
+ passed++;
35
+ else
36
+ failed++;
37
+ // Test 2: Member access
38
+ console.log("\nšŸ“‹ Member Access (datum.field)");
39
+ if (assertEqual(await vega2ol("datum.value"), ["get", "value"], "Simple datum access"))
40
+ passed++;
41
+ else
42
+ failed++;
43
+ if (assertEqual(await vega2ol("datum.population"), ["get", "population"], "Datum field access"))
44
+ passed++;
45
+ else
46
+ failed++;
47
+ // Test 3: Comparison operators
48
+ console.log("\nšŸ“‹ Comparison Operators");
49
+ if (assertEqual(await vega2ol("datum.value > 100"), [">", ["get", "value"], 100], "Greater than operator"))
50
+ passed++;
51
+ else
52
+ failed++;
53
+ if (assertEqual(await vega2ol("datum.value == 100"), ["==", ["get", "value"], 100], "Equality operator"))
54
+ passed++;
55
+ else
56
+ failed++;
57
+ // Test 4: Ternary/Conditional (the main example from docs)
58
+ console.log("\nšŸ“‹ Conditional Expressions");
59
+ if (assertEqual(await vega2ol("datum.value > 100 ? 'red' : 'blue'"), ["case", [">", ["get", "value"], 100], "red", "blue"], "Simple ternary expression"))
60
+ passed++;
61
+ else
62
+ failed++;
63
+ // Test 5: Complex nested conditional
64
+ if (assertEqual(await vega2ol("datum.value > 100 ? 'red' : datum.value > 50 ? 'yellow' : 'green'"), [
65
+ "case",
66
+ [">", ["get", "value"], 100],
67
+ "red",
68
+ ["case", [">", ["get", "value"], 50], "yellow", "green"],
69
+ ], "Nested ternary expression"))
70
+ passed++;
71
+ else
72
+ failed++;
73
+ // Test 6: Logical operators
74
+ console.log("\nšŸ“‹ Logical Operators");
75
+ if (assertEqual(await vega2ol("datum.x > 0 && datum.y < 100"), ["all", [">", ["get", "x"], 0], ["<", ["get", "y"], 100]], "AND operator"))
76
+ passed++;
77
+ else
78
+ failed++;
79
+ if (assertEqual(await vega2ol("datum.a > 1 || datum.b < 5"), ["any", [">", ["get", "a"], 1], ["<", ["get", "b"], 5]], "OR operator"))
80
+ passed++;
81
+ else
82
+ failed++;
83
+ // Test 7: Arithmetic operators
84
+ console.log("\nšŸ“‹ Arithmetic Operators");
85
+ if (assertEqual(await vega2ol("datum.a + datum.b"), ["+", ["get", "a"], ["get", "b"]], "Addition operator"))
86
+ passed++;
87
+ else
88
+ failed++;
89
+ if (assertEqual(await vega2ol("datum.a * datum.b"), ["*", ["get", "a"], ["get", "b"]], "Multiplication operator"))
90
+ passed++;
91
+ else
92
+ failed++;
93
+ // Test 8: Unary operations
94
+ console.log("\nšŸ“‹ Unary Operations");
95
+ if (assertEqual(await vega2ol("!datum.active"), ["!", ["get", "active"]], "Negation operator"))
96
+ passed++;
97
+ else
98
+ failed++;
99
+ if (assertEqual(await vega2ol("-datum.value"), ["*", ["get", "value"], -1], "Unary minus"))
100
+ passed++;
101
+ else
102
+ failed++;
103
+ // Test 9: Function calls
104
+ console.log("\nšŸ“‹ Function Calls");
105
+ if (assertEqual(await vega2ol("floor(datum.value)"), ["floor", ["get", "value"]], "Floor function"))
106
+ passed++;
107
+ else
108
+ failed++;
109
+ if (assertEqual(await vega2ol("pow(datum.base, 2)"), ["pow", ["get", "base"], 2], "Pow function with multiple args"))
110
+ passed++;
111
+ else
112
+ failed++;
113
+ // Test 10: Real-world example - Color mapping
114
+ console.log("\nšŸ“‹ Real-world Examples");
115
+ if (assertEqual(await vega2ol("datum.population > 1000000 ? '#FF0000' : datum.population > 500000 ? '#FFA500' : '#FFFF00'"), [
116
+ "case",
117
+ [">", ["get", "population"], 1000000],
118
+ "#FF0000",
119
+ ["case", [">", ["get", "population"], 500000], "#FFA500", "#FFFF00"],
120
+ ], "Color mapping by population size"))
121
+ passed++;
122
+ else
123
+ failed++;
124
+ // Test 11: Array expressions
125
+ console.log("\nšŸ“‹ Array Expressions");
126
+ if (assertEqual(await vega2ol("[1, 2, 3]"), [1, 2, 3], "Array literal"))
127
+ passed++;
128
+ else
129
+ failed++;
130
+ // Test 12: Constants
131
+ console.log("\nšŸ“‹ Vega Constants");
132
+ if (assertEqual(await vega2ol("PI"), Math.PI, "PI constant"))
133
+ passed++;
134
+ else
135
+ failed++;
136
+ // Test 13: Error handling
137
+ console.log("\nšŸ“‹ Error Handling");
138
+ try {
139
+ await vega2ol(null);
140
+ console.log("āŒ Should throw for null input");
141
+ failed++;
142
+ }
143
+ catch (error) {
144
+ if (error instanceof Vega2OLError) {
145
+ console.log(" āœ… Throws Vega2OLError for invalid input");
146
+ passed++;
147
+ }
148
+ else {
149
+ console.log("āŒ Wrong error type");
150
+ failed++;
151
+ }
152
+ }
153
+ }
154
+ catch (error) {
155
+ console.error("šŸ”„ Test suite error:", error);
156
+ failed++;
157
+ }
158
+ // Summary
159
+ console.log("\n" + "=".repeat(50));
160
+ console.log(`šŸ“Š Test Results: ${passed} passed, ${failed} failed`);
161
+ console.log("=".repeat(50));
162
+ return failed === 0 ? 0 : 1;
163
+ }
164
+ // Run the tests
165
+ runTests().then((code) => process.exit(code));
package/package.json ADDED
@@ -0,0 +1,45 @@
1
+ {
2
+ "name": "vega2ol",
3
+ "version": "1.0.0",
4
+ "description": "Vega expression to Open-Layers expression transpiler",
5
+ "keywords": [
6
+ "vega",
7
+ "openlayers",
8
+ "expression",
9
+ "transpiler",
10
+ "geospatial"
11
+ ],
12
+ "homepage": "https://github.com/nakul-py/vega2ol#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/nakul-py/vega2ol/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/nakul-py/vega2ol.git"
19
+ },
20
+ "license": "BSD-3-Clause",
21
+ "author": "nakul",
22
+ "type": "module",
23
+ "main": "dist/index.js",
24
+ "types": "dist/index.d.ts",
25
+ "directories": {
26
+ "test": "tests"
27
+ },
28
+ "files": [
29
+ "dist"
30
+ ],
31
+ "scripts": {
32
+ "build": "tsc",
33
+ "test": "node dist/tests/test-basic.js",
34
+ "dev": "tsc --watch"
35
+ },
36
+ "dependencies": {
37
+ "vega-expression": "^6.1.0"
38
+ },
39
+ "devDependencies": {
40
+ "@types/node": "^20.19.39",
41
+ "ol": "^9.2.4",
42
+ "ts-node": "^10.9.2",
43
+ "typescript": "^5.9.3"
44
+ }
45
+ }