[
  {
    "path": ".eslintrc.js",
    "content": "module.exports = {\n    \"env\": {\n        \"es6\": true,\n        \"node\": true,\n        \"jest\": true\n    },\n    \"parserOptions\": {\n        \"ecmaVersion\": 2018\n    },\n    \"extends\": [\n        \"eslint:recommended\",\n        \"plugin:jest/recommended\"\n    ],\n    \"rules\": {\n        \"indent\": [\n            \"error\",\n            2,\n            {\n              \"SwitchCase\": 1\n            }\n        ],\n        \"linebreak-style\": [\n            \"error\",\n            \"unix\"\n        ],\n        \"no-console\": 0,\n        \"quotes\": [\n            \"error\",\n            \"single\"\n        ],\n        \"semi\": [\n            \"error\",\n            \"always\"\n        ]\n    }\n};\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/BugReport.md",
    "content": "---\nname: \"\\U0001F41B Bug report\"\nabout: Something isn’t working as expected\n---\n\n**Steps to reproduce**\n\n<!--\nA step by step description of how to get to the error state.\nAre you using the the telnet or the local client?\nIf you run MapSCII locally, what is your NodeJS version? (run `node -v`)\nIt might help to know which operating system and keyboard language your are using.\n-->\n\n**Current behavior**\n\n<!--\nA clear and concise description of what the bug is.\nPlease include any JavaScript errors\n-->\n\n**Expected behavior**\n\n<!--\nWhat did you expect to happen?\n-->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/FeatureRequest.md",
    "content": "---\nname: \"\\U0001F680 Feature request\"\nabout: I have a suggestion (and might want to implement myself)\n---\n\n<!-- Consider opening a pull request instead: it’s a more productive way to discuss new features -->\n\n**The problem**\n\n<!-- A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] -->\n\n**Proposed solution**\n\n<!-- A clear and concise description of what you want to happen. Add any considered drawbacks. -->\n\n**Alternative solutions**\n\n<!-- A clear and concise description of any alternative solutions or features you’ve considered. -->\n\n**Additional context**\n\n<!-- Add any other context or screenshots about the feature request here. -->\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Question.md",
    "content": "---\nname: \"\\U0001F914 Support question\"\nabout: I have a question or don’t know how to do something\n---\n\n--------------^ Click “Preview”!\n\nIf you have a support question, feel free to ask it here.\n\nBefore submitting a new question, make sure you:\n\n- Searched opened and closed [GitHub issues](https://github.com/rastapasta/mapscii/issues?utf8=%E2%9C%93&q=is%3Aissue).\n- Read [the introduction](https://github.com/rastapasta/mapscii/blob/master/README.md).\n- Read [the wiki article](hhttps://wiki.openstreetmap.org/wiki/Mapscii).\n"
  },
  {
    "path": ".github/ISSUE_TEMPLATE/Support.md",
    "content": "---\nname: \"\\U0001F984 Support MapSCII’s development\"\nabout: I want to support efforts in maintaining this community-driven project\n---\n\n--------------^ Click “Preview”!\n\nDeveloping and maintaining an open source project is a big effort. MapSCII isn’t supported by any big company, and all the contributors are working on it in their free time. We need your help to make it sustainable.\n\nThere are many ways you can help:\n\n- Answer questions in [GitHub issues](https://github.com/rastapasta/mapscii/issues).\n- Review [pull requests](https://github.com/rastapasta/mapscii/pulls).\n- Fix bugs and add new features.\n- Write articles and talk about MapSCII on conferences and meetups (we’re always happy to review your texts and slides).\n"
  },
  {
    "path": ".gitignore",
    "content": "node_modules\nbundle*\n*.log\ntmp\nmbtiles\ntiles\n.DS_Store\n"
  },
  {
    "path": ".npmignore",
    "content": "node_modules\nbundle*\n*.log\ntmp\nmbtiles\ntiles\n.DS_Store\ndocs\n"
  },
  {
    "path": ".travis.yml",
    "content": "language: node_js\nnode_js:\n  - \"14\"\n  - \"16\"\n  - \"18\"\n\nscript:\n  - npm run-script lint\n  - npm test\n"
  },
  {
    "path": "AUTHORS",
    "content": "# This is the list of MapSCII authors for copyright purposes.\n#\nMichael Straßburger\nChristian Paul (https://chrpaul.de)\nJannis R <mail@jannisr.de>\nAlexander Zhukov (https://github.com/ZhukovAlexander)\nQuincy Morgan (https://github.com/quincylvania)\nlennonhill (https://github.com/lennonhill)\n"
  },
  {
    "path": "LICENSE",
    "content": "MIT License\n\nCopyright (c) 2017 Michael Straßburger\nCopyright (c) 2019 The MapSCII authors\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "# MapSCII - The Whole World In Your Console. [![Build Status](https://travis-ci.com/rastapasta/mapscii.svg?branch=master)](https://travis-ci.com/rastapasta/mapscii)\n\nA node.js based [Vector Tile](http://wiki.openstreetmap.org/wiki/Vector_tiles) to [Braille](http://www.fileformat.info/info/unicode/block/braille_patterns/utf8test.htm) and [ASCII](https://de.wikipedia.org/wiki/American_Standard_Code_for_Information_Interchange) renderer for [xterm](https://en.wikipedia.org/wiki/Xterm)-compatible terminals.\n\n<a href=\"https://asciinema.org/a/117813?autoplay=1\" target=\"_blank\">![asciicast](https://cloud.githubusercontent.com/assets/1259904/25480718/497a64e2-2b4a-11e7-9cf0-ed52ee0b89c0.png)</a>\n\n## Try it out!\n\n```sh\n$ telnet mapscii.me\n```\n\nIf you're on Windows, use the open source telnet client [PuTTY](https://www.chiark.greenend.org.uk/~sgtatham/putty/latest.html) to connect.\n\n## Features\n\n* Use your mouse to drag and zoom in and out!\n* Discover Point-of-Interests around any given location\n* Highly customizable layer styling with [Mapbox Styles](https://www.mapbox.com/mapbox-gl-style-spec/) support\n* Connect to any public or private vector tile server\n* Or just use the supplied and optimized [OSM2VectorTiles](https://github.com/osm2vectortiles) based one\n* Work offline and discover local [VectorTile](https://github.com/mapbox/vector-tile-spec)/[MBTiles](https://github.com/mapbox/mbtiles-spec)\n* Compatible with most Linux and OSX terminals\n* Highly optimized algorithms for a smooth experience\n* 100% pure JavaScript! :sunglasses:\n\n## How to run it locally\n\nWith a modern node installation available, just start it with\n\n```\nnpx mapscii\n```\n\n## How to install it locally\n\n### With npm\n\nIf you haven't already got Node.js >= version 10, then [go get it](http://nodejs.org/).\n\n```\nnpm install -g mapscii\n```\n\nIf you're on OSX, or get an error about file permissions, you may need to do ```sudo npm install -g mapscii```\n\n### With snap\n\nIn any of the [supported Linux distros](https://snapcraft.io/docs/core/install):\n\n    sudo snap install mapscii\n    \n(This snap is maintained by [@nathanhaines](https://github.com/nathanhaines/))\n\n## Running\n\nThis is pretty simple too.\n\n```\nmapscii\n```\n\n## Keyboard shortcuts\n\n* Arrows **up**, **down**, **left**, **right** to scroll around\n* Press **a** or **z** to zoom in and out\n* Press **c** to switch to block character mode\n* Press **q** to quit\n\n## Mouse control\n\nIf your terminal supports mouse events you can drag the map and use your scroll wheel to zoom in and out.\n\n## Behind the scenes\n### Libraries\n#### Mastering the console\n  * [`x256`](https://github.com/substack/node-x256) for converting RGB values to closest xterm-256 [color code](https://en.wikipedia.org/wiki/File:Xterm_256color_chart.svg)\n  * [`term-mouse`](https://github.com/CoderPuppy/term-mouse) for mouse handling\n  * [`keypress`](https://github.com/TooTallNate/keypress) for input handling\n  * [`string-width`](https://github.com/sindresorhus/string-width) to determine visual string lengths\n\n#### Discovering the map data\n* [`vector-tile`](https://github.com/mapbox/vector-tile-js) for [VectorTile](https://github.com/mapbox/vector-tile-spec/tree/master/2.1) parsing\n* [`pbf`](https://github.com/mapbox/pbf) for [Protobuf](https://developers.google.com/protocol-buffers/) decoding\n* [`mbtiles`](https://github.com/mapbox/node-mbtiles) for [MBTiles](https://github.com/mapbox/mbtiles-spec/blob/master/1.2/spec.md) parsing\n\n#### Juggling the vectors and numbers\n* [`earcut`](https://github.com/mapbox/earcut) for polygon triangulation\n* [`rbush`](https://github.com/mourner/rbush) for 2D spatial indexing of geo and label data\n* [`bresenham`](https://github.com/madbence/node-bresenham) for line point calculations\n* [`simplify-js`](https://github.com/mourner/simplify-js) for polyline simplifications\n\n#### Handling the flow\n* [`node-fetch`](https://github.com/bitinn/node-fetch) for HTTP requests\n* [`env-paths`](https://github.com/sindresorhus/env-paths) to determine where to persist downloaded tiles\n\n### TODOs\n* MapSCII\n  * [ ] GeoJSON support via [geojson-vt](https://github.com/mapbox/geojson-vt)\n  * [ ] CLI support\n    * [-] startup parameters\n      * [X] TileSource\n      * [X] Style\n      * [X] center position\n      * [X] zoom\n      * [ ] demo mode?\n\n  * [ ] mouse control\n    * [ ] hover POIs/labels\n    * [ ] hover maybe even polygons/-lines?\n\n* Styler\n  * [ ] respect zoom based style ranges\n\n* Renderer\n  * [ ] download and process tiles in a different thread ([#3](https://github.com/rastapasta/mapscii/issues/3))\n  * [ ] optimize renderer for large areas ([#6](https://github.com/rastapasta/mapscii/issues/6))\n  * [ ] label drawing\n    * [ ] multi line label?\n\n* TileSource\n  * [ ] implement single vector-tile handling\n\n## Special thanks\n\n* [lukasmartinelli](https://github.com/lukasmartinelli) & [manuelroth](https://github.com/manuelroth) for all their work on [OSM2VectorTiles](https://github.com/osm2vectortiles) (global vector tiles from [OSM Planet](https://wiki.openstreetmap.org/wiki/Planet.osm))\n* [mourner](https://github.com/mourner) for all his work on mindblowing GIS algorithms (like the used [earcut](https://github.com/mapbox/earcut), [rbush](https://github.com/mourner/rbush), [simplify-js](https://github.com/mourner/simplify-js), ..)\n\n## Licenses\n\n### Map data\n\n#### The Open Data Commons Open Database License (oDbl)\n\n[OpenStreetMap](https://www.openstreetmap.org) is open data, licensed under the [Open Data Commons Open Database License](http://opendatacommons.org/licenses/odbl/) (ODbL) by the [OpenStreetMap Foundation](http://osmfoundation.org/) (OSMF).\n\nYou are free to copy, distribute, transmit and adapt our data, as long as you credit OpenStreetMap and its contributors. If you alter or build upon our data, you may distribute the result only under the same licence. The full [legal code](http://opendatacommons.org/licenses/odbl/1.0/) explains your rights and responsibilities.\n\nThe cartography in our map tiles, and our documentation, are licenced under the [Creative Commons Attribution-ShareAlike 2.0](http://creativecommons.org/licenses/by-sa/2.0/) licence (CC BY-SA).\n\n### MapSCII\n* [License](./LICENSE)\n* [Authors](./AUTHORS)\n"
  },
  {
    "path": "bin/mapscii.sh",
    "content": "#!/bin/sh\n':' //; # Based on https://github.com/MrRio/vtop/blob/master/bin/vtop.js\n':' //; export TERM=xterm-256color\n':' //; exec \"$(command -v node || command -v nodejs)\" \"$0\" \"$@\"\n'use strict'\nrequire('../main.js');\n"
  },
  {
    "path": "main.js",
    "content": "/*#\n  MapSCII - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n  Discover the planet in your console!\n\n  This scripts boots up the application.\n\n  TODO: params parsing and so on\n#*/\n'use strict';\nconst config = require('./src/config');\nconst Mapscii = require('./src/Mapscii');\nconst argv = require('yargs')\n  .option('latitude', {\n    alias: 'lat',\n    description: 'Latitude of initial centre',\n    default: config.initialLat,\n    type: 'number',\n  })\n  .option('longitude', {\n    alias: 'lon',\n    description: 'Longitude of initial centre',\n    default: config.initialLon,\n    type: 'number',\n  })\n  .option('zoom', {\n    alias: 'z',\n    description: 'Initial zoom',\n    default: config.initialZoom,\n    type: 'number',\n  })\n  .option('width', {\n    alias: 'w',\n    description: 'Fixed width of rendering',\n    type: 'number',\n  })\n  .option('height', {\n    alias: 'h',\n    description: 'Fixed height of rendering',\n    type: 'number',\n  })\n  .option('braille', {\n    alias: 'b',\n    description: 'Activate braille rendering',\n    default: config.useBraille,\n    type: 'boolean',\n  })\n  .option('headless', {\n    alias: 'H',\n    description: 'Activate headless mode',\n    default: config.headless,\n    type: 'boolean',\n  })\n  .option('tile_source', {\n    alias: 'tileSource',\n    description: 'URL or path to osm2vectortiles source',\n    default: config.source,\n    type: 'string',\n  })\n  .option('style_file', {\n    alias: 'style',\n    description: 'path to json style file',\n    default: config.styleFile,\n    type: 'string',\n  })\n  .strict()\n  .argv;\n\nconst options = {\n  initialLat: argv.latitude,\n  initialLon: argv.longitude,\n  initialZoom: argv.zoom,\n  size: {\n    width: argv.width,\n    height: argv.height\n  },\n  useBraille: argv.braille,\n  headless: argv.headless,\n  source: argv.tile_source,\n  styleFile: argv.style_file,\n};\n\nconst mapscii = new Mapscii(options);\nmapscii.init().catch((err) => {\n  console.error('Failed to start MapSCII.');\n  console.error(err);\n});\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"mapscii\",\n  \"version\": \"0.3.1\",\n  \"description\": \"MapSCII is a Braille & ASCII world map renderer for your console, based on OpenStreetMap\",\n  \"main\": \"main.js\",\n  \"scripts\": {\n    \"lint\": \"eslint src\",\n    \"start\": \"node main\",\n    \"test\": \"jest\"\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/rastapasta/mapscii.git\"\n  },\n  \"bin\": {\n    \"mapscii\": \"./bin/mapscii.sh\"\n  },\n  \"engines\": {\n    \"node\": \">=10\"\n  },\n  \"keywords\": [\n    \"map\",\n    \"console\",\n    \"terminal\",\n    \"ascii\",\n    \"osm\",\n    \"vectortile\",\n    \"mbtiles\"\n  ],\n  \"author\": \"Michael Straßburger <codepoet@cpan.org>\",\n  \"license\": \"MIT\",\n  \"dependencies\": {\n    \"@mapbox/vector-tile\": \"^1.3.1\",\n    \"bresenham\": \"0.0.4\",\n    \"earcut\": \"^2.2.2\",\n    \"env-paths\": \"^2.2.0\",\n    \"keypress\": \"^0.2.1\",\n    \"node-fetch\": \"^2.6.1\",\n    \"pbf\": \"^3.2.1\",\n    \"rbush\": \"^3.0.1\",\n    \"simplify-js\": \"^1.2.4\",\n    \"string-width\": \"^4.2.0\",\n    \"term-mouse\": \"^0.2.2\",\n    \"x256\": \"0.0.2\",\n    \"yargs\": \"^15.4.1\"\n  },\n  \"devDependencies\": {\n    \"eslint\": \"^7.8.1\",\n    \"eslint-plugin-jest\": \"^24.0.0\",\n    \"jest\": \"^26.4.2\"\n  }\n}\n"
  },
  {
    "path": "snap/snapcraft.yaml",
    "content": "name: mapscii\nversion: master\nsummary: The Whole World In Your Console\ndescription: |\n  A node.js based Vector Tile to Braille and ASCII renderer for\n  xterm-compatible terminals.\n\ngrade: devel # must be 'stable' to release into candidate/stable channels\nconfinement: strict\n\napps:\n  mapscii:\n    command: mapscii\n    plugs: [network]\n\nparts:\n  mapscii:\n    source: .\n    plugin: nodejs\n"
  },
  {
    "path": "src/BrailleBuffer.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Simple pixel to braille character mapper\n\n  Implementation inspired by node-drawille (https://github.com/madbence/node-drawille)\n  * added color support\n  * added text label support\n  * general optimizations\n\n  Will either be merged into node-drawille or become an own module at some point\n*/\n'use strict';\nconst stringWidth = require('string-width');\nconst config = require('./config');\nconst utils = require('./utils');\n\nconst asciiMap = {\n  // '▬': [2+32, 4+64],\n  // '¯': [1+16],\n  '▀': [1+2+16+32],\n  '▄': [4+8+64+128],\n  '■': [2+4+32+64],\n  '▌': [1+2+4+8],\n  '▐': [16+32+64+128],\n  // '▓': [1+4+32+128, 2+8+16+64],\n  '█': [255],\n};\nconst termReset = '\\x1B[39;49m';\n\nclass BrailleBuffer {\n  constructor(width, height) {\n    this.brailleMap = [[0x1, 0x8],[0x2, 0x10],[0x4, 0x20],[0x40, 0x80]];\n\n    this.pixelBuffer = null;\n    this.charBuffer = null;\n    this.foregroundBuffer = null;\n    this.backgroundBuffer = null;\n\n    this.asciiToBraille = [];\n\n    this.globalBackground = null;\n\n    this.width = width;\n    this.height = height;\n\n    const size = width*height/8;\n    this.pixelBuffer = Buffer.alloc(size);\n    this.foregroundBuffer = Buffer.alloc(size);\n    this.backgroundBuffer = Buffer.alloc(size);\n\n    this._mapBraille();\n    this.clear();\n  }\n\n  clear() {\n    this.pixelBuffer.fill(0);\n    this.charBuffer = [];\n    this.foregroundBuffer.fill(0);\n    this.backgroundBuffer.fill(0);\n  }\n\n  setGlobalBackground(background) {\n    this.globalBackground = background;\n  }\n\n  setBackground(x, y, color) {\n    if (0 <= x && x < this.width && 0 <= y && y < this.height) {\n      const idx = this._project(x, y);\n      this.backgroundBuffer[idx] = color;\n    }\n  }\n\n  setPixel(x, y, color) {\n    this._locate(x, y, (idx, mask) => {\n      this.pixelBuffer[idx] |= mask;\n      this.foregroundBuffer[idx] = color;\n    });\n  }\n\n  unsetPixel(x, y) {\n    this._locate(x, y, (idx, mask) => {\n      this.pixelBuffer[idx] &= ~mask;\n    });\n  }\n\n  _project(x, y) {\n    return (x>>1) + (this.width>>1)*(y>>2);\n  }\n\n  _locate(x, y, cb) {\n    if (!((0 <= x && x < this.width) && (0 <= y && y < this.height))) {\n      return;\n    }\n    const idx = this._project(x, y);\n    const mask = this.brailleMap[y & 3][x & 1];\n    return cb(idx, mask);\n  }\n\n  _mapBraille() {\n    this.asciiToBraille = [' '];\n\n    const masks = [];\n    for (const char in asciiMap) {\n      const bits = asciiMap[char];\n      if (!(bits instanceof Array)) continue;\n      for (const mask of bits) {\n        masks.push({\n          mask: mask,\n          char: char,\n        });\n      }\n    }\n\n    //TODO Optimize this part\n    var i, k;\n    const results = [];\n    for (i = k = 1; k <= 255; i = ++k) {\n      const braille = (i & 7) + ((i & 56) << 1) + ((i & 64) >> 3) + (i & 128);\n      results.push(this.asciiToBraille[i] = masks.reduce((function(best, mask) {\n        const covered = utils.population(mask.mask & braille);\n        if (!best || best.covered < covered) {\n          return {\n            char: mask.char,\n            covered: covered,\n          };\n        } else {\n          return best;\n        }\n      }), void 0).char);\n    }\n    return results;\n  }\n\n  _termColor(foreground, background) {\n    background |= this.globalBackground;\n    if (foreground && background) {\n      return `\\x1B[38;5;${foreground};48;5;${background}m`;\n    } else if (foreground) {\n      return `\\x1B[49;38;5;${foreground}m`;\n    } else if (background) {\n      return `\\x1B[39;48;5;${background}m`;\n    } else {\n      return termReset;\n    }\n  }\n\n  frame() {\n    const output = [];\n    let currentColor = null;\n    let skip = 0;\n\n    for (let y = 0; y < this.height/4; y++) {\n      skip = 0;\n\n      for (let x = 0; x < this.width/2; x++) {\n        const idx = y*this.width/2 + x;\n\n        if (idx && !x) {\n          output.push(config.delimeter);\n        }\n\n        const colorCode = this._termColor(this.foregroundBuffer[idx], this.backgroundBuffer[idx]);\n        if (currentColor !== colorCode) {\n          output.push(currentColor = colorCode);\n        }\n\n        const char = this.charBuffer[idx];\n        if (char) {\n          skip += stringWidth(char)-1;\n          if (skip+x < this.width/2) {\n            output.push(char);\n          }\n        } else {\n          if (!skip) {\n            if (config.useBraille) {\n              output.push(String.fromCharCode(0x2800+this.pixelBuffer[idx]));\n            } else {\n              output.push(this.asciiToBraille[this.pixelBuffer[idx]]);\n            }\n          } else {\n            skip--;\n          }\n        }\n      }\n    }\n\n    output.push(termReset+config.delimeter);\n    return output.join('');\n  }\n\n  setChar(char, x, y, color) {\n    if (0 <= x && x < this.width && 0 <= y && y < this.height) {\n      const idx = this._project(x, y);\n      this.charBuffer[idx] = char;\n      this.foregroundBuffer[idx] = color;\n    }\n  }\n\n  writeText(text, x, y, color, center = true) {\n    if (center) {\n      x -= text.length/2+1;\n    }\n    for (let i = 0; i < text.length; i++) {\n      this.setChar(text.charAt(i), x+i*2, y, color);\n    }\n  }\n}\n\nmodule.exports = BrailleBuffer;\n"
  },
  {
    "path": "src/Canvas.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Canvas-like painting abstraction for BrailleBuffer\n\n  Implementation inspired by node-drawille-canvas (https://github.com/madbence/node-drawille-canvas)\n  * added support for filled polygons\n  * improved text rendering\n\n  Will most likely be turned into a stand alone module at some point\n */\n'use strict';\nconst bresenham = require('bresenham');\nconst earcut = require('earcut');\nconst BrailleBuffer = require('./BrailleBuffer');\n\nclass Canvas {\n  constructor(width, height) {\n    this.width = width;\n    this.height = height;\n    this.buffer = new BrailleBuffer(width, height);\n  }\n\n  frame() {\n    return this.buffer.frame();\n  }\n\n  clear() {\n    this.buffer.clear();\n  }\n\n  text(text, x, y, color, center = false) {\n    this.buffer.writeText(text, x, y, color, center);\n  }\n\n  line(from, to, color, width = 1) {\n    this._line(from.x, from.y, to.x, to.y, color, width);\n  }\n\n  polyline(points, color, width = 1) {\n    for (let i = 1; i < points.length; i++) {\n      const x1 = points[i - 1].x;\n      const y1 = points[i - 1].y;\n      this._line(x1, y1, points[i].x, points[i].y, width, color);\n    }\n  }\n\n  setBackground(color) {\n    this.buffer.setGlobalBackground(color);\n  }\n\n  background(x, y, color) {\n    this.buffer.setBackground(x, y, color);\n  }\n\n  polygon(rings, color) {\n    const vertices = [];\n    const holes = [];\n    for (const ring of rings) {\n      if (vertices.length) {\n        if (ring.length < 3) continue;\n        holes.push(vertices.length / 2);\n      } else {\n        if (ring.length < 3) return false;\n      }\n      for (const point of ring) {\n        vertices.push(point.x);\n        vertices.push(point.y);\n      }\n    }\n\n    let triangles;\n    try {\n      triangles = earcut(vertices, holes);\n    } catch (error) {\n      return false;\n    }\n    for (let i = 0; i < triangles.length; i += 3) {\n      const pa = this._polygonExtract(vertices, triangles[i]);\n      const pb = this._polygonExtract(vertices, triangles[i + 1]);\n      const pc = this._polygonExtract(vertices, triangles[i + 2]);\n      this._filledTriangle(pa, pb, pc, color);\n    }\n    return true;\n  }\n\n  _polygonExtract(vertices, pointId) {\n    return [vertices[pointId * 2], vertices[pointId * 2 + 1]];\n  }\n\n  // Inspired by Alois Zingl's \"The Beauty of Bresenham's Algorithm\"\n  // -> http://members.chello.at/~easyfilter/bresenham.html\n  _line(x0, y0, x1, y1, width, color) {\n    // Fall back to width-less bresenham algorithm if we dont have a width\n    if (!(width = Math.max(0, width - 1))) {\n      return bresenham(x0, y0, x1, y1, (x, y) => {\n        return this.buffer.setPixel(x, y, color);\n      });\n    }\n\n    const dx = Math.abs(x1 - x0);\n    const sx = x0 < x1 ? 1 : -1;\n    const dy = Math.abs(y1 - y0);\n    const sy = y0 < y1 ? 1 : -1;\n\n    let err = dx - dy;\n\n    const ed = dx + dy === 0 ? 1 : Math.sqrt(dx * dx + dy * dy);\n\n    width = (width + 1) / 2;\n\n    /* eslint-disable no-constant-condition */\n    while (true) {\n      this.buffer.setPixel(x0, y0, color);\n      let e2 = err;\n      let x2 = x0;\n      if (2 * e2 >= -dx) {\n        e2 += dy;\n        let y2 = y0;\n        while (e2 < ed * width && (y1 !== y2 || dx > dy)) {\n          this.buffer.setPixel(x0, y2 += sy, color);\n          e2 += dx;\n        }\n        if (x0 === x1) {\n          break;\n        }\n        e2 = err;\n        err -= dy;\n        x0 += sx;\n      }\n      if (2 * e2 <= dy) {\n        e2 = dx - e2;\n        while (e2 < ed * width && (x1 !== x2 || dx < dy)) {\n          this.buffer.setPixel(x2 += sx, y0, color);\n          e2 += dy;\n        }\n        if (y0 === y1) {\n          break;\n        }\n        err += dx;\n        y0 += sy;\n      }\n    }\n    /* eslint-enable */\n  }\n\n  _filledRectangle(x, y, width, height, color) {\n    const pointA = [x, y];\n    const pointB = [x + width, y];\n    const pointC = [x, y + height];\n    const pointD = [x + width, y + height];\n    this._filledTriangle(pointA, pointB, pointC, color);\n    this._filledTriangle(pointC, pointB, pointD, color);\n  }\n\n  _bresenham(pointA, pointB) {\n    return bresenham(pointA[0], pointA[1], pointB[0], pointB[1]);\n  }\n\n  // Draws a filled triangle\n  _filledTriangle(pointA, pointB, pointC, color) {\n    const a = this._bresenham(pointB, pointC);\n    const b = this._bresenham(pointA, pointC);\n    const c = this._bresenham(pointA, pointB);\n    \n    const points = a.concat(b).concat(c).filter((point) => {\n      var ref;\n      return (0 <= (ref = point.y) && ref < this.height);\n    }).sort(function(a, b) {\n      if (a.y === b.y) {\n        return a.x - b.x;\n      } else {\n        return a.y - b.y;\n      }\n    });\n    \n    for (let i = 0; i < points.length; i++) {\n      const point = points[i];\n      const next = points[i * 1 + 1];\n      \n      if (point.y === (next || {}).y) {\n        const left = Math.max(0, point.x);\n        const right = Math.min(this.width - 1, next.x);\n        if (left >= 0 && right <= this.width) {\n          for (let x = left; x <= right; x++) {\n            this.buffer.setPixel(x, point.y, color);\n          }\n        }\n      } else {\n        this.buffer.setPixel(point.x, point.y, color);\n      }\n      if (!next) {\n        break;\n      }\n    }\n  }\n}\n\nCanvas.prototype.stack = [];\n\nmodule.exports = Canvas;\n"
  },
  {
    "path": "src/LabelBuffer.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Using 2D spatial indexing to avoid overlapping labels and markers\n  and to find labels underneath a mouse cursor's position\n*/\n'use strict';\nconst RBush = require('rbush');\nconst stringWidth = require('string-width');\n\nmodule.exports = class LabelBuffer {\n\n  constructor() {\n    this.tree = new RBush();\n    this.margin = 5;\n  }\n\n  clear() {\n    this.tree.clear();\n  }\n\n  project(x, y) {\n    return [Math.floor(x/2), Math.floor(y/4)];\n  }\n\n  writeIfPossible(text, x, y, feature, margin) {\n    margin = margin || this.margin;\n\n    const point = this.project(x, y);\n\n    if (this._hasSpace(text, point[0], point[1])) {\n      const data = this._calculateArea(text, point[0], point[1], margin);\n      data.feature = feature;\n      return this.tree.insert(data);\n    } else {\n      return false;\n    }\n  }\n\n  featuresAt(x, y) {\n    this.tree.search({minX: x, maxX: x, minY: y, maxY: y});\n  }\n\n  _hasSpace(text, x, y) {\n    return !this.tree.collides(this._calculateArea(text, x, y));\n  }\n\n  _calculateArea(text, x, y, margin = 0) {\n    return {\n      minX: x-margin,\n      minY: y-margin/2,\n      maxX: x+margin+stringWidth(text),\n      maxY: y+margin/2,\n    };\n  }\n};\n"
  },
  {
    "path": "src/Mapscii.js",
    "content": "/*\n  MapSCII - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  UI and central command center\n*/\n'use strict';\nconst fs = require('fs');\nconst keypress = require('keypress');\nconst TermMouse = require('term-mouse');\n\nconst Renderer = require('./Renderer');\nconst TileSource = require('./TileSource');\nconst utils = require('./utils');\nlet config = require('./config');\n\nclass Mapscii {\n  constructor(options) {\n    this.width = null;\n    this.height = null;\n    this.canvas = null;\n    this.mouse = null;\n\n    this.mouseDragging = false;\n    this.mousePosition = {\n      x: 0,\n      y: 0,\n    };\n\n    this.tileSource = null;\n    this.renderer = null;\n\n    this.zoom = 0;\n    this.minZoom = null;\n    config = Object.assign(config, options);\n\n    this.center = {\n      lat: config.initialLat,\n      lon: config.initialLon\n    };\n  }\n\n  async init() {\n    if (!config.headless) {\n      this._initKeyboard();\n      this._initMouse();\n    }\n    this._initTileSource();\n    this._initRenderer();\n    this._draw();\n    this.notify('Welcome to MapSCII! Use your cursors to navigate, a/z to zoom, q to quit.');\n  }\n\n\n  _initTileSource() {\n    this.tileSource = new TileSource();\n    this.tileSource.init(config.source);\n  }\n\n  _initKeyboard() {\n    keypress(config.input);\n    if (config.input.setRawMode) {\n      config.input.setRawMode(true);\n    }\n    config.input.resume();\n\n    config.input.on('keypress', (ch, key) => this._onKey(key));\n  }\n\n  _initMouse() {\n    this.mouse = TermMouse({\n      input: config.input,\n      output: config.output,\n    });\n    this.mouse.start();\n\n    this.mouse.on('click', (event) => this._onClick(event));\n    this.mouse.on('scroll', (event) => this._onMouseScroll(event));\n    this.mouse.on('move', (event) => this._onMouseMove(event));\n  }\n\n  _initRenderer() {\n    const style = JSON.parse(fs.readFileSync(config.styleFile, 'utf8'));\n    this.renderer = new Renderer(config.output, this.tileSource, style);\n\n    config.output.on('resize', () => {\n      this._resizeRenderer();\n      this._draw();\n    });\n\n    this._resizeRenderer();\n    this.zoom = (config.initialZoom !== null) ? config.initialZoom : this.minZoom;\n  }\n\n  _resizeRenderer() {\n    this.width = config.size && config.size.width ? config.size.width * 2 : config.output.columns >> 1 << 2;\n    this.height = config.size && config.size.height ? config.size.height * 4 : config.output.rows * 4 - 4;\n\n    this.minZoom = 4-Math.log(4096/this.width)/Math.LN2;\n\n    this.renderer.setSize(this.width, this.height);\n  }\n\n  _colrow2ll(x, y) {\n    const projected = {\n      x: (x-0.5)*2,\n      y: (y-0.5)*4,\n    };\n\n    const size = utils.tilesizeAtZoom(this.zoom);\n    const [dx, dy] = [projected.x-this.width/2, projected.y-this.height/2];\n\n    const z = utils.baseZoom(this.zoom);\n    const center = utils.ll2tile(this.center.lon, this.center.lat, z);\n\n    return utils.normalize(utils.tile2ll(center.x+(dx/size), center.y+(dy/size), z));\n  }\n\n  _updateMousePosition(event) {\n    this.mousePosition = this._colrow2ll(event.x, event.y);\n  }\n\n  _onClick(event) {\n    if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) {\n      return;\n    }\n    this._updateMousePosition(event);\n\n    if (this.mouseDragging && event.button === 'left') {\n      this.mouseDragging = false;\n    } else {\n      this.setCenter(this.mousePosition.lat, this.mousePosition.lon);\n    }\n\n    this._draw();\n  }\n\n  _onMouseScroll(event) {\n    this._updateMousePosition(event);\n\n    // the location of the pointer, where we want to zoom toward\n    const targetMouseLonLat = this._colrow2ll(event.x, event.y);\n\n    // zoom toward the center\n    this.zoomBy(config.zoomStep * (event.button === 'up' ? 1 : -1));\n\n    // the location the pointer ended up after zooming\n    const offsetMouseLonLat = this._colrow2ll(event.x, event.y);\n\n    const z = utils.baseZoom(this.zoom);\n    // the projected locations\n    const targetMouseTile = utils.ll2tile(targetMouseLonLat.lon, targetMouseLonLat.lat, z);\n    const offsetMouseTile = utils.ll2tile(offsetMouseLonLat.lon, offsetMouseLonLat.lat, z);\n\n    // the projected center\n    const centerTile = utils.ll2tile(this.center.lon, this.center.lat, z);\n\n    // calculate a new center that puts the pointer back in the target location\n    const offsetCenterLonLat = utils.tile2ll(\n      centerTile.x - (offsetMouseTile.x - targetMouseTile.x),\n      centerTile.y - (offsetMouseTile.y - targetMouseTile.y),\n      z\n    );\n    // move to the new center\n    this.setCenter(offsetCenterLonLat.lat, offsetCenterLonLat.lon);\n\n    this._draw();\n  }\n\n  _onMouseMove(event) {\n    if (event.x < 0 || event.x > this.width/2 || event.y < 0 || event.y > this.height/4) {\n      return;\n    }\n    if (config.mouseCallback && !config.mouseCallback(event)) {\n      return;\n    }\n\n    // start dragging\n    if (event.button === 'left') {\n      if (this.mouseDragging) {\n        const dx = (this.mouseDragging.x-event.x)*2;\n        const dy = (this.mouseDragging.y-event.y)*4;\n\n        const size = utils.tilesizeAtZoom(this.zoom);\n\n        const newCenter = utils.tile2ll(\n          this.mouseDragging.center.x+(dx/size),\n          this.mouseDragging.center.y+(dy/size),\n          utils.baseZoom(this.zoom)\n        );\n\n        this.setCenter(newCenter.lat, newCenter.lon);\n\n        this._draw();\n\n      } else {\n        this.mouseDragging = {\n          x: event.x,\n          y: event.y,\n          center: utils.ll2tile(this.center.lon, this.center.lat, utils.baseZoom(this.zoom)),\n        };\n      }\n    }\n\n    this._updateMousePosition(event);\n    this.notify(this._getFooter());\n  }\n\n  _onKey(key) {\n    if (config.keyCallback && !config.keyCallback(key)) return;\n    if (!key || !key.name) return;\n\n    // check if the pressed key is configured\n    let draw = true;\n    switch (key.name) {\n      case 'q':\n        if (config.quitCallback) {\n          config.quitCallback();\n        } else {\n          process.exit(0);\n        }\n        break;\n      case 'a':\n        this.zoomBy(config.zoomStep);\n        break;\n      case 'y':\n      case 'z':\n        this.zoomBy(-config.zoomStep);\n        break;\n      case 'left':\n      case 'h':\n        this.moveBy(0, -8/Math.pow(2, this.zoom));\n        break;\n      case 'right':\n      case 'l':\n        this.moveBy(0, 8/Math.pow(2, this.zoom));\n        break;\n      case 'up':\n      case 'k':\n        this.moveBy(6/Math.pow(2, this.zoom), 0);\n        break;\n      case 'down':\n      case 'j':\n        this.moveBy(-6/Math.pow(2, this.zoom), 0);\n        break;\n      case 'c':\n        config.useBraille = !config.useBraille;\n        break;\n      default:\n        draw = false;\n    }\n\n    if (draw) {\n      this._draw();\n    }\n  }\n\n  _draw() {\n    this.renderer.draw(this.center, this.zoom).then((frame) => {\n      this._write(frame);\n      this.notify(this._getFooter());\n    }).catch(() => {\n      this.notify('renderer is busy');\n    });\n  }\n\n  _getFooter() {\n    // tile = utils.ll2tile(this.center.lon, this.center.lat, this.zoom);\n    // `tile: ${utils.digits(tile.x, 3)}, ${utils.digits(tile.x, 3)}   `+\n\n    let footer = `center: ${utils.digits(this.center.lat, 3)}, ${utils.digits(this.center.lon, 3)} `;\n    footer += `  zoom: ${utils.digits(this.zoom, 2)} `;\n    if (this.mousePosition.lat !== undefined) {\n      footer += `  mouse: ${utils.digits(this.mousePosition.lat, 3)}, ${utils.digits(this.mousePosition.lon, 3)} `;\n    }\n    return footer;\n  }\n\n  notify(text) {\n    config.onUpdate && config.onUpdate();\n    if (!config.headless) {\n      this._write('\\r\\x1B[K' + text);\n    }\n  }\n\n  _write(output) {\n    config.output.write(output);\n  }\n\n  zoomBy(step) {\n    if (this.zoom+step < this.minZoom) {\n      return this.zoom = this.minZoom;\n    }\n    if (this.zoom+step > config.maxZoom) {\n      return this.zoom = config.maxZoom;\n    }\n\n    this.zoom += step;\n  }\n\n  moveBy(lat, lon) {\n    this.setCenter(this.center.lat+lat, this.center.lon+lon);\n  }\n\n  setCenter(lat, lon) {\n    this.center = utils.normalize({\n      lon: lon,\n      lat: lat,\n    });\n  }\n}\n\nmodule.exports = Mapscii;\n"
  },
  {
    "path": "src/Renderer.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  The Console Vector Tile renderer - bäm!\n*/\n'use strict';\nconst x256 = require('x256');\nconst simplify = require('simplify-js');\n\nconst Canvas = require('./Canvas');\nconst LabelBuffer = require('./LabelBuffer');\nconst Styler = require('./Styler');\nconst utils = require('./utils');\nconst config = require('./config');\n\nclass Renderer {\n  constructor(output, tileSource, style) {\n    this.output = output;\n    this.tileSource = tileSource;\n    this.labelBuffer = new LabelBuffer();\n    this.styler = new Styler(style);\n    this.tileSource.useStyler(this.styler);\n  }\n\n  setSize(width, height) {\n    this.width = width;\n    this.height = height;\n    this.canvas = new Canvas(width, height);\n  }\n\n  async draw(center, zoom) {\n    if (this.isDrawing) return Promise.reject();\n    this.isDrawing = true;\n\n    this.labelBuffer.clear();\n    this._seen = {};\n\n    let ref;\n    const color = ((ref = this.styler.styleById['background']) !== null ?\n      ref.paint['background-color']\n      :\n      void 0\n    );\n    if (color) {\n      this.canvas.setBackground(x256(utils.hex2rgb(color)));\n    }\n\n    this.canvas.clear();\n\n    try {\n      let tiles = this._visibleTiles(center, zoom);\n      await Promise.all(tiles.map(async(tile) => {\n        await this._getTile(tile);\n        this._getTileFeatures(tile, zoom);\n      }));\n      await this._renderTiles(tiles);\n      return this._getFrame();\n    } catch(e) {\n      console.error(e);\n    } finally {\n      this.isDrawing = false;\n      this.lastDrawAt = Date.now();\n    }\n  }\n\n  _visibleTiles(center, zoom) {\n    const z = utils.baseZoom(zoom);\n    center = utils.ll2tile(center.lon, center.lat, z);\n    \n    const tiles = [];\n    const tileSize = utils.tilesizeAtZoom(zoom);\n    \n    for (let y = Math.floor(center.y) - 1; y <= Math.floor(center.y) + 1; y++) {\n      for (let x = Math.floor(center.x) - 1; x <= Math.floor(center.x) + 1; x++) {\n        const tile = {x, y, z};\n        const position = {\n          x: this.width / 2 - (center.x - tile.x) * tileSize,\n          y: this.height / 2 - (center.y - tile.y) * tileSize,\n        };\n        \n        const gridSize = Math.pow(2, z);\n        \n        tile.x %= gridSize;\n        \n        if (tile.x < 0) {\n          tile.x = z === 0 ? 0 : tile.x + gridSize;\n        }\n        \n        if (tile.y < 0 || tile.y >= gridSize || position.x + tileSize < 0 || position.y + tileSize < 0 || position.x > this.width || position.y > this.height) {\n          continue;\n        }\n        \n        tiles.push({\n          xyz: tile,\n          zoom,\n          position,\n          size: tileSize,\n        });\n      }\n    }\n    return tiles;\n  }\n\n  async _getTile(tile) {\n    tile.data = await this.tileSource.getTile(tile.xyz.z, tile.xyz.x, tile.xyz.y);\n    return tile;\n  }\n\n  _getTileFeatures(tile, zoom) {\n    const position = tile.position;\n    const layers = {};\n    const drawOrder = this._generateDrawOrder(zoom);\n    for (const layerId of drawOrder) {\n      const layer = (tile.data.layers || {})[layerId];\n      if (!layer) {\n        continue;\n      }\n      \n      const scale = layer.extent / utils.tilesizeAtZoom(zoom);\n      layers[layerId] = {\n        scale: scale,\n        features: layer.tree.search({\n          minX: -position.x * scale,\n          minY: -position.y * scale,\n          maxX: (this.width - position.x) * scale,\n          maxY: (this.height - position.y) * scale\n        }),\n      };\n    }\n    tile.layers = layers;\n    return tile;\n  }\n\n  _renderTiles(tiles) {\n    const labels = [];\n    if (tiles.length === 0) return;\n    \n    const drawOrder = this._generateDrawOrder(tiles[0].xyz.z);\n    for (const layerId of drawOrder) {\n      for (const tile of tiles) {\n        const layer = tile.layers[layerId];\n        if (!layer) continue;\n        for (const feature of layer.features) {\n          // continue if feature.id and drawn[feature.id]\n          // drawn[feature.id] = true;\n          if (layerId.match(/label/)) {\n            labels.push({\n              tile,\n              feature,\n              scale: layer.scale\n            });\n          } else {\n            this._drawFeature(tile, feature, layer.scale);\n          }\n        }\n      }\n    }\n\n    labels.sort((a, b) => {\n      return a.feature.sorty - b.feature.sort;\n    });\n\n    for (const label of labels) {\n      this._drawFeature(label.tile, label.feature, label.scale);\n    }\n  }\n\n  _getFrame() {\n    let frame = '';\n    if (!this.lastDrawAt) {\n      frame += this.terminal.CLEAR;\n    }\n    frame += this.terminal.MOVE;\n    frame += this.canvas.frame();\n    return frame;\n  }\n\n  featuresAt(x, y) {\n    return this.labelBuffer.featuresAt(x, y);\n  }\n\n  _drawFeature(tile, feature, scale) {\n    let points, placed;\n    if (feature.style.minzoom && tile.zoom < feature.style.minzoom) {\n      return false;\n    } else if (feature.style.maxzoom && tile.zoom > feature.style.maxzoom) {\n      return false;\n    }\n    \n    switch (feature.style.type) {\n      case 'line': {\n        let width = feature.style.paint['line-width'];\n        if (width instanceof Object) {\n          // TODO: apply the correct zoom based value\n          width = width.stops[0][1];\n        }\n        points = this._scaleAndReduce(tile, feature, feature.points, scale);\n        if (points.length) {\n          this.canvas.polyline(points, feature.color, width);\n        }\n        break;\n      }\n      case 'fill': {\n        points = feature.points.map((p) => {\n          return this._scaleAndReduce(tile, feature, p, scale, false);\n        });\n        this.canvas.polygon(points, feature.color);\n        break;\n      }\n      case 'symbol': {\n        const genericSymbol = config.poiMarker;\n        const text = feature.label || config.poiMarker;\n        \n        if (this._seen[text] && !genericSymbol) {\n          return false;\n        }\n        \n        placed = false;\n        const pointsOfInterest = this._scaleAndReduce(tile, feature, feature.points, scale);\n        for (const point of pointsOfInterest) {\n          const x = point.x - text.length;\n          const layerMargin = (config.layers[feature.layer] || {}).margin;\n          const margin = layerMargin || config.labelMargin;\n          if (this.labelBuffer.writeIfPossible(text, x, point.y, feature, margin)) {\n            this.canvas.text(text, x, point.y, feature.color);\n            placed = true;\n            break;\n          } else {\n            const cluster = (config.layers[feature.layer] || {}).cluster;\n            if (cluster && this.labelBuffer.writeIfPossible(config.poiMarker, point.x, point.y, feature, 3)) {\n              this.canvas.text(config.poiMarker, point.x, point.y, feature.color);\n              placed = true;\n              break;\n            }\n          }\n        }\n        if (placed) {\n          this._seen[text] = true;\n        }\n        break;\n      }\n    }\n    return true;\n  }\n\n  _scaleAndReduce(tile, feature, points, scale, filter = true) {\n    let lastX;\n    let lastY;\n    let outside;\n    const scaled = [];\n    \n    const minX = -this.tilePadding;\n    const minY = -this.tilePadding;\n    const maxX = this.width + this.tilePadding;\n    const maxY = this.height + this.tilePadding;\n    \n    for (const point of points) {\n      const x = Math.floor(tile.position.x + (point.x / scale));\n      const y = Math.floor(tile.position.y + (point.y / scale));\n      if (lastX === x && lastY === y) {\n        continue;\n      }\n      lastY = y;\n      lastX = x;\n      if (filter) {\n        if (x < minX || x > maxX || y < minY || y > maxY) {\n          if (outside) {\n            continue;\n          }\n          outside = true;\n        } else {\n          if (outside) {\n            outside = null;\n            scaled.push({x: lastX, y: lastY});\n          }\n        }\n      }\n      scaled.push({x, y});\n    }\n    if (feature.style.type !== 'symbol') {\n      if (scaled.length < 2) {\n        return [];\n      }\n      if (config.simplifyPolylines) {\n        return simplify(scaled, .5, true);\n      } else {\n        return scaled;\n      }\n    } else {\n      return scaled;\n    }\n  }\n\n  _generateDrawOrder(zoom) {\n    if (zoom < 2) {\n      return [\n        'admin',\n        'water',\n        'country_label',\n        'marine_label',\n      ];\n    } else {\n      return [\n        'landuse',\n        'water',\n        'marine_label',\n        'building',\n        'road',\n        'admin',\n        'country_label',\n        'state_label',\n        'water_label',\n        'place_label',\n        'rail_station_label',\n        'poi_label',\n        'road_label',\n        'housenum_label',\n      ];\n    }\n  }\n}\n\nRenderer.prototype.terminal = {\n  CLEAR: '\\x1B[2J',\n  MOVE: '\\x1B[?6h',\n};\n\nRenderer.prototype.isDrawing = false;\nRenderer.prototype.lastDrawAt = 0;\nRenderer.prototype.labelBuffer = null;\nRenderer.prototype.tileSource = null;\nRenderer.prototype.tilePadding = 64;\n\nmodule.exports = Renderer;\n"
  },
  {
    "path": "src/Styler.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Minimalistic parser and compiler for Mapbox (Studio) Map Style files\n  See: https://www.mapbox.com/mapbox-gl-style-spec/\n\n  Compiles layer filter instructions into a chain of true/false returning\n  anonymous functions to improve rendering speed compared to realtime parsing.\n*/\n'use strict';\n\nclass Styler {\n  constructor(style) {\n    this.styleById = {};\n    this.styleByLayer = {};\n    var base, name;\n    this.styleName = style.name;\n    if (style.constants) {\n      this._replaceConstants(style.constants, style.layers);\n    }\n\n    for (const layer of style.layers) {\n      if (layer.ref && this.styleById[layer.ref]) {\n        for (const ref of ['type', 'source-layer', 'minzoom', 'maxzoom', 'filter']) {\n          if (this.styleById[layer.ref][ref] && !layer[ref]) {\n            layer[ref] = this.styleById[layer.ref][ref];\n          }\n        }\n      }\n\n      layer.appliesTo = this._compileFilter(layer.filter);\n\n      //TODO Better translation of: @styleByLayer[style['source-layer']] ?= []\n      if ((base = this.styleByLayer)[name = layer['source-layer']] == null) {\n        base[name] = [];\n      }\n      this.styleByLayer[layer['source-layer']].push(layer);\n      this.styleById[layer.id] = layer;\n    }\n  }\n\n  getStyleFor(layer, feature) {\n    if (!this.styleByLayer[layer]) {\n      return false;\n    }\n\n    for (const style of this.styleByLayer[layer]) {\n      if (style.appliesTo(feature)) {\n        return style;\n      }\n    }\n\n    return false;\n  }\n\n  _replaceConstants(constants, tree) {\n    for (const id in tree) {\n      const node = tree[id];\n      switch (typeof node) {\n        case 'object':\n          if (node.constructor.name.match(/Stream/)) {\n            continue;\n          }\n          this._replaceConstants(constants, node);\n          break;\n        case 'string':\n          if (node.charAt(0) === '@') {\n            tree[id] = constants[node];\n          }\n      }\n    }\n  }\n\n  //TODO Better translation of the long cases.\n  _compileFilter(filter) {\n    let filters;\n    switch (filter != null ? filter[0] : void 0) {\n      case 'all':\n        filter = filter.slice(1);\n        filters = (() => {\n          return filter.map((sub) => this._compileFilter(sub));\n        }).call(this);\n        return (feature) => !!filters.find((appliesTo) => {\n          return !appliesTo(feature);\n        });\n      case 'any':\n        filter = filter.slice(1);\n        filters = (() => {\n          return filter.map((sub) => this._compileFilter(sub));\n        }).call(this);\n        return (feature) => !!filters.find((appliesTo) => {\n          return appliesTo(feature);\n        });\n      case 'none':\n        filter = filter.slice(1);\n        filters = (() => {\n          return filter.map((sub) => this._compileFilter(sub));\n        }).call(this);\n        return (feature) => !filters.find((appliesTo) => {\n          return !appliesTo(feature);\n        });\n      case '==':\n        return (feature) => feature.properties[filter[1]] === filter[2];\n      case '!=':\n        return (feature) => feature.properties[filter[1]] !== filter[2];\n      case 'in':\n        return (feature) => !!filter.slice(2).find((value) => {\n          return feature.properties[filter[1]] === value;\n        });\n      case '!in':\n        return (feature) => !filter.slice(2).find((value) => {\n          return feature.properties[filter[1]] === value;\n        });\n      case 'has':\n        return (feature) => !!feature.properties[filter[1]];\n      case '!has':\n        return (feature) => !feature.properties[filter[1]];\n      case '>':\n        return (feature) => feature.properties[filter[1]] > filter[2];\n      case '>=':\n        return (feature) => feature.properties[filter[1]] >= filter[2];\n      case '<':\n        return (feature) => feature.properties[filter[1]] < filter[2];\n      case '<=':\n        return (feature) => feature.properties[filter[1]] <= filter[2];\n      default:\n        return () => true;\n    }\n  }\n}\n\nmodule.exports = Styler;\n"
  },
  {
    "path": "src/Tile.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Handling of and access to single VectorTiles\n*/\n'use strict';\nconst VectorTile = require('@mapbox/vector-tile').VectorTile;\nconst Protobuf = require('pbf');\nconst zlib = require('zlib');\nconst RBush = require('rbush');\nconst x256 = require('x256');\n\nconst config = require('./config');\nconst utils = require('./utils');\n\nclass Tile {\n  constructor(styler) {\n    this.styler = styler;\n  }\n\n  load(buffer) {\n    return this._unzipIfNeeded(buffer).then((buffer) => {\n      return this._loadTile(buffer);\n    }).then(() => {\n      return this._loadLayers();\n    }).then(() => {\n      return this;\n    });\n  }\n\n  _loadTile(buffer) {\n    this.tile = new VectorTile(new Protobuf(buffer));\n  }\n\n  _unzipIfNeeded(buffer) {\n    return new Promise((resolve, reject) => {\n      if (this._isGzipped(buffer)) {\n        zlib.gunzip(buffer, (err, data) => {\n          if (err) {\n            reject(err);\n          }\n          resolve(data);\n        });\n      } else {\n        resolve(buffer);\n      }\n    });\n  }\n\n  _isGzipped(buffer) {\n    return buffer.slice(0, 2).indexOf(Buffer.from([0x1f, 0x8b])) === 0;\n  }\n\n  _loadLayers() {\n    const layers = {};\n    const colorCache = {};\n    for (const name in this.tile.layers) {\n      const layer = this.tile.layers[name];\n      const nodes = [];\n      //continue if name is 'water'\n      for (let i = 0; i < layer.length; i++) {\n        // TODO: caching of similar attributes to avoid looking up the style each time\n        //continue if @styler and not @styler.getStyleFor layer, feature\n\n        const feature = layer.feature(i);\n        feature.properties.$type = [undefined, 'Point', 'LineString', 'Polygon'][feature.type];\n        let style;\n        if (this.styler) {\n          style = this.styler.getStyleFor(name, feature);\n          if (!style) {\n            continue;\n          }\n        }\n        let color = (\n          style.paint['line-color'] ||\n          style.paint['fill-color'] ||\n          style.paint['text-color']\n        );\n        // TODO: style zoom stops handling\n        if (color instanceof Object) {\n          color = color.stops[0][1];\n        }\n        const colorCode = colorCache[color] || (colorCache[color] = x256(utils.hex2rgb(color)));\n        // TODO: monkey patching test case for tiles with a reduced extent 4096 / 8 -> 512\n        // use feature.loadGeometry() again as soon as we got a 512 extent tileset\n        const geometries = feature.loadGeometry(); //@_reduceGeometry feature, 8\n        const sort = feature.properties.localrank || feature.properties.scalerank;\n        const label = style.type === 'symbol' ? feature.properties['name_' + config.language] || feature.properties.name_en || feature.properties.name || feature.properties.house_num : void 0;\n        if (style.type === 'fill') {\n          nodes.push(this._addBoundaries(true, {\n            //            id: feature.id\n            layer: name,\n            style,\n            label,\n            sort,\n            points: geometries,\n            color: colorCode,\n          }));\n        } else {\n          for (const points of geometries) {\n            nodes.push(this._addBoundaries(false, {\n              //id: feature.id,\n              layer: name,\n              style,\n              label,\n              sort,\n              points,\n              color: colorCode,\n            }));\n          }\n        }\n      }\n      const tree = new RBush(18);\n      tree.load(nodes);\n      layers[name] = {\n        extent: layer.extent,\n        tree,\n      };\n    }\n    return this.layers = layers;\n  }\n\n  _addBoundaries(deep, data) {\n    let minX = 2e308;\n    let maxX = -2e308;\n    let minY = 2e308;\n    let maxY = -2e308;\n    const points = (deep ? data.points[0] : data.points);\n    for (const p of points) {\n      if (p.x < minX) minX = p.x;\n      if (p.x > maxX) maxX = p.x;\n      if (p.y < minY) minY = p.y;\n      if (p.y > maxY) maxY = p.y;\n    }\n    data.minX = minX;\n    data.maxX = maxX;\n    data.minY = minY;\n    data.maxY = maxY;\n    return data;\n  }\n\n  _reduceGeometry(feature, factor) {\n    const results = [];\n    const geometries = feature.loadGeometry();\n    for (const points of geometries) {\n      const reduced = [];\n      let last;\n      for (const point of points) {\n        const p = {\n          x: Math.floor(point.x / factor),\n          y: Math.floor(point.y / factor)\n        };\n        if (last && last.x === p.x && last.y === p.y) {\n          continue;\n        }\n        reduced.push(last = p);\n      }\n      results.push(reduced);\n    }\n    return results;\n  }\n}\n\nTile.prototype.layers = {};\n\nmodule.exports = Tile;\n"
  },
  {
    "path": "src/TileSource.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  Source for VectorTiles - supports\n  * remote TileServer\n  * local MBTiles and VectorTiles\n*/\n'use strict';\nconst fs = require('fs');\nconst path = require('path');\nconst fetch = require('node-fetch');\nconst envPaths = require('env-paths');\nconst paths = envPaths('mapscii');\n\nconst Tile = require('./Tile');\nconst config = require('./config');\n\n// https://github.com/mapbox/node-mbtiles has native build dependencies (sqlite3)\n// To maximize MapSCII’s compatibility, MBTiles support must be manually added via\n// $> npm install -g @mapbox/mbtiles\nlet MBTiles = null;\ntry {\n  MBTiles = require('@mapbox/mbtiles');\n} catch (err) {void 0;}\n\nconst modes = {\n  MBTiles: 1,\n  VectorTile: 2,\n  HTTP: 3,\n};\n\nclass TileSource {\n  init(source) {\n    this.source = source;\n    \n    this.cache = {};\n    this.cacheSize = 16;\n    this.cached = [];\n    \n    this.mode = null;\n    this.mbtiles = null;\n    this.styler = null;\n    \n    if (this.source.startsWith('http')) {\n      if (config.persistDownloadedTiles) {\n        this._initPersistence();\n      }\n\n      this.mode = modes.HTTP;\n\n    } else if (this.source.endsWith('.mbtiles')) {\n      if (!MBTiles) {\n        throw new Error('MBTiles support must be installed with following command: \\'npm install -g @mapbox/mbtiles\\'');\n      }\n\n      this.mode = modes.MBTiles;\n      this.loadMBTiles(source);\n    } else {\n      throw new Error('source type isn\\'t supported yet');\n    }\n  }\n\n  loadMBTiles(source) {\n    return new Promise((resolve, reject) => {\n      new MBTiles(source, (err, mbtiles) => {\n        if (err) {\n          reject(err);\n        }\n        this.mbtiles = mbtiles;\n        resolve();\n      });\n    });\n  }\n\n  useStyler(styler) {\n    this.styler = styler;\n  }\n\n  getTile(z, x, y) {\n    if (!this.mode) {\n      throw new Error('no TileSource defined');\n    }\n    \n    const cached = this.cache[[z, x, y].join('-')];\n    if (cached) {\n      return Promise.resolve(cached);\n    }\n    \n    if (this.cached.length > this.cacheSize) {\n      const overflow = Math.abs(this.cacheSize - this.cache.length);\n      for (const tile in this.cached.splice(0, overflow)) {\n        delete this.cache[tile];\n      }\n    }\n  \n    switch (this.mode) {\n      case modes.MBTiles:\n        return this._getMBTile(z, x, y);\n      case modes.HTTP:\n        return this._getHTTP(z, x, y);\n    }\n  }\n\n  _getHTTP(z, x, y) {\n    let promise;\n    const persistedTile = this._getPersited(z, x, y);\n    if (config.persistDownloadedTiles && persistedTile) {\n      promise = Promise.resolve(persistedTile);\n    } else {\n      promise = fetch(this.source + [z,x,y].join('/') + '.pbf')\n        .then((res) => res.buffer())\n        .then((buffer) => {\n          if (config.persistDownloadedTiles) {\n            this._persistTile(z, x, y, buffer);\n            return buffer;\n          }\n        });\n    }\n    return promise.then((buffer) => {\n      return this._createTile(z, x, y, buffer);\n    });\n  }\n\n  _getMBTile(z, x, y) {\n    return new Promise((resolve, reject) => {\n      this.mbtiles.getTile(z, x, y, (err, buffer) => {\n        if (err) {\n          reject(err);\n        }\n        resolve(this._createTile(z, x, y, buffer));\n      });\n    });\n  }\n\n  _createTile(z, x, y, buffer) {\n    const name = [z, x, y].join('-');\n    this.cached.push(name);\n    \n    const tile = this.cache[name] = new Tile(this.styler);\n    return tile.load(buffer);\n  }\n\n  _initPersistence() {\n    try {\n      this._createFolder(paths.cache);\n    } catch (error) {\n      config.persistDownloadedTiles = false;\n    }\n  }\n\n  _persistTile(z, x, y, buffer) {\n    const zoom = z.toString();\n    this._createFolder(path.join(paths.cache, zoom));\n    const filePath = path.join(paths.cache, zoom, `${x}-${y}.pbf`);\n    return fs.writeFile(filePath, buffer, () => null);\n  }\n\n  _getPersited(z, x, y) {\n    try {\n      return fs.readFileSync(path.join(paths.cache, z.toString(), `${x}-${y}.pbf`));\n    } catch (error) {\n      return false;\n    }\n  }\n\n  _createFolder(path) {\n    try {\n      fs.mkdirSync(path);\n      return true;\n    } catch (error) {\n      if (error.code === 'EEXIST') return true;\n      throw error;\n    }\n  }\n}\n\nmodule.exports = TileSource;\n"
  },
  {
    "path": "src/TileSource.spec.js",
    "content": "'use strict';\nconst TileSource = require('./TileSource');\n\ndescribe('TileSource', () => {\n  describe('with a HTTP source', () => {\n    test('sets the mode to 3', async () => {\n      const tileSource = new TileSource();\n      await tileSource.init('http://mapscii.me/');\n      expect(tileSource.mode).toBe(3);\n    });\n  });\n});\n"
  },
  {
    "path": "src/config.js",
    "content": "module.exports = {\n  language: 'en',\n\n  // TODO: adapt to osm2vectortiles successor openmaptiles v3)\n  // mapscii.me hosts the last available version, 2016-06-20\n  source: 'http://mapscii.me/',\n\n  //source: __dirname+\"/../mbtiles/regensburg.mbtiles\",\n\n  styleFile: __dirname+'/../styles/dark.json',\n\n  initialZoom: null,\n  maxZoom: 18,\n  zoomStep: 0.2,\n\n  // sf lat: 37.787946, lon: -122.407522\n  // iceland lat: 64.124229, lon: -21.811552\n  // rgbg\n  // lat: 49.019493, lon: 12.098341\n  initialLat: 52.51298,\n  initialLon: 13.42012,\n\n  simplifyPolylines: false,\n\n  useBraille: true,\n\n  // Downloaded files get persisted in ~/.mapscii\n  persistDownloadedTiles: true,\n\n  tileRange: 14,\n  projectSize: 256,\n\n  labelMargin: 5,\n\n  layers: {\n    housenum_label: {\n      margin: 4\n    },\n    poi_label: {\n      cluster: true,\n      margin: 5,\n    },\n    place_label: {\n      cluster: true,\n    },\n    state_label: {\n      cluster: true,\n    },\n  },\n\n  input: process.stdin,\n  output: process.stdout,\n\n  headless: false,\n\n  delimeter: '\\n\\r',\n\n  poiMarker: '◉',\n};\n"
  },
  {
    "path": "src/utils.js",
    "content": "/*\n  termap - Terminal Map Viewer\n  by Michael Strassburger <codepoet@cpan.org>\n\n  methods used all around\n*/\n'use strict';\nconst config = require('./config');\n\nconst constants = {\n  RADIUS: 6378137,\n};\n\nconst utils = {\n  clamp: (num, min, max) => {\n    if (num <= min) {\n      return min;\n    } else if (num >= max) {\n      return max;\n    } else {\n      return num;\n    }\n  },\n\n  baseZoom: (zoom) => {\n    return Math.min(config.tileRange, Math.max(0, Math.floor(zoom)));\n  },\n\n  tilesizeAtZoom: (zoom) => {\n    return config.projectSize * Math.pow(2, zoom-utils.baseZoom(zoom));\n  },\n\n  deg2rad: (angle) => {\n    // (angle / 180) * Math.PI\n    return angle * 0.017453292519943295;\n  },\n\n  ll2tile: (lon, lat, zoom) => {\n    return {\n      x: (lon+180)/360*Math.pow(2, zoom),\n      y: (1-Math.log(Math.tan(lat*Math.PI/180)+1/Math.cos(lat*Math.PI/180))/Math.PI)/2*Math.pow(2, zoom),\n      z: zoom,\n    };\n  },\n\n  tile2ll: (x, y, zoom) => {\n    const n = Math.PI - 2*Math.PI*y/Math.pow(2, zoom);\n\n    return {\n      lon: x/Math.pow(2, zoom)*360-180,\n      lat: 180/Math.PI*Math.atan(0.5*(Math.exp(n)-Math.exp(-n))),\n    };\n  },\n\n  metersPerPixel: (zoom, lat = 0) => {\n    return (Math.cos(lat * Math.PI/180) * 2 * Math.PI * constants.RADIUS) / (256 * Math.pow(2, zoom));\n  },\n\n  hex2rgb: (color) => {\n    if (typeof color !== 'string') return [255, 0, 0];\n\n    if (!/^#[a-fA-F0-9]{3,6}$/.test(color)) {\n      throw new Error(`${color} isn't a supported hex color`);\n    }\n\n    color = color.substr(1);\n    const decimal = parseInt(color, 16);\n\n    if (color.length === 3) {\n      const rgb = [decimal>>8, (decimal>>4)&15, decimal&15];\n      return rgb.map((c) => {\n        return c + (c<<4);\n      });\n    } else {\n      return [(decimal>>16)&255, (decimal>>8)&255, decimal&255];\n    }\n  },\n\n  digits: (number, digits) => {\n    return Math.floor(number*Math.pow(10, digits))/Math.pow(10, digits);\n  },\n\n  normalize: (ll) => {\n    if (ll.lon < -180) ll.lon += 360;\n    if (ll.lon > 180) ll.lon -= 360;\n\n    if (ll.lat > 85.0511) ll.lat = 85.0511;\n    if (ll.lat < -85.0511) ll.lat = -85.0511;\n\n    return ll;\n  },\n\n  population: (val) => {\n    let bits = 0;\n    while (val > 0) {\n      bits += val & 1;\n      val >>= 1;\n    }\n    return bits;\n  },\n};\n\nmodule.exports = utils;\n"
  },
  {
    "path": "src/utils.spec.js",
    "content": "'use strict';\nconst utils = require('./utils');\n\ndescribe('utils', () => {\n  describe('hex2rgb', () => {\n    describe.each([\n      ['#ff0000', 255, 0, 0],\n      ['#ffff00', 255, 255, 0],\n      ['#0000ff', 0, 0, 255],\n      ['#112233', 17, 34, 51],\n      ['#888', 136, 136, 136],\n    ])('when given \"%s\"', (input, r, g, b) => {\n      test(`returns [${r},${g},${b}]`, () => {\n        expect(utils.hex2rgb(input)).toEqual([r, g, b]);\n      });\n    });\n\n    test('throws an Error when given \"33\"', () => {\n      function wrapper() {\n        utils.hex2rgb('33');\n      }\n      expect(wrapper).toThrow('isn\\'t a supported hex color');\n    });\n  });\n});\n\ndescribe('normalize', () => {\n  describe.each([\n    [0, 0, 0, 0],\n    [61, 48, 61, 48],\n    [-61, -48, -61, -48],\n    [181, 85.06, -179, 85.0511],\n    [-181, -85.06, 179, -85.0511],\n  ])('when given lon=%f and lat=%f', (lon, lat, expected_lon, expected_lat) => {\n    const input = {\n      lon,\n      lat,\n    };\n    test(`returns lon=${expected_lon} and lat=${expected_lat}`, () => {\n      const expected = {\n        lon: expected_lon,\n        lat: expected_lat,\n      };\n      expect(utils.normalize(input)).toEqual(expected);\n    });\n  });\n});\n"
  },
  {
    "path": "styles/bright.json",
    "content": "{\n  \"name\": \"Bright\",\n  \"constants\": {\n    \"@background\": \"#000\",\n    \"@water\": \"#5f87ff\",\n\n    \"@building\": \"#99b\",\n    \"@housenum_label\": \"#88a\",\n\n    \"@country_label_1\": \"#ff0\",\n    \"@country_label_2\": \"#ff0\",\n    \"@country_label_3\": \"#ff0\",\n    \"@country_label_4\": \"#ff0\",\n\n    \"@admin_level_2\": \"#fff\",\n    \"@admin_level_3\": \"#aac\",\n    \"@admin_level_4\": \"#777\",\n\n    \"@poi_label_1\": \"#ff0\",\n    \"@poi_label_2\": \"#ee0\",\n    \"@poi_label_3\": \"#cc0\",\n    \"@poi_label_4\": \"#aa0\",\n\n    \"@place_label_city\": \"#f00\",\n    \"@place_label_town\": \"#d33\",\n    \"@place_label_village\": \"#c33\",\n    \"@place_label_other\": \"#b33\"\n  },\n  \"layers\": [\n    {\n      \"type\": \"background\",\n      \"id\": \"background\",\n      \"paint\": {\n        \"background-color\": \"@background\"\n      }\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"landuse_hospital\",\n      \"paint\": {\n        \"fill-color\": \"#fde\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"hospital\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway\",\n      \"paint\": {\n        \"line-color\": \"#303090\",\n        \"line-width\": {\n          \"base\": 1.3,\n          \"stops\": [\n            [\n              13,\n              0.5\n            ],\n            [\n              20,\n              2\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"!=\",\n          \"class\",\n          \"river\"\n        ],\n        [\n          \"!=\",\n          \"class\",\n          \"stream\"\n        ],\n        [\n          \"!=\",\n          \"class\",\n          \"canal\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway_river\",\n      \"paint\": {\n        \"line-color\": \"#303090\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              11,\n              0.5\n            ],\n            [\n              20,\n              6\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"river\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway_stream_canal\",\n      \"paint\": {\n        \"line-color\": \"#a0c8f0\",\n        \"line-width\": {\n          \"base\": 1.3,\n          \"stops\": [\n            [\n              13,\n              0.5\n            ],\n            [\n              20,\n              6\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"in\",\n        \"class\",\n        \"stream\",\n        \"canal\"\n      ]\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"water\",\n      \"paint\": {\n        \"fill-color\": \"@water\"\n      },\n      \"source-layer\": \"water\"\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"aeroway_fill\",\n      \"paint\": {\n        \"fill-color\": \"#f0ede9\"\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"==\",\n        \"$type\",\n        \"Polygon\"\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"aeroway_runway\",\n      \"paint\": {\n        \"line-color\": \"#f0ede9\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              11,\n              3\n            ],\n            [\n              20,\n              16\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"type\",\n          \"runway\"\n        ]\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"aeroway_taxiway\",\n      \"paint\": {\n        \"line-color\": \"#f0ede9\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              11,\n              0.5\n            ],\n            [\n              20,\n              6\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"type\",\n          \"taxiway\"\n        ]\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"building\",\n      \"paint\": {\n        \"line-color\": \"@building\"\n      },\n      \"source-layer\": \"building\",\n      \"minzoom\": 14.5\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_motorway_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_service_track_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1\n            ],\n            [\n              16,\n              4\n            ],\n            [\n              20,\n              11\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_street_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              0.5\n            ],\n            [\n              13,\n              1\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"street\",\n          \"street_limited\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_secondary_tertiary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              8,\n              1.5\n            ],\n            [\n              20,\n              17\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_trunk_primary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_motorway_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"#cba\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1.2\n            ],\n            [\n              20,\n              4\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"==\",\n            \"structure\",\n            \"tunnel\"\n          ],\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_major_rail\",\n      \"paint\": {\n        \"line-color\": \"#bbb\",\n        \"line-width\": {\n          \"base\": 1.4,\n          \"stops\": [\n            [\n              14,\n              0.4\n            ],\n            [\n              15,\n              0.75\n            ],\n            [\n              20,\n              2\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"major_rail\",\n          \"minor_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_motorway_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ],\n      \"minzoom\": 12\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_service_track_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1\n            ],\n            [\n              16,\n              4\n            ],\n            [\n              20,\n              11\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ],\n      \"minzoom\": 13\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_street_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              0.5\n            ],\n            [\n              13,\n              1\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"in\",\n            \"class\",\n            \"street\",\n            \"street_limited\"\n          ],\n          [\n            \"!in\",\n            \"structure\",\n            \"bridge\",\n            \"tunnel\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_secondary_tertiary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              8,\n              1.5\n            ],\n            [\n              20,\n              17\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_trunk_primary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_motorway_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ],\n      \"minzoom\": 5\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"#cba\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1.2\n            ],\n            [\n              20,\n              4\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ],\n          [\n            \"!in\",\n            \"structure\",\n            \"bridge\",\n            \"tunnel\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_major_rail\",\n      \"paint\": {\n        \"line-color\": \"#bbb\",\n        \"line-width\": {\n          \"base\": 1.4,\n          \"stops\": [\n            [\n              14,\n              0.4\n            ],\n            [\n              15,\n              0.75\n            ],\n            [\n              20,\n              2\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_motorway_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_service_track_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1\n            ],\n            [\n              16,\n              4\n            ],\n            [\n              20,\n              11\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_link_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              1\n            ],\n            [\n              13,\n              3\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_street_casing\",\n      \"paint\": {\n        \"line-color\": \"#cfcdca\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              0.5\n            ],\n            [\n              13,\n              1\n            ],\n            [\n              14,\n              4\n            ],\n            [\n              20,\n              15\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"street\",\n          \"street_limited\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_secondary_tertiary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              8,\n              1.5\n            ],\n            [\n              20,\n              17\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_trunk_primary_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_motorway_casing\",\n      \"paint\": {\n        \"line-color\": \"#e9ac77\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              5,\n              0.4\n            ],\n            [\n              6,\n              0.6\n            ],\n            [\n              7,\n              1.5\n            ],\n            [\n              20,\n              22\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"#cba\",\n        \"line-width\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              15,\n              1.2\n            ],\n            [\n              20,\n              4\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"==\",\n            \"structure\",\n            \"bridge\"\n          ],\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_major_rail\",\n      \"paint\": {\n        \"line-color\": \"#bbb\",\n        \"line-width\": {\n          \"base\": 1.4,\n          \"stops\": [\n            [\n              14,\n              0.4\n            ],\n            [\n              15,\n              0.75\n            ],\n            [\n              20,\n              2\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_4\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_4\",\n        \"line-width\": 1\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \">=\",\n          \"admin_level\",\n          4\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_3\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_3\",\n        \"line-width\": 1\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          3\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_2\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_2\",\n        \"line-width\": {\n          \"base\": 1,\n          \"stops\": [\n            [\n              4,\n              1.4\n            ],\n            [\n              5,\n              2\n            ],\n            [\n              12,\n              8\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          2\n        ],\n        [\n          \"==\",\n          \"disputed\",\n          0\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_2_disputed\",\n      \"paint\": {\n        \"line-color\": \"#9e9cab\",\n        \"line-width\": {\n          \"base\": 1,\n          \"stops\": [\n            [\n              4,\n              1.4\n            ],\n            [\n              5,\n              2\n            ],\n            [\n              12,\n              8\n            ]\n          ]\n        }\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          2\n        ],\n        [\n          \"==\",\n          \"disputed\",\n          1\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"water_label\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"water_label\",\n      \"filter\": [\n        \"==\",\n        \"$type\",\n        \"Point\"\n      ],\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_4\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_4\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          4\n        ]\n      ],\n      \"minzoom\": 16,\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_3\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_3\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          3\n        ]\n      ],\n      \"minzoom\": 15,\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_2\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_2\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          2\n        ]\n      ],\n      \"minzoom\": 14,\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"rail_station_label\",\n      \"paint\": {\n        \"text-color\": \"#666\"\n      },\n      \"source-layer\": \"rail_station_label\",\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_1\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_1\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          1\n        ]\n      ],\n      \"minzoom\": 13,\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"airport_label\",\n      \"paint\": {\n        \"text-color\": \"#666\"\n      },\n      \"source-layer\": \"airport_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"in\",\n          \"scalerank\",\n          1,\n          2,\n          3\n        ]\n      ],\n      \"minzoom\": 11,\n      \"layout\": {\n        \"text-size\": 12\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"road_label\",\n      \"paint\": {\n        \"text-color\": \"#765\"\n      },\n      \"source-layer\": \"road_label\",\n      \"filter\": [\n        \"!=\",\n        \"class\",\n        \"ferry\"\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"base\": 1,\n          \"stops\": [\n            [\n              13,\n              12\n            ],\n            [\n              14,\n              13\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"housenum_label\",\n      \"paint\": {\n        \"text-color\": \"@housenum_label\"\n      },\n      \"minzoom\": 16.5,\n      \"source-layer\": \"housenum_label\"\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_other\",\n      \"paint\": {\n        \"text-color\": \"@place_label_other\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"in\",\n        \"type\",\n        \"hamlet\",\n        \"suburb\",\n        \"neighbourhood\",\n        \"island\",\n        \"islet\"\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              12,\n              10\n            ],\n            [\n              15,\n              14\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_village\",\n      \"paint\": {\n        \"text-color\": \"@place_label_village\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"village\"\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              10,\n              12\n            ],\n            [\n              15,\n              22\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_town\",\n      \"paint\": {\n        \"text-color\": \"@place_label_town\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"town\"\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              10,\n              14\n            ],\n            [\n              15,\n              24\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_city\",\n      \"paint\": {\n        \"text-color\": \"@place_label_city\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"city\"\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"base\": 1.2,\n          \"stops\": [\n            [\n              7,\n              14\n            ],\n            [\n              11,\n              24\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_4\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \">=\",\n          \"labelrank\",\n          4\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              11\n            ],\n            [\n              4,\n              12\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_4\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \">=\",\n          \"labelrank\",\n          4\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              11\n            ],\n            [\n              4,\n              12\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_3\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          3\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              11\n            ],\n            [\n              4,\n              14\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_3\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          3\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              11\n            ],\n            [\n              4,\n              14\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_2\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          2\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              14\n            ],\n            [\n              4,\n              16\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_2\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          2\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              14\n            ],\n            [\n              4,\n              16\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_1\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          1\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              18\n            ],\n            [\n              4,\n              22\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_1\",\n      \"paint\": {\n        \"text-color\": \"#74aee9\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          1\n        ]\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              18\n            ],\n            [\n              4,\n              22\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_4\",\n      \"paint\": {\n        \"text-color\": \"@country_label_4\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \">=\",\n        \"scalerank\",\n        4\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              4,\n              11\n            ],\n            [\n              6,\n              15\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_3\",\n      \"paint\": {\n        \"text-color\": \"@country_label_3\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        3\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              3,\n              11\n            ],\n            [\n              7,\n              17\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_2\",\n      \"paint\": {\n        \"text-color\": \"@country_label_2\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        2\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              2,\n              11\n            ],\n            [\n              5,\n              17\n            ]\n          ]\n        }\n      }\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_1\",\n      \"paint\": {\n        \"text-color\": \"@country_label_1\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        1\n      ],\n      \"layout\": {\n        \"text-size\": {\n          \"stops\": [\n            [\n              1,\n              11\n            ],\n            [\n              4,\n              17\n            ]\n          ]\n        }\n      }\n    }\n  ]\n}\n"
  },
  {
    "path": "styles/dark.json",
    "content": "{\n  \"name\": \"dark\",\n  \"constants\": {\n    \"@admin_level_2\": \"#fff\",\n    \"@admin_level_2_disputed\": \"#fff\",\n    \"@admin_level_2_maritime\": \"#9bf\",\n    \"@admin_level_3\": \"#aac\",\n    \"@admin_level_3_maritime\": \"#8af\",\n    \"@admin_level_4\": \"#777\",\n\n    \"@aeroway_fill\": \"#f0ede9\",\n    \"@aeroway_runway\": \"#f0ede9\",\n    \"@aeroway_taxiway\": \"#f0ede9\",\n\n    \"@airport_label\": \"#666\",\n\n    \"@background\": \"#000\",\n\n    \"@bridge_link\": \"#fea\",\n    \"@bridge_major_rail\": \"#bbb\",\n    \"@bridge_major_rail_hatching\": \"#bbb\",\n    \"@bridge_motorway\": \"#fc8\",\n    \"@bridge_motorway_link\": \"#fc8\",\n    \"@bridge_path_pedestrian\": \"#cba\",\n    \"@bridge_secondary_tertiary\": \"#fea\",\n    \"@bridge_service_track\": \"#fff\",\n    \"@bridge_street\": \"#fff\",\n    \"@bridge_trunk_primary\": \"#fea\",\n\n    \"@building\": \"#99b\",\n\n    \"@country_label_1\": \"#ff0\",\n    \"@country_label_2\": \"#ff0\",\n    \"@country_label_3\": \"#ff0\",\n    \"@country_label_4\": \"#ff0\",\n\n    \"@landuse_cemetery\": \"#e0e4dd\",\n    \"@landuse_hospital\": \"#d9b\",\n    \"@landuse_overlay_national_park\": \"#d8e8c8\",\n    \"@landuse_park\": \"#7b5\",\n    \"@landuse_school\": \"#f0e8f8\",\n    \"@landuse_wood\": \"#6a4\",\n\n    \"@marine_label_4\": \"#74aee9\",\n    \"@marine_label_line_1\": \"#74aee9\",\n    \"@marine_label_line_2\": \"#74aee9\",\n    \"@marine_label_line_3\": \"#74aee9\",\n    \"@marine_label_line_4\": \"#74aee9\",\n    \"@marine_label_point_1\": \"#74aee9\",\n    \"@marine_label_point_2\": \"#74aee9\",\n    \"@marine_label_point_3\": \"#74aee9\",\n\n    \"@place_label_city\": \"#f00\",\n    \"@place_label_town\": \"#d33\",\n    \"@place_label_village\": \"#c33\",\n    \"@place_label_other\": \"#b33\",\n\n    \"@poi_label_1\": \"#ff0\",\n    \"@poi_label_2\": \"#ee0\",\n    \"@poi_label_3\": \"#cc0\",\n    \"@poi_label_4\": \"#aa0\",\n\n    \"@rail_station_label\": \"#666\",\n\n    \"@road_label\": \"#765\",\n    \"@road_link\": \"#fea\",\n    \"@road_major_rail\": \"#bbb\",\n    \"@road_major_rail_hatching\": \"#bbb\",\n    \"@road_motorway\": \"#fc8\",\n    \"@road_motorway_link\": \"#fc8\",\n    \"@road_path_pedestrian\": \"#cba\",\n    \"@road_secondary_tertiary\": \"#fea\",\n    \"@road_service_track\": \"#fff\",\n    \"@road_street\": \"#fff\",\n    \"@road_trunk_primary\": \"#fea\",\n\n    \"@tunnel_link\": \"#fff4c6\",\n    \"@tunnel_major_rail\": \"#bbb\",\n    \"@tunnel_major_rail_hatching\": \"#bbb\",\n    \"@tunnel_motorway\": \"#ffdaa6\",\n    \"@tunnel_motorway_link\": \"#fc8\",\n    \"@tunnel_path_pedestrian\": \"#cba\",\n    \"@tunnel_secondary_tertiary\": \"#fff4c6\",\n    \"@tunnel_service_track\": \"#fff\",\n    \"@tunnel_street\": \"#fff\",\n    \"@tunnel_trunk_primary\": \"#fff4c6\",\n\n    \"@water\": \"#5f87ff\",\n    \"@water_label\": \"#74aee9\",\n\n    \"@waterway\": \"#a0c8f0\",\n    \"@waterway_river\": \"#a0c8f0\",\n    \"@waterway_stream_canal\": \"#a0c8f0\"\n  },\n  \"layers\": [\n    {\n      \"type\": \"background\",\n      \"id\": \"background\",\n      \"paint\": {\n        \"background-color\": \"@background\"\n      }\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"landuse_overlay_national_park\",\n      \"paint\": {\n        \"fill-color\": \"@landuse_overlay_national_park\"\n      },\n      \"source-layer\": \"landuse_overlay\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"national_park\"\n      ]\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"landuse_park\",\n      \"paint\": {\n        \"fill-color\": \"@landuse_park\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"park\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"landuse_cemetery\",\n      \"paint\": {\n        \"fill-color\": \"@landuse_cemetery\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"cemetery\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"landuse_hospital\",\n      \"paint\": {\n        \"line-color\": \"@landuse_hospital\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"hospital\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"landuse_school\",\n      \"paint\": {\n        \"line-color\": \"@landuse_school\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"school\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"landuse_wood\",\n      \"paint\": {\n        \"line-color\": \"@landuse_wood\"\n      },\n      \"source-layer\": \"landuse\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"wood\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway\",\n      \"paint\": {\n        \"line-color\": \"@waterway\"\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"!=\",\n          \"class\",\n          \"river\"\n        ],\n        [\n          \"!=\",\n          \"class\",\n          \"stream\"\n        ],\n        [\n          \"!=\",\n          \"class\",\n          \"canal\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway_river\",\n      \"paint\": {\n        \"line-color\": \"@waterway_river\"\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"==\",\n        \"class\",\n        \"river\"\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"waterway_stream_canal\",\n      \"paint\": {\n        \"line-color\": \"@waterway_stream_canal\"\n      },\n      \"source-layer\": \"waterway\",\n      \"filter\": [\n        \"in\",\n        \"class\",\n        \"stream\",\n        \"canal\"\n      ]\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"water\",\n      \"paint\": {\n        \"fill-color\": \"@water\"\n      },\n      \"source-layer\": \"water\"\n    },\n    {\n      \"type\": \"fill\",\n      \"id\": \"aeroway_fill\",\n      \"paint\": {\n        \"fill-color\": \"@aeroway_fill\"\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"==\",\n        \"$type\",\n        \"Polygon\"\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"aeroway_runway\",\n      \"paint\": {\n        \"line-color\": \"@aeroway_runway\"\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"type\",\n          \"runway\"\n        ]\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"aeroway_taxiway\",\n      \"paint\": {\n        \"line-color\": \"@aeroway_taxiway\"\n      },\n      \"source-layer\": \"aeroway\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"type\",\n          \"taxiway\"\n        ]\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"building\",\n      \"paint\": {\n        \"line-color\": \"@building\"\n      },\n      \"source-layer\": \"building\"\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_path_pedestrian\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"==\",\n            \"structure\",\n            \"tunnel\"\n          ],\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_motorway_link\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_motorway_link\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_service_track\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_service_track\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_link\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_link\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_street\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_street\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"street\",\n          \"street_limited\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_secondary_tertiary\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_secondary_tertiary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_trunk_primary\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_trunk_primary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_motorway\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_motorway\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_major_rail\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_major_rail\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"major_rail\",\n          \"minor_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"tunnel_major_rail_hatching\",\n      \"paint\": {\n        \"line-color\": \"@tunnel_major_rail_hatching\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"tunnel\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"major_rail\",\n          \"minor_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"@road_path_pedestrian\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ],\n          [\n            \"!in\",\n            \"structure\",\n            \"bridge\",\n            \"tunnel\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_motorway_link\",\n      \"paint\": {\n        \"line-color\": \"@road_motorway_link\"\n      },\n      \"source-layer\": \"road\",\n      \"minzoom\": 12,\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_service_track\",\n      \"paint\": {\n        \"line-color\": \"@road_service_track\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_link\",\n      \"paint\": {\n        \"line-color\": \"@road_link\"\n      },\n      \"source-layer\": \"road\",\n      \"minzoom\": 13,\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_street\",\n      \"paint\": {\n        \"line-color\": \"@road_street\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"in\",\n            \"class\",\n            \"street\",\n            \"street_limited\"\n          ],\n          [\n            \"!in\",\n            \"structure\",\n            \"bridge\",\n            \"tunnel\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_secondary_tertiary\",\n      \"paint\": {\n        \"line-color\": \"@road_secondary_tertiary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_trunk_primary\",\n      \"paint\": {\n        \"line-color\": \"@road_trunk_primary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_motorway\",\n      \"paint\": {\n        \"line-color\": \"@road_motorway\"\n      },\n      \"source-layer\": \"road\",\n      \"minzoom\": 5,\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_major_rail\",\n      \"paint\": {\n        \"line-color\": \"@road_major_rail\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"road_major_rail_hatching\",\n      \"paint\": {\n        \"line-color\": \"@road_major_rail_hatching\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ],\n        [\n          \"!in\",\n          \"structure\",\n          \"bridge\",\n          \"tunnel\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_path_pedestrian\",\n      \"paint\": {\n        \"line-color\": \"@bridge_path_pedestrian\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"all\",\n          [\n            \"==\",\n            \"structure\",\n            \"bridge\"\n          ],\n          [\n            \"in\",\n            \"class\",\n            \"path\",\n            \"pedestrian\"\n          ]\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_motorway_link\",\n      \"paint\": {\n        \"line-color\": \"@bridge_motorway_link\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway_link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_service_track\",\n      \"paint\": {\n        \"line-color\": \"@bridge_service_track\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"service\",\n          \"track\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_link\",\n      \"paint\": {\n        \"line-color\": \"@bridge_link\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"link\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_street\",\n      \"paint\": {\n        \"line-color\": \"@bridge_street\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"street\",\n          \"street_limited\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_secondary_tertiary\",\n      \"paint\": {\n        \"line-color\": \"@bridge_secondary_tertiary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"secondary\",\n          \"tertiary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_trunk_primary\",\n      \"paint\": {\n        \"line-color\": \"@bridge_trunk_primary\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"in\",\n          \"class\",\n          \"trunk\",\n          \"primary\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_motorway\",\n      \"paint\": {\n        \"line-color\": \"@bridge_motorway\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"motorway\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_major_rail\",\n      \"paint\": {\n        \"line-color\": \"@bridge_major_rail\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"bridge_major_rail_hatching\",\n      \"paint\": {\n        \"line-color\": \"@bridge_major_rail_hatching\"\n      },\n      \"source-layer\": \"road\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"structure\",\n          \"bridge\"\n        ],\n        [\n          \"==\",\n          \"class\",\n          \"major_rail\"\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_3\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_3\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \">=\",\n          \"admin_level\",\n          4\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_3\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_3\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          3\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_2\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_2\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          2\n        ],\n        [\n          \"==\",\n          \"disputed\",\n          0\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_2_disputed\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_2_disputed\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          2\n        ],\n        [\n          \"==\",\n          \"disputed\",\n          1\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          0\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_3_maritime\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_3_maritime\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \">=\",\n          \"admin_level\",\n          3\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          1\n        ]\n      ]\n    },\n    {\n      \"type\": \"line\",\n      \"id\": \"admin_level_2_maritime\",\n      \"paint\": {\n        \"line-color\": \"@admin_level_2_maritime\"\n      },\n      \"source-layer\": \"admin\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"admin_level\",\n          2\n        ],\n        [\n          \"==\",\n          \"maritime\",\n          1\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"water_label\",\n      \"paint\": {\n        \"text-color\": \"@water_label\"\n      },\n      \"source-layer\": \"water_label\",\n      \"filter\": [\n        \"==\",\n        \"$type\",\n        \"Point\"\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_4\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_4\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          4\n        ]\n      ],\n      \"minzoom\": 16\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_3\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_3\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          3\n        ]\n      ],\n      \"minzoom\": 15\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_2\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_2\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          2\n        ]\n      ],\n      \"minzoom\": 14\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"rail_station_label\",\n      \"paint\": {\n        \"text-color\": \"@rail_station_label\"\n      },\n      \"source-layer\": \"rail_station_label\"\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"poi_label_1\",\n      \"paint\": {\n        \"text-color\": \"@poi_label_1\"\n      },\n      \"source-layer\": \"poi_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"scalerank\",\n          1\n        ]\n      ],\n      \"minzoom\": 13\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"airport_label\",\n      \"paint\": {\n        \"text-color\": \"@airport_label\"\n      },\n      \"source-layer\": \"airport_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"in\",\n          \"scalerank\",\n          1,\n          2,\n          3\n        ]\n      ],\n      \"minzoom\": 11\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"road_label\",\n      \"paint\": {\n        \"text-color\": \"@road_label\"\n      },\n      \"source-layer\": \"road_label\",\n      \"filter\": [\n        \"!=\",\n        \"class\",\n        \"ferry\"\n      ],\n      \"minzoom\": 15.5\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_other\",\n      \"paint\": {\n        \"text-color\": \"@place_label_other\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"in\",\n        \"type\",\n        \"hamlet\",\n        \"suburb\",\n        \"neighbourhood\",\n        \"island\",\n        \"islet\"\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_village\",\n      \"paint\": {\n        \"text-color\": \"@place_label_village\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"village\"\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_town\",\n      \"paint\": {\n        \"text-color\": \"@place_label_town\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"town\"\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"place_label_city\",\n      \"paint\": {\n        \"text-color\": \"@place_label_city\"\n      },\n      \"source-layer\": \"place_label\",\n      \"filter\": [\n        \"==\",\n        \"type\",\n        \"city\"\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_4\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_line_4\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \">=\",\n          \"labelrank\",\n          4\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_4\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_4\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \">=\",\n          \"labelrank\",\n          4\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_3\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_line_3\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          3\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_3\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_point_3\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          3\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_2\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_line_2\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          2\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_2\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_point_2\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          2\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_line_1\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_line_1\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"LineString\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          1\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"marine_label_point_1\",\n      \"paint\": {\n        \"text-color\": \"@marine_label_point_1\"\n      },\n      \"source-layer\": \"marine_label\",\n      \"filter\": [\n        \"all\",\n        [\n          \"==\",\n          \"$type\",\n          \"Point\"\n        ],\n        [\n          \"==\",\n          \"labelrank\",\n          1\n        ]\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_4\",\n      \"paint\": {\n        \"text-color\": \"@country_label_4\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \">=\",\n        \"scalerank\",\n        4\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_3\",\n      \"paint\": {\n        \"text-color\": \"@country_label_3\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        3\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_2\",\n      \"paint\": {\n        \"text-color\": \"@country_label_2\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        2\n      ]\n    },\n    {\n      \"type\": \"symbol\",\n      \"id\": \"country_label_1\",\n      \"paint\": {\n        \"text-color\": \"@country_label_1\"\n      },\n      \"source-layer\": \"country_label\",\n      \"filter\": [\n        \"==\",\n        \"scalerank\",\n        1\n      ]\n    }\n  ]\n}\n"
  }
]