├── .gitignore ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── cli.js ├── docs ├── assets │ ├── bass.css │ ├── github.css │ └── style.css ├── index.html └── index.json ├── example ├── README.md ├── browser.js └── example.js ├── index.js ├── lib ├── browser │ └── tilelive-sources.js ├── cover.js ├── debug.js ├── fix.js └── tilelive-sources.js ├── package.json └── test ├── basic.js ├── bbox.js ├── data ├── bad-tile │ └── 16 │ │ └── 19087 │ │ ├── 24820.pbf │ │ └── 24821.pbf ├── dragon.geojson ├── expected-bounds-z13.geojson ├── expected-z13.geojson ├── original.geojson └── test.mbtiles ├── geojson-bounds.js ├── missing.js └── remote.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | 5 | # Runtime data 6 | pids 7 | *.pid 8 | *.seed 9 | 10 | # coverage 11 | lib-cov 12 | coverage 13 | .nyc_output 14 | 15 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 16 | .grunt 17 | 18 | # node-waf configuration 19 | .lock-wscript 20 | 21 | # Compiled binary addons (http://nodejs.org/api/addons.html) 22 | build/Release 23 | 24 | # Dependency directory 25 | # https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git 26 | node_modules 27 | 28 | # Local config 29 | local.js 30 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - '0.12' 4 | - 'iojs' 5 | script: 6 | - npm test 7 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## This is an **[OPEN Open Source Project](http://openopensource.org/)** 2 | ----------------------------------------- 3 | 4 | ## What? 5 | 6 | Individuals making significant and valuable contributions are given commit-access to a project to contribute as they see fit. A project is more like an open wiki than a standard guarded open source project. 7 | 8 | ## Rules 9 | 10 | There are a few basic ground-rules for contributors: 11 | 12 | 1. **No `--force` pushes** or modifying the Git history in any way. 13 | 1. **Non-master branches** ought to be used for ongoing work. 14 | 1. **External API changes and significant modifications** ought to be subject to an **internal pull-request** to solicit feedback from other contributors. 15 | 1. Internal pull-requests to solicit feedback are *encouraged* for any other non-trivial contribution but left to the discretion of the contributor. 16 | 1. Contributors should attempt to adhere to the prevailing code-style. 17 | 18 | ## Releases 19 | 20 | Declaring formal releases remains the prerogative of the project maintainer(s). 21 | 22 | ## Changes to this arrangement 23 | 24 | This is an experiment and feedback is welcome! This document may also be subject to pull-requests or changes by contributors where you believe you have something valuable to add or change. 25 | 26 | ----------------------------------------- 27 | 28 | These guidelines were copied directly from those of the [Level community](https://github.com/Level/community/blob/master/CONTRIBUTING.md). 29 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Development Seed 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 15 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 16 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 17 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 18 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 19 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 20 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 21 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 22 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 23 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 24 | 25 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vt-geojson 2 | 3 | [![js-standard-style](https://cdn.rawgit.com/feross/standard/master/badge.svg)](https://github.com/feross/standard) 4 | 5 | [![Build Status](https://travis-ci.org/developmentseed/vt-geojson.svg)](https://travis-ci.org/developmentseed/vt-geojson) 6 | 7 | Extract GeoJSON from Mapbox vector tiles. 8 | 9 | # Usage 10 | 11 | ## CLI 12 | 13 | Install with `npm install -g vt-geojson`, and then: 14 | 15 | ```bash 16 | vt-geojson /path/to/tiles.mbtiles --bounds minx miny maxx maxy 17 | cat bounding_polygon.geojson | vt-geojson tilejson+http://api.tiles.mapbox.com/v4/YOUR-MAPID?access_token=YOUR_MAPBOX_TOKEN -z 12 18 | vt-geojson someone.blahblah --tile tilex tiley tilez # ('someone.blahblah' is a mapid) 19 | ``` 20 | 21 | ## Node 22 | 23 | First `npm install vt-geojson` and then: 24 | 25 | ```javascript 26 | var cover = require('tile-cover') 27 | var vtGeoJson = require('vt-geojson') 28 | 29 | var polygon = JSON.parse(fs.readFileSync('my-polygon.geojson')) 30 | var source = 'tilejson+http://api.tiles.mapbox.com/v4/YOUR-MAPID?access_token=YOUR_MAPBOX_TOKEN' 31 | 32 | // get an array of tiles ([x, y, z]) that we want to pull data from. 33 | var tiles = cover.tiles(polygon.geometry, { min_zoom: 10, max_zoom: 12 }) 34 | 35 | // stream geojson from the chosen tiles: 36 | vtGeoJson(source, tiles) 37 | .on('data', function (feature) { 38 | console.log("it's a GeoJSON feature!", feature.geometry.type, feature.properties) 39 | }) 40 | .on('end', function () { 41 | console.log('all done') 42 | }) 43 | ``` 44 | 45 | ## Browser 46 | 47 | This module should work with browserify. There's a minimal example of 48 | using it in the browser 49 | [here](https://github.com/developmentseed/vt-geojson/blob/master/example/browser.js). 50 | Try it with: 51 | 52 | npm install -g budo 53 | budo example/browser.js 54 | 55 | Then go to 56 | 57 | # API 58 | 59 | ## vtgeojson 60 | 61 | Stream GeoJSON from a Mapbox Vector Tile source 62 | 63 | **Parameters** 64 | 65 | - `uri` **string** the tilelive URI for the vector tile source to use. 66 | - `options` **object** options 67 | - `options.layers` **Array<string>** An array of layer names to read from tiles. If empty, read all layers 68 | - `options.tiles` **Array** The tiles to read from the tilelive source. If empty, use `options.bounds` instead. 69 | - `options.bounds` **Array** The [minx, miny, maxx, maxy] bounds or a GeoJSON Feature, FeatureCollection, or Geometry defining the region to read from source. Ignored if `options.tiles` is set. If empty, use the bounds from the input source's metadata. 70 | - `options.minzoom` **number** Defaults to the source metadata minzoom. Ignored if `options.tiles` is set. 71 | - `options.maxzoom` **number** Defaults to the source metadata minzoom. Ignored if `options.tiles` is set. 72 | - `options.tilesOnly` **boolean** Output [z, y, x] tile coordinates instead of actually reading tiles. Useful for debugging. 73 | - `options.strict` **boolean** Emit an error and end the stream if a tile is not found or can't be read 74 | 75 | Returns **ReadableStream<Feature>** A stream of GeoJSON Feature objects. Emits `warning` events with `{ tile, error }` when a tile from the requested set is not found or can't be read. 76 | 77 | # [Contributing](CONTRIBUTING.md) 78 | 79 | This is an [OPEN Open Source](http://openopensource.org/) Project. This means that: 80 | 81 | Individuals making significant and valuable contributions are given commit-access to the project to contribute as they see fit. This project is more like an open wiki than a standard guarded open source project. 82 | -------------------------------------------------------------------------------- /cli.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var path = require('path') 3 | var concat = require('concat-stream') 4 | var JSONStream = require('JSONStream') 5 | var vectorTilesToGeoJSON = require('./') 6 | var fix = require('./lib/fix') 7 | var argv = require('yargs') 8 | .usage('cat polygon.geojson | $0 INPUT [--layers layer1 layer2 ...]\n' + 9 | 'INPUT can be a full tilelive uri, "path/to/file.mbtiles", or just a Mapbox map id.') 10 | .options({ 11 | tile: { 12 | describe: 'The x y z coordinates of the tile to read.', 13 | nargs: 3 14 | }, 15 | layers: { 16 | describe: 'The layers to read (omit for all layers)', 17 | array: true 18 | }, 19 | bounds: { 20 | describe: 'The minx miny maxx maxy region to read.', 21 | nargs: 4 22 | }, 23 | minzoom: { 24 | alias: 'z' 25 | }, 26 | maxzoom: { 27 | alias: 'Z' 28 | }, 29 | tilesOnly: { 30 | describe: 'Only list the tiles that would be read, instead of actually reading them.', 31 | boolean: true 32 | }, 33 | clean: { 34 | describe: 'Attempt to fix degenerate features and filter out any that don\'t pass geojsonhint', 35 | boolean: true, 36 | default: true 37 | }, 38 | strict: { 39 | describe: 'Emit an error and end the stream if a tile is not found or can\'t be read', 40 | boolean: true, 41 | default: false 42 | } 43 | }) 44 | .example('cat bounding_polygon.geojson | vt-geojson tilelive_uri minzoom [maxzoom=minzoom] [--layers=layer1,layer2,...]') 45 | .example('vt-geojson tilelive_uri minx miny maxx maxy [--layers=layer1,layer2,...]') 46 | .example('vt-geojson tilelive_uri tilex tiley tilez [--layers=layer1,layer2,...]') 47 | .demand(1) 48 | .argv 49 | 50 | if (!argv.maxzoom) { 51 | argv.maxzoom = argv.minzoom 52 | } 53 | 54 | if (argv.tile) { 55 | argv.tiles = [argv.tile] 56 | } 57 | 58 | if (argv.testargs) { 59 | console.log(argv) 60 | process.exit() 61 | } 62 | 63 | var uri = argv._.shift() 64 | if (!/^[^\/]*\:\/\//.test(uri)) { 65 | if (/mbtiles$/.test(uri)) { 66 | uri = 'mbtiles://' + path.resolve(uri) 67 | } else if (!process.env.MapboxAccessToken) { 68 | throw new Error('MapboxAccessToken environment variable is required for mapbox.com sources.') 69 | } else { 70 | uri = 'tilejson+http://api.mapbox.com/v4/' + uri + '.json?access_token=' + process.env.MapboxAccessToken 71 | } 72 | } 73 | 74 | var featureCollection = JSONStream.stringify( 75 | '{ "type": "FeatureCollection", "features": [ ', 76 | '\n,\n', 77 | '] }') 78 | 79 | if (!process.stdin.isTTY) { 80 | process.stdin.pipe(concat(function (data) { 81 | argv.bounds = JSON.parse(data) 82 | go() 83 | })) 84 | } else { 85 | go() 86 | } 87 | 88 | function go () { 89 | var s = vectorTilesToGeoJSON(uri, argv) 90 | if (!argv.tilesOnly) { 91 | if (argv.clean) { s = s.pipe(fix()) } 92 | s.pipe(featureCollection) 93 | .pipe(process.stdout) 94 | } else { 95 | s.on('data', function (t) { 96 | process.stdout.write(t.join(' ') + '\n') 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /docs/assets/bass.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | Basscss v5.2.0 4 | Low-level CSS toolkit 5 | http://basscss.com 6 | 7 | */ 8 | 9 | 10 | body, button { margin: 0 } 11 | 12 | button, input, select, textarea { 13 | font-family: inherit; 14 | font-size: 100%; 15 | } 16 | 17 | img { max-width: 100% } 18 | svg { max-height: 100% } 19 | /* Basscss Base Forms */ 20 | 21 | input, 22 | select, 23 | textarea, 24 | fieldset { 25 | font-size: 1rem; 26 | margin-top: 0; 27 | margin-bottom: .5rem; 28 | margin-bottom: .5rem; 29 | } 30 | 31 | input[type=text], 32 | input[type=datetime], 33 | input[type=datetime-local], 34 | input[type=email], 35 | input[type=month], 36 | input[type=number], 37 | input[type=password], 38 | input[type=search], 39 | input[type=tel], 40 | input[type=time], 41 | input[type=url], 42 | input[type=week] { 43 | box-sizing: border-box; 44 | height: 2.25rem; 45 | padding: .5rem .5rem; 46 | vertical-align: middle; 47 | -webkit-appearance: none; 48 | } 49 | 50 | select { 51 | box-sizing: border-box; 52 | line-height: 1.75; 53 | padding: .5rem .5rem; 54 | } 55 | 56 | select:not([multiple]) { 57 | height: 2.25rem; 58 | vertical-align: middle; 59 | } 60 | 61 | textarea { 62 | box-sizing: border-box; 63 | line-height: 1.75; 64 | padding: .5rem .5rem; 65 | } 66 | 67 | .fieldset-reset { 68 | padding: 0; 69 | margin-left: 0; 70 | margin-right: 0; 71 | border: 0; 72 | } 73 | .fieldset-reset legend { 74 | padding: 0; 75 | } 76 | /* Basscss Base Buttons */ 77 | 78 | button, 79 | .button { 80 | font-size: inherit; 81 | font-weight: bold; 82 | text-decoration: none; 83 | cursor: pointer; 84 | display: inline-block; 85 | box-sizing: border-box; 86 | line-height: 1.125rem; 87 | padding: .4rem 0.6rem; 88 | margin: 0; 89 | height: auto; 90 | border: 1px solid transparent; 91 | vertical-align: middle; 92 | -webkit-appearance: none; 93 | } 94 | 95 | ::-moz-focus-inner { 96 | border: 0; 97 | padding: 0; 98 | } 99 | 100 | .button:hover { text-decoration: none } 101 | /* Basscss Base Tables */ 102 | 103 | table { 104 | border-collapse: separate; 105 | border-spacing: 0; 106 | max-width: 100%; 107 | width: 100%; 108 | } 109 | 110 | th { 111 | text-align: left; 112 | font-weight: bold; 113 | } 114 | 115 | th, 116 | td { 117 | padding: .25rem 1rem; 118 | line-height: inherit; 119 | } 120 | 121 | th { vertical-align: bottom } 122 | td { vertical-align: top } 123 | /* Basscss Base Typography */ 124 | 125 | body { 126 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 127 | line-height: 1.5; 128 | font-size: 100%; 129 | } 130 | 131 | h1, h2, h3, h4, h5, h6 { 132 | font-family: 'Helvetica Neue', Helvetica, sans-serif; 133 | font-weight: bold; 134 | line-height: 1.25; 135 | margin-top: 1em; 136 | margin-bottom: .5em; 137 | } 138 | 139 | p, dl, ol, ul { 140 | font-size: 1rem; 141 | margin-top: 0; 142 | margin-bottom: 1rem; 143 | } 144 | 145 | ol, ul { 146 | padding-left: 2rem; 147 | } 148 | 149 | pre, code, samp { 150 | font-family: Consolas, 'Source Code Pro', monospace; 151 | font-size: inherit; 152 | } 153 | 154 | pre { 155 | margin-top: 0; 156 | margin-bottom: 1rem; 157 | overflow-x: scroll; 158 | padding: 1rem; 159 | background-color: rgba(0,0,0,.03125); 160 | } 161 | 162 | hr { 163 | margin-top: 2rem; 164 | margin-bottom: 2rem; 165 | } 166 | 167 | blockquote { 168 | margin-top: 2rem; 169 | margin-bottom: 2rem; 170 | margin-left: 0; 171 | padding-left: 1rem; 172 | padding-right: 1rem; 173 | } 174 | blockquote, 175 | blockquote p { 176 | font-size: 1.25rem; 177 | font-style: italic; 178 | } 179 | 180 | h1, .h1 { font-size: 2rem } 181 | h2, .h2 { font-size: 1.5rem } 182 | h3, .h3 { font-size: 1.25rem } 183 | h4, .h4 { font-size: 1rem } 184 | h5, .h5 { font-size: .875rem } 185 | h6, .h6 { font-size: .75rem } 186 | 187 | .list-reset { 188 | list-style: none; 189 | padding-left: 0; 190 | } 191 | 192 | /* Basscss Utility Layout */ 193 | 194 | .inline { display: inline } 195 | .block { display: block } 196 | .inline-block { display: inline-block } 197 | 198 | .overflow-hidden { overflow: hidden } 199 | .overflow-scroll { overflow: scroll } 200 | .overflow-auto { overflow: auto } 201 | 202 | .clearfix:before, 203 | .clearfix:after { 204 | content: " "; 205 | display: table 206 | } 207 | .clearfix:after { clear: both } 208 | 209 | .left { float: left } 210 | .right { float: right } 211 | 212 | .fit { max-width: 100% } 213 | 214 | .half-width { width: 50% } 215 | .full-width { width: 100% } 216 | /* Basscss Utility Typography */ 217 | 218 | .bold { font-weight: bold } 219 | .regular { font-weight: normal } 220 | .italic { font-style: italic } 221 | .caps { text-transform: uppercase; letter-spacing: .2em; } 222 | 223 | .left-align { text-align: left } 224 | .center { text-align: center } 225 | .right-align { text-align: right } 226 | .justify { text-align: justify } 227 | 228 | .nowrap { white-space: nowrap } 229 | /* Basscss Utility White Space */ 230 | 231 | .m0 { margin: 0 } 232 | .mt0 { margin-top: 0 } 233 | .mr0 { margin-right: 0 } 234 | .mb0 { margin-bottom: 0 } 235 | .ml0 { margin-left: 0 } 236 | 237 | .m1 { margin: .5rem } 238 | .mt1 { margin-top: .5rem } 239 | .mr1 { margin-right: .5rem } 240 | .mb1 { margin-bottom: .5rem } 241 | .ml1 { margin-left: .5rem } 242 | 243 | .m2 { margin: 1rem } 244 | .mt2 { margin-top: 1rem } 245 | .mr2 { margin-right: 1rem } 246 | .mb2 { margin-bottom: 1rem } 247 | .ml2 { margin-left: 1rem } 248 | 249 | .m3 { margin: 2rem } 250 | .mt3 { margin-top: 2rem } 251 | .mr3 { margin-right: 2rem } 252 | .mb3 { margin-bottom: 2rem } 253 | .ml3 { margin-left: 2rem } 254 | 255 | .m4 { margin: 4rem } 256 | .mt4 { margin-top: 4rem } 257 | .mr4 { margin-right: 4rem } 258 | .mb4 { margin-bottom: 4rem } 259 | .ml4 { margin-left: 4rem } 260 | 261 | .mxn1 { margin-left: -.5rem; margin-right: -.5rem; } 262 | .mxn2 { margin-left: -1rem; margin-right: -1rem; } 263 | .mxn3 { margin-left: -2rem; margin-right: -2rem; } 264 | .mxn4 { margin-left: -4rem; margin-right: -4rem; } 265 | 266 | .mx-auto { margin-left: auto; margin-right: auto; } 267 | .p1 { padding: .5rem } 268 | .py1 { padding-top: .5rem; padding-bottom: .5rem } 269 | .px1 { padding-left: .5rem; padding-right: .5rem } 270 | 271 | .p2 { padding: 1rem } 272 | .py2 { padding-top: 1rem; padding-bottom: 1rem } 273 | .px2 { padding-left: 1rem; padding-right: 1rem } 274 | 275 | .p3 { padding: 2rem } 276 | .py3 { padding-top: 2rem; padding-bottom: 2rem } 277 | .px3 { padding-left: 2rem; padding-right: 2rem } 278 | 279 | .p4 { padding: 4rem } 280 | .py4 { padding-top: 4rem; padding-bottom: 4rem } 281 | .px4 { padding-left: 4rem; padding-right: 4rem } 282 | /* Basscss Utility Responsive States */ 283 | 284 | .sm-show, .md-show, .lg-show { 285 | display: none !important 286 | } 287 | 288 | @media (min-width: 40em) { 289 | .sm-show { display: block !important } 290 | } 291 | 292 | @media (min-width: 52em) { 293 | .md-show { display: block !important } 294 | } 295 | 296 | @media (min-width: 64em) { 297 | .lg-show { display: block !important } 298 | } 299 | 300 | 301 | @media (min-width: 40em) { 302 | .sm-hide { display: none !important } 303 | } 304 | 305 | @media (min-width: 52em) { 306 | .md-hide { display: none !important } 307 | } 308 | 309 | @media (min-width: 64em) { 310 | .lg-hide { display: none !important } 311 | } 312 | 313 | .display-none { display: none !important } 314 | 315 | .hide { 316 | position: absolute !important; 317 | height: 1px; 318 | width: 1px; 319 | overflow: hidden; 320 | clip: rect(1px, 1px, 1px, 1px); 321 | } 322 | /* Basscss Positions */ 323 | 324 | .relative { position: relative } 325 | .absolute { position: absolute } 326 | .fixed { position: fixed } 327 | 328 | .top-0 { top: 0 } 329 | .right-0 { right: 0 } 330 | .bottom-0 { bottom: 0 } 331 | .left-0 { left: 0 } 332 | 333 | .z1 { z-index: 1 } 334 | .z2 { z-index: 2 } 335 | .z3 { z-index: 3 } 336 | .z4 { z-index: 4 } 337 | 338 | .absolute-center { 339 | top: 0; 340 | right: 0; 341 | bottom: 0; 342 | left: 0; 343 | margin: auto; 344 | display: table; 345 | } 346 | /* Basscss UI Utility Button Sizes */ 347 | 348 | .button-small { 349 | padding: .25rem .5rem; 350 | } 351 | 352 | .button-big { 353 | padding: 1rem 1.25rem; 354 | } 355 | 356 | .button-narrow { 357 | padding-left: .5rem; 358 | padding-right: .5rem; 359 | } 360 | 361 | /* Basscss Grid */ 362 | 363 | .container { 364 | max-width: 64em; 365 | margin-left: auto; 366 | margin-right: auto; 367 | } 368 | .col { 369 | float: left; 370 | box-sizing: border-box; 371 | } 372 | 373 | .col-right { 374 | float: right; 375 | box-sizing: border-box; 376 | } 377 | 378 | .col-1 { 379 | width: 8.33333%; 380 | } 381 | 382 | .col-2 { 383 | width: 16.66667%; 384 | } 385 | 386 | .col-3 { 387 | width: 25%; 388 | } 389 | 390 | .col-4 { 391 | width: 33.33333%; 392 | } 393 | 394 | .col-5 { 395 | width: 41.66667%; 396 | } 397 | 398 | .col-6 { 399 | width: 50%; 400 | } 401 | 402 | .col-7 { 403 | width: 58.33333%; 404 | } 405 | 406 | .col-8 { 407 | width: 66.66667%; 408 | } 409 | 410 | .col-9 { 411 | width: 75%; 412 | } 413 | 414 | .col-10 { 415 | width: 83.33333%; 416 | } 417 | 418 | .col-11 { 419 | width: 91.66667%; 420 | } 421 | 422 | .col-12 { 423 | width: 100%; 424 | } 425 | @media (min-width: 40em) { 426 | 427 | .sm-col { 428 | float: left; 429 | box-sizing: border-box; 430 | } 431 | 432 | .sm-col-right { 433 | float: right; 434 | box-sizing: border-box; 435 | } 436 | 437 | .sm-col-1 { 438 | width: 8.33333%; 439 | } 440 | 441 | .sm-col-2 { 442 | width: 16.66667%; 443 | } 444 | 445 | .sm-col-3 { 446 | width: 25%; 447 | } 448 | 449 | .sm-col-4 { 450 | width: 33.33333%; 451 | } 452 | 453 | .sm-col-5 { 454 | width: 41.66667%; 455 | } 456 | 457 | .sm-col-6 { 458 | width: 50%; 459 | } 460 | 461 | .sm-col-7 { 462 | width: 58.33333%; 463 | } 464 | 465 | .sm-col-8 { 466 | width: 66.66667%; 467 | } 468 | 469 | .sm-col-9 { 470 | width: 75%; 471 | } 472 | 473 | .sm-col-10 { 474 | width: 83.33333%; 475 | } 476 | 477 | .sm-col-11 { 478 | width: 91.66667%; 479 | } 480 | 481 | .sm-col-12 { 482 | width: 100%; 483 | } 484 | 485 | } 486 | @media (min-width: 52em) { 487 | 488 | .md-col { 489 | float: left; 490 | box-sizing: border-box; 491 | } 492 | 493 | .md-col-right { 494 | float: right; 495 | box-sizing: border-box; 496 | } 497 | 498 | .md-col-1 { 499 | width: 8.33333%; 500 | } 501 | 502 | .md-col-2 { 503 | width: 16.66667%; 504 | } 505 | 506 | .md-col-3 { 507 | width: 25%; 508 | } 509 | 510 | .md-col-4 { 511 | width: 33.33333%; 512 | } 513 | 514 | .md-col-5 { 515 | width: 41.66667%; 516 | } 517 | 518 | .md-col-6 { 519 | width: 50%; 520 | } 521 | 522 | .md-col-7 { 523 | width: 58.33333%; 524 | } 525 | 526 | .md-col-8 { 527 | width: 66.66667%; 528 | } 529 | 530 | .md-col-9 { 531 | width: 75%; 532 | } 533 | 534 | .md-col-10 { 535 | width: 83.33333%; 536 | } 537 | 538 | .md-col-11 { 539 | width: 91.66667%; 540 | } 541 | 542 | .md-col-12 { 543 | width: 100%; 544 | } 545 | 546 | } 547 | @media (min-width: 64em) { 548 | 549 | .lg-col { 550 | float: left; 551 | box-sizing: border-box; 552 | } 553 | 554 | .lg-col-right { 555 | float: right; 556 | box-sizing: border-box; 557 | } 558 | 559 | .lg-col-1 { 560 | width: 8.33333%; 561 | } 562 | 563 | .lg-col-2 { 564 | width: 16.66667%; 565 | } 566 | 567 | .lg-col-3 { 568 | width: 25%; 569 | } 570 | 571 | .lg-col-4 { 572 | width: 33.33333%; 573 | } 574 | 575 | .lg-col-5 { 576 | width: 41.66667%; 577 | } 578 | 579 | .lg-col-6 { 580 | width: 50%; 581 | } 582 | 583 | .lg-col-7 { 584 | width: 58.33333%; 585 | } 586 | 587 | .lg-col-8 { 588 | width: 66.66667%; 589 | } 590 | 591 | .lg-col-9 { 592 | width: 75%; 593 | } 594 | 595 | .lg-col-10 { 596 | width: 83.33333%; 597 | } 598 | 599 | .lg-col-11 { 600 | width: 91.66667%; 601 | } 602 | 603 | .lg-col-12 { 604 | width: 100%; 605 | } 606 | 607 | } 608 | /* 609 | * Basscss Flex Object 610 | */ 611 | 612 | .flex { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex } 613 | 614 | .flex-column { -webkit-box-orient: vertical; -webkit-box-direction: normal; -webkit-flex-direction: column; -ms-flex-direction: column; flex-direction: column } 615 | .flex-wrap { -webkit-flex-wrap: wrap; -ms-flex-wrap: wrap; flex-wrap: wrap } 616 | 617 | .flex-center { -webkit-box-align: center; -webkit-align-items: center; -ms-flex-align: center; align-items: center } 618 | .flex-baseline { -webkit-box-align: baseline; -webkit-align-items: baseline; -ms-flex-align: baseline; align-items: baseline } 619 | .flex-stretch { -webkit-box-align: stretch; -webkit-align-items: stretch; -ms-flex-align: stretch; align-items: stretch } 620 | .flex-start { -webkit-box-align: start; -webkit-align-items: flex-start; -ms-flex-align: start; align-items: flex-start } 621 | .flex-end { -webkit-box-align: end; -webkit-align-items: flex-end; -ms-flex-align: end; align-items: flex-end } 622 | 623 | .flex-first { -webkit-box-ordinal-group: 0; -webkit-order: -1; -ms-flex-order: -1; order: -1 } 624 | .flex-last { -webkit-box-ordinal-group: 1025; -webkit-order: 1024; -ms-flex-order: 1024; order: 1024 } 625 | 626 | .flex-auto { -webkit-box-flex: 1; -webkit-flex: 1 1 auto; -ms-flex: 1 1 auto; flex: 1 1 auto } 627 | .flex-grow { -webkit-box-flex: 1; -webkit-flex: 1 0 auto; -ms-flex: 1 0 auto; flex: 1 0 auto } 628 | .flex-none { -webkit-box-flex: 0; -webkit-flex: none; -ms-flex: none; flex: none } 629 | 630 | .flex > div { box-sizing: border-box } 631 | @media (min-width: 40em) { 632 | .sm-flex { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex } 633 | .sm-flex > div { box-sizing: border-box } 634 | } 635 | @media (min-width: 52em) { 636 | .md-flex { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex } 637 | .md-flex > div { box-sizing: border-box } 638 | } 639 | @media (min-width: 64em) { 640 | .lg-flex { display: -webkit-box; display: -webkit-flex; display: -ms-flexbox; display: flex } 641 | .lg-flex > div { box-sizing: border-box } 642 | } /* New */ 643 | 644 | /* Basscss Color Base */ 645 | 646 | /* 647 | 648 | COLOR VARIABLES 649 | 650 | - Cool 651 | - Warm 652 | - Gray Scale 653 | 654 | */ 655 | 656 | :root { 657 | 658 | /* Cool */ 659 | 660 | 661 | /* Warm */ 662 | 663 | 664 | /* Gray scale */ 665 | 666 | } 667 | 668 | body { 669 | color: #222; 670 | background-color: white; 671 | } 672 | 673 | a { 674 | color: #0074d9 ; 675 | color: #0074d9; 676 | text-decoration: none; 677 | } 678 | 679 | a:hover { 680 | text-decoration: underline; 681 | } 682 | 683 | hr { 684 | border: 0; 685 | border-bottom-style: solid; 686 | border-bottom-width: 1px; 687 | border-bottom-color: rgba(0,0,0,.125); 688 | } 689 | 690 | .button { 691 | color: white; 692 | background-color: #0074d9 ; 693 | background-color: #0074d9; 694 | border-radius: 3px; 695 | /* 696 | -webkit-transition-duration: .05s; 697 | transition-duration: .05s; 698 | -webkit-transition-timing-function: ease-out; 699 | transition-timing-function: ease-out; 700 | -webkit-transition-property: box-shadow, background-color; 701 | transition-property: box-shadow, background-color; 702 | */ 703 | } 704 | 705 | .button:hover { 706 | box-shadow: inset 0 0 0 20rem rgba(0,0,0,.0625); 707 | } 708 | 709 | .button:focus { 710 | outline: none; 711 | border-color: rgba(0,0,0,.125); 712 | box-shadow: 0 0 2px 1px rgba(0,0,0,.25); 713 | } 714 | 715 | .button:active, 716 | .button.is-active { 717 | box-shadow: inset 0 0 0 20rem rgba(0,0,0,.125), 718 | inset 0 3px 4px 0 rgba(0,0,0,.25), 719 | 0 0 1px rgba(0,0,0,.125); 720 | } 721 | 722 | .button:disabled, 723 | .button.is-disabled { 724 | opacity: .5; 725 | } 726 | /* Basscss Color Forms */ 727 | 728 | /* 729 | 730 | COLOR VARIABLES 731 | 732 | - Cool 733 | - Warm 734 | - Gray Scale 735 | 736 | */ 737 | 738 | :root { 739 | 740 | /* Cool */ 741 | 742 | 743 | /* Warm */ 744 | 745 | 746 | /* Gray scale */ 747 | 748 | } 749 | 750 | .field-light { 751 | background-color: white; 752 | -webkit-transition: box-shadow .2s ease; 753 | transition: box-shadow .2s ease; 754 | border-style: solid; 755 | border-width: 1px; 756 | border-color: rgba(0,0,0,.125); 757 | border-radius: 3px; 758 | } 759 | 760 | .field-light:focus { 761 | outline: none; 762 | border-color: #0074d9; 763 | box-shadow: 0 0 2px rgba(0, 116, 217, 0.5); 764 | } 765 | 766 | .field-light:disabled { 767 | color: #aaa; 768 | background-color: rgba(0,0,0,.125); 769 | } 770 | 771 | .field-light:read-only:not(select) { 772 | background-color: rgba(0,0,0,.125); 773 | } 774 | 775 | .field-light:invalid { 776 | border-color: #ff4136; 777 | } 778 | 779 | .field-light.is-success { 780 | border-color: #2ecc40; 781 | } 782 | 783 | .field-light.is-warning { 784 | border-color: #ffdc00; 785 | } 786 | 787 | .field-light.is-error { 788 | border-color: #ff4136; 789 | } 790 | 791 | 792 | .radio-light, 793 | .checkbox-light { 794 | -webkit-transition: box-shadow .2s ease; 795 | transition: box-shadow .2s ease; 796 | } 797 | 798 | .radio-light { 799 | border-radius: 50%; 800 | } 801 | 802 | .radio-light:focus, 803 | .checkbox-light:focus { 804 | outline: none; 805 | box-shadow: 0 0 2px rgba(0, 116, 217, 0.5); 806 | } 807 | /* Basscss Color Forms Dark */ 808 | 809 | /* 810 | 811 | COLOR VARIABLES 812 | 813 | - Cool 814 | - Warm 815 | - Gray Scale 816 | 817 | */ 818 | 819 | :root { 820 | 821 | /* Cool */ 822 | 823 | 824 | /* Warm */ 825 | 826 | 827 | /* Gray scale */ 828 | 829 | } 830 | 831 | .field-dark { 832 | color: white; 833 | background-color: rgba(0,0,0,.25); 834 | border: 1px solid rgba(0,0,0,.0625); 835 | border-radius: 3px; 836 | border-radius: 3px; 837 | } 838 | 839 | .field-dark::-webkit-input-placeholder { 840 | color: rgba(255,255,255,.75); 841 | } 842 | 843 | .field-dark::-moz-placeholder { 844 | color: rgba(255,255,255,.75); 845 | } 846 | 847 | .field-dark:-ms-input-placeholder { 848 | color: rgba(255,255,255,.75); 849 | } 850 | 851 | .field-dark::placeholder { 852 | color: rgba(255,255,255,.75); 853 | } 854 | 855 | .field-dark:focus { 856 | outline: 0; 857 | border: 1px solid rgba(255,255,255,.5); 858 | } 859 | 860 | .field-dark:read-only:not(select) { 861 | background-color: rgba(255,255,255,.25); 862 | } 863 | 864 | .field-dark:invalid { 865 | border-color: #ff4136; 866 | } 867 | 868 | .field-dark.is-success { 869 | border-color: #2ecc40; 870 | } 871 | 872 | .field-dark.is-warning { 873 | border-color: #ffdc00; 874 | } 875 | 876 | .field-dark.is-error { 877 | border-color: #ff4136; 878 | } 879 | /* Basscss Input Range */ 880 | 881 | input[type=range] { 882 | vertical-align: middle; 883 | background-color: transparent; 884 | } 885 | 886 | .range-light { 887 | color: inherit; 888 | -webkit-appearance: none; 889 | padding-top: .5rem; 890 | padding-bottom: .5rem; 891 | } 892 | 893 | .range-light::-webkit-slider-thumb { 894 | -webkit-appearance: none; 895 | position: relative; 896 | width: .5rem; 897 | height: 1.25rem; 898 | border-radius: 3px; 899 | background-color: currentcolor; 900 | cursor: pointer; 901 | margin-top: -0.5rem; 902 | } 903 | 904 | /* Touch screen friendly pseudo element */ 905 | .range-light::-webkit-slider-thumb:before { 906 | content: ''; 907 | display: block; 908 | position: absolute; 909 | top: -0.5rem; 910 | left: -0.875rem; 911 | width: 2.25rem; 912 | height: 2.25rem; 913 | opacity: 0; 914 | } 915 | 916 | .range-light::-moz-range-thumb { 917 | width: .5rem; 918 | height: 1.25rem; 919 | border-radius: 3px; 920 | border-color: transparent; 921 | border-width: 0; 922 | background-color: currentcolor; 923 | cursor: pointer; 924 | } 925 | 926 | .range-light::-webkit-slider-runnable-track { 927 | height: 0.25rem; 928 | cursor: pointer; 929 | border-radius: 3px; 930 | background-color: rgba(0,0,0,.25); 931 | } 932 | 933 | .range-light::-moz-range-track { 934 | height: 0.25rem; 935 | cursor: pointer; 936 | border-radius: 3px; 937 | background-color: rgba(0,0,0,.25); 938 | } 939 | 940 | .range-light:focus { 941 | outline: none; 942 | } 943 | 944 | .range-light:focus::-webkit-slider-thumb { 945 | outline: none; 946 | border: 0; 947 | box-shadow: 0 0 1px 2px currentcolor; 948 | } 949 | 950 | .range-light:focus::-moz-range-thumb { 951 | outline: none; 952 | border: 0; 953 | box-shadow: 0 0 1px 2px currentcolor; 954 | } 955 | /* Basscss Progress */ 956 | 957 | .progress { 958 | display: block; 959 | width: 100%; 960 | height: 0.5625rem; 961 | margin: .5rem 0; 962 | background-color: rgba(0,0,0,.125); 963 | border: 0; 964 | border-radius: 10000px; 965 | overflow: hidden; 966 | -webkit-appearance: none; 967 | cursor: pointer; 968 | } 969 | 970 | .progress::-webkit-progress-bar { 971 | -webkit-appearance: none; 972 | background-color: rgba(0,0,0,.125) 973 | } 974 | 975 | .progress::-webkit-progress-value { 976 | -webkit-appearance: none; 977 | background-color: currentColor; 978 | } 979 | 980 | .progress::-moz-progress-bar { 981 | background-color: currentColor; 982 | } 983 | /* Basscss Color Tables */ 984 | 985 | .table-light th, 986 | .table-light td { 987 | border-bottom-style: solid; 988 | border-bottom-width: 1px; 989 | border-bottom-color: rgba(0,0,0,.125); 990 | } 991 | 992 | .table-light tr:last-child td { 993 | border-bottom: 0; 994 | } 995 | 996 | /* Basscss Button Outline */ 997 | 998 | .button-outline { 999 | position: relative; 1000 | z-index: 2; 1001 | color: inherit; 1002 | background-color: transparent; 1003 | border-radius: 3px; 1004 | border: 1px solid currentcolor; 1005 | -webkit-transition-duration: .1s; 1006 | transition-duration: .1s; 1007 | -webkit-transition-timing-function: ease-out; 1008 | transition-timing-function: ease-out; 1009 | -webkit-transition-property: box-shadow, background-color; 1010 | transition-property: box-shadow, background-color; 1011 | } 1012 | 1013 | .button-outline:before { 1014 | content: ''; 1015 | width: 100%; 1016 | height: 100%; 1017 | display: block; 1018 | position: absolute; 1019 | z-index: -1; 1020 | top: -1px; 1021 | left: -1px; 1022 | border: 1px solid transparent; 1023 | background-color: currentcolor; 1024 | border-radius: 3px; 1025 | -webkit-transition-duration: .1s; 1026 | transition-duration: .1s; 1027 | -webkit-transition-timing-function: ease-out; 1028 | transition-timing-function: ease-out; 1029 | -webkit-transition-property: opacity; 1030 | transition-property: opacity; 1031 | opacity: 0; 1032 | } 1033 | 1034 | .button-outline:hover { 1035 | box-shadow: none; 1036 | } 1037 | 1038 | .button-outline:hover:before { 1039 | opacity: .125; 1040 | } 1041 | 1042 | .button-outline:focus { 1043 | outline: none; 1044 | border: 1px solid currentcolor; 1045 | box-shadow: 0 0 3px 1px; 1046 | } 1047 | 1048 | .button-outline:active, 1049 | .button-outline.is-active { 1050 | box-shadow: inset 0 1px 5px 0, 0 0 1px; 1051 | } 1052 | 1053 | .button-outline:disabled, 1054 | .button-outline.is-disabled { 1055 | opacity: .5; 1056 | } /* New */ 1057 | /* Basscss Button Transparent */ 1058 | 1059 | .button-transparent { 1060 | position: relative; 1061 | z-index: 2; 1062 | color: inherit; 1063 | background-color: transparent; 1064 | border-radius: 0; 1065 | border: 1px solid transparent; 1066 | /* 1067 | -webkit-transition-duration: .1s; 1068 | transition-duration: .1s; 1069 | -webkit-transition-timing-function: ease-out; 1070 | transition-timing-function: ease-out; 1071 | -webkit-transition-property: box-shadow; 1072 | transition-property: box-shadow; 1073 | */ 1074 | } 1075 | 1076 | .button-transparent:before { 1077 | content: ''; 1078 | width: 100%; 1079 | height: 100%; 1080 | display: block; 1081 | position: absolute; 1082 | z-index: -1; 1083 | top: -1px; 1084 | left: -1px; 1085 | border: 1px solid transparent; 1086 | background-color: currentcolor; 1087 | -webkit-transition-duration: .1s; 1088 | transition-duration: .1s; 1089 | -webkit-transition-timing-function: ease-out; 1090 | transition-timing-function: ease-out; 1091 | -webkit-transition-property: opacity; 1092 | transition-property: opacity; 1093 | opacity: 0; 1094 | } 1095 | 1096 | .button-transparent:hover { 1097 | box-shadow: none; 1098 | } 1099 | 1100 | .button-transparent:hover:before { 1101 | opacity: .0625; 1102 | opacity: .09375; 1103 | } 1104 | 1105 | .button-transparent:focus { 1106 | outline: none; 1107 | border-color: transparent; 1108 | box-shadow: 0 0 3px; 1109 | } 1110 | 1111 | .button-transparent:active:before, 1112 | .button-transparent.is-active:before { 1113 | opacity: .0625; 1114 | } 1115 | 1116 | .button-transparent:disabled, 1117 | .button-transparent.is-disabled { 1118 | opacity: .5; 1119 | } /* New */ 1120 | /* Basscss Background Images */ 1121 | 1122 | .bg-cover { background-size: cover } 1123 | .bg-contain { background-size: contain } 1124 | 1125 | .bg-center { background-position: center } 1126 | .bg-top { background-position: top } 1127 | .bg-right { background-position: right } 1128 | .bg-bottom { background-position: bottom } 1129 | .bg-left { background-position: left } /* New */ 1130 | /* Basscss Color Borders */ 1131 | 1132 | .border { 1133 | border-style: solid; 1134 | border-width: 1px; 1135 | border-color: rgba(0,0,0,.125); 1136 | } 1137 | 1138 | .border-top { 1139 | border-top-style: solid; 1140 | border-top-width: 1px; 1141 | border-top-color: rgba(0,0,0,.125); 1142 | } 1143 | 1144 | .border-right { 1145 | border-right-style: solid; 1146 | border-right-width: 1px; 1147 | border-right-color: rgba(0,0,0,.125); 1148 | } 1149 | 1150 | .border-bottom { 1151 | border-bottom-style: solid; 1152 | border-bottom-width: 1px; 1153 | border-bottom-color: rgba(0,0,0,.125); 1154 | } 1155 | 1156 | .border-left { 1157 | border-left-style: solid; 1158 | border-left-width: 1px; 1159 | border-left-color: rgba(0,0,0,.125); 1160 | } 1161 | 1162 | .rounded { border-radius: 3px } 1163 | .circle { border-radius: 50% } 1164 | 1165 | .rounded-top { border-radius: 3px 3px 0 0 } 1166 | .rounded-right { border-radius: 0 3px 3px 0 } 1167 | .rounded-bottom { border-radius: 0 0 3px 3px } 1168 | .rounded-left { border-radius: 3px 0 0 3px } 1169 | 1170 | .not-rounded { border-radius: 0 } 1171 | 1172 | /* Basscss Colors */ 1173 | 1174 | 1175 | /* Color */ 1176 | 1177 | .black, .dark-gray { color: #222 } 1178 | .gray, .mid-gray { color: #aaa } 1179 | .silver, .light-gray { color: #ddd } 1180 | .white { color: #fff } 1181 | 1182 | .aqua { color: #7fdbff } 1183 | .blue { color: #0074d9 } 1184 | .navy { color: #001f3f } 1185 | .teal { color: #39cccc } 1186 | .green { color: #2ecc40 } 1187 | .olive { color: #3d9970 } 1188 | .lime { color: #01ff70 } 1189 | 1190 | .yellow { color: #ffdc00 } 1191 | .orange { color: #ff851b } 1192 | .red { color: #ff4136 } 1193 | .fuchsia { color: #f012be } 1194 | .purple { color: #b10dc9 } 1195 | .maroon { color: #85144b } 1196 | 1197 | 1198 | /* Background Color */ 1199 | 1200 | .bg-black, .bg-dark-gray { background-color: #222 } 1201 | .bg-gray, .bg-mid-gray { background-color: #aaa } 1202 | .bg-silver, .bg-light-gray { background-color: #ddd } 1203 | .bg-white { background-color: #fff } 1204 | 1205 | .bg-aqua { background-color: #7fdbff } 1206 | .bg-blue { background-color: #0074d9 } 1207 | .bg-navy { background-color: #001f3f } 1208 | .bg-teal { background-color: #39cccc } 1209 | .bg-green { background-color: #2ecc40 } 1210 | .bg-olive { background-color: #3d9970 } 1211 | .bg-lime { background-color: #01ff70 } 1212 | 1213 | .bg-yellow { background-color: #ffdc00 } 1214 | .bg-orange { background-color: #ff851b } 1215 | .bg-red { background-color: #ff4136 } 1216 | .bg-fuchsia { background-color: #f012be } 1217 | .bg-purple { background-color: #b10dc9 } 1218 | .bg-maroon { background-color: #85144b } 1219 | 1220 | .bg-darken-0 { background-color: rgba(0,0,0,.03125) } 1221 | .bg-darken-1 { background-color: rgba(0,0,0,.0625) } 1222 | .bg-darken-2 { background-color: rgba(0,0,0,.125) } 1223 | .bg-darken-3 { background-color: rgba(0,0,0,.25) } 1224 | .bg-darken-4 { background-color: rgba(0,0,0,.5) } 1225 | 1226 | 1227 | /* Border Color */ 1228 | 1229 | .border-aqua { border-color: #7fdbff } 1230 | .border-blue { border-color: #0074d9 } 1231 | .border-navy { border-color: #001f3f } 1232 | .border-teal { border-color: #39cccc } 1233 | .border-green { border-color: #2ecc40 } 1234 | .border-olive { border-color: #3d9970 } 1235 | .border-lime { border-color: #01ff70 } 1236 | 1237 | .border-yellow { border-color: #ffdc00 } 1238 | .border-orange { border-color: #ff851b } 1239 | .border-red { border-color: #ff4136 } 1240 | .border-fuchsia { border-color: #f012be } 1241 | .border-purple { border-color: #b10dc9 } 1242 | .border-maroon { border-color: #85144b } 1243 | 1244 | .border-black { border-color: #222 } 1245 | .border-gray { border-color: #aaa } 1246 | .border-silver { border-color: #ddd } 1247 | .border-white { border-color: #fff } 1248 | 1249 | .border-darken-1 { border-color: rgba(0,0,0,.0625) } 1250 | .border-darken-2 { border-color: rgba(0,0,0,.125) } 1251 | .border-darken-3 { border-color: rgba(0,0,0,.25) } 1252 | .border-darken-4 { border-color: rgba(0,0,0,.5) } 1253 | 1254 | /* Opacity */ 1255 | .muted { opacity: .5 } 1256 | 1257 | 1258 | /* Variables */ 1259 | /* Basscss Defaults */ 1260 | 1261 | :root { 1262 | 1263 | /* Legacy support */ 1264 | 1265 | } 1266 | 1267 | -------------------------------------------------------------------------------- /docs/assets/github.css: -------------------------------------------------------------------------------- 1 | /* 2 | 3 | github.com style (c) Vasily Polovnyov 4 | 5 | */ 6 | 7 | .hljs { 8 | display: block; 9 | overflow-x: auto; 10 | padding: 0.5em; 11 | color: #333; 12 | background: #f8f8f8; 13 | -webkit-text-size-adjust: none; 14 | } 15 | 16 | .hljs-comment, 17 | .diff .hljs-header, 18 | .hljs-javadoc { 19 | color: #998; 20 | font-style: italic; 21 | } 22 | 23 | .hljs-keyword, 24 | .css .rule .hljs-keyword, 25 | .hljs-winutils, 26 | .nginx .hljs-title, 27 | .hljs-subst, 28 | .hljs-request, 29 | .hljs-status { 30 | color: #333; 31 | font-weight: bold; 32 | } 33 | 34 | .hljs-number, 35 | .hljs-hexcolor, 36 | .ruby .hljs-constant { 37 | color: #008080; 38 | } 39 | 40 | .hljs-string, 41 | .hljs-tag .hljs-value, 42 | .hljs-phpdoc, 43 | .hljs-dartdoc, 44 | .tex .hljs-formula { 45 | color: #d14; 46 | } 47 | 48 | .hljs-title, 49 | .hljs-id, 50 | .scss .hljs-preprocessor { 51 | color: #900; 52 | font-weight: bold; 53 | } 54 | 55 | .hljs-list .hljs-keyword, 56 | .hljs-subst { 57 | font-weight: normal; 58 | } 59 | 60 | .hljs-class .hljs-title, 61 | .hljs-type, 62 | .vhdl .hljs-literal, 63 | .tex .hljs-command { 64 | color: #458; 65 | font-weight: bold; 66 | } 67 | 68 | .hljs-tag, 69 | .hljs-tag .hljs-title, 70 | .hljs-rules .hljs-property, 71 | .django .hljs-tag .hljs-keyword { 72 | color: #000080; 73 | font-weight: normal; 74 | } 75 | 76 | .hljs-attribute, 77 | .hljs-variable, 78 | .lisp .hljs-body { 79 | color: #008080; 80 | } 81 | 82 | .hljs-regexp { 83 | color: #009926; 84 | } 85 | 86 | .hljs-symbol, 87 | .ruby .hljs-symbol .hljs-string, 88 | .lisp .hljs-keyword, 89 | .clojure .hljs-keyword, 90 | .scheme .hljs-keyword, 91 | .tex .hljs-special, 92 | .hljs-prompt { 93 | color: #990073; 94 | } 95 | 96 | .hljs-built_in { 97 | color: #0086b3; 98 | } 99 | 100 | .hljs-preprocessor, 101 | .hljs-pragma, 102 | .hljs-pi, 103 | .hljs-doctype, 104 | .hljs-shebang, 105 | .hljs-cdata { 106 | color: #999; 107 | font-weight: bold; 108 | } 109 | 110 | .hljs-deletion { 111 | background: #fdd; 112 | } 113 | 114 | .hljs-addition { 115 | background: #dfd; 116 | } 117 | 118 | .diff .hljs-change { 119 | background: #0086b3; 120 | } 121 | 122 | .hljs-chunk { 123 | color: #aaa; 124 | } 125 | -------------------------------------------------------------------------------- /docs/assets/style.css: -------------------------------------------------------------------------------- 1 | .documentation a { 2 | color: #416381; 3 | } 4 | 5 | .container-small { 6 | max-width: 58rem; 7 | margin-left: auto; 8 | margin-right: auto; 9 | } 10 | 11 | .font-smaller { 12 | font-size:80%; 13 | } 14 | 15 | .fade { 16 | opacity:0.50; 17 | } 18 | 19 | .button-indent { 20 | padding: .25rem 1.5rem; 21 | font-size: 90%; 22 | } 23 | 24 | .section-indent { 25 | border-left: 2px solid #eee; 26 | } 27 | 28 | .bg-cloudy { 29 | background: rgba(230, 235, 237, 1); 30 | } 31 | 32 | .bg-cloudy-light { 33 | background: rgba(230, 235, 237, 0.5); 34 | } 35 | 36 | .keyline-top { 37 | border-top:5px solid #eee; 38 | } 39 | 40 | .keyline-top:first-child { 41 | border-top:0; 42 | } 43 | 44 | .force-inline * { 45 | display:inline; 46 | } 47 | 48 | section:target { 49 | background: #fcfbf2; 50 | } 51 | 52 | .documentation-sidebar a:active { 53 | background: #fcfbf2; 54 | } 55 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | vt-geojson 1.0.8 4 | 5 | 6 | 7 | 8 | 9 |
10 |
11 |
12 | 22 |
23 |
24 |
25 |
26 |

27 | 28 | 29 | vectorTilesToGeoJSON(uri, tiles, layers) 30 | 31 | 32 |

33 |

Stream GeoJSON from a Mapbox Vector Tile source

34 | 35 | 36 |

37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 48 | 49 | 50 | 51 | 52 | 60 | 61 | 62 | 63 | 64 | 67 | 68 |
parametertypedescription
uriString

the tilelive URI for the vector tile source to use.

47 |
tilesArray

The tiles to read from the tilelive source. Can be:

53 |
    54 |
  • An array of [x, y, z] tiles.
  • 55 |
  • A single [x, y, z] tile.
  • 56 |
  • A [minx, miny, maxx, maxy] bounding box
  • 57 |
  • Omitted (will attempt to read entire extent of the tile source.
  • 58 |
59 |
layersArray

The layers to read from the tiles. If empty, 65 | read all layers.

66 |
69 |

70 | 71 |

Returns

72 | ReadableStream<Feature> 73 | : 74 | 75 |

A stream of GeoJSON Feature objects. 76 | Emits 'warning' events with { tile, error } when a tile from the 77 | requested set is not found.

78 | 79 |
80 | 81 | 82 | 83 | 84 |
85 | 86 |
87 |
88 |
89 |
90 | 91 | 92 | -------------------------------------------------------------------------------- /docs/index.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "description": "Stream GeoJSON from a Mapbox Vector Tile source", 4 | "tags": [ 5 | { 6 | "title": "param", 7 | "description": "the tilelive URI for the vector tile source to use.", 8 | "type": { 9 | "type": "NameExpression", 10 | "name": "String" 11 | }, 12 | "name": "uri" 13 | }, 14 | { 15 | "title": "param", 16 | "description": "The tiles to read from the tilelive source. Can be:\n- An array of [x, y, z] tiles.\n- A single [x, y, z] tile.\n- A [minx, miny, maxx, maxy] bounding box\n- Omitted (will attempt to read entire extent of the tile source.", 17 | "type": { 18 | "type": "NameExpression", 19 | "name": "Array" 20 | }, 21 | "name": "tiles" 22 | }, 23 | { 24 | "title": "param", 25 | "description": "The layers to read from the tiles. If empty,\nread all layers.", 26 | "type": { 27 | "type": "NameExpression", 28 | "name": "Array" 29 | }, 30 | "name": "layers" 31 | }, 32 | { 33 | "title": "returns", 34 | "description": "A stream of GeoJSON Feature objects.\nEmits 'warning' events with { tile, error } when a tile from the\nrequested set is not found.", 35 | "type": { 36 | "type": "TypeApplication", 37 | "expression": { 38 | "type": "NameExpression", 39 | "name": "ReadableStream" 40 | }, 41 | "applications": [ 42 | { 43 | "type": "NameExpression", 44 | "name": "Feature" 45 | } 46 | ] 47 | } 48 | }, 49 | { 50 | "title": "name", 51 | "name": "vectorTilesToGeoJSON" 52 | }, 53 | { 54 | "title": "kind", 55 | "kind": "function" 56 | } 57 | ], 58 | "context": { 59 | "loc": { 60 | "start": { 61 | "line": 29, 62 | "column": 0 63 | }, 64 | "end": { 65 | "line": 116, 66 | "column": 1 67 | } 68 | }, 69 | "file": "/Users/anand/ds/vt-geojson/index.js", 70 | "code": "var zlib = require('zlib')\nvar Pbf = require('pbf')\nvar through = require('through2')\nvar cover = require('tile-cover')\nvar VectorTile = require('vector-tile').VectorTile\nvar bboxPoly = require('turf-bbox-polygon')\n\n// this is abstracted out for browserify purposes\nvar loadSource = require('./lib/tilelive-sources')\n\nmodule.exports = vectorTilesToGeoJSON\n\n/**\n * Stream GeoJSON from a Mapbox Vector Tile source\n *\n * @param {String} uri - the tilelive URI for the vector tile source to use.\n * @param {Array} tiles - The tiles to read from the tilelive source. Can be:\n * - An array of [x, y, z] tiles.\n * - A single [x, y, z] tile.\n * - A [minx, miny, maxx, maxy] bounding box\n * - Omitted (will attempt to read entire extent of the tile source.\n * @param {Array} layers - The layers to read from the tiles. If empty,\n * read all layers.\n *\n * @return {ReadableStream} A stream of GeoJSON Feature objects.\n * Emits 'warning' events with { tile, error } when a tile from the\n * requested set is not found.\n */\nfunction vectorTilesToGeoJSON (uri, tiles, layers) {\n if (!tiles) tiles = []\n if (!layers && tiles.length > 0 && typeof tiles[0] === 'string') {\n layers = tiles\n }\n if (layers && layers.length === 0) layers = null\n var stream = through.obj()\n\n loadSource(uri, function (err, source) {\n if (err) return loadError(err)\n\n source.getInfo(function (err, info) {\n if (err) return loadError(err)\n\n var limits = { min_zoom: info.minzoom, max_zoom: info.maxzoom }\n if (tiles.length === 0) {\n tiles = cover.tiles(bboxPoly(info.bounds).geometry, limits)\n } else if (tiles.length === 4 && typeof tiles[0] === 'number') {\n tiles = cover.tiles(bboxPoly(tiles).geometry, limits)\n } else if (tiles.length === 3 && typeof tiles[0] === 'number') {\n tiles = [tiles]\n }\n\n (function next () {\n if (tiles.length === 0) return stream.end()\n var tile = tiles.pop()\n writeTile(source, tile, stream, next)\n })()\n })\n })\n\n return stream\n\n function loadError (err) {\n stream.emit('error', err)\n stream.end()\n }\n\n function tileError (tile, err) {\n stream.emit('warning', {\n tile: tile,\n error: err\n })\n }\n\n function writeTile (source, tile, stream, next) {\n var x = tile[0]\n var y = tile[1]\n var z = tile[2]\n source.getTile(z, x, y, function (err, tiledata, opts) {\n if (err) {\n tileError(tile, err)\n return next()\n }\n\n if (opts['Content-Encoding'] === 'gzip') {\n zlib.gunzip(tiledata, processTile)\n } else {\n processTile(null, tiledata)\n }\n\n function processTile (err, tiledata) {\n if (err) {\n tileError(tile, err)\n return next()\n }\n\n var vt = new VectorTile(new Pbf(tiledata))\n\n Object.keys(vt.layers)\n .filter(function (ln) {\n return !layers || layers.indexOf(ln) >= 0\n })\n .forEach(function (ln) {\n var layer = vt.layers[ln]\n if (layers && layers.indexOf(ln) < 0) return\n\n for (var i = 0; i < layer.length; i++) {\n var feat = layer.feature(i).toGeoJSON(x, y, z)\n stream.write(feat)\n }\n })\n\n next()\n }\n })\n }\n}" 71 | }, 72 | "params": [ 73 | { 74 | "title": "param", 75 | "description": "the tilelive URI for the vector tile source to use.", 76 | "type": { 77 | "type": "NameExpression", 78 | "name": "String" 79 | }, 80 | "name": "uri" 81 | }, 82 | { 83 | "title": "param", 84 | "description": "The tiles to read from the tilelive source. Can be:\n- An array of [x, y, z] tiles.\n- A single [x, y, z] tile.\n- A [minx, miny, maxx, maxy] bounding box\n- Omitted (will attempt to read entire extent of the tile source.", 85 | "type": { 86 | "type": "NameExpression", 87 | "name": "Array" 88 | }, 89 | "name": "tiles" 90 | }, 91 | { 92 | "title": "param", 93 | "description": "The layers to read from the tiles. If empty,\nread all layers.", 94 | "type": { 95 | "type": "NameExpression", 96 | "name": "Array" 97 | }, 98 | "name": "layers" 99 | } 100 | ], 101 | "returns": [ 102 | { 103 | "title": "returns", 104 | "description": "A stream of GeoJSON Feature objects.\nEmits 'warning' events with { tile, error } when a tile from the\nrequested set is not found.", 105 | "type": { 106 | "type": "TypeApplication", 107 | "expression": { 108 | "type": "NameExpression", 109 | "name": "ReadableStream" 110 | }, 111 | "applications": [ 112 | { 113 | "type": "NameExpression", 114 | "name": "Feature" 115 | } 116 | ] 117 | } 118 | } 119 | ], 120 | "name": "vectorTilesToGeoJSON", 121 | "kind": "function", 122 | "members": { 123 | "instance": [], 124 | "static": [] 125 | }, 126 | "path": [ 127 | "vectorTilesToGeoJSON" 128 | ] 129 | } 130 | ] -------------------------------------------------------------------------------- /example/README.md: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/vt-geojson/94debe0be2fc471612cee0d023f6f487b99594a1/example/README.md -------------------------------------------------------------------------------- /example/browser.js: -------------------------------------------------------------------------------- 1 | var qs = require('querystring') 2 | var vtgeojson = require('../') 3 | 4 | // parse query string 5 | var query = qs.parse(window.location.search.substring(1)) 6 | var token = query.access_token 7 | var mapid = query.mapid 8 | var tile = query.tile.split('/').map(Number) 9 | var layers = query.layers 10 | 11 | // grab vector tiles and make 'em into geojson 12 | var featurecollection = { type: 'FeatureCollection', features: [] } 13 | var tiles = 'tilejson+http://api.mapbox.com/v4/' + mapid + '.json?access_token=' + token 14 | vtgeojson(tiles, { 15 | tiles: [tile], 16 | layers: layers ? layers.split(',') : undefined 17 | }) 18 | .on('data', function (data) { 19 | featurecollection.features.push(data) 20 | }) 21 | .on('end', function () { 22 | document.write('
' + JSON.stringify(featurecollection, null, 2) + '
') 23 | }) 24 | -------------------------------------------------------------------------------- /example/example.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var path = require('path') 3 | var cover = require('../lib/cover') 4 | var vtgj = require('../') 5 | 6 | var data = path.join(__dirname, '../test/data') 7 | 8 | var tileUri = 'mbtiles://' + data + '/test.mbtiles' 9 | var original = fs.readFileSync(data + '/original.geojson') 10 | original = JSON.parse(original) 11 | 12 | var limits = { 13 | min_zoom: 13, 14 | max_zoom: 13 15 | } 16 | 17 | var tiles = cover(original, limits) 18 | vtgj(tileUri, { tiles: tiles }) 19 | .on('data', function (data) { 20 | console.log(data) 21 | }) 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var zlib = require('zlib') 2 | var Pbf = require('pbf') 3 | var through = require('through2') 4 | var cover = require('tile-cover') 5 | var envelope = require('turf-envelope') 6 | var VectorTile = require('vector-tile').VectorTile 7 | var bboxPoly = require('turf-bbox-polygon') 8 | 9 | // see https://github.com/substack/insert-module-globals/pull/40 10 | var setImmediate = require('timers').setImmediate 11 | 12 | // this is abstracted out for browserify purposes 13 | var loadSource = require('./lib/tilelive-sources') 14 | 15 | module.exports = vtgeojson 16 | 17 | /** 18 | * Stream GeoJSON from a Mapbox Vector Tile source 19 | * 20 | * @param {string} uri - the tilelive URI for the vector tile source to use. 21 | * @param {object} options - options 22 | * @param {Array} options.layers - An array of layer names to read from tiles. If empty, read all layers 23 | * @param {Array} options.tiles - The tiles to read from the tilelive source. If empty, use `options.bounds` instead. 24 | * @param {Array} options.bounds - The [minx, miny, maxx, maxy] bounds or a GeoJSON Feature, FeatureCollection, or Geometry defining the region to read from source. Ignored if `options.tiles` is set. If empty, use the bounds from the input source's metadata. 25 | * @param {number} options.minzoom - Defaults to the source metadata minzoom. Ignored if `options.tiles` is set. 26 | * @param {number} options.maxzoom - Defaults to the source metadata minzoom. Ignored if `options.tiles` is set. 27 | * @param {boolean} options.tilesOnly - Output [z, y, x] tile coordinates instead of actually reading tiles. Useful for debugging. 28 | * @param {boolean} options.strict - Emit an error and end the stream if a tile is not found or can't be read 29 | * @return {ReadableStream} A stream of GeoJSON Feature objects. Emits `warning` events with `{ tile, error }` when a tile from the requested set is not found or can't be read. 30 | */ 31 | function vtgeojson (uri, options) { 32 | options = options || {} 33 | 34 | if (options.layers && options.layers.length === 0) options.layers = null 35 | var stream = (options.tilesOnly) ? through.obj() : through.obj(writeTile) 36 | 37 | var source 38 | loadSource(uri, function (err, src) { 39 | if (err) return loadError(err) 40 | 41 | source = src 42 | var tiles = options.tiles 43 | if (tiles) return next() 44 | 45 | source.getInfo(function (err, info) { 46 | if (err) return loadError(err) 47 | 48 | var limits = { 49 | min_zoom: options.minzoom || info.minzoom, 50 | max_zoom: options.maxzoom || info.maxzoom 51 | } 52 | 53 | if (Array.isArray(options.bounds)) { 54 | tiles = cover.tiles(bboxPoly(options.bounds).geometry, limits) 55 | } else if (options.bounds) { 56 | tiles = cover.tiles(envelope(options.bounds).geometry, limits) 57 | } else { 58 | tiles = cover.tiles(bboxPoly(info.bounds).geometry, limits) 59 | } 60 | 61 | next() 62 | }) 63 | 64 | function next () { 65 | if (tiles.length === 0) { 66 | return stream.end() 67 | } 68 | var tile = tiles.pop() 69 | stream.write(tile) 70 | // ensure async, because some tilelive sources callback sync 71 | setImmediate(next) 72 | } 73 | }) 74 | 75 | return stream 76 | 77 | function loadError (err) { 78 | stream.emit('error', err) 79 | stream.end() 80 | } 81 | 82 | function tileError (tile, err) { 83 | stream.emit('warning', { 84 | tile: tile, 85 | error: err 86 | }) 87 | if (options.strict) { return err } 88 | } 89 | 90 | function writeTile (tile, _, next) { 91 | var self = this 92 | var x = tile[0] 93 | var y = tile[1] 94 | var z = tile[2] 95 | 96 | source.getTile(z, x, y, function (err, tiledata, opts) { 97 | if (err) { 98 | return next(tileError(tile, err)) 99 | } 100 | 101 | if (opts['Content-Encoding'] === 'gzip') { 102 | zlib.gunzip(tiledata, processTile) 103 | } else { 104 | processTile(null, tiledata) 105 | } 106 | 107 | function processTile (err, tiledata) { 108 | if (err) { 109 | return next(tileError(tile, err)) 110 | } 111 | 112 | var vt = new VectorTile(new Pbf(tiledata)) 113 | 114 | var layers = Object.keys(vt.layers) 115 | .filter(function (ln) { 116 | return !options.layers || options.layers.indexOf(ln) >= 0 117 | }) 118 | 119 | for (var j = 0; j < layers.length; j++) { 120 | var ln = layers[j] 121 | var layer = vt.layers[ln] 122 | if (options.layers && options.layers.indexOf(ln) < 0) return 123 | 124 | for (var i = 0; i < layer.length; i++) { 125 | try { 126 | var feat = layer.feature(i).toGeoJSON(x, y, z) 127 | self.push(feat) 128 | } catch (e) { 129 | var error = new Error( 130 | 'Error reading feature ' + i + ' from layer ' + ln + ':' + e.toString() 131 | ) 132 | if (options.strict) { 133 | return next(error) 134 | } else { 135 | tileError(tile, error) 136 | } 137 | } 138 | } 139 | } 140 | 141 | next() 142 | } 143 | }) 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /lib/browser/tilelive-sources.js: -------------------------------------------------------------------------------- 1 | var TileJSON = require('tilejson') 2 | var debug = require('../debug') 3 | 4 | module.exports = function (uri, callback) { 5 | debug('loading tilejson', uri) 6 | return new TileJSON(uri, callback) 7 | } 8 | -------------------------------------------------------------------------------- /lib/cover.js: -------------------------------------------------------------------------------- 1 | var cover = require('tile-cover') 2 | 3 | function join (tile) { return tile.join(' ') } 4 | function split (tile) { return tile.split(' ').map(Number) } 5 | 6 | module.exports = function coverFeatureOrCollection (geojson, limits) { 7 | var features = geojson.features ? geojson.features : [geojson] 8 | var tiles = [] 9 | features.forEach(function (feat) { 10 | cover.tiles(feat.geometry, limits) 11 | .map(join) 12 | .forEach(function (tile) { 13 | if (tiles.indexOf(tile) < 0) tiles.push(tile) 14 | }) 15 | }) 16 | return tiles.map(split) 17 | } 18 | -------------------------------------------------------------------------------- /lib/debug.js: -------------------------------------------------------------------------------- 1 | module.exports = function () { 2 | if (module.exports.enabled) { 3 | var message = [].slice.call(arguments).join(' ') 4 | console.log('\t' + message) 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /lib/fix.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | var hint = require('geojsonhint').hint 3 | var through = require('through2') 4 | var debug = require('debug')('vt-geojson') 5 | 6 | var count = 0 7 | module.exports = function fix () { 8 | return through.obj(function (feat, _, next) { 9 | /* 10 | * Strip degenerate polygons 11 | */ 12 | var geom = feat.geometry 13 | if (geom.type === 'Polygon') { 14 | geom.coordinates = filterPolygonLineStrings(geom.coordinates) 15 | } else if (geom.type === 'MultiPolygon') { 16 | geom.coordinates = geom.coordinates.map(filterPolygonLineStrings) 17 | } 18 | 19 | /* 20 | * Filter out anything that doesn't pass geojsonhint 21 | */ 22 | var hints = hint(feat) 23 | if (hints.length > 0) { 24 | debug('Dropping', count++, hints) 25 | next() 26 | } else { 27 | next(null, feat) 28 | } 29 | }) 30 | } 31 | 32 | // filter out degenerate polygon rings, but make sure not to strip 33 | // off the outer ring if there are holes inside (because that could turn 34 | // negative space holes into positive space outer ring) 35 | function filterPolygonLineStrings (rings) { 36 | return rings 37 | .map(function (ring) { 38 | var e = ring.length - 1 39 | if (ring[0][0] !== ring[e][0] || ring[0][1] !== ring[e][1]) { 40 | debug('Attempting to fix broken ring', ring) 41 | ring.push([ring[0][0], ring[0][1]]) 42 | } 43 | return ring 44 | }) 45 | .filter(function (ring, i) { 46 | return (i === 0 && rings.length > 1) || (ring.length >= 4) 47 | }) 48 | } 49 | -------------------------------------------------------------------------------- /lib/tilelive-sources.js: -------------------------------------------------------------------------------- 1 | var tilelive = require('tilelive') 2 | require('tilejson').registerProtocols(tilelive) 3 | require('mbtiles').registerProtocols(tilelive) 4 | 5 | module.exports = function (uri, callback) { 6 | tilelive.auto(uri) 7 | return tilelive.load(uri, callback) 8 | } 9 | 10 | module.exports.tilelive = tilelive 11 | 12 | // for conditionally testing in node/browser 13 | module.exports.mbtiles = true 14 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vt-geojson", 3 | "version": "2.1.1", 4 | "description": "Stream GeoJSON from Mapbox vector tiles.", 5 | "keywords": [ 6 | "vector tile", 7 | "geojson", 8 | "vector", 9 | "tile", 10 | "mapbox" 11 | ], 12 | "repository": { 13 | "type": "git", 14 | "url": "https://github.com/developmentseed/vt-geojson.git" 15 | }, 16 | "main": "index.js", 17 | "bin": "cli.js", 18 | "scripts": { 19 | "test": "standard && node_modules/.bin/tap --coverage test/*.js", 20 | "docs": "documentation-readme -s API", 21 | "prepublish": "documentation-readme -s API -c" 22 | }, 23 | "author": "Development Seed (https://developmentseed.org)", 24 | "license": "BSD", 25 | "dependencies": { 26 | "JSONStream": "^0.10.0", 27 | "concat-stream": "^1.4.8", 28 | "debug": "^2.2.0", 29 | "geojsonhint": "^1.0.0", 30 | "mbtiles": "^0.8.1", 31 | "pbf": "^1.3.2", 32 | "through2": "^0.6.5", 33 | "tile-cover": "^2.4.1", 34 | "tilejson": "^1.0.0", 35 | "tilelive": "^5.6.2", 36 | "turf-bbox-polygon": "^1.0.1", 37 | "turf-envelope": "^1.0.2", 38 | "vector-tile": "^1.1.2", 39 | "yargs": "^3.26.0" 40 | }, 41 | "devDependencies": { 42 | "concat-stream": "^1.4.8", 43 | "documentation-readme": "^2.1.0", 44 | "geojson-equality": "^0.1.6", 45 | "phantomjs-polyfill": "0.0.1", 46 | "standard": "^3.6.1", 47 | "tap": "^2.0.0", 48 | "tilelive-file": "0.0.3" 49 | }, 50 | "browser": { 51 | "zlib": false, 52 | "./lib/tilelive-sources.js": "./lib/browser/tilelive-sources.js" 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /test/basic.js: -------------------------------------------------------------------------------- 1 | require('phantomjs-polyfill') 2 | var fs = require('fs') 3 | var test = require('tap').test 4 | var cover = require('../lib/cover') 5 | var vtgj = require('../') 6 | 7 | var GeojsonEquality = require('geojson-equality') 8 | var eq = new GeojsonEquality({ precision: 5 }) 9 | 10 | test('basic', function (t) { 11 | var tileUri = 'mbtiles://' + __dirname + '/data/test.mbtiles' 12 | var original = fs.readFileSync(__dirname + '/data/original.geojson') 13 | original = JSON.parse(original) 14 | var expected = fs.readFileSync(__dirname + '/data/expected-z13.geojson') 15 | expected = JSON.parse(expected).features 16 | 17 | var limits = { 18 | min_zoom: 13, 19 | max_zoom: 13 20 | } 21 | 22 | t.plan(expected.length * 2) 23 | 24 | var tiles = cover(original, limits) 25 | vtgj(tileUri, { tiles: tiles }) 26 | .on('data', function (data) { 27 | var exp = expected.shift() 28 | t.ok(eq.compare(data.geometry, exp.geometry)) 29 | t.deepEqual(exp.properties, data.properties) 30 | }) 31 | }) 32 | 33 | -------------------------------------------------------------------------------- /test/bbox.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var test = require('tap').test 3 | var vtgj = require('../') 4 | 5 | var GeojsonEquality = require('geojson-equality') 6 | var eq = new GeojsonEquality({ precision: 5 }) 7 | 8 | test('bbox bounds', function (t) { 9 | var tileUri = 'mbtiles://' + __dirname + '/data/test.mbtiles' 10 | var original = fs.readFileSync(__dirname + '/data/original.geojson') 11 | original = JSON.parse(original) 12 | var expected = fs.readFileSync(__dirname + '/data/expected-bounds-z13.geojson') 13 | expected = JSON.parse(expected).features 14 | 15 | t.plan(expected.length * 2) 16 | 17 | vtgj(tileUri, { 18 | bounds: [ -77.1175, 38.8175, -76.9478, 38.9546 ], 19 | minzoom: 13, 20 | maxzoom: 13 21 | }) 22 | .on('data', function (data) { 23 | var exp = expected.shift() 24 | t.ok(eq.compare(data.geometry, exp.geometry)) 25 | t.deepEqual(exp.properties, data.properties) 26 | }) 27 | }) 28 | 29 | -------------------------------------------------------------------------------- /test/data/bad-tile/16/19087/24820.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/vt-geojson/94debe0be2fc471612cee0d023f6f487b99594a1/test/data/bad-tile/16/19087/24820.pbf -------------------------------------------------------------------------------- /test/data/bad-tile/16/19087/24821.pbf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/vt-geojson/94debe0be2fc471612cee0d023f6f487b99594a1/test/data/bad-tile/16/19087/24821.pbf -------------------------------------------------------------------------------- /test/data/dragon.geojson: -------------------------------------------------------------------------------- 1 | { 2 | "type": "FeatureCollection", 3 | "features": [ 4 | { 5 | "type": "Feature", 6 | "properties": {}, 7 | "geometry": { 8 | "type": "Polygon", 9 | "coordinates": [ 10 | [ 11 | [ 12 | 13.359375, 13 | 32.24997445586331 14 | ], 15 | [ 16 | -6.6796875, 17 | -3.513421045640032 18 | ], 19 | [ 20 | 30.234375, 21 | -45.58328975600631 22 | ], 23 | [ 24 | 70.3125, 25 | -35.46066995149529 26 | ], 27 | [ 28 | 97.03125, 29 | -14.944784875088372 30 | ], 31 | [ 32 | 88.24218749999999, 33 | -4.565473550710278 34 | ], 35 | [ 36 | 79.8046875, 37 | -4.565473550710278 38 | ], 39 | [ 40 | 73.828125, 41 | 9.44906182688142 42 | ], 43 | [ 44 | 88.9453125, 45 | 13.923403897723347 46 | ], 47 | [ 48 | 91.7578125, 49 | 25.48295117535531 50 | ], 51 | [ 52 | 85.78125, 53 | 27.059125784374068 54 | ], 55 | [ 56 | 82.96875, 57 | 23.241346102386135 58 | ], 59 | [ 60 | 85.078125, 61 | 18.312810846425442 62 | ], 63 | [ 64 | 80.5078125, 65 | 17.644022027872726 66 | ], 67 | [ 68 | 67.1484375, 69 | 17.644022027872726 70 | ], 71 | [ 72 | 66.796875, 73 | 40.44694705960048 74 | ], 75 | [ 76 | 84.0234375, 77 | 51.39920565355378 78 | ], 79 | [ 80 | 105.8203125, 81 | 47.27922900257082 82 | ], 83 | [ 84 | 108.28125, 85 | 59.88893689676585 86 | ], 87 | [ 88 | 83.3203125, 89 | 64.32087157990324 90 | ], 91 | [ 92 | 57.30468749999999, 93 | 66.23145747862573 94 | ], 95 | [ 96 | 3.1640625, 97 | 49.38237278700955 98 | ], 99 | [ 100 | 3.515625, 101 | 39.36827914916011 102 | ], 103 | [ 104 | 13.359375, 105 | 45.089035564831015 106 | ], 107 | [ 108 | 22.5, 109 | 50.51342652633956 110 | ], 111 | [ 112 | 40.078125, 113 | 54.16243396806779 114 | ], 115 | [ 116 | 53.78906249999999, 117 | 56.17002298293205 118 | ], 119 | [ 120 | 54.4921875, 121 | 45.089035564831015 122 | ], 123 | [ 124 | 52.03125, 125 | 28.92163128242129 126 | ], 127 | [ 128 | 40.078125, 129 | 1.7575368113083254 130 | ], 131 | [ 132 | 15.1171875, 133 | -12.554563528593656 134 | ], 135 | [ 136 | 14.414062499999998, 137 | 13.923403897723347 138 | ], 139 | [ 140 | 24.960937499999996, 141 | 31.653381399664 142 | ], 143 | [ 144 | 13.359375, 145 | 32.24997445586331 146 | ] 147 | ] 148 | ] 149 | } 150 | } 151 | ] 152 | } -------------------------------------------------------------------------------- /test/data/expected-bounds-z13.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.05719351768494,38.82366088659336],[-77.07595825195312,38.817508682154994],[-77.07921981811523,38.82366088659336]]},"properties":{}} 2 | , 3 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.07870483398438,38.82269128096158],[-77.07921981811523,38.82366088659336]]},"properties":{}} 4 | , 5 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.95279121398926,38.857889530636584],[-76.99356079101562,38.844520926194036]]},"properties":{}} 6 | , 7 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-76.99081420898438,38.845423386066756],[-77.03750610351562,38.83011344289997]],[[-77.02537178993225,38.857889530636584],[-77.03750610351562,38.85387085805712]]]},"properties":{}} 8 | , 9 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.03475952148438,38.8310160854935],[-77.06371665000916,38.82152104968421]],[[-77.07808256149292,38.82152104968421],[-77.08145141601562,38.82786514445979]],[[-77.03475952148438,38.85478155427842],[-77.0584487915039,38.846927460416424],[-77.06613063812256,38.857889530636584]]]},"properties":{}} 10 | , 11 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.07870483398438,38.82269128096158],[-77.09737300872803,38.857889530636584]]},"properties":{}} 12 | , 13 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.94961547851562,38.86089712036954],[-76.9478988647461,38.85949359432408],[-76.94961547851562,38.85893384704323]]},"properties":{}} 14 | , 15 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.89210170772432],[-76.97931289672852,38.89210170772432],[-76.99356079101562,38.882932188069105],[-76.99356079101562,38.89210170772432]]]},"properties":{}} 16 | , 17 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-76.98768138885498,38.89210170772432],[-76.9478988647461,38.85949359432408],[-76.95931434631348,38.855750722769756]],[[-76.99356079101562,38.87066255537718],[-76.99150085449219,38.86911721659655],[-76.99356079101562,38.86843224487802]]]},"properties":{}} 18 | , 19 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.89210170772432],[-76.99081420898438,38.89210170772432],[-76.99081420898438,38.884702715757356],[-77.0199966430664,38.865909487242476],[-77.03750610351562,38.87763704482552],[-77.03750610351562,38.89210170772432]]]},"properties":{}} 20 | , 21 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.02205657958984,38.89210170772432],[-76.99150085449219,38.86911721659655],[-77.03183054924011,38.855750722769756]],[[-77.03750610351562,38.88775927764394],[-77.02171325683594,38.8771359067301],[-77.03750610351562,38.87427935210604]]]},"properties":{}} 22 | , 23 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03475952148438,38.89210170772432],[-77.03475952148438,38.875799521198644],[-77.05909252166748,38.89210170772432],[-77.03475952148438,38.89210170772432]]]},"properties":{}} 24 | , 25 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.06462860107422,38.855750722769756],[-77.08145141601562,38.87975849028288]],[[-77.04396486282349,38.89210170772432],[-77.03475952148438,38.885905313175016]],[[-77.03475952148438,38.8747721577364],[-77.05570220947266,38.8709883252109],[-77.05638885498047,38.88381745742845]]]},"properties":{}} 26 | , 27 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.09623575210571,38.855750722769756],[-77.11551547050476,38.89210170772432]],[[-77.07870483398438,38.8758412836269],[-77.08728790283203,38.88809332015177],[-77.08221316337585,38.89210170772432]]]},"properties":{}} 28 | , 29 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.926297413586155],[-76.99191927909851,38.926297413586155],[-76.9643783569336,38.90172091499795],[-76.98263883590698,38.88996392916758],[-76.99356079101562,38.88996392916758],[-76.99356079101562,38.926297413586155]]]},"properties":{}} 30 | , 31 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.99356079101562,38.89691982423537],[-76.9850742816925,38.88996392916758]]},"properties":{}} 32 | , 33 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.926297413586155],[-76.99191927909851,38.926297413586155],[-76.99081420898438,38.925312513848496],[-76.99081420898438,38.88996392916758],[-77.03750610351562,38.88996392916758],[-77.03750610351562,38.926297413586155]]]},"properties":{}} 34 | , 35 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.02940583229065,38.926297413586155],[-76.99081420898438,38.89466528700845]],[[-77.03750610351562,38.903716403305424],[-77.01922416687012,38.88996392916758]]]},"properties":{}} 36 | , 37 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.07664489746094,38.90385833966778],[-77.05242991447449,38.926297413586155],[-77.03475952148438,38.926297413586155],[-77.03475952148438,38.88996392916758],[-77.05590605735779,38.88996392916758],[-77.07664489746094,38.90385833966778],[-77.07664489746094,38.90385833966778],[-77.07664489746094,38.90385833966778]]]},"properties":{}} 38 | , 39 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.08145141601562,38.924110583779594],[-77.07866191864014,38.926297413586155]],[[-77.0675253868103,38.926297413586155],[-77.03475952148438,38.901654119440195]],[[-77.08145141601562,38.892702946350795],[-77.06462860107422,38.90599569999114],[-77.04078912734985,38.88996392916758]]]},"properties":{}} 40 | , 41 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.11437821388245,38.88996392916758],[-77.11750030517578,38.89584266537804],[-77.07870483398438,38.926264027378295]],[[-77.08491683006287,38.88996392916758],[-77.07870483398438,38.894874043462096]]]},"properties":{}} 42 | , 43 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.924160664605694],[-76.99356079101562,38.927758044800385],[-76.98952674865723,38.924160664605694],[-76.99356079101562,38.924160664605694]]]},"properties":{}} 44 | , 45 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.924160664605694],[-77.03750610351562,38.940126298768575],[-77.02274322509766,38.953802307929436],[-76.99081420898438,38.925312513848496],[-76.99081420898438,38.924160664605694],[-77.03750610351562,38.924160664605694]]]},"properties":{}} 46 | , 47 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.03750610351562,38.93293261050829],[-77.02679872512817,38.924160664605694]]},"properties":{}} 48 | , 49 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.0547366142273,38.924160664605694],[-77.03475952148438,38.94267145451869],[-77.03475952148438,38.924160664605694],[-77.0547366142273,38.924160664605694]]]},"properties":{}} 50 | , 51 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.08145141601562,38.94252959578276],[-77.0639419555664,38.95460326144689],[-77.03475952148438,38.930687563038816]],[[-77.08138704299927,38.924160664605694],[-77.07321166992188,38.930570718474144],[-77.06469297409058,38.924160664605694]]]},"properties":{}} 52 | , 53 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.10033416748047,38.92950241638661],[-77.07870483398438,38.94442380372959]],[[-77.08138704299927,38.924160664605694],[-77.07870483398438,38.926264027378295]]]},"properties":{}}] } -------------------------------------------------------------------------------- /test/data/expected-z13.geojson: -------------------------------------------------------------------------------- 1 | { "type": "FeatureCollection", "features": [ {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.05719351768494,38.82366088659336],[-77.07595825195312,38.817508682154994],[-77.07921981811523,38.82366088659336]]},"properties":{}} 2 | , 3 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.95279121398926,38.857889530636584],[-76.99356079101562,38.844520926194036]]},"properties":{}} 4 | , 5 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-76.99081420898438,38.845423386066756],[-77.03750610351562,38.83011344289997]],[[-77.02537178993225,38.857889530636584],[-77.03750610351562,38.85387085805712]]]},"properties":{}} 6 | , 7 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.03475952148438,38.8310160854935],[-77.06371665000916,38.82152104968421]],[[-77.07808256149292,38.82152104968421],[-77.08145141601562,38.82786514445979]],[[-77.03475952148438,38.85478155427842],[-77.0584487915039,38.846927460416424],[-77.06613063812256,38.857889530636584]]]},"properties":{}} 8 | , 9 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.07870483398438,38.82269128096158],[-77.09737300872803,38.857889530636584]]},"properties":{}} 10 | , 11 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.94961547851562,38.86089712036954],[-76.9478988647461,38.85949359432408],[-76.94961547851562,38.85893384704323]]},"properties":{}} 12 | , 13 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.09623575210571,38.855750722769756],[-77.11551547050476,38.89210170772432]],[[-77.07870483398438,38.8758412836269],[-77.08728790283203,38.88809332015177],[-77.08221316337585,38.89210170772432]]]},"properties":{}} 14 | , 15 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.11437821388245,38.88996392916758],[-77.11750030517578,38.89584266537804],[-77.07870483398438,38.926264027378295]],[[-77.08491683006287,38.88996392916758],[-77.07870483398438,38.894874043462096]]]},"properties":{}} 16 | , 17 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.10033416748047,38.92950241638661],[-77.07870483398438,38.94442380372959]],[[-77.08138704299927,38.924160664605694],[-77.07870483398438,38.926264027378295]]]},"properties":{}} 18 | , 19 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.89210170772432],[-76.97931289672852,38.89210170772432],[-76.99356079101562,38.882932188069105],[-76.99356079101562,38.89210170772432]]]},"properties":{}} 20 | , 21 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-76.98768138885498,38.89210170772432],[-76.9478988647461,38.85949359432408],[-76.95931434631348,38.855750722769756]],[[-76.99356079101562,38.87066255537718],[-76.99150085449219,38.86911721659655],[-76.99356079101562,38.86843224487802]]]},"properties":{}} 22 | , 23 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.89210170772432],[-76.99081420898438,38.89210170772432],[-76.99081420898438,38.884702715757356],[-77.0199966430664,38.865909487242476],[-77.03750610351562,38.87763704482552],[-77.03750610351562,38.89210170772432]]]},"properties":{}} 24 | , 25 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.02205657958984,38.89210170772432],[-76.99150085449219,38.86911721659655],[-77.03183054924011,38.855750722769756]],[[-77.03750610351562,38.88775927764394],[-77.02171325683594,38.8771359067301],[-77.03750610351562,38.87427935210604]]]},"properties":{}} 26 | , 27 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03475952148438,38.89210170772432],[-77.03475952148438,38.875799521198644],[-77.05909252166748,38.89210170772432],[-77.03475952148438,38.89210170772432]]]},"properties":{}} 28 | , 29 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.06462860107422,38.855750722769756],[-77.08145141601562,38.87975849028288]],[[-77.04396486282349,38.89210170772432],[-77.03475952148438,38.885905313175016]],[[-77.03475952148438,38.8747721577364],[-77.05570220947266,38.8709883252109],[-77.05638885498047,38.88381745742845]]]},"properties":{}} 30 | , 31 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.926297413586155],[-76.99191927909851,38.926297413586155],[-76.9643783569336,38.90172091499795],[-76.98263883590698,38.88996392916758],[-76.99356079101562,38.88996392916758],[-76.99356079101562,38.926297413586155]]]},"properties":{}} 32 | , 33 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-76.99356079101562,38.89691982423537],[-76.9850742816925,38.88996392916758]]},"properties":{}} 34 | , 35 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.926297413586155],[-76.99191927909851,38.926297413586155],[-76.99081420898438,38.925312513848496],[-76.99081420898438,38.88996392916758],[-77.03750610351562,38.88996392916758],[-77.03750610351562,38.926297413586155]]]},"properties":{}} 36 | , 37 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.02940583229065,38.926297413586155],[-76.99081420898438,38.89466528700845]],[[-77.03750610351562,38.903716403305424],[-77.01922416687012,38.88996392916758]]]},"properties":{}} 38 | , 39 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.07664489746094,38.90385833966778],[-77.05242991447449,38.926297413586155],[-77.03475952148438,38.926297413586155],[-77.03475952148438,38.88996392916758],[-77.05590605735779,38.88996392916758],[-77.07664489746094,38.90385833966778],[-77.07664489746094,38.90385833966778],[-77.07664489746094,38.90385833966778]]]},"properties":{}} 40 | , 41 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.08145141601562,38.924110583779594],[-77.07866191864014,38.926297413586155]],[[-77.0675253868103,38.926297413586155],[-77.03475952148438,38.901654119440195]],[[-77.08145141601562,38.892702946350795],[-77.06462860107422,38.90599569999114],[-77.04078912734985,38.88996392916758]]]},"properties":{}} 42 | , 43 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-76.99356079101562,38.924160664605694],[-76.99356079101562,38.927758044800385],[-76.98952674865723,38.924160664605694],[-76.99356079101562,38.924160664605694]]]},"properties":{}} 44 | , 45 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.03750610351562,38.924160664605694],[-77.03750610351562,38.940126298768575],[-77.02274322509766,38.953802307929436],[-76.99081420898438,38.925312513848496],[-76.99081420898438,38.924160664605694],[-77.03750610351562,38.924160664605694]]]},"properties":{}} 46 | , 47 | {"type":"Feature","geometry":{"type":"LineString","coordinates":[[-77.03750610351562,38.93293261050829],[-77.02679872512817,38.924160664605694]]},"properties":{}} 48 | , 49 | {"type":"Feature","geometry":{"type":"Polygon","coordinates":[[[-77.0547366142273,38.924160664605694],[-77.03475952148438,38.94267145451869],[-77.03475952148438,38.924160664605694],[-77.0547366142273,38.924160664605694]]]},"properties":{}} 50 | , 51 | {"type":"Feature","geometry":{"type":"MultiLineString","coordinates":[[[-77.08145141601562,38.94252959578276],[-77.0639419555664,38.95460326144689],[-77.03475952148438,38.930687563038816]],[[-77.08138704299927,38.924160664605694],[-77.07321166992188,38.930570718474144],[-77.06469297409058,38.924160664605694]]]},"properties":{}}] } -------------------------------------------------------------------------------- /test/data/original.geojson: -------------------------------------------------------------------------------- 1 | {"type":"FeatureCollection","features":[{"type":"Feature","properties":{},"geometry":{"type":"Polygon","coordinates":[[[-77.07664489746094,38.90385833966778],[-77.02274322509766,38.953802307929436],[-76.9643783569336,38.90172091499795],[-77.0199966430664,38.86590948724251],[-77.07664489746094,38.90385833966778]]]}},{"type":"Feature","properties":{},"geometry":{"type":"LineString","coordinates":[[-77.10033416748047,38.929502416386605],[-77.0639419555664,38.95460326144689],[-76.9478988647461,38.8594935943241],[-77.07595825195312,38.81750868215498],[-77.11750030517577,38.89584266537804],[-77.07321166992188,38.93057071847416],[-76.99150085449219,38.86911721659656],[-77.0584487915039,38.846927460416424],[-77.08728790283203,38.888093320151775],[-77.06462860107422,38.905995699991145],[-77.02171325683594,38.8771359067301],[-77.05570220947266,38.87098832521089],[-77.05638885498047,38.88381745742844]]}}]} -------------------------------------------------------------------------------- /test/data/test.mbtiles: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/developmentseed/vt-geojson/94debe0be2fc471612cee0d023f6f487b99594a1/test/data/test.mbtiles -------------------------------------------------------------------------------- /test/geojson-bounds.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var test = require('tap').test 3 | var GeojsonEquality = require('geojson-equality') 4 | var vtgj = require('../') 5 | 6 | var eq = new GeojsonEquality({ precision: 5 }) 7 | 8 | test('geojson bounds', function (t) { 9 | var tileUri = 'mbtiles://' + __dirname + '/data/test.mbtiles' 10 | var original = fs.readFileSync(__dirname + '/data/original.geojson') 11 | original = JSON.parse(original) 12 | var expected = fs.readFileSync(__dirname + '/data/expected-bounds-z13.geojson') 13 | expected = JSON.parse(expected).features 14 | 15 | t.plan(expected.length * 2) 16 | 17 | vtgj(tileUri, { 18 | bounds: original, 19 | minzoom: 13, 20 | maxzoom: 13 21 | }) 22 | .on('data', function (data) { 23 | var exp = expected.shift() 24 | t.ok(eq.compare(exp.geometry, data.geometry)) 25 | t.deepEqual(exp.properties, data.properties) 26 | }) 27 | }) 28 | -------------------------------------------------------------------------------- /test/missing.js: -------------------------------------------------------------------------------- 1 | require('phantomjs-polyfill') 2 | var fs = require('fs') 3 | var test = require('tap').test 4 | var vtgj = require('../') 5 | 6 | test('warn on missing tile', function (t) { 7 | var tileUri = 'mbtiles://' + __dirname + '/data/test.mbtiles' 8 | 9 | var tile = [0, 0, 14] 10 | vtgj(tileUri, { tiles: [tile] }) 11 | .on('data', function (data) { 12 | t.error(data) // should not get any data 13 | }) 14 | .on('warning', function (info) { 15 | t.deepEqual(info.tile, tile) 16 | t.match(info.error.toString(), /Tile does not exist/) 17 | t.end() 18 | }) 19 | }) 20 | 21 | test('warn on tile reading error', function (t) { 22 | var tileUri = 'file://' + __dirname + '/data/bad-tile?filetype=pbf' 23 | 24 | t.plan(201) // 200 features + 1 warning 25 | 26 | var tile = [19087, 24820, 16] 27 | vtgj(tileUri, { tiles: [tile] }) 28 | .on('data', function (data) { 29 | t.ok(data, 'parsed feature') 30 | }) 31 | .on('warning', function (info) { 32 | t.deepEqual(info.tile, tile, 'tile read error') 33 | }) 34 | .on('end', function () { 35 | t.end() 36 | }) 37 | }) 38 | 39 | test('strict mode: error on missing tile', function (t) { 40 | var tileUri = 'mbtiles://' + __dirname + '/data/test.mbtiles' 41 | var original = fs.readFileSync(__dirname + '/data/original.geojson') 42 | original = JSON.parse(original) 43 | 44 | var tile = [0, 0, 14] 45 | vtgj(tileUri, { tiles: [tile], strict: true }) 46 | .on('data', function (data) { 47 | t.error(data) // should not get any data 48 | }) 49 | .on('error', function (error) { 50 | t.match(error.toString(), /Tile does not exist/) 51 | t.end() 52 | }) 53 | }) 54 | 55 | test('strict mode: error on tile reading error', function (t) { 56 | var tileUri = 'file://' + __dirname + '/data/bad-tile?filetype=pbf' 57 | 58 | t.plan(5) // 4 features before the error 59 | 60 | var tile = [19087, 24820, 16] 61 | vtgj(tileUri, { tiles: [tile], strict: true }) 62 | .on('data', function (data) { 63 | t.ok(data, 'parsed feature') 64 | }) 65 | .on('error', function (err) { 66 | t.match(err.toString(), /Error reading feature \d+ from layer \w+/, 'tile read error') 67 | }) 68 | .on('end', function () { 69 | t.end() 70 | }) 71 | }) 72 | 73 | -------------------------------------------------------------------------------- /test/remote.js: -------------------------------------------------------------------------------- 1 | var fs = require('fs') 2 | var test = require('tap').test 3 | var cover = require('../lib/cover') 4 | var vtgj = require('../') 5 | 6 | var GeojsonEquality = require('geojson-equality') 7 | var eq = new GeojsonEquality({ precision: 5 }) 8 | 9 | var accessToken = process.env.MAPBOX_API_KEY 10 | 11 | test('remote', function (t) { 12 | t.plan(2) 13 | t.ok(accessToken, 'MAPBOX_API_KEY environment variable is set.') 14 | var tileUri = 'tilejson+http://api.tiles.mapbox.com/v4/devseed.73553afc.json?access_token=' + accessToken 15 | var dragon = fs.readFileSync(__dirname + '/data/dragon.geojson') 16 | dragon = JSON.parse(dragon) 17 | 18 | var limits = { 19 | min_zoom: 0, 20 | max_zoom: 0 21 | } 22 | 23 | var expected = dragon.features 24 | var tiles = cover(dragon, limits) 25 | vtgj(tileUri, {tiles: tiles}) 26 | .on('data', function (data) { 27 | var exp = expected.shift() 28 | // hack - only check the beginning of the coordinates, because vt 29 | // simplification does funny things with the end. 30 | exp.geometry.coordinates[0] = exp.geometry.coordinates[0].slice(0, 10) 31 | data.geometry.coordinates[0] = data.geometry.coordinates[0].slice(0, 10) 32 | t.ok(eq.compare(exp.geometry, data.geometry)) 33 | }) 34 | .on('end', function () { 35 | t.end() 36 | // TODO this should NOT be necessary. I think it may be because 37 | // node-tilejson is holding onto the http connection, but there isn't an 38 | // obvious way to close it. 39 | if (typeof process.exit === 'function') { 40 | process.exit() 41 | } 42 | }) 43 | .on('error', function (err) { 44 | t.error(err) 45 | }) 46 | }) 47 | --------------------------------------------------------------------------------