tilestrata-vtile-raster
Version:
A TileStrata plugin for rendering vector tiles to images
253 lines (222 loc) • 8.08 kB
JavaScript
var vtileraster = require('../index.js');
var tilestrata = require('tilestrata');
var TileServer = tilestrata.TileServer;
var TileRequest = tilestrata.TileRequest;
var Backend = require('../backend.js');
var assert = require('chai').assert;
var assertImage = require('./utils/assertImage.js');
var fs = require('fs');
describe('"tilestrata-vtile-raster"', function() {
this.timeout(5000);
it('should match tilelive-mapnik metatile calculation (consistency)', function() {
// this test is to enforce consistency w/tilelive-mapnik/tilestrata-mapnik
// https://github.com/mapbox/tilelive-mapnik/blob/37a96814534910b4b7df48ce2fad119edd7defe4/lib/render.js
var EARTH_RADIUS = 6378137;
var EARTH_DIAMETER = EARTH_RADIUS * 2;
var EARTH_CIRCUMFERENCE = EARTH_DIAMETER * Math.PI;
var MAX_RES = EARTH_CIRCUMFERENCE / 256;
var ORIGIN_SHIFT = EARTH_CIRCUMFERENCE / 2;
function tileliveCalculateMetatile(options) {
var z = +options.z, x = +options.x, y = +options.y;
var total = 1 << z;
var resolution = MAX_RES / total;
// Make sure we start at a metatile boundary.
x -= x % options.metatile;
y -= y % options.metatile;
// Make sure we don't calculcate a metatile that is larger than the bounds.
var metaWidth = Math.min(options.metatile, total, total - x);
var metaHeight = Math.min(options.metatile, total, total - y);
// Generate all tile coordinates that are within the metatile.
var tiles = [];
for (var dx = 0; dx < metaWidth; dx++) {
for (var dy = 0; dy < metaHeight; dy++) {
tiles.push([ z, x + dx, y + dy ]);
}
}
var minx = (x * 256) * resolution - ORIGIN_SHIFT;
var miny = -((y + metaHeight) * 256) * resolution + ORIGIN_SHIFT;
var maxx = ((x + metaWidth) * 256) * resolution - ORIGIN_SHIFT;
var maxy = -((y * 256) * resolution - ORIGIN_SHIFT);
return {
width: metaWidth * options.tileSize,
height: metaHeight * options.tileSize,
x: x, y: y,
tiles: tiles,
bbox: [ minx, miny, maxx, maxy ]
};
}
var req = {metatile: 4, z: 13, x: 1588, y: 2952};
var tilelive_result = tileliveCalculateMetatile(req);
var tilestrata_result = Backend.calculateMetatile(req.metatile, req.z, req.x, req.y);
assert.equal(tilelive_result.x, tilestrata_result[1], 'x');
assert.equal(tilelive_result.y, tilestrata_result[2], 'y');
});
it('should be able to rasterize output', function(done) {
var server = new TileServer();
var opts = {
xml: __dirname + '/data/test.xml',
metatile: 4,
bufferSize: 128
};
var req = TileRequest.parse('/layer/5/5/12/tile.png');
server.layer('layer').route('tile.pbf').use({
serve: function(server, req, callback) {
return callback(null, fs.readFileSync(__dirname + '/data/world_metatile.pbf'), {});
}
});
server.layer('layer').route('tile.png').use(vtileraster(opts, {
tilesource: ['layer', 'tile.pbf']
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'image/png');
assert.instanceOf(buffer, Buffer);
assertImage(__dirname + '/fixtures/world.png', buffer);
done();
});
});
});
it('should be able to rasterize output when caches are disabled', function(done) {
var server = new TileServer();
var opts = {
xml: __dirname + '/data/test.xml',
metatile: 4,
bufferSize: 128
};
var req = TileRequest.parse('/layer/5/5/12/tile.png');
req.headers['x-tilestrata-skipcache'] = '1';
server.layer('layer').route('tile.pbf').use({
serve: function(server, req, callback) {
return callback(null, fs.readFileSync(__dirname + '/data/world_metatile.pbf'), {});
}
});
server.layer('layer').route('tile.png').use(vtileraster(opts, {
tilesource: ['layer', 'tile.pbf']
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'image/png');
assert.instanceOf(buffer, Buffer);
assertImage(__dirname + '/fixtures/world.png', buffer);
done();
});
});
});
it('should be able to rasterize 2X output', function(done) {
var server = new TileServer();
var opts = {
xml: __dirname + '/data/test.xml',
metatile: 4,
bufferSize: 256,
tileSize: 512,
scale: 2
};
var req = TileRequest.parse('/layer/5/5/12/tile.png');
server.layer('layer').route('tile.pbf').use({
serve: function(server, req, callback) {
return callback(null, fs.readFileSync(__dirname + '/data/world_metatile.pbf'), {});
}
});
server.layer('layer').route('tile.png').use(vtileraster(opts, {
tilesource: ['layer', 'tile.pbf']
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'image/png');
assert.instanceOf(buffer, Buffer);
// fs.writeFileSync(__dirname + '/fixtures/world@2x.png', buffer);
assertImage(__dirname + '/fixtures/world@2x.png', buffer);
done();
});
});
});
it('should be able to build utfgrid', function(done) {
var server = new TileServer();
var opts = {
xml: __dirname + '/data/test.xml',
metatile: 4,
bufferSize: 128
};
var req = TileRequest.parse('/layer/5/5/12/tile.json');
server.layer('layer').route('tile.pbf').use({
serve: function(server, req, callback) {
return callback(null, fs.readFileSync(__dirname + '/data/world_metatile.pbf'), {});
}
});
server.layer('layer').route('tile.json').use(vtileraster(opts, {
tilesource: ['layer', 'tile.pbf'],
interactivity: true
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'application/json; charset=utf-8');
assert.instanceOf(buffer, Buffer);
var json_actual = buffer.toString('utf8');
var json_expected = fs.readFileSync(__dirname + '/fixtures/world.json', 'utf8');
assert.equal(json_actual, json_expected);
// for utfmerge, to prevent reparsing
assert.deepEqual(buffer._utfgrid, JSON.parse(json_expected));
done();
});
});
});
it('should not crash when given an empty buffer', function(done) {
var server = new TileServer();
var req = TileRequest.parse('/layer/5/5/12/tile.png');
server.layer('vtilelayer').route('tile.pbf').use({
serve: function(server, req, callback) {
return callback(null, new Buffer([]), {});
}
});
server.layer('layer').route('tile.png').use(vtileraster({
xml: __dirname + '/data/test.xml',
tilesource: ['vtilelayer', 'tile.pbf']
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'image/png');
assert.instanceOf(buffer, Buffer);
assertImage(__dirname + '/fixtures/world_empty.png', buffer);
done();
});
});
});
it('should support overzooming', function(done) {
var server = new TileServer();
var opts = {
xml: __dirname + '/data/test.xml',
metatile: 4,
bufferSize: 128
};
var req = TileRequest.parse('/layer/5/5/12/tile.png');
server.layer('vtilelayer', {maxZoom: 4}).route('tile.pbf').use({
serve: function(server, req, callback) {
assert.equal(req.z, 4);
return callback(null, fs.readFileSync(__dirname + '/data/world_metatile.pbf'), {});
}
});
server.layer('layer').route('tile.png').use(vtileraster(opts, {
tilesource: ['vtilelayer', 'tile.pbf']
}));
server.initialize(function(err) {
if (err) throw err;
server.serve(req, false, function(status, buffer, headers) {
assert.equal(status, 200);
assert.equal(headers['Content-Type'], 'image/png');
assert.instanceOf(buffer, Buffer);
assertImage(__dirname + '/fixtures/world_overzoom.png', buffer);
done();
});
});
});
});