├── .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 |
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 |
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 |
29 |
30 |
31 |
32 |
37 |
38 | Defines a circle-color property for layer without filter for the below GeoJSON source
39 |
40 |
47 |
48 |
55 |
56 |
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 |
29 |
30 |
31 |
32 |
37 |
38 | Defines a circle-color property for layer without filter for the below GeoJSON source
39 |
40 |
47 |
48 |
55 |
56 |
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]
5 | [][circleci]
6 | [][dm-prod]
7 | [][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 |
--------------------------------------------------------------------------------