simplify serve_data code
split response function into standard express middleware stack - each middleware is now responsible for a single task only
This commit is contained in:
parent
49a779970e
commit
07e03deedb
1 changed files with 138 additions and 80 deletions
|
@ -18,6 +18,24 @@ try {
|
||||||
|
|
||||||
var utils = require('./utils');
|
var utils = require('./utils');
|
||||||
|
|
||||||
|
function isGzipped(data) {
|
||||||
|
return data[0] === 0x1f && data[1] === 0x8b;
|
||||||
|
}
|
||||||
|
|
||||||
|
function unzipTile(tile) {
|
||||||
|
if (tile.isGzipped) {
|
||||||
|
tile.data = zlib.gunzipSync(tile.data);
|
||||||
|
tile.isGzipped = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function zipTile(tile) {
|
||||||
|
if (!tile.isGzipped) {
|
||||||
|
tile.data = zlib.gzipSync(tile.data);
|
||||||
|
tile.isGzipped = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = function(options, repo, params, id, styles) {
|
module.exports = function(options, repo, params, id, styles) {
|
||||||
var app = express().disable('x-powered-by');
|
var app = express().disable('x-powered-by');
|
||||||
|
|
||||||
|
@ -28,10 +46,29 @@ module.exports = function(options, repo, params, id, styles) {
|
||||||
|
|
||||||
var shrinkers = {};
|
var shrinkers = {};
|
||||||
|
|
||||||
|
function lookupShrinker(style) {
|
||||||
|
if (shrinkers[style]) {
|
||||||
|
return shrinkers[style];
|
||||||
|
}
|
||||||
|
var styleJSON = styles[style];
|
||||||
|
if (!styleJSON) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
var sourceName = null;
|
||||||
|
for (var sourceName_ in styleJSON.sources) {
|
||||||
|
var source = styleJSON.sources[sourceName_];
|
||||||
|
if (source && source.type == 'vector' && source.url.endsWith('/' + id + '.json')) {
|
||||||
|
sourceName = sourceName_;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
shrinkers[style] = tileshrinkGl.createPBFShrinker(styleJSON, sourceName);
|
||||||
|
return shrinkers[style];
|
||||||
|
}
|
||||||
|
|
||||||
repo[id] = tileJSON;
|
repo[id] = tileJSON;
|
||||||
|
|
||||||
var mbtilesFileStats = fs.statSync(mbtilesFile);
|
var mbtilesFileStats = fs.statSync(mbtilesFile);
|
||||||
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size == 0) {
|
if (!mbtilesFileStats.isFile() || mbtilesFileStats.size === 0) {
|
||||||
throw Error('Not valid MBTiles file: ' + mbtilesFile);
|
throw Error('Not valid MBTiles file: ' + mbtilesFile);
|
||||||
}
|
}
|
||||||
var source = new mbtiles(mbtilesFile, function(err) {
|
var source = new mbtiles(mbtilesFile, function(err) {
|
||||||
|
@ -53,7 +90,7 @@ module.exports = function(options, repo, params, id, styles) {
|
||||||
|
|
||||||
var tilePattern = '/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)';
|
var tilePattern = '/' + id + '/:z(\\d+)/:x(\\d+)/:y(\\d+).:format([\\w.]+)';
|
||||||
|
|
||||||
app.get(tilePattern, function(req, res, next) {
|
function checkParams(req, res, next) {
|
||||||
var z = req.params.z | 0,
|
var z = req.params.z | 0,
|
||||||
x = req.params.x | 0,
|
x = req.params.x | 0,
|
||||||
y = req.params.y | 0;
|
y = req.params.y | 0;
|
||||||
|
@ -70,87 +107,108 @@ module.exports = function(options, repo, params, id, styles) {
|
||||||
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
x >= Math.pow(2, z) || y >= Math.pow(2, z)) {
|
||||||
return res.status(404).send('Out of bounds');
|
return res.status(404).send('Out of bounds');
|
||||||
}
|
}
|
||||||
source.getTile(z, x, y, function(err, data, headers) {
|
req.params.format = format;
|
||||||
|
req.params.z = z;
|
||||||
|
req.params.x = x;
|
||||||
|
req.params.y = y;
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function getTile(req, res, next) {
|
||||||
|
var p = req.params;
|
||||||
|
source.getTile(p.z, p.x, p.y, function(err, data, headers) {
|
||||||
if (err) {
|
if (err) {
|
||||||
if (/does not exist/.test(err.message)) {
|
var status = /does not exist/.test(err.message) ? 404 : 500;
|
||||||
return res.status(404).send(err.message);
|
return res.status(status).send(err.message);
|
||||||
} else {
|
|
||||||
return res.status(500).send(err.message);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (data == null) {
|
|
||||||
return res.status(404).send('Not found');
|
|
||||||
} else {
|
|
||||||
if (tileJSON['format'] == 'pbf') {
|
|
||||||
var isGzipped = data.slice(0,2).indexOf(
|
|
||||||
new Buffer([0x1f, 0x8b])) === 0;
|
|
||||||
var style = req.query.style;
|
|
||||||
if (style && tileshrinkGl) {
|
|
||||||
if (!shrinkers[style]) {
|
|
||||||
var styleJSON = styles[style];
|
|
||||||
if (styleJSON) {
|
|
||||||
var sourceName = null;
|
|
||||||
for (var sourceName_ in styleJSON.sources) {
|
|
||||||
var source = styleJSON.sources[sourceName_];
|
|
||||||
if (source &&
|
|
||||||
source.type == 'vector' &&
|
|
||||||
source.url.endsWith('/' + id + '.json')) {
|
|
||||||
sourceName = sourceName_;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
shrinkers[style] = tileshrinkGl.createPBFShrinker(styleJSON, sourceName);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (shrinkers[style]) {
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
data = shrinkers[style](data, z, tileJSON.maxzoom);
|
|
||||||
//console.log(shrinkers[style].getStats());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (format == 'pbf') {
|
|
||||||
headers['Content-Type'] = 'application/x-protobuf';
|
|
||||||
} else if (format == 'geojson') {
|
|
||||||
headers['Content-Type'] = 'application/json';
|
|
||||||
|
|
||||||
if (isGzipped) {
|
|
||||||
data = zlib.unzipSync(data);
|
|
||||||
isGzipped = false;
|
|
||||||
}
|
|
||||||
|
|
||||||
var tile = new VectorTile(new pbf(data));
|
|
||||||
var geojson = {
|
|
||||||
"type": "FeatureCollection",
|
|
||||||
"features": []
|
|
||||||
};
|
|
||||||
for (var layerName in tile.layers) {
|
|
||||||
var layer = tile.layers[layerName];
|
|
||||||
for (var i = 0; i < layer.length; i++) {
|
|
||||||
var feature = layer.feature(i);
|
|
||||||
var featureGeoJSON = feature.toGeoJSON(x, y, z);
|
|
||||||
featureGeoJSON.properties.layer = layerName;
|
|
||||||
geojson.features.push(featureGeoJSON);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data = JSON.stringify(geojson);
|
|
||||||
}
|
|
||||||
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
|
||||||
headers['Content-Encoding'] = 'gzip';
|
|
||||||
res.set(headers);
|
|
||||||
|
|
||||||
if (!isGzipped) {
|
|
||||||
data = zlib.gzipSync(data);
|
|
||||||
isGzipped = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return res.status(200).send(data);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
if (data == null) {
|
||||||
|
return res.status(404).send('Not found');
|
||||||
|
}
|
||||||
|
req.tile = {
|
||||||
|
data: data,
|
||||||
|
headers: headers,
|
||||||
|
contentType: 'application/x-protobuf',
|
||||||
|
isGzipped: isGzipped(data)
|
||||||
|
};
|
||||||
|
next();
|
||||||
});
|
});
|
||||||
});
|
}
|
||||||
|
|
||||||
|
function shrinkTile(req, res, next) {
|
||||||
|
if (!tileshrinkGl) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
if (tileJSON['format'] !== 'pbf') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var style = req.query.style;
|
||||||
|
if (!style) {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var tile = req.tile;
|
||||||
|
var shrinker = lookupShrinker(style);
|
||||||
|
if (shrinker) {
|
||||||
|
unzipTile(tile);
|
||||||
|
tile.data = shrinker(tile.data, req.params.z, tileJSON.maxzoom);
|
||||||
|
//console.log(shrinker.getStats());
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatTile(req, res, next) {
|
||||||
|
var format = req.params.format;
|
||||||
|
if (format !== 'geojson') {
|
||||||
|
return next();
|
||||||
|
}
|
||||||
|
var tile = req.tile;
|
||||||
|
var x = req.params.x,
|
||||||
|
y = req.params.y,
|
||||||
|
z = req.params.z;
|
||||||
|
|
||||||
|
tile.contentType = 'application/json';
|
||||||
|
unzipTile(tile);
|
||||||
|
|
||||||
|
var vectorTile = new VectorTile(new pbf(tile.data));
|
||||||
|
var geojson = {
|
||||||
|
"type": "FeatureCollection",
|
||||||
|
"features": []
|
||||||
|
};
|
||||||
|
|
||||||
|
for (var layerName in vectorTile.layers) {
|
||||||
|
var layer = vectorTile.layers[layerName];
|
||||||
|
for (var i = 0; i < layer.length; i++) {
|
||||||
|
var feature = layer.feature(i);
|
||||||
|
var featureGeoJSON = feature.toGeoJSON(x, y, z);
|
||||||
|
featureGeoJSON.properties.layer = layerName;
|
||||||
|
geojson.features.push(featureGeoJSON);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
tile.data = JSON.stringify(geojson);
|
||||||
|
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
|
||||||
|
function sendTile(req, res) {
|
||||||
|
var headers = req.tile.headers;
|
||||||
|
|
||||||
|
delete headers['ETag']; // do not trust the tile ETag -- regenerate
|
||||||
|
headers['Content-Type'] = req.tile.contentType;
|
||||||
|
headers['Content-Encoding'] = 'gzip';
|
||||||
|
res.set(headers);
|
||||||
|
|
||||||
|
zipTile(req.tile);
|
||||||
|
|
||||||
|
return res.status(200).send(req.tile.data);
|
||||||
|
}
|
||||||
|
|
||||||
|
app.get(tilePattern,
|
||||||
|
checkParams,
|
||||||
|
getTile,
|
||||||
|
shrinkTile,
|
||||||
|
formatTile,
|
||||||
|
sendTile
|
||||||
|
);
|
||||||
|
|
||||||
app.get('/' + id + '.json', function(req, res, next) {
|
app.get('/' + id + '.json', function(req, res, next) {
|
||||||
var info = clone(tileJSON);
|
var info = clone(tileJSON);
|
||||||
|
|
Loading…
Reference in a new issue