├── .gitignore ├── CHANGELOG.md ├── README.md ├── cli.js ├── mvtview.js ├── package-lock.json ├── package.json ├── test └── test.js ├── utils.js └── views └── index.html /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules 3 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## 0.3.0 2 | 3 | * Add `--token` option to CLI 4 | * Update to Mapbox GL JS v1.5.0 5 | * Publish publicly as @mapbox/mvtview 6 | 7 | ## 0.2.0 8 | 9 | * add layer sidebar to main view, enabling filtering of layers on click 10 | * update colors to match Mapbox [assembly](https://www.mapbox.com/assembly/documentation/#Colors) colors 11 | 12 | ## 0.1.0 13 | 14 | * first! 15 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mvtview 2 | 3 | Visualize a [Mapbox Vector Tile](https://github.com/mapbox/vector-tile-spec) locally. Heavily inspired by [mbview](https://github.com/mapbox/mbview). 4 | 5 | ### Usage 6 | 7 | Set `MapboxAccessToken` as an environment variable. 8 | 9 | ```shell 10 | npm install @mapbox/mvtview -g 11 | mvtview path/to/tile.mvt 12 | # navigate to localhost:3000 13 | ``` 14 | 15 |  16 | 17 |  18 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const fs = require('fs'); 4 | const path = require('path'); 5 | const open = require('open'); 6 | const argv = require('minimist')(process.argv.slice(2)); 7 | const utils = require('./utils.js'); 8 | const TileView = require('./mvtview.js'); 9 | 10 | if (!argv._[0]) { 11 | console.log('No tile provided.'); 12 | console.log(utils.usage); 13 | process.exit(1); 14 | } 15 | const tileBuffer = fs.readFileSync(path.resolve(argv._[0])); 16 | const token = argv.token || process.env.MapboxAccessToken; 17 | if (!token.includes('pk.')) { 18 | console.log('Token must be a public (pk.) token.'); 19 | console.log(utils.usage); 20 | process.exit(1); 21 | } 22 | 23 | const params = { 24 | accessToken: token, 25 | buffer: tileBuffer 26 | }; 27 | 28 | TileView.serve(params, (err, config) => { 29 | console.log('Listening on http://localhost:3000'); 30 | open('http://localhost:3000'); 31 | }); 32 | -------------------------------------------------------------------------------- /mvtview.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const express = require('express'); 3 | const ejs = require('ejs'); 4 | const zlib = require('zlib'); 5 | const utils = require('./utils.js'); 6 | 7 | const app = express(); 8 | app.set('views', __dirname + '/views'); 9 | app.engine('html', require('ejs').renderFile); 10 | 11 | module.exports = { 12 | serve: function(params, callback) { 13 | const listen = this.listen; 14 | const buffer = params.buffer; 15 | listen({ buffer, params }, callback); 16 | }, 17 | 18 | listen: function(config, onListen) { 19 | app.get('/', (req, res) => { 20 | utils.makeStyle(config.buffer).then((style) => { 21 | return res.render('./index.html', { style: style, params: config.params }); 22 | }); 23 | }); 24 | 25 | app.get('/:z(\\d+)/:x(\\d+)/:y(\\d+).mvt', (req, res) => { 26 | res.set({ 27 | 'Content-Type': 'application/vnd.mapbox-vector-tile', 28 | 'Content-Encoding': 'gzip', 29 | "Access-Control-Allow-Origin": "*", 30 | "Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept" 31 | }); 32 | 33 | const z = parseInt(req.params.z); 34 | const x = parseInt(req.params.x); 35 | const y = parseInt(req.params.y); 36 | 37 | if (z === 7 && x === 20 && y === 44) { 38 | return res.send(zlib.gzipSync(config.buffer)); 39 | } else { 40 | return res.status(404).send("Sorry can't find that!"); 41 | } 42 | }); 43 | 44 | config.server = app.listen(3000, function () { 45 | onListen(null, config); 46 | }); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/mvtview", 3 | "version": "0.3.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@mapbox/mvt-fixtures": { 8 | "version": "3.4.0", 9 | "resolved": "https://registry.npmjs.org/@mapbox/mvt-fixtures/-/mvt-fixtures-3.4.0.tgz", 10 | "integrity": "sha512-NBZ4vQuY2p67cKa21CjtZuhMlHX2gu8RG4EoCglFapQtQJXauV2lxsEFq5hzIgFI8gEo/lIS0ewMQHNwnLhwfw==", 11 | "requires": { 12 | "@mapbox/sphericalmercator": "^1.0.5", 13 | "@mapbox/vector-tile": "^1.3.0", 14 | "d3-queue": "^3.0.7", 15 | "pbf": "^3.0.5", 16 | "protocol-buffers-schema": "^3.3.1" 17 | } 18 | }, 19 | "@mapbox/point-geometry": { 20 | "version": "0.1.0", 21 | "resolved": "https://registry.npmjs.org/@mapbox/point-geometry/-/point-geometry-0.1.0.tgz", 22 | "integrity": "sha1-ioP5M1x4YO/6Lu7KJUMyqgru2PI=" 23 | }, 24 | "@mapbox/sphericalmercator": { 25 | "version": "1.1.0", 26 | "resolved": "https://registry.npmjs.org/@mapbox/sphericalmercator/-/sphericalmercator-1.1.0.tgz", 27 | "integrity": "sha512-pEsfZyG4OMThlfFQbCte4gegvHUjxXCjz0KZ4Xk8NdOYTQBLflj6U8PL05RPAiuRAMAQNUUKJuL6qYZ5Y4kAWA==" 28 | }, 29 | "@mapbox/vector-tile": { 30 | "version": "1.3.1", 31 | "resolved": "https://registry.npmjs.org/@mapbox/vector-tile/-/vector-tile-1.3.1.tgz", 32 | "integrity": "sha512-MCEddb8u44/xfQ3oD+Srl/tNcQoqTw3goGk2oLsrFxOTc3dUp+kAnby3PvAeeBYSMSjSPD1nd1AJA6W49WnoUw==", 33 | "requires": { 34 | "@mapbox/point-geometry": "~0.1.0" 35 | } 36 | }, 37 | "accepts": { 38 | "version": "1.3.5", 39 | "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.5.tgz", 40 | "integrity": "sha1-63d99gEXI6OxTopywIBcjoZ0a9I=", 41 | "requires": { 42 | "mime-types": "~2.1.18", 43 | "negotiator": "0.6.1" 44 | } 45 | }, 46 | "array-flatten": { 47 | "version": "1.1.1", 48 | "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", 49 | "integrity": "sha1-ml9pkFGx5wczKPKgCJaLZOopVdI=" 50 | }, 51 | "balanced-match": { 52 | "version": "1.0.0", 53 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", 54 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", 55 | "dev": true 56 | }, 57 | "brace-expansion": { 58 | "version": "1.1.11", 59 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", 60 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", 61 | "dev": true, 62 | "requires": { 63 | "balanced-match": "^1.0.0", 64 | "concat-map": "0.0.1" 65 | } 66 | }, 67 | "bytes": { 68 | "version": "3.0.0", 69 | "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.0.0.tgz", 70 | "integrity": "sha1-0ygVQE1olpn4Wk6k+odV3ROpYEg=" 71 | }, 72 | "concat-map": { 73 | "version": "0.0.1", 74 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", 75 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", 76 | "dev": true 77 | }, 78 | "content-disposition": { 79 | "version": "0.5.2", 80 | "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.2.tgz", 81 | "integrity": "sha1-DPaLud318r55YcOoUXjLhdunjLQ=" 82 | }, 83 | "content-type": { 84 | "version": "1.0.4", 85 | "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.4.tgz", 86 | "integrity": "sha512-hIP3EEPs8tB9AT1L+NUqtwOAps4mk2Zob89MWXMHjHWg9milF/j4osnnQLXBCBFBk/tvIG/tUc9mOUJiPBhPXA==" 87 | }, 88 | "cookie": { 89 | "version": "0.3.1", 90 | "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.3.1.tgz", 91 | "integrity": "sha1-5+Ch+e9DtMi6klxcWpboBtFoc7s=" 92 | }, 93 | "cookie-signature": { 94 | "version": "1.0.6", 95 | "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.6.tgz", 96 | "integrity": "sha1-4wOogrNCzD7oylE6eZmXNNqzriw=" 97 | }, 98 | "d3-queue": { 99 | "version": "3.0.7", 100 | "resolved": "https://registry.npmjs.org/d3-queue/-/d3-queue-3.0.7.tgz", 101 | "integrity": "sha1-yTouVLQXwJWRKdfXP2z31Ckudhg=" 102 | }, 103 | "debug": { 104 | "version": "2.6.9", 105 | "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", 106 | "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", 107 | "requires": { 108 | "ms": "2.0.0" 109 | } 110 | }, 111 | "deep-equal": { 112 | "version": "1.0.1", 113 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.0.1.tgz", 114 | "integrity": "sha1-9dJgKStmDghO/0zbyfCK0yR0SLU=", 115 | "dev": true 116 | }, 117 | "define-properties": { 118 | "version": "1.1.2", 119 | "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.2.tgz", 120 | "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", 121 | "dev": true, 122 | "requires": { 123 | "foreach": "^2.0.5", 124 | "object-keys": "^1.0.8" 125 | } 126 | }, 127 | "defined": { 128 | "version": "1.0.0", 129 | "resolved": "https://registry.npmjs.org/defined/-/defined-1.0.0.tgz", 130 | "integrity": "sha1-yY2bzvdWdBiOEQlpFRGZ45sfppM=", 131 | "dev": true 132 | }, 133 | "depd": { 134 | "version": "1.1.2", 135 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", 136 | "integrity": "sha1-m81S4UwJd2PnSbJ0xDRu0uVgtak=" 137 | }, 138 | "destroy": { 139 | "version": "1.0.4", 140 | "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.0.4.tgz", 141 | "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=" 142 | }, 143 | "ee-first": { 144 | "version": "1.1.1", 145 | "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", 146 | "integrity": "sha1-WQxhFWsK4vTwJVcyoViyZrxWsh0=" 147 | }, 148 | "ejs": { 149 | "version": "2.6.1", 150 | "resolved": "https://registry.npmjs.org/ejs/-/ejs-2.6.1.tgz", 151 | "integrity": "sha512-0xy4A/twfrRCnkhfk8ErDi5DqdAsAqeGxht4xkCUrsvhhbQNs7E+4jV0CN7+NKIY0aHE72+XvqtBIXzD31ZbXQ==" 152 | }, 153 | "encodeurl": { 154 | "version": "1.0.2", 155 | "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-1.0.2.tgz", 156 | "integrity": "sha1-rT/0yG7C0CkyL1oCw6mmBslbP1k=" 157 | }, 158 | "es-abstract": { 159 | "version": "1.12.0", 160 | "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.12.0.tgz", 161 | "integrity": "sha512-C8Fx/0jFmV5IPoMOFPA9P9G5NtqW+4cOPit3MIuvR2t7Ag2K15EJTpxnHAYTzL+aYQJIESYeXZmDBfOBE1HcpA==", 162 | "dev": true, 163 | "requires": { 164 | "es-to-primitive": "^1.1.1", 165 | "function-bind": "^1.1.1", 166 | "has": "^1.0.1", 167 | "is-callable": "^1.1.3", 168 | "is-regex": "^1.0.4" 169 | } 170 | }, 171 | "es-to-primitive": { 172 | "version": "1.1.1", 173 | "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.1.1.tgz", 174 | "integrity": "sha1-RTVSSKiJeQNLZ5Lhm7gfK3l13Q0=", 175 | "dev": true, 176 | "requires": { 177 | "is-callable": "^1.1.1", 178 | "is-date-object": "^1.0.1", 179 | "is-symbol": "^1.0.1" 180 | } 181 | }, 182 | "escape-html": { 183 | "version": "1.0.3", 184 | "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", 185 | "integrity": "sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg=" 186 | }, 187 | "etag": { 188 | "version": "1.8.1", 189 | "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", 190 | "integrity": "sha1-Qa4u62XvpiJorr/qg6x9eSmbCIc=" 191 | }, 192 | "express": { 193 | "version": "4.16.3", 194 | "resolved": "https://registry.npmjs.org/express/-/express-4.16.3.tgz", 195 | "integrity": "sha1-avilAjUNsyRuzEvs9rWjTSL37VM=", 196 | "requires": { 197 | "accepts": "~1.3.5", 198 | "array-flatten": "1.1.1", 199 | "body-parser": "1.18.2", 200 | "content-disposition": "0.5.2", 201 | "content-type": "~1.0.4", 202 | "cookie": "0.3.1", 203 | "cookie-signature": "1.0.6", 204 | "debug": "2.6.9", 205 | "depd": "~1.1.2", 206 | "encodeurl": "~1.0.2", 207 | "escape-html": "~1.0.3", 208 | "etag": "~1.8.1", 209 | "finalhandler": "1.1.1", 210 | "fresh": "0.5.2", 211 | "merge-descriptors": "1.0.1", 212 | "methods": "~1.1.2", 213 | "on-finished": "~2.3.0", 214 | "parseurl": "~1.3.2", 215 | "path-to-regexp": "0.1.7", 216 | "proxy-addr": "~2.0.3", 217 | "qs": "6.5.1", 218 | "range-parser": "~1.2.0", 219 | "safe-buffer": "5.1.1", 220 | "send": "0.16.2", 221 | "serve-static": "1.13.2", 222 | "setprototypeof": "1.1.0", 223 | "statuses": "~1.4.0", 224 | "type-is": "~1.6.16", 225 | "utils-merge": "1.0.1", 226 | "vary": "~1.1.2" 227 | }, 228 | "dependencies": { 229 | "body-parser": { 230 | "version": "1.18.2", 231 | "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.18.2.tgz", 232 | "integrity": "sha1-h2eKGdhLR9hZuDGZvVm84iKxBFQ=", 233 | "requires": { 234 | "bytes": "3.0.0", 235 | "content-type": "~1.0.4", 236 | "debug": "2.6.9", 237 | "depd": "~1.1.1", 238 | "http-errors": "~1.6.2", 239 | "iconv-lite": "0.4.19", 240 | "on-finished": "~2.3.0", 241 | "qs": "6.5.1", 242 | "raw-body": "2.3.2", 243 | "type-is": "~1.6.15" 244 | } 245 | }, 246 | "iconv-lite": { 247 | "version": "0.4.19", 248 | "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.19.tgz", 249 | "integrity": "sha512-oTZqweIP51xaGPI4uPa56/Pri/480R+mo7SeU+YETByQNhDG55ycFyNLIgta9vXhILrxXDmF7ZGhqZIcuN0gJQ==" 250 | }, 251 | "raw-body": { 252 | "version": "2.3.2", 253 | "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.3.2.tgz", 254 | "integrity": "sha1-vNYMd9Prk83gBQKVw/N5OJvIj4k=", 255 | "requires": { 256 | "bytes": "3.0.0", 257 | "http-errors": "1.6.2", 258 | "iconv-lite": "0.4.19", 259 | "unpipe": "1.0.0" 260 | }, 261 | "dependencies": { 262 | "depd": { 263 | "version": "1.1.1", 264 | "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.1.tgz", 265 | "integrity": "sha1-V4O04cRZ8G+lyif5kfPQbnoxA1k=" 266 | }, 267 | "http-errors": { 268 | "version": "1.6.2", 269 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.2.tgz", 270 | "integrity": "sha1-CgAsyFcHGSp+eUbO7cERVfYOxzY=", 271 | "requires": { 272 | "depd": "1.1.1", 273 | "inherits": "2.0.3", 274 | "setprototypeof": "1.0.3", 275 | "statuses": ">= 1.3.1 < 2" 276 | } 277 | }, 278 | "setprototypeof": { 279 | "version": "1.0.3", 280 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.0.3.tgz", 281 | "integrity": "sha1-ZlZ+NwQ+608E2RvWWMDL77VbjgQ=" 282 | } 283 | } 284 | } 285 | } 286 | }, 287 | "finalhandler": { 288 | "version": "1.1.1", 289 | "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.1.1.tgz", 290 | "integrity": "sha512-Y1GUDo39ez4aHAw7MysnUD5JzYX+WaIj8I57kO3aEPT1fFRL4sr7mjei97FgnwhAyyzRYmQZaTHb2+9uZ1dPtg==", 291 | "requires": { 292 | "debug": "2.6.9", 293 | "encodeurl": "~1.0.2", 294 | "escape-html": "~1.0.3", 295 | "on-finished": "~2.3.0", 296 | "parseurl": "~1.3.2", 297 | "statuses": "~1.4.0", 298 | "unpipe": "~1.0.0" 299 | } 300 | }, 301 | "for-each": { 302 | "version": "0.3.3", 303 | "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", 304 | "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", 305 | "dev": true, 306 | "requires": { 307 | "is-callable": "^1.1.3" 308 | } 309 | }, 310 | "foreach": { 311 | "version": "2.0.5", 312 | "resolved": "https://registry.npmjs.org/foreach/-/foreach-2.0.5.tgz", 313 | "integrity": "sha1-C+4AUBiusmDQo6865ljdATbsG5k=", 314 | "dev": true 315 | }, 316 | "forwarded": { 317 | "version": "0.1.2", 318 | "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.1.2.tgz", 319 | "integrity": "sha1-mMI9qxF1ZXuMBXPozszZGw/xjIQ=" 320 | }, 321 | "fresh": { 322 | "version": "0.5.2", 323 | "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", 324 | "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=" 325 | }, 326 | "fs.realpath": { 327 | "version": "1.0.0", 328 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", 329 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", 330 | "dev": true 331 | }, 332 | "function-bind": { 333 | "version": "1.1.1", 334 | "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", 335 | "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", 336 | "dev": true 337 | }, 338 | "glob": { 339 | "version": "7.1.2", 340 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.2.tgz", 341 | "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", 342 | "dev": true, 343 | "requires": { 344 | "fs.realpath": "^1.0.0", 345 | "inflight": "^1.0.4", 346 | "inherits": "2", 347 | "minimatch": "^3.0.4", 348 | "once": "^1.3.0", 349 | "path-is-absolute": "^1.0.0" 350 | } 351 | }, 352 | "has": { 353 | "version": "1.0.3", 354 | "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", 355 | "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", 356 | "dev": true, 357 | "requires": { 358 | "function-bind": "^1.1.1" 359 | } 360 | }, 361 | "http-errors": { 362 | "version": "1.6.3", 363 | "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.6.3.tgz", 364 | "integrity": "sha1-i1VoC7S+KDoLW/TqLjhYC+HZMg0=", 365 | "requires": { 366 | "depd": "~1.1.2", 367 | "inherits": "2.0.3", 368 | "setprototypeof": "1.1.0", 369 | "statuses": ">= 1.4.0 < 2" 370 | } 371 | }, 372 | "ieee754": { 373 | "version": "1.1.11", 374 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.1.11.tgz", 375 | "integrity": "sha512-VhDzCKN7K8ufStx/CLj5/PDTMgph+qwN5Pkd5i0sGnVwk56zJ0lkT8Qzi1xqWLS0Wp29DgDtNeS7v8/wMoZeHg==" 376 | }, 377 | "inflight": { 378 | "version": "1.0.6", 379 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", 380 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", 381 | "dev": true, 382 | "requires": { 383 | "once": "^1.3.0", 384 | "wrappy": "1" 385 | } 386 | }, 387 | "inherits": { 388 | "version": "2.0.3", 389 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", 390 | "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=" 391 | }, 392 | "ipaddr.js": { 393 | "version": "1.6.0", 394 | "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.6.0.tgz", 395 | "integrity": "sha1-4/o1e3c9phnybpXwSdBVxyeW+Gs=" 396 | }, 397 | "is-callable": { 398 | "version": "1.1.3", 399 | "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.1.3.tgz", 400 | "integrity": "sha1-hut1OSgF3cM69xySoO7fdO52BLI=", 401 | "dev": true 402 | }, 403 | "is-date-object": { 404 | "version": "1.0.1", 405 | "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.1.tgz", 406 | "integrity": "sha1-mqIOtq7rv/d/vTPnTKAbM1gdOhY=", 407 | "dev": true 408 | }, 409 | "is-regex": { 410 | "version": "1.0.4", 411 | "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.0.4.tgz", 412 | "integrity": "sha1-VRdIm1RwkbCTDglWVM7SXul+lJE=", 413 | "dev": true, 414 | "requires": { 415 | "has": "^1.0.1" 416 | } 417 | }, 418 | "is-symbol": { 419 | "version": "1.0.1", 420 | "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.1.tgz", 421 | "integrity": "sha1-PMWfAAJRlLarLjjbrmaJJWtmBXI=", 422 | "dev": true 423 | }, 424 | "media-typer": { 425 | "version": "0.3.0", 426 | "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", 427 | "integrity": "sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g=" 428 | }, 429 | "merge-descriptors": { 430 | "version": "1.0.1", 431 | "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.1.tgz", 432 | "integrity": "sha1-sAqqVW3YtEVoFQ7J0blT8/kMu2E=" 433 | }, 434 | "methods": { 435 | "version": "1.1.2", 436 | "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", 437 | "integrity": "sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4=" 438 | }, 439 | "mime": { 440 | "version": "1.4.1", 441 | "resolved": "https://registry.npmjs.org/mime/-/mime-1.4.1.tgz", 442 | "integrity": "sha512-KI1+qOZu5DcW6wayYHSzR/tXKCDC5Om4s1z2QJjDULzLcmf3DvzS7oluY4HCTrc+9FiKmWUgeNLg7W3uIQvxtQ==" 443 | }, 444 | "mime-db": { 445 | "version": "1.33.0", 446 | "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.33.0.tgz", 447 | "integrity": "sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==" 448 | }, 449 | "mime-types": { 450 | "version": "2.1.18", 451 | "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.18.tgz", 452 | "integrity": "sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==", 453 | "requires": { 454 | "mime-db": "~1.33.0" 455 | } 456 | }, 457 | "minimatch": { 458 | "version": "3.0.4", 459 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", 460 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", 461 | "dev": true, 462 | "requires": { 463 | "brace-expansion": "^1.1.7" 464 | } 465 | }, 466 | "minimist": { 467 | "version": "1.2.0", 468 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.0.tgz", 469 | "integrity": "sha1-o1AIsg9BOD7sH7kU9M1d95omQoQ=" 470 | }, 471 | "ms": { 472 | "version": "2.0.0", 473 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 474 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 475 | }, 476 | "negotiator": { 477 | "version": "0.6.1", 478 | "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.1.tgz", 479 | "integrity": "sha1-KzJxhOiZIQEXeyhWP7XnECrNDKk=" 480 | }, 481 | "object-inspect": { 482 | "version": "1.6.0", 483 | "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.6.0.tgz", 484 | "integrity": "sha512-GJzfBZ6DgDAmnuaM3104jR4s1Myxr3Y3zfIyN4z3UdqN69oSRacNK8UhnobDdC+7J2AHCjGwxQubNJfE70SXXQ==", 485 | "dev": true 486 | }, 487 | "object-keys": { 488 | "version": "1.0.11", 489 | "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.0.11.tgz", 490 | "integrity": "sha1-xUYBd4rVYPEULODgG8yotW0TQm0=", 491 | "dev": true 492 | }, 493 | "on-finished": { 494 | "version": "2.3.0", 495 | "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.3.0.tgz", 496 | "integrity": "sha1-IPEzZIGwg811M3mSoWlxqi2QaUc=", 497 | "requires": { 498 | "ee-first": "1.1.1" 499 | } 500 | }, 501 | "once": { 502 | "version": "1.4.0", 503 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", 504 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", 505 | "dev": true, 506 | "requires": { 507 | "wrappy": "1" 508 | } 509 | }, 510 | "open": { 511 | "version": "0.0.5", 512 | "resolved": "https://registry.npmjs.org/open/-/open-0.0.5.tgz", 513 | "integrity": "sha1-QsPhjslUZra/DcQvOilFw/DK2Pw=" 514 | }, 515 | "parseurl": { 516 | "version": "1.3.2", 517 | "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.2.tgz", 518 | "integrity": "sha1-/CidTtiZMRlGDBViUyYs3I3mW/M=" 519 | }, 520 | "path-is-absolute": { 521 | "version": "1.0.1", 522 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", 523 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", 524 | "dev": true 525 | }, 526 | "path-parse": { 527 | "version": "1.0.5", 528 | "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", 529 | "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", 530 | "dev": true 531 | }, 532 | "path-to-regexp": { 533 | "version": "0.1.7", 534 | "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.7.tgz", 535 | "integrity": "sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=" 536 | }, 537 | "pbf": { 538 | "version": "3.1.0", 539 | "resolved": "https://registry.npmjs.org/pbf/-/pbf-3.1.0.tgz", 540 | "integrity": "sha512-/hYJmIsTmh7fMkHAWWXJ5b8IKLWdjdlAFb3IHkRBn1XUhIYBChVGfVwmHEAV3UfXTxsP/AKfYTXTS/dCPxJd5w==", 541 | "requires": { 542 | "ieee754": "^1.1.6", 543 | "resolve-protobuf-schema": "^2.0.0" 544 | } 545 | }, 546 | "protocol-buffers-schema": { 547 | "version": "3.3.2", 548 | "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-3.3.2.tgz", 549 | "integrity": "sha512-Xdayp8sB/mU+sUV4G7ws8xtYMGdQnxbeIfLjyO9TZZRJdztBGhlmbI5x1qcY4TG5hBkIKGnc28i7nXxaugu88w==" 550 | }, 551 | "proxy-addr": { 552 | "version": "2.0.3", 553 | "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.3.tgz", 554 | "integrity": "sha512-jQTChiCJteusULxjBp8+jftSQE5Obdl3k4cnmLA6WXtK6XFuWRnvVL7aCiBqaLPM8c4ph0S4tKna8XvmIwEnXQ==", 555 | "requires": { 556 | "forwarded": "~0.1.2", 557 | "ipaddr.js": "1.6.0" 558 | } 559 | }, 560 | "qs": { 561 | "version": "6.5.1", 562 | "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.1.tgz", 563 | "integrity": "sha512-eRzhrN1WSINYCDCbrz796z37LOe3m5tmW7RQf6oBntukAG1nmovJvhnwHHRMAfeoItc1m2Hk02WER2aQ/iqs+A==" 564 | }, 565 | "range-parser": { 566 | "version": "1.2.0", 567 | "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.0.tgz", 568 | "integrity": "sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=" 569 | }, 570 | "resolve": { 571 | "version": "1.7.1", 572 | "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.7.1.tgz", 573 | "integrity": "sha512-c7rwLofp8g1U+h1KNyHL/jicrKg1Ek4q+Lr33AL65uZTinUZHe30D5HlyN5V9NW0JX1D5dXQ4jqW5l7Sy/kGfw==", 574 | "dev": true, 575 | "requires": { 576 | "path-parse": "^1.0.5" 577 | } 578 | }, 579 | "resolve-protobuf-schema": { 580 | "version": "2.0.0", 581 | "resolved": "https://registry.npmjs.org/resolve-protobuf-schema/-/resolve-protobuf-schema-2.0.0.tgz", 582 | "integrity": "sha1-5nsGKmfwLRG9aIbnDv2niEB+D7Q=", 583 | "requires": { 584 | "protocol-buffers-schema": "^2.0.2" 585 | }, 586 | "dependencies": { 587 | "protocol-buffers-schema": { 588 | "version": "2.2.0", 589 | "resolved": "https://registry.npmjs.org/protocol-buffers-schema/-/protocol-buffers-schema-2.2.0.tgz", 590 | "integrity": "sha1-0pxs1z+2VZePtpiWkRgNuEQRn2E=" 591 | } 592 | } 593 | }, 594 | "resumer": { 595 | "version": "0.0.0", 596 | "resolved": "https://registry.npmjs.org/resumer/-/resumer-0.0.0.tgz", 597 | "integrity": "sha1-8ej0YeQGS6Oegq883CqMiT0HZ1k=", 598 | "dev": true, 599 | "requires": { 600 | "through": "~2.3.4" 601 | } 602 | }, 603 | "safe-buffer": { 604 | "version": "5.1.1", 605 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.1.tgz", 606 | "integrity": "sha512-kKvNJn6Mm93gAczWVJg7wH+wGYWNrDHdWvpUmHyEsgCtIwwo3bqPtV4tR5tuPaUhTOo/kvhVwd8XwwOllGYkbg==" 607 | }, 608 | "send": { 609 | "version": "0.16.2", 610 | "resolved": "https://registry.npmjs.org/send/-/send-0.16.2.tgz", 611 | "integrity": "sha512-E64YFPUssFHEFBvpbbjr44NCLtI1AohxQ8ZSiJjQLskAdKuriYEP6VyGEsRDH8ScozGpkaX1BGvhanqCwkcEZw==", 612 | "requires": { 613 | "debug": "2.6.9", 614 | "depd": "~1.1.2", 615 | "destroy": "~1.0.4", 616 | "encodeurl": "~1.0.2", 617 | "escape-html": "~1.0.3", 618 | "etag": "~1.8.1", 619 | "fresh": "0.5.2", 620 | "http-errors": "~1.6.2", 621 | "mime": "1.4.1", 622 | "ms": "2.0.0", 623 | "on-finished": "~2.3.0", 624 | "range-parser": "~1.2.0", 625 | "statuses": "~1.4.0" 626 | } 627 | }, 628 | "serve-static": { 629 | "version": "1.13.2", 630 | "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.13.2.tgz", 631 | "integrity": "sha512-p/tdJrO4U387R9oMjb1oj7qSMaMfmOyd4j9hOFoxZe2baQszgHcSWjuya/CiT5kgZZKRudHNOA0pYXOl8rQ5nw==", 632 | "requires": { 633 | "encodeurl": "~1.0.2", 634 | "escape-html": "~1.0.3", 635 | "parseurl": "~1.3.2", 636 | "send": "0.16.2" 637 | } 638 | }, 639 | "setprototypeof": { 640 | "version": "1.1.0", 641 | "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.0.tgz", 642 | "integrity": "sha512-BvE/TwpZX4FXExxOxZyRGQQv651MSwmWKZGqvmPcRIjDqWub67kTKuIMx43cZZrS/cBBzwBcNDWoFxt2XEFIpQ==" 643 | }, 644 | "statuses": { 645 | "version": "1.4.0", 646 | "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.4.0.tgz", 647 | "integrity": "sha512-zhSCtt8v2NDrRlPQpCNtw/heZLtfUDqxBM1udqikb/Hbk52LK4nQSwr10u77iopCW5LsyHpuXS0GnEc48mLeew==" 648 | }, 649 | "string.prototype.trim": { 650 | "version": "1.1.2", 651 | "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.1.2.tgz", 652 | "integrity": "sha1-0E3iyJ4Tf019IG8Ia17S+ua+jOo=", 653 | "dev": true, 654 | "requires": { 655 | "define-properties": "^1.1.2", 656 | "es-abstract": "^1.5.0", 657 | "function-bind": "^1.0.2" 658 | } 659 | }, 660 | "tape": { 661 | "version": "4.9.1", 662 | "resolved": "https://registry.npmjs.org/tape/-/tape-4.9.1.tgz", 663 | "integrity": "sha512-6fKIXknLpoe/Jp4rzHKFPpJUHDHDqn8jus99IfPnHIjyz78HYlefTGD3b5EkbQzuLfaEvmfPK3IolLgq2xT3kw==", 664 | "dev": true, 665 | "requires": { 666 | "deep-equal": "~1.0.1", 667 | "defined": "~1.0.0", 668 | "for-each": "~0.3.3", 669 | "function-bind": "~1.1.1", 670 | "glob": "~7.1.2", 671 | "has": "~1.0.3", 672 | "inherits": "~2.0.3", 673 | "minimist": "~1.2.0", 674 | "object-inspect": "~1.6.0", 675 | "resolve": "~1.7.1", 676 | "resumer": "~0.0.0", 677 | "string.prototype.trim": "~1.1.2", 678 | "through": "~2.3.8" 679 | } 680 | }, 681 | "through": { 682 | "version": "2.3.8", 683 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", 684 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", 685 | "dev": true 686 | }, 687 | "type-is": { 688 | "version": "1.6.16", 689 | "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.16.tgz", 690 | "integrity": "sha512-HRkVv/5qY2G6I8iab9cI7v1bOIdhm94dVjQCPFElW9W+3GeDOSHmy2EBYe4VTApuzolPcmgFTN3ftVJRKR2J9Q==", 691 | "requires": { 692 | "media-typer": "0.3.0", 693 | "mime-types": "~2.1.18" 694 | } 695 | }, 696 | "unpipe": { 697 | "version": "1.0.0", 698 | "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", 699 | "integrity": "sha1-sr9O6FFKrmFltIF4KdIbLvSZBOw=" 700 | }, 701 | "utils-merge": { 702 | "version": "1.0.1", 703 | "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", 704 | "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=" 705 | }, 706 | "vary": { 707 | "version": "1.1.2", 708 | "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", 709 | "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=" 710 | }, 711 | "wrappy": { 712 | "version": "1.0.2", 713 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", 714 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", 715 | "dev": true 716 | } 717 | } 718 | } 719 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@mapbox/mvtview", 3 | "version": "0.3.0", 4 | "description": "Visualize a Mapbox Vector Tile locally", 5 | "main": "index.js", 6 | "bin": { 7 | "mvtview": "cli.js" 8 | }, 9 | "scripts": { 10 | "test": "tape test/test.js", 11 | "sample": "MapboxAccessToken=$MapboxToken node cli.js node_modules/@mapbox/mvt-fixtures/real-world/sanfrancisco/15-5238-12667.mvt" 12 | }, 13 | "author": "Mapbox", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@mapbox/mvt-fixtures": "^3.4.0", 17 | "@mapbox/vector-tile": "^1.3.1", 18 | "ejs": "^2.6.1", 19 | "express": "^4.16.3", 20 | "minimist": "^1.2.0", 21 | "open": "0.0.5", 22 | "pbf": "^3.1.0" 23 | }, 24 | "devDependencies": { 25 | "tape": "^4.9.1" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | const test = require('tape'); 2 | const mvtf = require('@mapbox/mvt-fixtures'); 3 | 4 | const makeStyle = require('../lib/make-style.js'); 5 | 6 | test('[lib/make-style.js] makes a style with a very simple tile', (assert) => { 7 | makeStyle(mvtf.get('017').buffer) 8 | .then((style) => { 9 | assert.equal(style.layers.length, 1, 'one layer'); 10 | assert.equal(style.layers[0].id, 'hello', 'expected layer id'); 11 | assert.equal(style.layers[0]['source-layer'], 'hello', 'expected source-layer id'); 12 | assert.equal(style.layers[0].type, 'circle', 'expected Point > circle type'); 13 | assert.deepEqual(style.layers[0].layout, {}, 'no layout properties'); 14 | assert.ok(style.layers[0].paint['circle-radius'], 'has circle radius'); 15 | assert.ok(style.layers[0].paint['circle-color'], 'has circle color'); 16 | assert.ok(style.layers[0].paint['circle-opacity'], 'has circle opacity'); 17 | assert.end(); 18 | }) 19 | .catch((err) => { 20 | assert.fail(err); 21 | }); 22 | }); 23 | -------------------------------------------------------------------------------- /utils.js: -------------------------------------------------------------------------------- 1 | const pbf = require('pbf'); 2 | const VT = require('@mapbox/vector-tile'); 3 | 4 | const usage = ` 5 | usage: 6 | mvtview [tile path] [options] 7 | 8 | example: 9 | mvtview path/to/tile.mvt --zxy 1/0/1 10 | 11 | --token (optional) A public (pk.) Mapbox Access Token (by default uses $MapboxAccessToken from environment) 12 | --zxy (optional) set the tile ZXY information in the form of {z}/{x}/{y} 13 | `; 14 | 15 | const makeStyle = (buffer) => { 16 | let tile; 17 | try { 18 | tile = new VT.VectorTile(new pbf(buffer)); 19 | } catch(err) { 20 | return Promise.reject(err); 21 | } 22 | 23 | const style = { layers: [] }; 24 | for (let l in tile.layers) { 25 | let styleLayer = {}; 26 | const layer = tile.layers[l]; 27 | const stylingTypeInfo = getSylingType(layer); 28 | styleLayer._info = {}; 29 | 30 | styleLayer.id = layer.name; 31 | styleLayer['source-layer'] = layer.name; 32 | styleLayer.source = 'view-tile'; 33 | styleLayer.type = stylingTypeInfo.styleType; 34 | styleLayer = setStyling(styleLayer); 35 | style.layers.push(styleLayer); 36 | styleLayer._info.geometry = stylingTypeInfo.type; 37 | styleLayer._info.total_features = layer.length; 38 | } 39 | 40 | return Promise.resolve({ layers: sortByWeight(style) }); 41 | }; 42 | 43 | const getSylingType = (layer) => { 44 | const counts = [ 45 | { type: 'Unknown', count: 0, styleType: 'line', weight: 0 }, 46 | { type: 'Point', count: 0, styleType: 'circle', weight: 1 }, 47 | { type: 'LineString', count: 0, styleType: 'line', weight: 2 }, 48 | { type: 'Polygon', count: 0, styleType: 'fill', weight: 3 } 49 | ]; 50 | 51 | for (let i = 0; i < layer.length; i++) { 52 | counts[layer.feature(i).type].count++; 53 | } 54 | 55 | counts.sort((a, b) => { 56 | return b.count - a.count; 57 | }); 58 | 59 | return counts[0]; 60 | }; 61 | 62 | const setStyling = (layer) => { 63 | const color = getColor(); 64 | layer._info.color = color; 65 | 66 | switch(layer.type) { 67 | case 'circle': 68 | layer.layout = {}; 69 | layer.paint = { 70 | 'circle-radius': 5, 71 | 'circle-color': color, 72 | 'circle-opacity': 0.75 73 | }; 74 | break; 75 | case 'fill': 76 | layer.layout = {}; 77 | layer.paint = { 78 | 'fill-color': 'rgba(200, 200, 200, 0.01)', 79 | 'fill-outline-color': color 80 | }; 81 | break; 82 | case 'line': 83 | default: 84 | layer.layout = { 85 | 'line-join': 'round' 86 | }; 87 | layer.paint = { 88 | 'line-color': color, 89 | 'line-opacity': 0.75, 90 | 'line-width': 1 91 | }; 92 | break; 93 | }; 94 | 95 | return layer; 96 | }; 97 | 98 | const sortByWeight = (style) => { 99 | return style.layers.sort((a, b) => { 100 | b.weight - a.weight; 101 | }); 102 | }; 103 | 104 | let colorIterator = 0; 105 | const colors = ['#8c50c7', '#ff3c96', '#dc2b28', '#448ee4', '#ff6e00', '#f0dc00', '#01aa46', '#666666']; 106 | const getColor = () => { 107 | const c = colors[colorIterator]; 108 | colorIterator++; 109 | if (colorIterator >= colors.length-1) colorIterator = 0; 110 | return c; 111 | }; 112 | 113 | module.exports = { usage, makeStyle }; 114 | -------------------------------------------------------------------------------- /views/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 |