-
Notifications
You must be signed in to change notification settings - Fork 13
/
Copy pathindex.js
145 lines (121 loc) · 4.6 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
var zlib = require('zlib')
var Pbf = require('pbf')
var through = require('through2')
var cover = require('tile-cover')
var envelope = require('turf-envelope')
var VectorTile = require('vector-tile').VectorTile
var bboxPoly = require('turf-bbox-polygon')
// see https://github.com/substack/insert-module-globals/pull/40
var setImmediate = require('timers').setImmediate
// this is abstracted out for browserify purposes
var loadSource = require('./lib/tilelive-sources')
module.exports = vtgeojson
/**
* Stream GeoJSON from a Mapbox Vector Tile source
*
* @param {string} uri - the tilelive URI for the vector tile source to use.
* @param {object} options - options
* @param {Array<string>} options.layers - An array of layer names to read from tiles. If empty, read all layers
* @param {Array} options.tiles - The tiles to read from the tilelive source. If empty, use `options.bounds` instead.
* @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.
* @param {number} options.minzoom - Defaults to the source metadata minzoom. Ignored if `options.tiles` is set.
* @param {number} options.maxzoom - Defaults to the source metadata minzoom. Ignored if `options.tiles` is set.
* @param {boolean} options.tilesOnly - Output [z, y, x] tile coordinates instead of actually reading tiles. Useful for debugging.
* @param {boolean} options.strict - Emit an error and end the stream if a tile is not found or can't be read
* @return {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.
*/
function vtgeojson (uri, options) {
options = options || {}
if (options.layers && options.layers.length === 0) options.layers = null
var stream = (options.tilesOnly) ? through.obj() : through.obj(writeTile)
var source
loadSource(uri, function (err, src) {
if (err) return loadError(err)
source = src
var tiles = options.tiles
if (tiles) return next()
source.getInfo(function (err, info) {
if (err) return loadError(err)
var limits = {
min_zoom: options.minzoom || info.minzoom,
max_zoom: options.maxzoom || info.maxzoom
}
if (Array.isArray(options.bounds)) {
tiles = cover.tiles(bboxPoly(options.bounds).geometry, limits)
} else if (options.bounds) {
tiles = cover.tiles(envelope(options.bounds).geometry, limits)
} else {
tiles = cover.tiles(bboxPoly(info.bounds).geometry, limits)
}
next()
})
function next () {
if (tiles.length === 0) {
return stream.end()
}
var tile = tiles.pop()
stream.write(tile)
// ensure async, because some tilelive sources callback sync
setImmediate(next)
}
})
return stream
function loadError (err) {
stream.emit('error', err)
stream.end()
}
function tileError (tile, err) {
stream.emit('warning', {
tile: tile,
error: err
})
if (options.strict) { return err }
}
function writeTile (tile, _, next) {
var self = this
var x = tile[0]
var y = tile[1]
var z = tile[2]
source.getTile(z, x, y, function (err, tiledata, opts) {
if (err) {
return next(tileError(tile, err))
}
if (opts['Content-Encoding'] === 'gzip') {
zlib.gunzip(tiledata, processTile)
} else {
processTile(null, tiledata)
}
function processTile (err, tiledata) {
if (err) {
return next(tileError(tile, err))
}
var vt = new VectorTile(new Pbf(tiledata))
var layers = Object.keys(vt.layers)
.filter(function (ln) {
return !options.layers || options.layers.indexOf(ln) >= 0
})
for (var j = 0; j < layers.length; j++) {
var ln = layers[j]
var layer = vt.layers[ln]
if (options.layers && options.layers.indexOf(ln) < 0) return
for (var i = 0; i < layer.length; i++) {
try {
var feat = layer.feature(i).toGeoJSON(x, y, z)
self.push(feat)
} catch (e) {
var error = new Error(
'Error reading feature ' + i + ' from layer ' + ln + ':' + e.toString()
)
if (options.strict) {
return next(error)
} else {
tileError(tile, error)
}
}
}
}
next()
}
})
}
}