From fdd4c4aaa0a4961e8f61c78c3f7bcd3f2ec220e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 2 Mar 2018 18:22:53 +0100 Subject: [PATCH 1/4] Going red: get default named map vector tile --- test/acceptance/mvt.js | 42 ++++++++++++++++++ test/support/test-client.js | 86 ++++++++++++++++++++++++++++++++++++- 2 files changed, 127 insertions(+), 1 deletion(-) diff --git a/test/acceptance/mvt.js b/test/acceptance/mvt.js index fa9189f2..d21ae629 100644 --- a/test/acceptance/mvt.js +++ b/test/acceptance/mvt.js @@ -34,6 +34,48 @@ return function () { serverOptions.renderer.mvt.usePostGIS = originalUsePostGIS; }); + describe('named map tile', function () { + it('should get default named vector tile', function (done) { + const apikeyToken = 1234; + const templateName = 'mvt-template'; + const template = { + version: '0.0.1', + name: templateName, + placeholders: { + buffersize: { + type: 'number', + default: 0 + } + }, + layergroup: { + version: '1.7.0', + layers: [{ + type: 'cartodb', + options: { + sql: 'select * from populated_places_simple_reduced limit 10', + cartocss: TestClient.CARTOCSS.POINTS, + cartocss_version: '2.3.0', + } + }] + } + }; + + const testClient = new TestClient(template, apikeyToken); + + testClient.getNamedTile(templateName, 0, 0, 0, 'mvt', {}, (err, res, tile) => { + if (err) { + return done(err); + } + + const tileJSON = tile.toJSON(); + + assert.equal(tileJSON[0].features.length, 10); + + done(); + }); + }); + }); + describe('analysis-layers-dataviews-mvt', function () { function createMapConfig(layers, dataviews, analysis) { diff --git a/test/support/test-client.js b/test/support/test-client.js index 6b200f2e..3e919175 100644 --- a/test/support/test-client.js +++ b/test/support/test-client.js @@ -414,7 +414,7 @@ TestClient.prototype.getDataview = function(dataviewName, params, callback) { var urlParams = {}; if (params.hasOwnProperty('no_filters')) { urlParams.no_filters = params.no_filters; - } + } if (params.hasOwnProperty('own_filter')) { urlParams.own_filter = params.own_filter; } @@ -1253,3 +1253,87 @@ TestClient.prototype.getAnalysesCatalog = function (params, callback) { } ); }; + +TestClient.prototype.getNamedTile = function (name, z, x, y, format, options, callback) { + const { params } = options; + + if (!this.apiKey) { + return callback(new Error('apiKey param is mandatory to create a new template')); + } + + const createTemplateRequest = { + url: `/api/v1/map/named?${qs.stringify({ api_key: this.apiKey })}`, + method: 'POST', + headers: { + host: 'localhost', + 'Content-Type': 'application/json' + }, + data: JSON.stringify(this.template) + }; + + const createTemplateResponse = { + status: 200, + headers: { + 'Content-Type': 'application/json; charset=utf-8' + } + }; + + assert.response(this.server, createTemplateRequest, createTemplateResponse, (res, err) => { + if (err) { + return callback(err); + } + + const templateId = JSON.parse(res.body).template_id; + const queryParams = params ? `?${qs.stringify(params)}` : ''; + const url = `/api/v1/map/named/${templateId}/all/${[z,x,y].join('/')}.${format}${queryParams}`; + const namedTileRequest = { + url, + method: 'GET', + headers: { + host: 'localhost' + }, + encoding: 'binary' + }; + + let contentType; + switch (format) { + case 'png': + contentType = 'image/png'; + break; + case 'mvt': + contentType = 'application/x-protobuf'; + break; + default: + contentType = 'application/json'; + break; + } + + const namedTileResponse = Object.assign({ + status: 200, + headers: { + 'content-type': contentType + } + }, options.response); + + assert.response(this.server, namedTileRequest, namedTileResponse, (res, err) => { + let body; + switch (res.headers['content-type']) { + case 'image/png': + body = mapnik.Image.fromBytes(new Buffer(res.body, 'binary')); + break; + case 'application/x-protobuf': + body = new mapnik.VectorTile(z, x, y); + body.setDataSync(new Buffer(res.body, 'binary')); + break; + case 'application/json; charset=utf-8': + body = JSON.parse(res.body); + break; + default: + body = res.body; + break; + } + + return callback(err, res, body); + }); + }); +}; From 6331bebb30506321b6f6b16daecffb417f2221d0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 2 Mar 2018 18:25:23 +0100 Subject: [PATCH 2/4] Going green: be able to request defaul named tiles in vector format --- lib/cartodb/controllers/named_maps.js | 39 ++++++++++++++++----------- 1 file changed, 24 insertions(+), 15 deletions(-) diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 92ba4ea6..4fe1a1c3 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -41,14 +41,18 @@ function NamedMapsController(prepareContext, namedMapProviderCache, tileBackend, module.exports = NamedMapsController; NamedMapsController.prototype.register = function(app) { + const tileOptions = { + label: 'NAMED_MAP_TILE' + }; + app.get( app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)', cors(), userMiddleware, this.prepareContext, - this.getNamedMapProvider('NAMED_MAP_TILE'), + this.getNamedMapProvider(tileOptions), this.getAffectedTables(), - this.getTile('NAMED_MAP_TILE'), + this.getTile(tileOptions), this.setSurrogateKey(), this.setCacheChannelHeader(), this.setLastModifiedHeader(), @@ -58,18 +62,23 @@ NamedMapsController.prototype.register = function(app) { vectorError() ); + const staticOptions = { + forcedFormat: 'png', + label: 'STATIC_VIZ_MAP' + }; + app.get( app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', cors(), userMiddleware, allowQueryParams(['layer', 'zoom', 'lon', 'lat', 'bbox']), this.prepareContext, - this.getNamedMapProvider('STATIC_VIZ_MAP'), + this.getNamedMapProvider(staticOptions), this.getAffectedTables(), - this.getTemplate('STATIC_VIZ_MAP'), - this.prepareLayerFilterFromPreviewLayers('STATIC_VIZ_MAP'), + this.getTemplate(staticOptions), + this.prepareLayerFilterFromPreviewLayers(staticOptions), this.getStaticImageOptions(), - this.getImage('STATIC_VIZ_MAP'), + this.getImage(staticOptions), this.incrementMapViews(), this.setSurrogateKey(), this.setCacheChannelHeader(), @@ -80,16 +89,16 @@ NamedMapsController.prototype.register = function(app) { ); }; -NamedMapsController.prototype.getNamedMapProvider = function (label) { +NamedMapsController.prototype.getNamedMapProvider = function ({ label, forcedFormat }) { return function getNamedMapProviderMiddleware (req, res, next) { const { user } = res.locals; const { config, auth_token } = req.query; const { template_id } = req.params; - // We force always the tile to be generated using PNG because - // is the only format we support by now - res.locals.format = 'png'; - res.locals.layer = res.locals.layer || 'all'; + if (forcedFormat) { + res.locals.format = forcedFormat; + res.locals.layer = res.locals.layer || 'all'; + } const params = getRequestParams(res.locals); @@ -124,7 +133,7 @@ NamedMapsController.prototype.getAffectedTables = function () { }.bind(this); }; -NamedMapsController.prototype.getTemplate = function (label) { +NamedMapsController.prototype.getTemplate = function ({ label }) { return function getTemplateMiddleware (req, res, next) { const { namedMapProvider } = res.locals; @@ -141,7 +150,7 @@ NamedMapsController.prototype.getTemplate = function (label) { }; }; -NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (label) { +NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function ({ label }) { return function prepareLayerFilterFromPreviewLayersMiddleware (req, res, next) { const { user, template } = res.locals; const { template_id } = req.params; @@ -183,7 +192,7 @@ NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (la }.bind(this); }; -NamedMapsController.prototype.getTile = function (label) { +NamedMapsController.prototype.getTile = function ({ label }) { return function getTileMiddleware (req, res, next) { const { namedMapProvider } = res.locals; @@ -306,7 +315,7 @@ function getImageOptionsFromBoundingBox (bbox = '') { } } -NamedMapsController.prototype.getImage = function (label) { +NamedMapsController.prototype.getImage = function ({ label }) { return function getImageMiddleware (req, res, next) { const { imageOpts, namedMapProvider } = res.locals; const { zoom, center, bounds } = imageOpts; From f03d98cd0df6d29b70477fa74669bb000c163e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Fri, 2 Mar 2018 18:37:13 +0100 Subject: [PATCH 3/4] Going green: fix test --- test/acceptance/mvt.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/acceptance/mvt.js b/test/acceptance/mvt.js index d21ae629..065de37a 100644 --- a/test/acceptance/mvt.js +++ b/test/acceptance/mvt.js @@ -37,7 +37,7 @@ return function () { describe('named map tile', function () { it('should get default named vector tile', function (done) { const apikeyToken = 1234; - const templateName = 'mvt-template'; + const templateName = `mvt-template-${usePostGIS ? 'postgis' : 'mapnik'}`; const template = { version: '0.0.1', name: templateName, @@ -71,7 +71,7 @@ return function () { assert.equal(tileJSON[0].features.length, 10); - done(); + testClient.drain(done); }); }); }); From 5de8c4f9c3beec257cbee844b53ca26b69fa507e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Daniel=20Garc=C3=ADa=20Aubert?= Date: Mon, 5 Mar 2018 11:19:17 +0100 Subject: [PATCH 4/4] Make explicit that `forceFormat` is optional --- lib/cartodb/controllers/named_maps.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 4fe1a1c3..93566cc9 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -89,7 +89,7 @@ NamedMapsController.prototype.register = function(app) { ); }; -NamedMapsController.prototype.getNamedMapProvider = function ({ label, forcedFormat }) { +NamedMapsController.prototype.getNamedMapProvider = function ({ label, forcedFormat = null }) { return function getNamedMapProviderMiddleware (req, res, next) { const { user } = res.locals; const { config, auth_token } = req.query;