forked from mapbox/tilelive
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathstream-scanline.js
149 lines (126 loc) · 5.11 KB
/
stream-scanline.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
146
147
148
149
var sm = new (require('sphericalmercator'))();
var Stats = require('./stream-util').Stats;
var Tile = require('./stream-util').Tile;
var limitBounds = require('./stream-util').limitBounds;
var Info = require('./stream-util').Info;
var isEmpty = require('./stream-util').isEmpty;
var multiread = require('./stream-util').multiread;
var getTileRetry = require('./stream-util').getTileRetry;
var Readable = require('stream').Readable;
var util = require('util');
var validate = require('./tilelive.js').validate;
module.exports = Scanline;
util.inherits(Scanline, Readable);
function Scanline(source, options) {
if (!source) throw new TypeError('Tilesource required');
options = options || {};
if (options.bounds !== undefined && !Array.isArray(options.bounds))
throw new TypeError('options.bounds must be an array of the form [w,s,e,n]');
if (options.minzoom !== undefined && typeof options.minzoom !== 'number')
throw new TypeError('options.minzoom must be a positive integer');
if (options.maxzoom !== undefined && typeof options.maxzoom !== 'number')
throw new TypeError('options.maxzoom must be a positive integer');
this.source = source;
this.bounds = options.bounds;
this.minzoom = options.minzoom;
this.maxzoom = options.maxzoom;
this.stats = new Stats();
this.bboxes = undefined;
this.cursor = undefined;
this.length = 0;
this.job = options.job || false;
this.retry = options.retry || 0;
Readable.call(this, { objectMode: true });
}
Scanline.prototype._params = function(callback) {
var stream = this;
stream.source.getInfo(function(err, info) {
if (err) return stream.emit('error', err);
stream.bounds = stream.bounds !== undefined ? stream.bounds : info.bounds;
stream.minzoom = stream.minzoom !== undefined ? stream.minzoom : info.minzoom;
stream.maxzoom = stream.maxzoom !== undefined ? stream.maxzoom : info.maxzoom;
if (stream.bounds === undefined) return stream.emit('error', new Error('No bounds determined'));
if (stream.minzoom === undefined) return stream.emit('error', new Error('No minzoom determined'));
if (stream.maxzoom === undefined) return stream.emit('error', new Error('No maxzoom determined'));
stream.bboxes = {};
var boundsArray = limitBounds(stream.bounds);
var valid = validate({bounds:boundsArray});
if (valid instanceof Error) return stream.emit('error', new Error(valid.message));
for (var z = stream.minzoom; z <= stream.maxzoom; z++) {
stream.bboxes[z] = sm.xyz(boundsArray, z);
if (stream.bboxes[z].minX < 0) stream.bboxes[z].minX = 0;
if (stream.bboxes[z].minY < 0) stream.bboxes[z].minY = 0;
stream.stats.total +=
(stream.bboxes[z].maxX - stream.bboxes[z].minX + 1) *
(stream.bboxes[z].maxY - stream.bboxes[z].minY + 1);
}
stream.cursor = {
z: stream.minzoom,
x: stream.bboxes[stream.minzoom].minX,
y: stream.bboxes[stream.minzoom].minY
};
callback(null, info);
});
};
Scanline.prototype._read = function(/* size */) {
var stream = this;
// Defer gets until info is retrieved and there is a cursor to be used.
if (!stream.bboxes) return stream._params(function(err, info) {
if (err) return stream.emit('error', err);
stream.length = stream.stats.total;
stream.emit('length', stream.length);
stream.push(new Info(info));
});
multiread(stream, function get(push) {
if (!stream.cursor) return push(null) && false;
stream.stats.ops++;
var z = stream.cursor.z;
var x = stream.cursor.x;
var y = stream.cursor.y;
nextDeep(stream);
if (stream.job && x % stream.job.total !== stream.job.num)
return skip();
getTileRetry(stream.source, z, x, y, stream.retry, stream, function(err, buffer) {
if (err && !(/does not exist$/).test(err.message)) {
stream.emit('error', err);
} else if (err || isEmpty(buffer)) {
skip();
} else {
stream.stats.done++;
push(new Tile(z, x, y, buffer));
}
});
function skip() {
stream.stats.skipped++;
stream.stats.done++;
// Update length
stream.length--;
stream.emit('length', stream.length);
get(push);
}
return true;
});
};
// Increment a tile cursor to the next position,
// descending zoom levels until maxzoom is reached.
function nextDeep(stream) {
if (!stream.cursor) return false;
var cursor = stream.cursor;
cursor.x++;
var bbox = stream.bboxes[cursor.z];
if (cursor.x > bbox.maxX) {
cursor.x = bbox.minX;
cursor.y++;
}
if (cursor.y > bbox.maxY) {
cursor.z++;
if (cursor.z > stream.maxzoom) {
stream.cursor = false;
return false;
}
bbox = stream.bboxes[cursor.z];
cursor.x = bbox.minX;
cursor.y = bbox.minY;
}
return true;
}