├── .gitignore ├── examples ├── concat.expr └── concat.json ├── .babelrc ├── lib ├── errors.js ├── compile.js ├── decompile.js ├── util.js ├── textblock.js ├── transform.js ├── untransform.js ├── untokenize.js ├── source-map.js ├── tokenize.js ├── parse.js └── unparse.js ├── scripts └── build.sh ├── .circleci └── config.yml ├── package.json ├── test ├── perf.js └── index.js ├── LICENSE ├── docs ├── fmt.css ├── fmt.html ├── app.css ├── index.html └── base-style.json ├── site ├── fmt.css ├── fmt.html ├── app.css ├── index.html ├── app.js ├── fmt.js └── base-style.json ├── index.js ├── bin └── cli.js └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | /old 2 | /node_modules 3 | -------------------------------------------------------------------------------- /examples/concat.expr: -------------------------------------------------------------------------------- 1 | concat("Hello", " ", @name) 2 | -------------------------------------------------------------------------------- /examples/concat.json: -------------------------------------------------------------------------------- 1 | [ 2 | "concat", 3 | "Hello", 4 | " ", 5 | [ 6 | "get", 7 | "name" 8 | ] 9 | ] 10 | -------------------------------------------------------------------------------- /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "plugins": [ 3 | "transform-object-rest-spread", 4 | ["transform-runtime", { 5 | "polyfill": false, 6 | "regenerator": true 7 | }] 8 | ], 9 | "presets": ["env"] 10 | } 11 | -------------------------------------------------------------------------------- /lib/errors.js: -------------------------------------------------------------------------------- 1 | class ParseError extends Error { 2 | constructor(message, opts={}) { 3 | super(message); 4 | this.line = opts.token.row; 5 | this.column = opts.token.col; 6 | } 7 | } 8 | 9 | 10 | module.exports = { 11 | ParseError 12 | } 13 | -------------------------------------------------------------------------------- /scripts/build.sh: -------------------------------------------------------------------------------- 1 | DIR="$(dirname "$0")" 2 | 3 | mkdir -p $DIR/../docs 4 | cp $DIR/../site/* $DIR/../docs/ 5 | $DIR/../node_modules/.bin/browserify $DIR/../site/app.js > $DIR/../docs/app.js 6 | $DIR/../node_modules/.bin/browserify $DIR/../site/fmt.js > $DIR/../docs/fmt.js 7 | -------------------------------------------------------------------------------- /lib/compile.js: -------------------------------------------------------------------------------- 1 | const tokenize = require("./tokenize"); 2 | const parse = require("./parse"); 3 | const transform = require("./transform"); 4 | 5 | 6 | module.exports = function(code) { 7 | const tokens = tokenize(code); 8 | const ast = parse(tokens); 9 | const mglJSON = transform(ast); 10 | return mglJSON; 11 | } 12 | -------------------------------------------------------------------------------- /lib/decompile.js: -------------------------------------------------------------------------------- 1 | const untransform = require("./untransform"); 2 | const unparse = require("./unparse"); 3 | const untokenize = require("./untokenize"); 4 | 5 | 6 | module.exports = function(mglJSON) { 7 | const ast = untransform(mglJSON); 8 | const tokens = unparse(ast); 9 | const rslt = untokenize(tokens); 10 | return rslt.code; 11 | } 12 | -------------------------------------------------------------------------------- /lib/util.js: -------------------------------------------------------------------------------- 1 | function parseNumber(value) { 2 | // Work out the type 3 | if(value.match(/^[+-]?[0-9]+$/)) { 4 | value = parseInt(value, 10); 5 | } 6 | else if (value.match(/^[+-]?[0-9]+[.][0-9]+$/)) { 7 | value = parseFloat(value, 10); 8 | } 9 | else { 10 | throw `'${value}' is invalid number`; 11 | } 12 | 13 | return value; 14 | } 15 | 16 | module.exports = { 17 | parseNumber 18 | } 19 | -------------------------------------------------------------------------------- /.circleci/config.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | jobs: 3 | build: 4 | docker: 5 | # specify the version you desire here 6 | - image: circleci/node:8.9 7 | working_directory: ~/repo 8 | steps: 9 | - checkout 10 | # Download and cache dependencies 11 | - restore_cache: 12 | keys: 13 | - v1-dependencies-{{ checksum "package.json" }} 14 | # fallback to using the latest cache if no exact match is found 15 | - v1-dependencies- 16 | 17 | - run: npm install 18 | 19 | - save_cache: 20 | paths: 21 | - node_modules 22 | key: v1-dependencies-{{ checksum "package.json" }} 23 | 24 | # run tests! 25 | - run: npm test 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "simple-expr", 3 | "version": "0.1.2", 4 | "bin": { 5 | "simple-expr": "./bin/cli.js" 6 | }, 7 | "scripts": { 8 | "test": "mocha test/index.js", 9 | "perf": "node test/perf.js" 10 | }, 11 | "dependencies": { 12 | "@mapbox/mapbox-gl-style-spec": "^11.1.0", 13 | "source-map": "^0.6.1", 14 | "yargs": "^11.0.0" 15 | }, 16 | "browserify": { 17 | "transform": [ 18 | "babelify" 19 | ] 20 | }, 21 | "devDependencies": { 22 | "babel-core": "^6.26.0", 23 | "babel-plugin-transform-object-rest-spread": "^6.26.0", 24 | "babel-plugin-transform-runtime": "^6.23.0", 25 | "babel-polyfill": "^6.26.0", 26 | "babel-preset-env": "^1.6.1", 27 | "babelify": "^8.0.0", 28 | "benchmark": "^2.1.4", 29 | "browserify": "^16.1.0", 30 | "dom-delegate": "^2.0.3", 31 | "mocha": "^5.0.0", 32 | "readme-tester": "github:orangemug/readme-tester#v0.7.0" 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /lib/textblock.js: -------------------------------------------------------------------------------- 1 | function indexToColRow(input, positions) { 2 | let total = 0; 3 | const out = []; 4 | 5 | input.split("\n") 6 | .forEach(function(line, colIdx) { 7 | const prevTotal = total; 8 | total += line.length + 1/*The removed '\n' char */; 9 | 10 | positions.forEach(function(_pos, idx) { 11 | if(!out.hasOwnProperty(idx) && _pos < total) { 12 | out[idx] = { 13 | col: _pos - prevTotal, 14 | row: colIdx 15 | } 16 | } 17 | }) 18 | }) 19 | 20 | return out; 21 | } 22 | 23 | function colRowToIndex(input, positions) { 24 | let total = 0; 25 | const out = []; 26 | 27 | input.split("\n") 28 | .forEach(function(line, rowIdx) { 29 | positions.forEach(function(_pos, idx) { 30 | if(rowIdx == _pos.row) { 31 | out[idx] = total + _pos.col; 32 | } 33 | }) 34 | 35 | total += line.length + 1/*The removed '\n' char */; 36 | }) 37 | 38 | return out; 39 | } 40 | 41 | module.exports = { 42 | indexToColRow, 43 | colRowToIndex 44 | } 45 | -------------------------------------------------------------------------------- /test/perf.js: -------------------------------------------------------------------------------- 1 | var Benchmark = require('benchmark'); 2 | var simpleExr = require("../"); 3 | 4 | 5 | var suite = new Benchmark.Suite; 6 | 7 | var code = [ 8 | { 9 | name: "simple", 10 | code: `concat("Hello", " ", @name)` 11 | }, 12 | { 13 | name: "complex", 14 | code: ` 15 | interpolate( 16 | linear(), @score, 17 | 0, rgb(255, 0, 0), 18 | 50, rgb(0, 255, 0), 19 | 100, rgb(0, 0, 255) 20 | ) 21 | ` 22 | }, 23 | ] 24 | 25 | // Add in compiled code. 26 | code.forEach(function(item) { 27 | item.json = simpleExr.compiler(item.code); 28 | }) 29 | 30 | // Setup the benchmarks 31 | code.forEach(function(item) { 32 | var name = item.name; 33 | var code = item.code; 34 | var json = item.json; 35 | 36 | suite.add(`simpleExr.compile(${name})`, function() { 37 | simpleExr.compiler(code); 38 | }) 39 | 40 | suite.add(`simpleExr.decompile(${name})`, function() { 41 | simpleExr.decompile(json); 42 | }) 43 | }) 44 | 45 | // Setup logging 46 | suite 47 | .on('cycle', function(event) { 48 | console.log(String(event.target)); 49 | }) 50 | .run({ 'async': true }); 51 | 52 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2017 Jamie Blair 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /docs/fmt.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | } 7 | 8 | .input { 9 | width: 100%; 10 | height: 120px; 11 | font-family: monospace; 12 | font-size: 13px; 13 | border: solid 1px #ccc; 14 | padding: 4px; 15 | resize: vertical; 16 | } 17 | 18 | .debug { 19 | background: #eee; 20 | border: solid 1px #ccc; 21 | border-top-width: 0; 22 | padding: 4px; 23 | } 24 | 25 | .mappings-generated { 26 | margin: 0; 27 | border: solid 1px #ccc; 28 | padding: 4px; 29 | } 30 | 31 | h2 { 32 | font-size: 1.2em; 33 | margin-bottom: 0.2em; 34 | } 35 | 36 | h3 { 37 | font-size: 1em; 38 | margin-bottom: 0.2em; 39 | } 40 | 41 | .mappings-source { 42 | margin: 0; 43 | border: solid 1px #ccc; 44 | padding: 4px; 45 | } 46 | 47 | .mapping { 48 | width: 1px; 49 | height: 1em; 50 | background: red; 51 | display: inline-block; 52 | position: absolute; 53 | z-index: 1; 54 | } 55 | 56 | .output { 57 | width: 100%; 58 | height: 120px; 59 | border: solid 1px #ccc; 60 | padding: 4px; 61 | margin: 0; 62 | } 63 | 64 | .debug-selected { 65 | background-color: hsla(60, 95%, 50%, 0.8); 66 | border-left: solid 1px #000; 67 | margin-left: -1px; 68 | } 69 | -------------------------------------------------------------------------------- /site/fmt.css: -------------------------------------------------------------------------------- 1 | html { 2 | box-sizing: border-box; 3 | } 4 | *, *:before, *:after { 5 | box-sizing: inherit; 6 | } 7 | 8 | .input { 9 | width: 100%; 10 | height: 120px; 11 | font-family: monospace; 12 | font-size: 13px; 13 | border: solid 1px #ccc; 14 | padding: 4px; 15 | resize: vertical; 16 | } 17 | 18 | .debug { 19 | background: #eee; 20 | border: solid 1px #ccc; 21 | border-top-width: 0; 22 | padding: 4px; 23 | } 24 | 25 | .mappings-generated { 26 | margin: 0; 27 | border: solid 1px #ccc; 28 | padding: 4px; 29 | } 30 | 31 | h2 { 32 | font-size: 1.2em; 33 | margin-bottom: 0.2em; 34 | } 35 | 36 | h3 { 37 | font-size: 1em; 38 | margin-bottom: 0.2em; 39 | } 40 | 41 | .mappings-source { 42 | margin: 0; 43 | border: solid 1px #ccc; 44 | padding: 4px; 45 | } 46 | 47 | .mapping { 48 | width: 1px; 49 | height: 1em; 50 | background: red; 51 | display: inline-block; 52 | position: absolute; 53 | z-index: 1; 54 | } 55 | 56 | .output { 57 | width: 100%; 58 | height: 120px; 59 | border: solid 1px #ccc; 60 | padding: 4px; 61 | margin: 0; 62 | } 63 | 64 | .debug-selected { 65 | background-color: hsla(60, 95%, 50%, 0.8); 66 | border-left: solid 1px #000; 67 | margin-left: -1px; 68 | } 69 | -------------------------------------------------------------------------------- /lib/transform.js: -------------------------------------------------------------------------------- 1 | function removeUndefined(v) { 2 | return v !== undefined; 3 | } 4 | 5 | const IGNORE_NODES = [ 6 | "OpenParen", 7 | "CloseParen", 8 | "WhiteSpace" 9 | ] 10 | 11 | module.exports = function(nodes) { 12 | function walk(node) { 13 | if( 14 | IGNORE_NODES.indexOf(node.type) > -1 15 | ) { 16 | // Ignoring node. 17 | return; 18 | } 19 | if(node.type === "CallExpression") { 20 | const args = node.params 21 | .map(walk) 22 | .filter(removeUndefined) 23 | 24 | return [node.value].concat(args); 25 | } 26 | else if (node.type === "StringLiteral") { 27 | return node.value; 28 | } 29 | else if (node.type === "NumberLiteral") { 30 | return node.value; 31 | } 32 | else if (node.type === "FeatureRef") { 33 | return ["get", node.value]; 34 | } 35 | } 36 | 37 | if(nodes.body.length < 1) { 38 | return []; 39 | } 40 | else { 41 | const out = nodes.body 42 | .map(walk) 43 | .filter(removeUndefined) 44 | 45 | if(out.length > 1) { 46 | throw "Invalid AST" 47 | } 48 | else if(out.length < 1) { 49 | return []; 50 | } 51 | else { 52 | return out[0]; 53 | } 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /docs/fmt.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | fmt 7 | 8 | 9 | 10 | 11 |
12 |

fmt

13 |

14 | An attempt at an auto formatter for simple-expr. 15 |

16 |

17 | The steps are 18 |

19 |
    20 |
  1. Compile the code
  2. 21 |
  3. Decompile the code again, whitespace will differ
  4. 22 |
  5. Replace cursor to correct position on selection with sourcemap
  6. 23 |
24 |
25 | 26 |

Formatter

27 |

28 | Reformat the code, moving the cursor will translate to the generated (reformated) code. 29 |

30 |

Original (editable)

31 | 36 | 37 |

Reformatted

38 |

39 |     
40 | 41 |

Source maps

42 |

43 | The red lines show the soure map markers. 44 |

45 |

Source

46 |

47 |     

Generated

48 |

49 | 
50 |     
51 |     
52 |   
53 | 
54 | 
55 | 


--------------------------------------------------------------------------------
/site/fmt.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     fmt
 7 |     
 8 |   
 9 |   
10 | 
11 |     
12 |

fmt

13 |

14 | An attempt at an auto formatter for simple-expr. 15 |

16 |

17 | The steps are 18 |

19 |
    20 |
  1. Compile the code
  2. 21 |
  3. Decompile the code again, whitespace will differ
  4. 22 |
  5. Replace cursor to correct position on selection with sourcemap
  6. 23 |
24 |
25 | 26 |

Formatter

27 |

28 | Reformat the code, moving the cursor will translate to the generated (reformated) code. 29 |

30 |

Original (editable)

31 | 36 | 37 |

Reformatted

38 |

39 |     
40 | 41 |

Source maps

42 |

43 | The red lines show the soure map markers. 44 |

45 |

Source

46 |

47 |     

Generated

48 |

49 | 
50 |     
51 |     
52 |   
53 | 
54 | 
55 | 


--------------------------------------------------------------------------------
/lib/untransform.js:
--------------------------------------------------------------------------------
 1 | module.exports = function untransform(node) {
 2 |   function walk(node, depth) {
 3 |     depth = depth || 0;
 4 | 
 5 |     if(node.length < 1) {
 6 |       if(depth > 0) {
 7 |         throw "node requires function name";
 8 |       }
 9 |       else {
10 |         // Empty expression
11 |         return;
12 |       }
13 |     }
14 | 
15 |     const command = node[0];
16 |     let args = node.slice(1);
17 | 
18 |     if(command == "get") {
19 |       if(node.length !== 2) {
20 |         throw "'get' has too many params";
21 |       }
22 |       return {
23 |         type: "FeatureRef",
24 |         value: node[1]
25 |       }
26 |     }
27 |     else {
28 |       args = args.map(function(childNode) {
29 |         if(Array.isArray(childNode)) {
30 |           return walk(childNode, depth+1)
31 |         }
32 |         else if(typeof(childNode) === "number") {
33 |           return {
34 |             type: "NumberLiteral", 
35 |             value: childNode
36 |           }
37 |         }
38 |         else {
39 |           return {
40 |             type: "StringLiteral", 
41 |             value: childNode
42 |           }
43 |         }
44 |       })
45 | 
46 |       return {
47 |         type: "CallExpression",
48 |         value: command,
49 |         params: args
50 |       }
51 |     }
52 |   }
53 | 
54 |   const ast = {
55 |     "type": "Program",
56 |     "body": []
57 |   }
58 | 
59 |   const body = walk(node);
60 |   if(body) {
61 |     ast.body.push(body)
62 |   }
63 | 
64 |   return ast;
65 | }
66 | 


--------------------------------------------------------------------------------
/lib/untokenize.js:
--------------------------------------------------------------------------------
 1 | const sourceMap = require("source-map");
 2 | 
 3 | 
 4 | module.exports = function(tokens) {
 5 | 
 6 |   function walk(node) {
 7 |     const type = node.type;
 8 | 
 9 |     if(type === "feature_ref") {
10 |       return "@"+node.value
11 |     }
12 |     else if(type === "command") {
13 |       return node.value
14 |     }
15 |     else if(type === "string") {
16 |       return "\""+node.value+"\"";
17 |     }
18 |     else if(type === "open_paren") {
19 |       return node.value;
20 |     }
21 |     else if(type === "close_paren") {
22 |       return node.value;
23 |     }
24 |     else if(type === "number") {
25 |       return node.value;
26 |     }
27 |     else if(type === "arg_sep") {
28 |       return ",";
29 |     }
30 |     else if(type === "whitespace") {
31 |       return node.value;
32 |     }
33 |     else {
34 |       throw "Unrecognised node type '"+type+"'"
35 |     }
36 |   }
37 | 
38 | 
39 |   let map = new sourceMap.SourceMapGenerator({});
40 |   let genCol = 0;
41 |   let genRow = 0;
42 | 
43 |   function genMapping(token) {
44 |     if(token.hasOwnProperty("row") || token.hasOwnProperty("col")) {
45 |       const mapData = {
46 |         generated: {
47 |           line: genRow+1,
48 |           column: genCol
49 |         },
50 |         source: "foo.js",
51 |         original: {
52 |           line: token.row+1,
53 |           column: token.col
54 |         },
55 |         name: "foo"
56 |       };
57 |       map.addMapping(mapData);
58 |     }
59 |   }
60 | 
61 |   const rslt = tokens.map(function(token) {
62 |     genMapping(token);
63 |     const out = String(walk(token));
64 |     genCol += out.length;
65 |     return out;
66 |   }).join("");
67 | 
68 |   return {
69 |     code: rslt,
70 |     map: map.toJSON()
71 |   }
72 | }
73 | 


--------------------------------------------------------------------------------
/lib/source-map.js:
--------------------------------------------------------------------------------
 1 | const sourceMap = require("source-map");
 2 | 
 3 | 
 4 | async function genOrigPosition(map, origPos, source) {
 5 |   const consumer = await new sourceMap.SourceMapConsumer(map)
 6 |   consumer.computeColumnSpans();
 7 | 
 8 |   const genPos = consumer.generatedPositionFor({
 9 |     line: origPos.line,
10 |     column: origPos.column,
11 |     source: source,
12 |   })
13 | 
14 |   function getOrigPos(genPos, origPos) {
15 |     let sourcePos;
16 |     let skipRest = false;
17 |     let offset = 0;
18 | 
19 |     consumer.eachMapping(function (m) {
20 |       if(skipRest) {
21 |         return;
22 |       }
23 |       // If we've found an exact match stop!
24 |       else if(
25 |         m.originalLine == origPos.line
26 |         && m.originalColumn == origPos.column
27 |       )  {
28 |         sourcePos = m;
29 |         skipRest = true;
30 |         offset = 0;
31 |       }
32 |       // If we're still behind our target
33 |       else if(
34 |         m.generatedLine <= genPos.line &&
35 |         m.generatedColumn <= genPos.column
36 |       ) {
37 |         sourcePos = m;
38 |         offset = origPos.column - sourcePos.originalColumn
39 |       }
40 | 
41 |       // If the cursor position is ahead of sourcePos reset the offset
42 |       if(
43 |         sourcePos &&
44 |         sourcePos.originalColumn > origPos.column
45 |       ) {
46 |         offset = 0;
47 |       }
48 |     })
49 | 
50 |     return offset
51 |   }
52 | 
53 |   const sourcePos = consumer.originalPositionFor(genPos);
54 | 
55 |   var offset = getOrigPos(genPos, origPos);
56 | 
57 |   const newGenPos = {
58 |     line: genPos.line,
59 |     column: Math.min(genPos.column+offset)
60 |   };
61 | 
62 |   return newGenPos;
63 | }
64 | 
65 | async function getMappings(map) {
66 |   const out = [];
67 |   const consumer = await new sourceMap.SourceMapConsumer(map)
68 |   consumer.eachMapping(function(m) {
69 |     out.push(m);
70 |   })
71 | 
72 |   return out;
73 | }
74 | 
75 | 
76 | module.exports = {
77 |   genOrigPosition,
78 |   getMappings
79 | };
80 | 


--------------------------------------------------------------------------------
/docs/app.css:
--------------------------------------------------------------------------------
  1 | html {
  2 |   box-sizing: border-box;
  3 | }
  4 | *, *:before, *:after {
  5 |   box-sizing: inherit;
  6 | }
  7 | 
  8 | html, body {
  9 |   height: 100%;
 10 | }
 11 | 
 12 | body {
 13 |   margin: 0;
 14 |   overflow: hidden;
 15 | }
 16 | 
 17 | a {
 18 |   color: inherit;
 19 | }
 20 | 
 21 | .app {
 22 |   height: 100%;
 23 |   display: flex;
 24 |   flex-direction: column;
 25 |   overflow: hidden;
 26 | }
 27 | 
 28 | .app__header {
 29 |   display: flex;
 30 |   align-items: center;
 31 |   padding: 4px 6px;
 32 | }
 33 | 
 34 | .app__header__title {
 35 |   flex: 1;
 36 | }
 37 | 
 38 | .app__header__title h1 {
 39 |   font-size: 28px;
 40 |   margin: 0;
 41 | }
 42 | 
 43 | .app__header__subtitle {
 44 |   font-size: 14px;
 45 | }
 46 | 
 47 | .toolbox {
 48 |   padding: 0 6px;
 49 | }
 50 | 
 51 | .toolbox .icon {
 52 |   margin-right: 4px;
 53 | }
 54 | 
 55 | .icon {
 56 |   vertical-align: middle;
 57 |   text-decoration: none;
 58 | }
 59 | 
 60 | 
 61 | .app__main {
 62 |   flex: 1;
 63 |   position: relative;
 64 |   overflow: hidden;
 65 |   display: flex;
 66 |   flex-direction: column;
 67 | }
 68 | 
 69 | .row {
 70 |   flex: 1 1 50%;
 71 |   max-height: 50%;
 72 |   display: flex;
 73 | }
 74 | 
 75 | .row:nth-child(n+2) {
 76 |   border-top: solid 1px #555;
 77 | }
 78 | 
 79 | .cell {
 80 |   position: relative;
 81 |   display: flex;
 82 |   flex-direction: column;
 83 |   width: 50%;
 84 | }
 85 | 
 86 | .cell:nth-child(n+2) {
 87 |   border-left: solid 1px #555;
 88 | }
 89 | 
 90 | .cell__header {
 91 |   flex: 0;
 92 |   background: black;
 93 |   color: white;
 94 |   padding: 4px 6px;
 95 | }
 96 | 
 97 | .cell__content {
 98 |   flex: 1;
 99 |   position: relative;
100 |   overflow-x: auto;
101 | }
102 | 
103 | .editor {
104 |   width: 100%;
105 |   height: 100%;
106 |   overflow-y: scroll;
107 |   border: none;
108 |   resize: none;
109 |   font-family: monospace;
110 |   font-size: 13px;
111 | }
112 | 
113 | .editor__message {
114 |   background: #eee;
115 |   padding: 4px 6px;
116 |   border-bottom: solid 1px #ddd;
117 | }
118 | 
119 | .result {
120 |   margin: 0;
121 |   overflow-y: scroll;
122 | }
123 | 
124 | .geojson {
125 |   margin: 0;
126 |   overflow-y: scroll;
127 | }
128 | 
129 | .map {
130 |   background: #333;
131 |   height: 100%;
132 | }
133 | 
134 | .examples {
135 |   float: right;
136 | }
137 | 


--------------------------------------------------------------------------------
/site/app.css:
--------------------------------------------------------------------------------
  1 | html {
  2 |   box-sizing: border-box;
  3 | }
  4 | *, *:before, *:after {
  5 |   box-sizing: inherit;
  6 | }
  7 | 
  8 | html, body {
  9 |   height: 100%;
 10 | }
 11 | 
 12 | body {
 13 |   margin: 0;
 14 |   overflow: hidden;
 15 | }
 16 | 
 17 | a {
 18 |   color: inherit;
 19 | }
 20 | 
 21 | .app {
 22 |   height: 100%;
 23 |   display: flex;
 24 |   flex-direction: column;
 25 |   overflow: hidden;
 26 | }
 27 | 
 28 | .app__header {
 29 |   display: flex;
 30 |   align-items: center;
 31 |   padding: 4px 6px;
 32 | }
 33 | 
 34 | .app__header__title {
 35 |   flex: 1;
 36 | }
 37 | 
 38 | .app__header__title h1 {
 39 |   font-size: 28px;
 40 |   margin: 0;
 41 | }
 42 | 
 43 | .app__header__subtitle {
 44 |   font-size: 14px;
 45 | }
 46 | 
 47 | .toolbox {
 48 |   padding: 0 6px;
 49 | }
 50 | 
 51 | .toolbox .icon {
 52 |   margin-right: 4px;
 53 | }
 54 | 
 55 | .icon {
 56 |   vertical-align: middle;
 57 |   text-decoration: none;
 58 | }
 59 | 
 60 | 
 61 | .app__main {
 62 |   flex: 1;
 63 |   position: relative;
 64 |   overflow: hidden;
 65 |   display: flex;
 66 |   flex-direction: column;
 67 | }
 68 | 
 69 | .row {
 70 |   flex: 1 1 50%;
 71 |   max-height: 50%;
 72 |   display: flex;
 73 | }
 74 | 
 75 | .row:nth-child(n+2) {
 76 |   border-top: solid 1px #555;
 77 | }
 78 | 
 79 | .cell {
 80 |   position: relative;
 81 |   display: flex;
 82 |   flex-direction: column;
 83 |   width: 50%;
 84 | }
 85 | 
 86 | .cell:nth-child(n+2) {
 87 |   border-left: solid 1px #555;
 88 | }
 89 | 
 90 | .cell__header {
 91 |   flex: 0;
 92 |   background: black;
 93 |   color: white;
 94 |   padding: 4px 6px;
 95 | }
 96 | 
 97 | .cell__content {
 98 |   flex: 1;
 99 |   position: relative;
100 |   overflow-x: auto;
101 | }
102 | 
103 | .editor {
104 |   width: 100%;
105 |   height: 100%;
106 |   overflow-y: scroll;
107 |   border: none;
108 |   resize: none;
109 |   font-family: monospace;
110 |   font-size: 13px;
111 | }
112 | 
113 | .editor__message {
114 |   background: #eee;
115 |   padding: 4px 6px;
116 |   border-bottom: solid 1px #ddd;
117 | }
118 | 
119 | .result {
120 |   margin: 0;
121 |   overflow-y: scroll;
122 | }
123 | 
124 | .geojson {
125 |   margin: 0;
126 |   overflow-y: scroll;
127 | }
128 | 
129 | .map {
130 |   background: #333;
131 |   height: 100%;
132 | }
133 | 
134 | .examples {
135 |   float: right;
136 | }
137 | 


--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
  1 | var sourceMap = require("source-map");
  2 | 
  3 | 
  4 | function decompileFromAst(node) {
  5 |   var map = new sourceMap.SourceMapGenerator({});
  6 | 
  7 |   function genMapping(name, opts) {
  8 |     map.addMapping({
  9 |       generated: {
 10 |         line: opts.generated.line,
 11 |         column: opts.generated.column
 12 |       },
 13 |       source: "foo.js",
 14 |       original: {
 15 |         line: opts.original.line,
 16 |         column: opts.original.column
 17 |       },
 18 |       name: "foo"
 19 |     });
 20 |   }
 21 | 
 22 |   var outStr = "";
 23 | 
 24 |   function go(node, depth) {
 25 |     depth = depth || 0;
 26 | 
 27 |     if(node.type === "CallExpression") {
 28 |       genMapping(node.type, {
 29 |         original: {
 30 |           line: node.row+1,
 31 |           column: node.col
 32 |         },
 33 |         generated: {line: 1, column: outStr.length}
 34 |       })
 35 |       outStr = outStr + `${node.name}(`;
 36 |       node.params.forEach(function(_node, idx) {
 37 |         go(_node, depth)
 38 |         if(idx < node.params.length -1) {
 39 |           outStr += ", "
 40 |         }
 41 |       })
 42 | 
 43 |       outStr = outStr + `)`;
 44 |     }
 45 |     else if(node.type === "NumberLiteral") {
 46 |       genMapping(node.type, {
 47 |         original: {
 48 |           line: node.row+1,
 49 |           column: node.col
 50 |         },
 51 |         generated: {line: 1, column: outStr.length}
 52 |       })
 53 |       outStr = outStr + node.value;
 54 |     }
 55 |     else if(node.type === "StringLiteral") {
 56 |       genMapping(node.type, {
 57 |         original: {
 58 |           line: node.row+1,
 59 |           column: node.col
 60 |         },
 61 |         generated: {line: 1, column: outStr.length}
 62 |       })
 63 |       outStr = outStr + JSON.stringify(node.value);
 64 |     }
 65 |     else if(node.type === "FeatureRef") {
 66 |       genMapping(node.type, {
 67 |         original: {
 68 |           line: node.row+1,
 69 |           column: node.col
 70 |         },
 71 |         generated: {line: 1, column: outStr.length}
 72 |       })
 73 |       outStr = outStr + "@"+node.value;
 74 |     }
 75 | 
 76 |   }
 77 | 
 78 |   genMapping(node.type, {
 79 |     original: {line: 1, column: 0},
 80 |     generated: {line: 1, column: 0}
 81 |   })
 82 | 
 83 |   go(node.body[0])
 84 |   return {
 85 |     code: outStr,
 86 |     map: map.toJSON()
 87 |   };
 88 | }
 89 | 
 90 | module.exports = {
 91 |   compile: require("./lib/compile"),
 92 |   decompile: require("./lib/decompile"),
 93 |   // Compile steps
 94 |   tokenize: require("./lib/tokenize"),
 95 |   parse: require("./lib/parse"),
 96 |   transform: require("./lib/transform"),
 97 |   // Decompile steps
 98 |   untransform: require("./lib/untransform"),
 99 |   unparse: require("./lib/unparse"),
100 |   untokenize: require("./lib/untokenize"),
101 |   // Deprecated APIs
102 |   decompileFromAst,
103 | };
104 | 
105 | 


--------------------------------------------------------------------------------
/docs/index.html:
--------------------------------------------------------------------------------
 1 | 
 2 | 
 3 |   
 4 |     
 5 |     
 6 |     simple-expr
 7 |     
 8 |   
 9 |   
10 | 
11 |     
12 |
13 |
14 |

simple-expr

15 |
Simple expression language for mapbox-gl-js expressions
16 |
17 | 28 |
29 |
30 |
31 |
32 |
33 | Simple-expr editor 34 | 36 |
37 |
38 | Defines a circle-color property for layer without filter for the below GeoJSON source 39 |
40 | 47 |
48 |
49 |
MapboxGL expression
50 |
51 |
52 |               
53 |
54 |
55 |
56 |
57 |
58 |
GeoJSON
59 |
60 |
61 |               
62 |
63 |
64 |
65 |
Map with expression
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /site/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | simple-expr 7 | 8 | 9 | 10 | 11 |
12 |
13 |
14 |

simple-expr

15 |
Simple expression language for mapbox-gl-js expressions
16 |
17 | 28 |
29 |
30 |
31 |
32 |
33 | Simple-expr editor 34 | 36 |
37 |
38 | Defines a circle-color property for layer without filter for the below GeoJSON source 39 |
40 | 47 |
48 |
49 |
MapboxGL expression
50 |
51 |
52 |               
53 |
54 |
55 |
56 |
57 |
58 |
GeoJSON
59 |
60 |
61 |               
62 |
63 |
64 |
65 |
Map with expression
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 |
74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | -------------------------------------------------------------------------------- /site/app.js: -------------------------------------------------------------------------------- 1 | var simpleExpr = require("../"); 2 | 3 | function makePoint(lon, lat, props) { 4 | return { 5 | "type": "Feature", 6 | "geometry": { 7 | "type": "Point", 8 | "coordinates": [lon, lat] 9 | }, 10 | "properties": props 11 | } 12 | } 13 | 14 | var state = { 15 | geojson: { 16 | "type": "FeatureCollection", 17 | "features": [ 18 | makePoint(8.5217, 47.3769, {score: 20}), 19 | makePoint(8.5317, 47.3569, {score: 40}), 20 | makePoint(8.5417, 47.3969, {score: 60}), 21 | makePoint(8.5517, 47.3869, {score: 80}), 22 | makePoint(8.5117, 47.3769, {score: 100}) 23 | ] 24 | }, 25 | code: "rgb(@score, 100, 100)", 26 | error: "", 27 | result: "" 28 | } 29 | 30 | var editorEl = document.querySelector("textarea"); 31 | var resultEl = document.querySelector(".result"); 32 | var geojsonEl = document.querySelector(".geojson"); 33 | var mapEl = document.querySelector(".map"); 34 | var examplesEl = document.querySelector(".examples"); 35 | 36 | 37 | var EXAMPLES = [ 38 | { 39 | name: "color on score", 40 | code: [ 41 | "interpolate(", 42 | " linear(), @score,", 43 | " 0, rgb(255, 0, 0),", 44 | " 50, rgb(0, 255, 0),", 45 | " 100, rgb(0, 0, 255)", 46 | ")" 47 | ].join("\n") 48 | }, 49 | { 50 | name: "interpolate on zoom", 51 | code: [ 52 | "interpolate(", 53 | " linear(), zoom(),", 54 | " 0, rgb(0, 0, 0),", 55 | " 11, rgb(255, 0, 0),", 56 | " 16, rgb(0, 255, 0),", 57 | " 22, rgb(0, 0, 255)", 58 | ")" 59 | ].join("\n") 60 | }, 61 | ]; 62 | 63 | examplesEl.innerHTML = EXAMPLES.map(function(example) { 64 | return "" 65 | }).join("") 66 | 67 | editorEl.addEventListener("keyup", change); 68 | function change(e) { 69 | state.code = editorEl.value; 70 | state.error = ""; 71 | 72 | try { 73 | state.result = simpleExpr.compile(state.code) 74 | } 75 | catch(err) { 76 | console.error(err); 77 | state.error = err; 78 | } 79 | 80 | render(); 81 | } 82 | 83 | function render() { 84 | if(state.error) { 85 | resultEl.innerHTML = '
'+state.error+'
' 86 | } 87 | else { 88 | resultEl.innerHTML = JSON.stringify(state.result, null, 2); 89 | } 90 | 91 | geojsonEl.innerHTML = JSON.stringify(state.geojson, null, 2); 92 | 93 | var style = buildStyle({ 94 | sources: { 95 | "demo": { 96 | "type": "geojson", 97 | "data": state.geojson 98 | } 99 | }, 100 | layers: [ 101 | { 102 | "id": "example", 103 | "type": "circle", 104 | "source": "demo", 105 | "paint": { 106 | "circle-color": state.result, 107 | "circle-radius": 6 108 | } 109 | } 110 | ] 111 | }) 112 | 113 | map.setStyle(style); 114 | } 115 | 116 | var BASE_STYLE = require("./base-style.json"); 117 | 118 | function buildStyle(opts) { 119 | var baseStyle = JSON.parse( 120 | JSON.stringify(BASE_STYLE) 121 | ); 122 | 123 | baseStyle.sources = Object.assign(baseStyle.sources, opts.sources) 124 | baseStyle.layers = baseStyle.layers.concat(opts.layers); 125 | return baseStyle; 126 | } 127 | 128 | var map = new mapboxgl.Map({ 129 | container: mapEl, 130 | style: 'https://free.tilehosting.com/styles/positron/style.json?key=ozKuiN7rRsPFArLI4gsv', 131 | center: [8.5456, 47.3739], 132 | zoom: 11 133 | }); 134 | 135 | examplesEl.addEventListener("change", function() { 136 | editorEl.value = EXAMPLES[examplesEl.selectedIndex].code; 137 | change(); 138 | }) 139 | 140 | editorEl.value = EXAMPLES[examplesEl.selectedIndex].code; 141 | change(); 142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /lib/tokenize.js: -------------------------------------------------------------------------------- 1 | module.exports = function(source) { 2 | let current = 0; 3 | 4 | let tokens = []; 5 | 6 | let row = 0; 7 | let offset = 0; 8 | 9 | function addToken(obj) { 10 | const token = Object.assign({}, obj, { 11 | row: row, 12 | col: (current) - offset 13 | }) 14 | tokens.push(token) 15 | return token; 16 | } 17 | 18 | while (current < source.length) { 19 | let char = source[current]; 20 | 21 | if(char === "\n") { 22 | addToken({ 23 | type: 'whitespace', 24 | value: '\n', 25 | }); 26 | row += 1; 27 | offset = current + 1/*Including the '\n' */; 28 | current++ 29 | continue; 30 | } 31 | 32 | if (char === '(') { 33 | addToken({ 34 | type: 'open_paren', 35 | value: '(', 36 | }); 37 | current++; 38 | continue; 39 | } 40 | if (char === ')') { 41 | addToken({ 42 | type: 'close_paren', 43 | value: ')', 44 | }); 45 | current++; 46 | continue; 47 | } 48 | if(char === ",") { 49 | addToken({ 50 | type: 'arg_sep' 51 | }); 52 | current++; 53 | continue; 54 | } 55 | 56 | // Whitespace is ignored 57 | // Note: whitespace in strings are handled separately in the string handler 58 | let WHITESPACE = /\s/; 59 | if (WHITESPACE.test(char)) { 60 | let value = ""; 61 | let token = addToken({ 62 | type: "whitespace" 63 | }) 64 | 65 | while (WHITESPACE.test(char)) { 66 | value += char; 67 | char = source[++current]; 68 | } 69 | 70 | token.value = value; 71 | continue; 72 | } 73 | 74 | let NUMBERS = /[-+.0-9]/; 75 | if (NUMBERS.test(char)) { 76 | let value = ''; 77 | 78 | const token = addToken({type: 'number'}); 79 | 80 | while (NUMBERS.test(char)) { 81 | value += char; 82 | char = source[++current]; 83 | } 84 | 85 | token.value = value; 86 | 87 | if(!value.match(/^[+-]?([0-9]*\.)?[0-9]+$/)) { 88 | throw "Invalid number '"+value+"'" 89 | } 90 | 91 | continue; 92 | } 93 | 94 | // Feature reference 95 | if (char === '@') { 96 | let value = ''; 97 | 98 | const token = addToken({ type: 'feature_ref'}); 99 | 100 | // Skip the '@' 101 | char = source[++current]; 102 | 103 | while (char.match(/[a-zA-Z0-9_]/)) { 104 | value += char; 105 | char = source[++current]; 106 | } 107 | 108 | token.value = value; 109 | 110 | continue; 111 | } 112 | 113 | if (char === '"') { 114 | // Keep a `value` variable for building up our string token. 115 | let value = ''; 116 | 117 | const token = addToken({ type: 'string'}); 118 | 119 | // We'll skip the opening double quote in our token. 120 | char = source[++current]; 121 | 122 | // Iterate through each character until we reach another double quote. 123 | let prev; 124 | while (prev === "\\" || char !== '"') { 125 | value += char; 126 | prev = char; 127 | char = source[++current]; 128 | if(char === undefined) { 129 | throw "Missing closing quote"; 130 | } 131 | } 132 | 133 | token.value = value; 134 | 135 | if(char !== "\"") { 136 | throw "Missing closing quote" 137 | } 138 | 139 | // Skip the closing double quote. 140 | char = source[++current]; 141 | 142 | continue; 143 | } 144 | 145 | 146 | let LETTERS = /[^)( \t]/i; 147 | if (LETTERS.test(char)) { 148 | let value = ''; 149 | 150 | let token = addToken({ type: 'command'}); 151 | 152 | // This allows for log10 method name but not 10log 153 | while (char && LETTERS.test(char)) { 154 | value += char; 155 | char = source[++current]; 156 | } 157 | 158 | token.value = value; 159 | 160 | continue; 161 | } 162 | 163 | throw new TypeError('I don\'t know what this character is: ' + char); 164 | } 165 | 166 | // Always trailing whitespace so the source maps can map to it. 167 | // TODO: Is this needed? 168 | addToken({ 169 | type: 'whitespace', 170 | value: '\n', 171 | }); 172 | 173 | return tokens; 174 | } 175 | -------------------------------------------------------------------------------- /bin/cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var fs = require("fs"); 3 | var yargs = require("yargs"); 4 | var simpleExpr = require("../"); 5 | var mgl = require("@mapbox/mapbox-gl-style-spec"); 6 | 7 | 8 | function handleError(err) { 9 | console.log(err); 10 | process.eixt(1); 11 | } 12 | 13 | function parseOpts(str) { 14 | var out = {}; 15 | str = str || ""; 16 | 17 | var items = str 18 | .split(/\s+/) 19 | .filter(function(item) { 20 | return item != ""; 21 | }) 22 | 23 | items.forEach(function(item) { 24 | var parts = item.split("=") 25 | if(parts.length !== 2) { 26 | throw "Invalid format '"+item+"' expected format FOO=bar" 27 | } 28 | out[parts[0]] = parts[1]; 29 | }); 30 | 31 | return out; 32 | } 33 | 34 | function optOrStdin(filepath) { 35 | if(filepath) { 36 | return new Promise(function(resolve, reject) { 37 | fs.readFile(filepath, function(err, data) { 38 | if(err) { 39 | reject(err) 40 | } 41 | else { 42 | try { 43 | resolve(data.toString()); 44 | } 45 | catch(err) { 46 | reject(err); 47 | } 48 | } 49 | }) 50 | }) 51 | } 52 | else { 53 | return new Promise(function(resolve, reject) { 54 | var raw = ""; 55 | process.stdin.on('data', function(data) { 56 | raw += data; 57 | }); 58 | process.stdin.on('end', function() { 59 | try { 60 | resolve(raw) 61 | } catch(err) { 62 | reject(err); 63 | } 64 | }); 65 | }) 66 | } 67 | } 68 | 69 | var argv = yargs 70 | .command( 71 | "parse", 72 | "code -> ast", 73 | function (yargs) { 74 | return yargs 75 | }, 76 | function (argv) { 77 | optOrStdin(argv._[1]) 78 | .then(function(data) { 79 | var ast = simpleExpr.parse( 80 | simpleExpr.tokenize(data) 81 | ) 82 | var json = JSON.stringify(ast, null, 2); 83 | console.log(json) 84 | process.exit(0) 85 | }) 86 | .catch(handleError) 87 | } 88 | ) 89 | .command( 90 | "compile", 91 | "code -> json", 92 | function (yargs) { 93 | return yargs 94 | }, 95 | function(argv) { 96 | optOrStdin(argv._[1]) 97 | .then(function(data) { 98 | var json = simpleExpr.compile(data) 99 | console.log(JSON.stringify(json, null, 2)) 100 | process.exit(0) 101 | }) 102 | .catch(handleError) 103 | } 104 | ) 105 | .command( 106 | "decompile", 107 | "json -> code", 108 | function (yargs) { 109 | return yargs 110 | }, 111 | function(argv) { 112 | optOrStdin(argv._[1]) 113 | .then(function(data) { 114 | var json; 115 | try { 116 | json = JSON.parse(data); 117 | } 118 | catch(err) { 119 | console.error("Invalid JSON"); 120 | console.error(err); 121 | process.exit(1); 122 | } 123 | 124 | var code = simpleExpr.decompile(json); 125 | console.log(code); 126 | process.exit(0); 127 | }) 128 | .catch(handleError) 129 | } 130 | ) 131 | .command( 132 | "execute", 133 | "execute as js function", 134 | function (yargs) { 135 | return yargs 136 | .describe("feature-props", "feature properties") 137 | .describe("feature-id", "feature id") 138 | .describe("feature-type", "feature type") 139 | .describe("globals", "globals data") 140 | }, 141 | function(argv) { 142 | var featureOpts = parseOpts(argv["feature-props"]); 143 | var globalOpts = parseOpts(argv.globals); 144 | 145 | optOrStdin(argv._[1]) 146 | .then(function(data) { 147 | var json = simpleExpr.compile(data) 148 | 149 | var out = mgl.expression.createExpression(json, {}) 150 | var result = out.value.evaluate(globalOpts, { 151 | id: argv["feature-id"], 152 | type: argv["feature-type"], 153 | properties: featureOpts 154 | }) 155 | 156 | console.log(result); 157 | process.exit(0); 158 | }) 159 | .catch(handleError) 160 | } 161 | ) 162 | .example("simple-expr compile input.expr", "compile an expression") 163 | .example("simple-expr decompile input.json", "decompile an expression") 164 | .example("simple-expr compile input.expr | mgl-exec execute --data foo=1 bar=2", "Compile and run with some data") 165 | .argv; 166 | 167 | 168 | if(argv._.length < 1) { 169 | yargs.showHelp(); 170 | } 171 | 172 | -------------------------------------------------------------------------------- /lib/parse.js: -------------------------------------------------------------------------------- 1 | const util = require("./util") 2 | const ParseError = require("./errors").ParseError; 3 | 4 | 5 | function fetchAdditionalTokens(cursor, types) { 6 | const out = []; 7 | 8 | // Look ahead 9 | let idx = 0; 10 | let checkType; 11 | while(checkType = types[idx++]) { 12 | let token = cursor.fetch(); 13 | if(!token) { 14 | break; 15 | } 16 | 17 | let optional; 18 | if(checkType.match(/^(.*)(\?)$/)) { 19 | optional = true; 20 | checkType = RegExp.$1; 21 | } 22 | 23 | if(checkType == token.type) { 24 | out.push(token); 25 | cursor.move(+1) 26 | } 27 | else if(!optional) { 28 | throw new ParseError("Expected '"+checkType+"'", { 29 | token: token 30 | }); 31 | } 32 | } 33 | 34 | return out; 35 | } 36 | 37 | function buildNode(obj, token) { 38 | return Object.assign({}, obj, { 39 | tokens: Object.assign({ 40 | pre: [], 41 | post: [] 42 | }, obj.tokens, { 43 | // Always override... 44 | current: [ 45 | token 46 | ] 47 | }) 48 | }); 49 | } 50 | 51 | const parsers = { 52 | "open_paren": function(cursor) { 53 | throw new ParseError("Incorrectly placed open_paren", { 54 | token: cursor.fetch() 55 | }); 56 | }, 57 | "close_paren": function(cursor) { 58 | throw new ParseError("Incorrectly placed close_paren", { 59 | token: cursor.fetch() 60 | }); 61 | }, 62 | "whitespace": function(cursor) { 63 | cursor.move(+1); 64 | return; 65 | }, 66 | "arg_sep": function(cursor) { 67 | throw new ParseError("Misplaced arg_sep", { 68 | token: cursor.fetch() 69 | }); 70 | }, 71 | "number": function(cursor) { 72 | const token = cursor.fetch(); 73 | cursor.move(+1); 74 | 75 | const node = buildNode({ 76 | type: 'NumberLiteral', 77 | tokens: { 78 | post: fetchAdditionalTokens(cursor, ["whitespace?", "arg_sep?", "whitespace?"]) 79 | }, 80 | value: util.parseNumber(token.value) 81 | }, token); 82 | 83 | return node; 84 | }, 85 | "string": function(cursor) { 86 | let token = cursor.fetch(); 87 | cursor.move(+1); 88 | 89 | const node = buildNode({ 90 | type: 'StringLiteral', 91 | tokens: { 92 | post: fetchAdditionalTokens(cursor, ["whitespace?", "arg_sep?", "whitespace?"]) 93 | }, 94 | value: token.value 95 | }, token); 96 | 97 | return node; 98 | }, 99 | "feature_ref": function(cursor) { 100 | let token = cursor.fetch(); 101 | cursor.move(+1); 102 | 103 | const node = buildNode({ 104 | type: 'FeatureRef', 105 | tokens: { 106 | post: fetchAdditionalTokens(cursor, ["whitespace?", "arg_sep?", "whitespace?"]) 107 | }, 108 | value: token.value, 109 | }, token); 110 | 111 | return node; 112 | }, 113 | "command": function(cursor) { 114 | let token = cursor.fetch(); 115 | cursor.move(+1); 116 | 117 | let node = buildNode({ 118 | type: 'CallExpression', 119 | value: token.value, 120 | tokens: { 121 | pre: fetchAdditionalTokens(cursor, ["open_paren", "whitespace?"]) 122 | }, 123 | params: [], 124 | }, token); 125 | 126 | let argSepRequired = false; 127 | while ( 128 | (token = cursor.fetch()) 129 | && ( 130 | (token.type !== 'close_paren') 131 | ) 132 | ) { 133 | // we'll call the `walk` function which will return a `node` and we'll 134 | // push it into our `node.params`. 135 | const arg = parsers[token.type](cursor); 136 | 137 | if(arg) { 138 | if(!cursor.fetch()) { 139 | throw new ParseError("Missing close_paren", { 140 | token: token 141 | }) 142 | } 143 | else if(cursor.fetch().type !== "close_paren") { 144 | let argSep = arg.tokens.post 145 | .find(function(item) { 146 | return item.type === "arg_sep"; 147 | }) 148 | 149 | if(!argSep) { 150 | throw new ParseError("Missing arg_sep", { 151 | token: token 152 | }); 153 | } 154 | } 155 | node.params = node.params.concat(arg); 156 | } 157 | 158 | // Check we found a arg_sep 159 | 160 | argSepRequired = true; 161 | } 162 | 163 | node.tokens.post = fetchAdditionalTokens(cursor, ["close_paren", "arg_sep?", "whitespace?"]); 164 | return node; 165 | } 166 | } 167 | 168 | function Cursor(tokens, idx = 0) { 169 | this._tokens = tokens; 170 | this._idx = idx; 171 | } 172 | 173 | Cursor.prototype.move = function(offset) { 174 | this._idx += offset; 175 | } 176 | 177 | Cursor.prototype.currentIndex = function() { 178 | return this._idx; 179 | } 180 | 181 | Cursor.prototype.fetch = function() { 182 | return this.peek(0); 183 | } 184 | 185 | Cursor.prototype.peek = function(offset) { 186 | return this._tokens[this._idx+offset]; 187 | } 188 | 189 | Cursor.prototype.atEnd = function() { 190 | return this._idx > this._tokens.length-1; 191 | } 192 | 193 | function parse(tokens) { 194 | const cursor = new Cursor(tokens); 195 | let ast = []; 196 | 197 | while(!cursor.atEnd()) { 198 | const token = cursor.fetch(); 199 | let nodes = parsers[token.type](cursor); 200 | 201 | if(nodes) { 202 | ast = ast.concat(nodes); 203 | } 204 | } 205 | 206 | if(ast.length > 1) { 207 | throw new ParseError("Multiple top level functions not allowed", { 208 | token: token 209 | }) 210 | } 211 | 212 | return { 213 | type: 'Program', 214 | body: ast 215 | }; 216 | } 217 | 218 | module.exports = parse; 219 | -------------------------------------------------------------------------------- /lib/unparse.js: -------------------------------------------------------------------------------- 1 | module.exports = function(ast) { 2 | 3 | function addPosInfo(token, node) { 4 | if(node && node.hasOwnProperty("col") && node.hasOwnProperty("row")) { 5 | return Object.assign({}, token, { 6 | col: node.col, 7 | row: node.row 8 | }); 9 | } 10 | else { 11 | return token; 12 | } 13 | } 14 | 15 | function takeTokens(tokens, defTokens) { 16 | tokens = tokens || []; 17 | 18 | const out = [] 19 | let idx = 0; 20 | 21 | let skip = false; 22 | let defToken; 23 | for(defToken of defTokens) { 24 | let token = tokens[idx++]; 25 | 26 | if(!token) { 27 | if(!defToken.optional) { 28 | out.push({...defToken}) 29 | } 30 | continue; 31 | } 32 | 33 | if(Array.isArray(defToken)) { 34 | defToken = defToken.find(function(item) { 35 | return item.type === token.type; 36 | }) 37 | } 38 | 39 | 40 | let typeCheck = defToken.type == token.type; 41 | 42 | if(!skip && typeCheck) { 43 | out.push( 44 | Object.assign({}, token, defToken) 45 | ) 46 | } 47 | else if(defToken.optional) { 48 | // Skip optional nodes 49 | idx--; 50 | } 51 | else { 52 | skip = true; 53 | out.push({...defToken}) 54 | // Create the rest of the tokens ourselves 55 | } 56 | } 57 | 58 | return out; 59 | } 60 | 61 | 62 | function buildTokens(out, node, def) { 63 | function pushIt(item) { 64 | out.push(item); 65 | } 66 | 67 | const tokens = node.tokens || {} 68 | 69 | takeTokens(tokens.pre, def.pre ).map(pushIt); 70 | takeTokens(tokens.current, def.current).map(pushIt); 71 | takeTokens(tokens.post, def.post ).map(pushIt); 72 | } 73 | 74 | function walk(node) { 75 | let out = []; 76 | 77 | if(node.type === "CallExpression") { 78 | 79 | let args = [] 80 | 81 | node.params.forEach(function(_node, idx) { 82 | let isLast = node.params.length-1 <= idx; 83 | 84 | let out = walk(_node) 85 | let noSep = ( 86 | (out[out.length-1] && out[out.length-1].type === "arg_sep") 87 | || (out[out.length-2] && out[out.length-2].type === "arg_sep") 88 | ); 89 | 90 | if(!isLast && !noSep) { 91 | out.push({type: "arg_sep"}) 92 | } 93 | args = args.concat(out) 94 | }) 95 | 96 | let tokens = node.tokens || {} 97 | 98 | out = out.concat( 99 | takeTokens(tokens.current, [ 100 | { 101 | type: "command", 102 | value: node.value 103 | } 104 | ]), 105 | takeTokens(tokens.pre, [ 106 | { 107 | type: "open_paren", 108 | value: "(" 109 | }, 110 | { 111 | type: "whitespace", 112 | optional: true, 113 | value: "" 114 | } 115 | ]), 116 | args, 117 | takeTokens(tokens.post, [ 118 | { 119 | type: "arg_sep", 120 | optional: true, 121 | value: "," 122 | }, 123 | { 124 | type: "close_paren", 125 | value: ")" 126 | }, 127 | { 128 | type: "whitespace", 129 | optional: true, 130 | value: "" 131 | } 132 | ]) 133 | ) 134 | } 135 | else if(node.type === "FeatureRef") { 136 | buildTokens(out, node, { 137 | pre: [], 138 | current: [{ 139 | type: "feature_ref", 140 | value: node.value 141 | }], 142 | post: [ 143 | { 144 | type: "whitespace", 145 | optional: true, 146 | value: "" 147 | }, 148 | { 149 | type: "arg_sep", 150 | optional: true, 151 | value: "," 152 | } 153 | ] 154 | }); 155 | } 156 | else if(node.type === "StringLiteral") { 157 | buildTokens(out, node, { 158 | pre: [], 159 | current: [ 160 | { 161 | type: "string", 162 | value: node.value 163 | } 164 | ], 165 | post: [ 166 | { 167 | type: "whitespace", 168 | optional: true, 169 | value: "" 170 | }, 171 | { 172 | type: "arg_sep", 173 | optional: true, 174 | value: "," 175 | }, 176 | { 177 | type: "whitespace", 178 | optional: true, 179 | value: "" 180 | } 181 | ] 182 | }); 183 | } 184 | else if(node.type === "NumberLiteral") { 185 | buildTokens(out, node, { 186 | pre: [ 187 | ], 188 | current: [ 189 | { 190 | type: "number", 191 | value: node.value 192 | } 193 | ], 194 | post: [ 195 | { 196 | type: "whitespace", 197 | optional: true, 198 | value: "" 199 | }, 200 | { 201 | type: "arg_sep", 202 | optional: true, 203 | value: "," 204 | }, 205 | { 206 | type: "whitespace", 207 | optional: true, 208 | value: "" 209 | } 210 | ] 211 | }); 212 | } 213 | else { 214 | throw TypeError(node.type); 215 | } 216 | 217 | return out; 218 | } 219 | 220 | if(ast.body[0]) { 221 | return walk(ast.body[0]) 222 | } 223 | else { 224 | return []; 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # simple-expr 2 | Simple expression language for [mapbox-gl-js expressions][mapbox-gl-expressions] 3 | 4 | [![stability-experimental](https://img.shields.io/badge/stability-experimental-orange.svg)][stability] 5 | [![Build Status](https://circleci.com/gh/orangemug/simple-expr.png?style=shield)][circleci] 6 | [![Dependency Status](https://david-dm.org/orangemug/simple-expr.svg)][dm-prod] 7 | [![Dev Dependency Status](https://david-dm.org/orangemug/simple-expr/dev-status.svg)][dm-dev] 8 | 9 | [stability]: https://github.com/orangemug/stability-badges#experimental 10 | [circleci]: https://circleci.com/gh/orangemug/simple-expr 11 | [dm-prod]: https://david-dm.org/orangemug/simple-expr 12 | [dm-dev]: https://david-dm.org/orangemug/simple-expr#info=devDependencies 13 | 14 | ‼️ This is an experimental repository, if you find it useful or interesting please leave a comment. There is a general discussion issue in [issue #1](https://github.com/orangemug/simple-expr/issues/1), leave any general thoughts there. Or [open a new issue](https://github.com/orangemug/simple-expr/issues/new) for bugs or to suggest new features. 15 | 16 | 17 | The primary aim of this language is to support [MapboxGL expressions][mapbox-gl-expressions], although it should also be generally useful. 18 | 19 | This is a tiny functional language that gets converted into [MapboxGL expressions][mapbox-gl-expressions]. The idea behind the language is that it can be converted in a lossless way between [MapboxGL expressions][mapbox-gl-expressions] and back again. 20 | 21 | 22 | Basically it's just a nicer way to write MapboxGL expressions. Here's an example, the following JSON 23 | 24 | ```json 25 | [ 26 | "interpolate", 27 | ["linear"], ["get", "score"], 28 | 0, ["rgb", 255, 0, 0], 29 | 50, ["rgb", 0, 255, 0], 30 | 100, ["rgb", 0, 0, 255] 31 | ] 32 | ``` 33 | 34 | Would be written as 35 | 36 | ``` 37 | interpolate( 38 | linear(), @score, 39 | 0, rgb(255, 0, 0), 40 | 50, rgb(0, 255, 0), 41 | 100, rgb(0, 0, 255) 42 | ) 43 | ``` 44 | 45 | **See it in action:** 46 | 47 | 48 | ## Install 49 | To install 50 | 51 | ``` 52 | npm install orangemug/simple-expr --save 53 | ``` 54 | 55 | 56 | ## Syntax 57 | You are only allowed to define a single top level expression. 58 | 59 | **Valid** 60 | ``` 61 | rgb(255, 0, 0) 62 | ``` 63 | 64 | **Invalid!** 65 | ``` 66 | rgb(255, 0, 0) 67 | rgb(0, 0, 255) 68 | ``` 69 | 70 | Although sub expressions are allowed 71 | 72 | ``` 73 | rgb(get("rank"), 0, 0) 74 | ``` 75 | 76 | 77 | ### Types 78 | There are 2 basic types 79 | 80 | 81 | #### number 82 | Any integer or decimal number not in quotes, for example 83 | 84 | ``` 85 | 1 86 | -1 87 | +3 88 | 3.14 89 | -1000 90 | -9.8 91 | ``` 92 | 93 | 94 | #### string 95 | Any characters surrounded in quotes, for example 96 | 97 | ``` 98 | "foo bar" 99 | ``` 100 | 101 | You can escape quotes with `\` for example 102 | 103 | ``` 104 | "They said \"it couldn't be done\"" 105 | ``` 106 | 107 | 108 | ### Functions 109 | Functions are defined as 110 | 111 | ``` 112 | function_name(arg, arg, arg...) 113 | ``` 114 | 115 | Note that arguments can also be functions. This gets compiled into the [MapboxGL expressions][mapbox-gl-expressions] JSON format. 116 | 117 | Lets see an example 118 | 119 | ``` 120 | rgb(get("rating"), 0, 0) 121 | ``` 122 | 123 | Will become 124 | 125 | ``` 126 | ["rgb", ["get", "rating"], 0, 0] 127 | ``` 128 | 129 | 130 | ### Feature references 131 | As well as using the `get` function ([see spec](https://www.mapbox.com/mapbox-gl-js/style-spec/#expressions-get)) there is also a shorthand to reference feature data. The following 132 | 133 | ``` 134 | rgb(@rating, 0, 0) 135 | ``` 136 | 137 | Is the same as 138 | 139 | ``` 140 | rgb(get("rating"), 0, 0) 141 | ``` 142 | 143 | 144 | ### Variables 145 | **NOTE: Not yet available** 146 | 147 | You can also define variables before the expressions. Variables are also allowed to define a single expression. A quick example 148 | 149 | ``` 150 | &r = interpolate( 151 | linear(), @score, 152 | 1, 100 153 | 22, 255 154 | ) 155 | 156 | rgb(&r, 0, 0) 157 | ``` 158 | 159 | Variables **must** start with a `&` both in there definition and their usage. 160 | 161 | 162 | ## Usage 163 | You can parse, compile to JSON. It comes in 2 forms, the CLI (command line interface) and the JavaScript API 164 | 165 | 166 | ## JavaScript API 167 | 168 | ### Compile to expression JSON 169 | ```js 170 | var simpleExpr = require("simple-expr"); 171 | var out = simpleExpr.compile('concat("hello", " ", "world")', {format: "json"}) 172 | assert.deepEqual(out, [ 173 | "concat", "hello", " ", "world" 174 | ]) 175 | ``` 176 | 177 | ### Parse to AST 178 | 179 | ```js 180 | var simpleExpr = require("simple-expr"); 181 | var tokens = simpleExpr.tokenize('*(2,2)') 182 | var ast = simpleExpr.parse(tokens); 183 | ``` 184 | 185 | 186 | ## CLI 187 | The available CLI commands are 188 | 189 | ### Parse 190 | ```bash 191 | simple-expr parse examples/concat.expr > /tmp/concat.ast 192 | ``` 193 | 194 | ### Compile 195 | ```bash 196 | simple-expr compile examples/concat.expr > /tmp/concat.json 197 | ## >>/tmp/concat.json 198 | ## [ 199 | ## "concat", 200 | ## "Hello", 201 | ## " ", 202 | ## [ 203 | ## "get", 204 | ## "name" 205 | ## ] 206 | ## ] 207 | ## << 208 | ``` 209 | 210 | ### Decompile 211 | ```bash 212 | simple-expr decompile examples/concat.json > /tmp/concat.expr 213 | ## >>/tmp/concat.expr 214 | ## concat("Hello"," ",@name) 215 | ## << 216 | ``` 217 | 218 | ### Execute 219 | ```bash 220 | simple-expr execute --feature-props name=Maputnik examples/concat.expr > /tmp/concat.log 221 | ## >>/tmp/concat.log 222 | ## Hello Maputnik 223 | ## << 224 | ``` 225 | 226 | 227 | ## License 228 | [MIT](LICENSE) 229 | 230 | [mapbox-gl-expressions]: https://www.mapbox.com/mapbox-gl-js/style-spec#expressions 231 | 232 | -------------------------------------------------------------------------------- /site/fmt.js: -------------------------------------------------------------------------------- 1 | var simpleExpr = require("../"); 2 | var textblock = require("../lib/textblock"); 3 | var delegate = require('dom-delegate'); 4 | 5 | 6 | var inputEl = document.querySelector(".input"); 7 | var debugEl = document.querySelector(".debug"); 8 | var outputEl = document.querySelector(".output"); 9 | var mappingsSourceEl = document.querySelector(".mappings-source"); 10 | var mappingsGeneratedEl = document.querySelector(".mappings-generated"); 11 | 12 | var sourceMap = require("../lib/source-map"); 13 | 14 | 15 | function walk(node, parent, callback, opts={}) { 16 | if(node.type === "CallExpression") { 17 | const params = node.params; 18 | node = callback(node, parent); 19 | 20 | node.params = params.map(function(_node, idx) { 21 | return walk(_node, node, callback, { 22 | ...opts, 23 | }) 24 | }) 25 | 26 | return node; 27 | } 28 | else if(node.type === "FeatureRef") { 29 | return callback(node, parent); 30 | } 31 | else if(node.type === "StringLiteral") { 32 | return callback(node, parent); 33 | } 34 | else if(node.type === "NumberLiteral") { 35 | return callback(node, parent); 36 | } 37 | else { 38 | throw TypeError(node.type); 39 | } 40 | } 41 | 42 | function formatAst(ast) { 43 | if(ast.body[0]) { 44 | ast.body[0] = walk(ast.body[0], null, function(node, parent) { 45 | return node; 46 | }) 47 | } 48 | else { 49 | return []; 50 | } 51 | return ast; 52 | } 53 | 54 | async function update() { 55 | var input = inputEl.value; 56 | 57 | var pos = { 58 | start: inputEl.selectionStart, 59 | end: inputEl.selectionEnd 60 | }; 61 | 62 | var orignalPos = textblock.indexToColRow(input, [pos.start, pos.end]); 63 | 64 | try { 65 | var tokens = simpleExpr.tokenize(input); 66 | var ast = simpleExpr.parse(tokens); 67 | 68 | const formattedAst = formatAst(ast); 69 | 70 | var newTokens = simpleExpr.unparse(formattedAst); 71 | var codeBits = simpleExpr.untokenize(newTokens); 72 | var code = codeBits.code; 73 | var map = codeBits.map; 74 | 75 | 76 | // Get the original position of the node 77 | const startGeneratedPos = await sourceMap.genOrigPosition(map, { 78 | line: orignalPos[0].row+1, 79 | // IS this a HACK 80 | column: orignalPos[0].col, 81 | }, "foo.js"); 82 | 83 | const endGeneratedPos = await sourceMap.genOrigPosition(map, { 84 | line: orignalPos[1].row+1, 85 | // IS this a HACK 86 | column: orignalPos[1].col, 87 | }, "foo.js"); 88 | 89 | var newPos = textblock.colRowToIndex(code, [ 90 | { 91 | row: startGeneratedPos.line-1, 92 | col: startGeneratedPos.column 93 | }, 94 | { 95 | row: endGeneratedPos.line-1, 96 | col: endGeneratedPos.column 97 | }, 98 | ]) 99 | 100 | if(newPos.length > 0) { 101 | if(newPos.length < 2) { 102 | newPos[1] = code.length; 103 | } 104 | 105 | var hlCode = code.substring(0, newPos[0]) 106 | + ""+code.substring(newPos[0], newPos[1])+"" 107 | + code.substring(newPos[1]) 108 | } 109 | 110 | 111 | var mappings = await sourceMap.getMappings(map) 112 | 113 | function insertIntoString(orig, idx, text) { 114 | return orig.substring(0, idx) + text + orig.substring(idx); 115 | } 116 | 117 | var offset = 0; 118 | var mappedCode = code + ""; 119 | mappings.forEach(function(mapping, idx) { 120 | var opts = {row: mapping.generatedLine-1, col: mapping.generatedColumn} 121 | var pos = textblock.colRowToIndex(code, [opts])[0]; 122 | 123 | pos += offset; 124 | var toInsert = ""; 125 | offset += toInsert.length; 126 | mappedCode = insertIntoString(mappedCode, pos, toInsert) 127 | }) 128 | 129 | mappingsGeneratedEl.innerHTML = mappedCode; 130 | 131 | var offset = 0; 132 | var origCode = input + ""; 133 | mappings.forEach(function(mapping, idx) { 134 | var opts = {row: mapping.originalLine-1, col: mapping.originalColumn} 135 | var pos = textblock.colRowToIndex(input, [opts])[0]; 136 | 137 | pos += offset; 138 | var toInsert = ""; 139 | offset += toInsert.length; 140 | origCode = insertIntoString(origCode, pos, toInsert) 141 | }) 142 | 143 | mappingsSourceEl.innerHTML = origCode; 144 | 145 | outputEl.innerHTML = hlCode; 146 | } 147 | catch(err) { 148 | console.error("Error [%s, %s]:", err.line, err.column, String(err)); 149 | outputEl.innerHTML = "ERR: "+err; 150 | } 151 | 152 | debugEl.innerHTML = "cursor: "+JSON.stringify(newPos); 153 | 154 | } 155 | 156 | inputEl.addEventListener("keydown", function(e) { 157 | // // Allow tabs 158 | // if(e.keyCode === 9) { 159 | // var start = this.selectionStart; 160 | // var end = this.selectionEnd; 161 | 162 | // var value = inputEl.value; 163 | 164 | // inputEl.value = value.substring(0, start) 165 | // + " " 166 | // + value.substring(end); 167 | 168 | // // put caret at right position again (add one for the tab) 169 | // inputEl.selectionStart = inputEl.selectionEnd = start + 2; 170 | 171 | // // prevent the focus lose 172 | // e.preventDefault(); 173 | // } 174 | }); 175 | 176 | inputEl.addEventListener("keyup", update); 177 | inputEl.addEventListener("keydown", update); 178 | inputEl.addEventListener("click", update); 179 | 180 | var del = delegate(document.body); 181 | del.on("mouseover", ".mapping", function(e, target) { 182 | var type = target.getAttribute("data-type"); 183 | var idx = target.getAttribute("data-idx"); 184 | }) 185 | 186 | var downHdl; 187 | inputEl.addEventListener("mousedown", function() { 188 | downHdl = setInterval(update, 1000/60); 189 | }); 190 | 191 | document.addEventListener("mouseup", function() { 192 | clearInterval(downHdl); 193 | }); 194 | update(); 195 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | var assert = require("assert"); 2 | var readmeTester = require("readme-tester"); 3 | var simpleExpr = require("../"); 4 | 5 | 6 | const tests = [ 7 | { 8 | name: "number: integer", 9 | input: ` 10 | number(1) 11 | `, 12 | output: ["number", 1] 13 | }, 14 | { 15 | name: "number: integer with +sign", 16 | input: ` 17 | number(+1) 18 | `, 19 | decompiled: ` 20 | number(1) 21 | `, 22 | output: ["number", 1] 23 | }, 24 | { 25 | name: "number: integer with -sign", 26 | input: ` 27 | number(-1) 28 | `, 29 | output: ["number", -1] 30 | }, 31 | { 32 | name: "number: float", 33 | input: ` 34 | number(3.14) 35 | `, 36 | output: ["number", 3.14] 37 | }, 38 | { 39 | name: "number: float with +sign", 40 | input: ` 41 | number(+3.14) 42 | `, 43 | decompiled: ` 44 | number(3.14) 45 | `, 46 | output: ["number", 3.14] 47 | }, 48 | { 49 | name: "number: float with -sign", 50 | input: ` 51 | number(-3.14) 52 | `, 53 | output: ["number", -3.14] 54 | }, 55 | { 56 | name: "number: invalid float", 57 | input: ` 58 | number(3.14.3) 59 | `, 60 | throw: true 61 | }, 62 | { 63 | name: "string: simple", 64 | input: ` 65 | string("hello") 66 | `, 67 | output: ["string", "hello"] 68 | }, 69 | { 70 | name: "string: escaped", 71 | input: ` 72 | string("\\\"") 73 | `, 74 | output: ["string", "\\\""] 75 | }, 76 | { 77 | name: "string: missing quote", 78 | input: ` 79 | concat("Hello","world) 80 | `, 81 | throw: true 82 | }, 83 | { 84 | name: "function: single arg", 85 | input: ` 86 | foo(1) 87 | `, 88 | output: [ 89 | "foo", 1 90 | ] 91 | }, 92 | { 93 | name: "function: args with spaces", 94 | input: ` 95 | foo( 96 | 1, 97 | bar( 1 , 2 ), 98 | @score 99 | ) 100 | `, 101 | decompiled: `foo(1,bar(1,2),@score)`, 102 | output: [ 103 | "foo", 1, ["bar", 1, 2], ["get", "score"] 104 | ] 105 | }, 106 | { 107 | name: "function: multiple args", 108 | input: ` 109 | foo(1,"2",3,"four") 110 | `, 111 | output: [ 112 | "foo", 1, "2", 3, "four" 113 | ] 114 | }, 115 | { 116 | name: "function: nested function", 117 | input: ` 118 | foo(1,"2",bar(3,baz("four"))) 119 | `, 120 | output: [ 121 | "foo", 1, "2", ["bar", 3, ["baz", "four"]] 122 | ] 123 | }, 124 | { 125 | name: "function: multiple top-level functions", 126 | input: ` 127 | foo() 128 | bar() 129 | `, 130 | throw: true 131 | }, 132 | { 133 | name: "function: no top-level function", 134 | input: ` 135 | `, 136 | output: [ 137 | ] 138 | }, 139 | { 140 | name: "function: missing arg separator", 141 | input: ` 142 | concat("hello" "world") 143 | `, 144 | throw: true, 145 | }, 146 | { 147 | name: "function: context references", 148 | input: ` 149 | rgb(@score,@rank,0) 150 | `, 151 | output: [ 152 | "rgb", ["get", "score"], ["get", "rank"], 0 153 | ] 154 | }, 155 | { 156 | name: "function: context references (alt)", 157 | input: ` 158 | rgb(0,@score,@rank) 159 | `, 160 | output: [ 161 | "rgb", 0, ["get", "score"], ["get", "rank"] 162 | ] 163 | }, 164 | { 165 | name: "function: missing paren", 166 | input: ` 167 | rgb(0, @score, @rank 168 | `, 169 | throw: true 170 | }, 171 | { 172 | name: "function: with numbers", 173 | input: ` 174 | log10(1) 175 | `, 176 | output: [ 177 | "log10", 1 178 | ] 179 | }, 180 | { 181 | name: "function: with starting number", 182 | input: ` 183 | 1func(1) 184 | `, 185 | throw: true 186 | }, 187 | { 188 | name: "function: with dashes", 189 | input: ` 190 | to-color(1) 191 | `, 192 | output: [ 193 | "to-color", 1 194 | ] 195 | }, 196 | { 197 | name: "function: additional close paran", 198 | input: ` 199 | number(1)) 200 | `, 201 | throw: true 202 | }, 203 | ] 204 | 205 | var awkwardNames = [ 206 | {name: "!"}, 207 | {name: "!="}, 208 | {name: "<"}, 209 | {name: "<="}, 210 | {name: "=="}, 211 | {name: ">"}, 212 | {name: ">="}, 213 | {name: "-", skip: true}, 214 | {name: "*"}, 215 | {name: "/"}, 216 | {name: "%"}, 217 | {name: "^"}, 218 | {name: "+", skip: true}, 219 | {name: "e"} 220 | ]; 221 | awkwardNames.forEach(function(def) { 222 | var name = def.name; 223 | tests.push({ 224 | name: "function: awkward name: "+name, 225 | input: name+"(0,1)", 226 | skip: def.skip, 227 | only: def.only, 228 | output: [ 229 | name, 0, 1 230 | ] 231 | }) 232 | }) 233 | 234 | 235 | function buildTest(test, runner) { 236 | var name = test.name; 237 | if(test.throw) { 238 | name += " (throws error)"; 239 | } 240 | 241 | function fn() { 242 | var err, out; 243 | try { 244 | out = runner.fn(); 245 | } 246 | catch(_err) { 247 | err = _err; 248 | } 249 | 250 | if(test.throw) { 251 | assert(err); 252 | } 253 | else if(err) { 254 | throw err; 255 | } 256 | else { 257 | runner.assertion(out); 258 | } 259 | } 260 | 261 | if(test.only) { 262 | it.only(name, fn); 263 | } 264 | else if(test.skip) { 265 | it.skip(name, fn); 266 | } 267 | else { 268 | it(name, fn); 269 | } 270 | } 271 | 272 | 273 | describe("simple-expr", function() { 274 | describe("compile", function() { 275 | tests.forEach(function(test) { 276 | buildTest(test, { 277 | fn: function() { 278 | return simpleExpr.compile(test.input); 279 | }, 280 | assertion: function(actual) { 281 | assert.deepEqual(actual, test.output) 282 | } 283 | }) 284 | }) 285 | }) 286 | 287 | describe("decompile", function() { 288 | tests.forEach(function(test) { 289 | buildTest(test, { 290 | fn: function() { 291 | return simpleExpr.decompile(simpleExpr.compile(test.input)); 292 | }, 293 | assertion: function(actual) { 294 | var expected = test.decompiled || test.input; 295 | expected = expected 296 | .replace(/^\s+/, "") 297 | .replace(/\s+$/, "") 298 | 299 | assert.deepEqual(actual, expected) 300 | } 301 | }) 302 | }) 303 | }) 304 | 305 | describe("stringify", null, function() { 306 | tests.forEach(function(test) { 307 | it(test.name, function() { 308 | assert.deepEqual( 309 | simpleExpr.stringify(test.output, {newline: false}), 310 | test.input 311 | ) 312 | }) 313 | }) 314 | }) 315 | 316 | describe("cli", function() { 317 | it("parse") 318 | it("compile") 319 | it("decompile") 320 | it("execute") 321 | }) 322 | 323 | it("README.md", function(done) { 324 | this.timeout(10*1000); 325 | readmeTester(__dirname+"/../README.md", {bash: true}, function(err, assertions) { 326 | assert.ifError(err); 327 | done(err); 328 | }); 329 | }) 330 | }) 331 | -------------------------------------------------------------------------------- /docs/base-style.json: -------------------------------------------------------------------------------- 1 | {"version":8,"name":"Positron","metadata":{"mapbox:autocomposite":false,"mapbox:type":"template","mapbox:groups":{"b6371a3f2f5a9932464fa3867530a2e5":{"name":"Transportation","collapsed":false},"a14c9607bc7954ba1df7205bf660433f":{"name":"Boundaries"},"101da9f13b64a08fa4b6ac1168e89e5f":{"name":"Places","collapsed":false}},"openmaptiles:version":"3.x","openmaptiles:mapbox:owner":"openmaptiles","openmaptiles:mapbox:source:url":"mapbox://openmaptiles.4qljc88t"},"center":[10.184401828277089,-1.1368683772161603e-13],"zoom":0.8902641636539237,"bearing":0,"pitch":0,"sources":{"openmaptiles":{"type":"vector","url":"https://free.tilehosting.com/data/v3.json?key=ozKuiN7rRsPFArLI4gsv"}},"sprite":"https://free.tilehosting.com/styles/positron/sprite","glyphs":"https://free.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=ozKuiN7rRsPFArLI4gsv","layers":[{"id":"background","type":"background","paint":{"background-color":"rgb(242,243,240)"}},{"id":"park","type":"fill","source":"openmaptiles","source-layer":"park","filter":["==","$type","Polygon"],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(230, 233, 229)"}},{"id":"water","type":"fill","source":"openmaptiles","source-layer":"water","filter":["==","$type","Polygon"],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(194, 200, 202)","fill-antialias":true,"fill-outline-color":{"base":1,"stops":[[0,"hsla(180, 6%, 63%, 0.82)"],[22,"hsla(180, 6%, 63%, 0.18)"]]}}},{"id":"landcover_ice_shelf","type":"fill","source":"openmaptiles","source-layer":"landcover","maxzoom":8,"filter":["all",["==","$type","Polygon"],["==","subclass","ice_shelf"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"hsl(0, 0%, 98%)","fill-opacity":0.7}},{"id":"landcover_glacier","type":"fill","source":"openmaptiles","source-layer":"landcover","maxzoom":8,"filter":["all",["==","$type","Polygon"],["==","subclass","glacier"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"hsl(0, 0%, 98%)","fill-opacity":{"base":1,"stops":[[0,1],[8,0.5]]}}},{"id":"landuse_residential","type":"fill","source":"openmaptiles","source-layer":"landuse","maxzoom":16,"filter":["all",["==","$type","Polygon"],["==","class","residential"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(234, 234, 230)","fill-opacity":{"base":0.6,"stops":[[8,0.8],[9,0.6]]}}},{"id":"landcover_wood","type":"fill","source":"openmaptiles","source-layer":"landcover","minzoom":10,"filter":["all",["==","$type","Polygon"],["==","class","wood"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(220,224,220)","fill-opacity":{"base":1,"stops":[[8,0],[12,1]]}}},{"id":"waterway","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["==","$type","LineString"],"layout":{"visibility":"visible"},"paint":{"line-color":"hsl(195, 17%, 78%)"}},{"id":"water_name","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["all",["==","$type","LineString"],["!has","name:en"]],"layout":{"text-field":"{name:latin} {name:nonlatin}","symbol-placement":"line","text-rotation-alignment":"map","symbol-spacing":500,"text-font":["Metropolis Medium Italic","Klokantech Noto Sans Italic","Klokantech Noto Sans CJK Regular"],"text-size":12},"paint":{"text-color":"rgb(157,169,177)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"water_name-en","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["all",["==","$type","LineString"],["has","name:en"]],"layout":{"text-field":"{name:en} {name:nonlatin}","symbol-placement":"line","text-rotation-alignment":"map","symbol-spacing":500,"text-font":["Metropolis Medium Italic","Klokantech Noto Sans Italic","Klokantech Noto Sans CJK Regular"],"text-size":12},"paint":{"text-color":"rgb(157,169,177)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"building","type":"fill","source":"openmaptiles","source-layer":"building","minzoom":12,"filter":["==","$type","Polygon"],"paint":{"fill-color":"rgb(234, 234, 229)","fill-outline-color":"rgb(219, 219, 218)","fill-antialias":true}},{"id":"tunnel_motorway_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,3],[20,40]]},"line-opacity":1}},{"id":"tunnel_motorway_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(234,234,234)","line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"aeroway-taxiway","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":12,"filter":["all",["in","class","taxiway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.55,"stops":[[13,1.8],[20,20]]},"line-opacity":1}},{"id":"aeroway-runway-casing","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["in","class","runway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.5,"stops":[[11,6],[17,55]]},"line-opacity":1}},{"id":"aeroway-area","type":"fill","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":4,"filter":["all",["==","$type","Polygon"],["in","class","runway","taxiway"]],"layout":{"visibility":"visible"},"paint":{"fill-opacity":{"base":1,"stops":[[13,0],[14,1]]},"fill-color":"rgba(255, 255, 255, 1)"}},{"id":"aeroway-runway","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["in","class","runway"],["==","$type","LineString"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgba(255, 255, 255, 1)","line-width":{"base":1.5,"stops":[[11,4],[17,50]]},"line-opacity":1},"maxzoom":24},{"id":"highway_path","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","filter":["all",["==","$type","LineString"],["==","class","path"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(234, 234, 234)","line-width":{"base":1.2,"stops":[[13,1],[20,10]]},"line-opacity":0.9}},{"id":"highway_minor","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":8,"filter":["all",["==","$type","LineString"],["in","class","minor","service","track"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.55,"stops":[[13,1.8],[20,20]]},"line-opacity":0.9}},{"id":"highway_major_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-dasharray":[12,0],"line-width":{"base":1.3,"stops":[[10,3],[20,23]]}}},{"id":"highway_major_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"#fff","line-width":{"base":1.3,"stops":[[10,2],[20,20]]}}},{"id":"highway_major_subtle","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","maxzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsla(0, 0%, 85%, 0.69)","line-width":2}},{"id":"highway_motorway_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["!in","brunnel","bridge","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,3],[20,40]]},"line-dasharray":[2,0],"line-opacity":1}},{"id":"highway_motorway_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["!in","brunnel","bridge","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":{"base":1,"stops":[[5.8,"hsla(0, 0%, 85%, 0.53)"],[6,"#fff"]]},"line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"highway_motorway_subtle","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","maxzoom":6,"filter":["all",["==","$type","LineString"],["==","class","motorway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsla(0, 0%, 85%, 0.53)","line-width":{"base":1.4,"stops":[[4,2],[6,1.3]]}}},{"id":"railway_service","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["all",["==","$type","LineString"],["all",["==","class","rail"],["has","service"]]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#dddddd","line-width":3}},{"id":"railway_service_dashline","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["all",["==","$type","LineString"],["==","class","rail"],["has","service"]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#fafafa","line-width":2,"line-dasharray":[3,3]}},{"id":"railway","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["==","$type","LineString"],["all",["!has","service"],["==","class","rail"]]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#dddddd","line-width":{"base":1.3,"stops":[[16,3],[20,7]]}}},{"id":"railway_dashline","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"paint":{"line-color":"#fafafa","line-width":{"base":1.3,"stops":[[16,2],[20,6]]},"line-dasharray":[3,3]},"type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["==","$type","LineString"],["all",["!has","service"],["==","class","rail"]]],"layout":{"visibility":"visible","line-join":"round"}},{"id":"highway_motorway_bridge_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","bridge"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,5],[20,45]]},"line-dasharray":[2,0],"line-opacity":1}},{"id":"highway_motorway_bridge_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","bridge"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":{"base":1,"stops":[[5.8,"hsla(0, 0%, 85%, 0.53)"],[6,"#fff"]]},"line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"highway_name_other","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["!=","class","motorway"],["==","$type","LineString"],["!has","name:en"]],"layout":{"text-size":10,"text-max-angle":30,"text-transform":"uppercase","symbol-spacing":350,"text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"map","text-pitch-alignment":"viewport","text-field":"{name:latin} {name:nonlatin}"},"paint":{"text-color":"#bbb","text-halo-color":"#fff","text-translate":[0,0],"text-halo-width":2,"text-halo-blur":1}},{"id":"highway_name_other-en","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["!=","class","motorway"],["==","$type","LineString"],["has","name:en"]],"layout":{"text-size":10,"text-max-angle":30,"text-transform":"uppercase","symbol-spacing":350,"text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"map","text-pitch-alignment":"viewport","text-field":"{name:en} {name:nonlatin}"},"paint":{"text-color":"#bbb","text-halo-color":"#fff","text-translate":[0,0],"text-halo-width":2,"text-halo-blur":1}},{"id":"highway_name_motorway","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["==","$type","LineString"],["==","class","motorway"]],"layout":{"text-size":10,"symbol-spacing":350,"text-font":["Metropolis Light","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"viewport","text-pitch-alignment":"viewport","text-field":"{ref}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"hsl(0, 0%, 100%)","text-translate":[0,2],"text-halo-width":1,"text-halo-blur":1}},{"id":"boundary_state","type":"line","metadata":{"mapbox:group":"a14c9607bc7954ba1df7205bf660433f"},"source":"openmaptiles","source-layer":"boundary","filter":["==","admin_level",4],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(230, 204, 207)","line-width":{"base":1.3,"stops":[[3,1],[22,15]]},"line-blur":0.4,"line-dasharray":[2,2],"line-opacity":1}},{"id":"boundary_country","type":"line","metadata":{"mapbox:group":"a14c9607bc7954ba1df7205bf660433f"},"source":"openmaptiles","source-layer":"boundary","filter":["==","admin_level",2],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"rgb(230, 204, 207)","line-width":{"base":1.1,"stops":[[3,1],[22,20]]},"line-blur":{"base":1,"stops":[[0,0.4],[22,4]]},"line-opacity":1}},{"id":"place_other","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["!in","class","city","suburb","town","village"],["==","$type","Point"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_other-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["!in","class","city","suburb","town","village"],["==","$type","Point"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_suburb","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","suburb"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_suburb-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","suburb"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_village","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["==","class","village"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":"left","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_village-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["==","class","village"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":"left","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_town","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","town"],["!has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_town-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","town"],["has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["==","class","city"],[">","rank",3]],["!has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["==","class","city"],[">","rank",3]],["has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_capital","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["==","capital",2],["==","class","city"]],["!has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"star-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":1,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_capital-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["==","capital",2],["==","class","city"]],["has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"star-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":1,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city_large","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["<=","rank",3],["==","class","city"]],["!has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city_large-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["<=","rank",3],["==","class","city"]],["has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_state","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["==","class","state"],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":10},"paint":{"text-color":"rgb(113, 129, 144)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_state-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["==","class","state"],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":10},"paint":{"text-color":"rgb(113, 129, 144)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_country_other","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":8,"filter":["all",["==","$type","Point"],["all",["==","class","country"],[">=","rank",2]],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1,"stops":[[0,10],[6,12]]}},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_other-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":8,"filter":["all",["==","$type","Point"],["all",["==","class","country"],[">=","rank",2]],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1,"stops":[[0,10],[6,12]]}},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_major","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":6,"filter":["all",["==","$type","Point"],["all",["<=","rank",1],["==","class","country"]],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1.4,"stops":[[0,10],[3,12],[4,14]]},"text-anchor":"center"},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_major-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":6,"filter":["all",["==","$type","Point"],["all",["<=","rank",1],["==","class","country"]],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1.4,"stops":[[0,10],[3,12],[4,14]]},"text-anchor":"center"},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}}]} 2 | -------------------------------------------------------------------------------- /site/base-style.json: -------------------------------------------------------------------------------- 1 | {"version":8,"name":"Positron","metadata":{"mapbox:autocomposite":false,"mapbox:type":"template","mapbox:groups":{"b6371a3f2f5a9932464fa3867530a2e5":{"name":"Transportation","collapsed":false},"a14c9607bc7954ba1df7205bf660433f":{"name":"Boundaries"},"101da9f13b64a08fa4b6ac1168e89e5f":{"name":"Places","collapsed":false}},"openmaptiles:version":"3.x","openmaptiles:mapbox:owner":"openmaptiles","openmaptiles:mapbox:source:url":"mapbox://openmaptiles.4qljc88t"},"center":[10.184401828277089,-1.1368683772161603e-13],"zoom":0.8902641636539237,"bearing":0,"pitch":0,"sources":{"openmaptiles":{"type":"vector","url":"https://free.tilehosting.com/data/v3.json?key=ozKuiN7rRsPFArLI4gsv"}},"sprite":"https://free.tilehosting.com/styles/positron/sprite","glyphs":"https://free.tilehosting.com/fonts/{fontstack}/{range}.pbf?key=ozKuiN7rRsPFArLI4gsv","layers":[{"id":"background","type":"background","paint":{"background-color":"rgb(242,243,240)"}},{"id":"park","type":"fill","source":"openmaptiles","source-layer":"park","filter":["==","$type","Polygon"],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(230, 233, 229)"}},{"id":"water","type":"fill","source":"openmaptiles","source-layer":"water","filter":["==","$type","Polygon"],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(194, 200, 202)","fill-antialias":true,"fill-outline-color":{"base":1,"stops":[[0,"hsla(180, 6%, 63%, 0.82)"],[22,"hsla(180, 6%, 63%, 0.18)"]]}}},{"id":"landcover_ice_shelf","type":"fill","source":"openmaptiles","source-layer":"landcover","maxzoom":8,"filter":["all",["==","$type","Polygon"],["==","subclass","ice_shelf"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"hsl(0, 0%, 98%)","fill-opacity":0.7}},{"id":"landcover_glacier","type":"fill","source":"openmaptiles","source-layer":"landcover","maxzoom":8,"filter":["all",["==","$type","Polygon"],["==","subclass","glacier"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"hsl(0, 0%, 98%)","fill-opacity":{"base":1,"stops":[[0,1],[8,0.5]]}}},{"id":"landuse_residential","type":"fill","source":"openmaptiles","source-layer":"landuse","maxzoom":16,"filter":["all",["==","$type","Polygon"],["==","class","residential"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(234, 234, 230)","fill-opacity":{"base":0.6,"stops":[[8,0.8],[9,0.6]]}}},{"id":"landcover_wood","type":"fill","source":"openmaptiles","source-layer":"landcover","minzoom":10,"filter":["all",["==","$type","Polygon"],["==","class","wood"]],"layout":{"visibility":"visible"},"paint":{"fill-color":"rgb(220,224,220)","fill-opacity":{"base":1,"stops":[[8,0],[12,1]]}}},{"id":"waterway","type":"line","source":"openmaptiles","source-layer":"waterway","filter":["==","$type","LineString"],"layout":{"visibility":"visible"},"paint":{"line-color":"hsl(195, 17%, 78%)"}},{"id":"water_name","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["all",["==","$type","LineString"],["!has","name:en"]],"layout":{"text-field":"{name:latin} {name:nonlatin}","symbol-placement":"line","text-rotation-alignment":"map","symbol-spacing":500,"text-font":["Metropolis Medium Italic","Klokantech Noto Sans Italic","Klokantech Noto Sans CJK Regular"],"text-size":12},"paint":{"text-color":"rgb(157,169,177)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"water_name-en","type":"symbol","source":"openmaptiles","source-layer":"water_name","filter":["all",["==","$type","LineString"],["has","name:en"]],"layout":{"text-field":"{name:en} {name:nonlatin}","symbol-placement":"line","text-rotation-alignment":"map","symbol-spacing":500,"text-font":["Metropolis Medium Italic","Klokantech Noto Sans Italic","Klokantech Noto Sans CJK Regular"],"text-size":12},"paint":{"text-color":"rgb(157,169,177)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"building","type":"fill","source":"openmaptiles","source-layer":"building","minzoom":12,"filter":["==","$type","Polygon"],"paint":{"fill-color":"rgb(234, 234, 229)","fill-outline-color":"rgb(219, 219, 218)","fill-antialias":true}},{"id":"tunnel_motorway_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,3],[20,40]]},"line-opacity":1}},{"id":"tunnel_motorway_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(234,234,234)","line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"aeroway-taxiway","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":12,"filter":["all",["in","class","taxiway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.55,"stops":[[13,1.8],[20,20]]},"line-opacity":1}},{"id":"aeroway-runway-casing","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["in","class","runway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.5,"stops":[[11,6],[17,55]]},"line-opacity":1}},{"id":"aeroway-area","type":"fill","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":4,"filter":["all",["==","$type","Polygon"],["in","class","runway","taxiway"]],"layout":{"visibility":"visible"},"paint":{"fill-opacity":{"base":1,"stops":[[13,0],[14,1]]},"fill-color":"rgba(255, 255, 255, 1)"}},{"id":"aeroway-runway","type":"line","metadata":{"mapbox:group":"1444849345966.4436"},"source":"openmaptiles","source-layer":"aeroway","minzoom":11,"filter":["all",["in","class","runway"],["==","$type","LineString"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgba(255, 255, 255, 1)","line-width":{"base":1.5,"stops":[[11,4],[17,50]]},"line-opacity":1},"maxzoom":24},{"id":"highway_path","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","filter":["all",["==","$type","LineString"],["==","class","path"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(234, 234, 234)","line-width":{"base":1.2,"stops":[[13,1],[20,10]]},"line-opacity":0.9}},{"id":"highway_minor","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":8,"filter":["all",["==","$type","LineString"],["in","class","minor","service","track"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsl(0, 0%, 88%)","line-width":{"base":1.55,"stops":[[13,1.8],[20,20]]},"line-opacity":0.9}},{"id":"highway_major_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-dasharray":[12,0],"line-width":{"base":1.3,"stops":[[10,3],[20,23]]}}},{"id":"highway_major_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"#fff","line-width":{"base":1.3,"stops":[[10,2],[20,20]]}}},{"id":"highway_major_subtle","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","maxzoom":11,"filter":["all",["==","$type","LineString"],["in","class","primary","secondary","tertiary","trunk"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsla(0, 0%, 85%, 0.69)","line-width":2}},{"id":"highway_motorway_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["!in","brunnel","bridge","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,3],[20,40]]},"line-dasharray":[2,0],"line-opacity":1}},{"id":"highway_motorway_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["!in","brunnel","bridge","tunnel"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":{"base":1,"stops":[[5.8,"hsla(0, 0%, 85%, 0.53)"],[6,"#fff"]]},"line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"highway_motorway_subtle","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","maxzoom":6,"filter":["all",["==","$type","LineString"],["==","class","motorway"]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"hsla(0, 0%, 85%, 0.53)","line-width":{"base":1.4,"stops":[[4,2],[6,1.3]]}}},{"id":"railway_service","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["all",["==","$type","LineString"],["all",["==","class","rail"],["has","service"]]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#dddddd","line-width":3}},{"id":"railway_service_dashline","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":16,"filter":["all",["==","$type","LineString"],["==","class","rail"],["has","service"]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#fafafa","line-width":2,"line-dasharray":[3,3]}},{"id":"railway","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["==","$type","LineString"],["all",["!has","service"],["==","class","rail"]]],"layout":{"visibility":"visible","line-join":"round"},"paint":{"line-color":"#dddddd","line-width":{"base":1.3,"stops":[[16,3],[20,7]]}}},{"id":"railway_dashline","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"paint":{"line-color":"#fafafa","line-width":{"base":1.3,"stops":[[16,2],[20,6]]},"line-dasharray":[3,3]},"type":"line","source":"openmaptiles","source-layer":"transportation","minzoom":13,"filter":["all",["==","$type","LineString"],["all",["!has","service"],["==","class","rail"]]],"layout":{"visibility":"visible","line-join":"round"}},{"id":"highway_motorway_bridge_casing","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","bridge"],["==","class","motorway"]]],"layout":{"line-cap":"butt","line-join":"miter","visibility":"visible"},"paint":{"line-color":"rgb(213, 213, 213)","line-width":{"base":1.4,"stops":[[5.8,0],[6,5],[20,45]]},"line-dasharray":[2,0],"line-opacity":1}},{"id":"highway_motorway_bridge_inner","type":"line","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation","minzoom":6,"filter":["all",["==","$type","LineString"],["all",["==","brunnel","bridge"],["==","class","motorway"]]],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":{"base":1,"stops":[[5.8,"hsla(0, 0%, 85%, 0.53)"],[6,"#fff"]]},"line-width":{"base":1.4,"stops":[[4,2],[6,1.3],[20,30]]}}},{"id":"highway_name_other","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["!=","class","motorway"],["==","$type","LineString"],["!has","name:en"]],"layout":{"text-size":10,"text-max-angle":30,"text-transform":"uppercase","symbol-spacing":350,"text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"map","text-pitch-alignment":"viewport","text-field":"{name:latin} {name:nonlatin}"},"paint":{"text-color":"#bbb","text-halo-color":"#fff","text-translate":[0,0],"text-halo-width":2,"text-halo-blur":1}},{"id":"highway_name_other-en","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["!=","class","motorway"],["==","$type","LineString"],["has","name:en"]],"layout":{"text-size":10,"text-max-angle":30,"text-transform":"uppercase","symbol-spacing":350,"text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"map","text-pitch-alignment":"viewport","text-field":"{name:en} {name:nonlatin}"},"paint":{"text-color":"#bbb","text-halo-color":"#fff","text-translate":[0,0],"text-halo-width":2,"text-halo-blur":1}},{"id":"highway_name_motorway","type":"symbol","metadata":{"mapbox:group":"b6371a3f2f5a9932464fa3867530a2e5"},"source":"openmaptiles","source-layer":"transportation_name","filter":["all",["==","$type","LineString"],["==","class","motorway"]],"layout":{"text-size":10,"symbol-spacing":350,"text-font":["Metropolis Light","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"symbol-placement":"line","visibility":"visible","text-rotation-alignment":"viewport","text-pitch-alignment":"viewport","text-field":"{ref}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"hsl(0, 0%, 100%)","text-translate":[0,2],"text-halo-width":1,"text-halo-blur":1}},{"id":"boundary_state","type":"line","metadata":{"mapbox:group":"a14c9607bc7954ba1df7205bf660433f"},"source":"openmaptiles","source-layer":"boundary","filter":["==","admin_level",4],"layout":{"line-cap":"round","line-join":"round","visibility":"visible"},"paint":{"line-color":"rgb(230, 204, 207)","line-width":{"base":1.3,"stops":[[3,1],[22,15]]},"line-blur":0.4,"line-dasharray":[2,2],"line-opacity":1}},{"id":"boundary_country","type":"line","metadata":{"mapbox:group":"a14c9607bc7954ba1df7205bf660433f"},"source":"openmaptiles","source-layer":"boundary","filter":["==","admin_level",2],"layout":{"line-cap":"round","line-join":"round"},"paint":{"line-color":"rgb(230, 204, 207)","line-width":{"base":1.1,"stops":[[3,1],[22,20]]},"line-blur":{"base":1,"stops":[[0,0.4],[22,4]]},"line-opacity":1}},{"id":"place_other","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["!in","class","city","suburb","town","village"],["==","$type","Point"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_other-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["!in","class","city","suburb","town","village"],["==","$type","Point"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_suburb","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","suburb"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_suburb-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","suburb"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"center","visibility":"visible","text-offset":[0.5,0],"text-anchor":"center","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_village","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["==","class","village"],["!has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":"left","text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_village-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["==","class","village"],["has","name:en"]],"layout":{"text-size":10,"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":"left","text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_town","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","town"],["!has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_town-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":15,"filter":["all",["==","$type","Point"],["==","class","town"],["has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["==","class","city"],[">","rank",3]],["!has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":14,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["==","class","city"],[">","rank",3]],["has","name:en"]],"layout":{"text-size":10,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_capital","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["==","capital",2],["==","class","city"]],["!has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"star-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":1,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_capital-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["==","capital",2],["==","class","city"]],["has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"star-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":1,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city_large","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["<=","rank",3],["==","class","city"]],["!has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:latin}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_city_large-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["all",["!=","capital",2],["<=","rank",3],["==","class","city"]],["has","name:en"]],"layout":{"text-size":14,"icon-image":{"base":1,"stops":[[0,"circle-11"],[8,""]]},"text-transform":"uppercase","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-justify":"left","visibility":"visible","text-offset":[0.5,0.2],"icon-size":0.4,"text-anchor":{"base":1,"stops":[[0,"left"],[8,"center"]]},"text-field":"{name:en}\n{name:nonlatin}"},"paint":{"text-color":"rgb(117, 129, 145)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1,"icon-opacity":0.7}},{"id":"place_state","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["==","class","state"],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":10},"paint":{"text-color":"rgb(113, 129, 144)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_state-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":12,"filter":["all",["==","$type","Point"],["==","class","state"],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":10},"paint":{"text-color":"rgb(113, 129, 144)","text-halo-color":"rgb(242,243,240)","text-halo-width":1,"text-halo-blur":1}},{"id":"place_country_other","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":8,"filter":["all",["==","$type","Point"],["all",["==","class","country"],[">=","rank",2]],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1,"stops":[[0,10],[6,12]]}},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_other-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":8,"filter":["all",["==","$type","Point"],["all",["==","class","country"],[">=","rank",2]],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1,"stops":[[0,10],[6,12]]}},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_major","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":6,"filter":["all",["==","$type","Point"],["all",["<=","rank",1],["==","class","country"]],["!has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:latin}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1.4,"stops":[[0,10],[3,12],[4,14]]},"text-anchor":"center"},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}},{"id":"place_country_major-en","type":"symbol","metadata":{"mapbox:group":"101da9f13b64a08fa4b6ac1168e89e5f"},"source":"openmaptiles","source-layer":"place","maxzoom":6,"filter":["all",["==","$type","Point"],["all",["<=","rank",1],["==","class","country"]],["has","name:en"]],"layout":{"visibility":"visible","text-field":"{name:en}\n{name:nonlatin}","text-font":["Metropolis Regular","Klokantech Noto Sans Regular","Klokantech Noto Sans CJK Regular"],"text-transform":"uppercase","text-size":{"base":1.4,"stops":[[0,10],[3,12],[4,14]]},"text-anchor":"center"},"paint":{"text-halo-width":1.4,"text-halo-color":"rgba(236,236,234,0.7)","text-color":{"base":1,"stops":[[3,"rgb(157,169,177)"],[4,"rgb(153, 153, 153)"]]}}}]} 2 | --------------------------------------------------------------------------------