diff --git a/lib/cartodb/controllers/named_maps.js b/lib/cartodb/controllers/named_maps.js index 6bb6d2e8..03885713 100644 --- a/lib/cartodb/controllers/named_maps.js +++ b/lib/cartodb/controllers/named_maps.js @@ -120,7 +120,7 @@ NamedMapsController.prototype.staticMap = function(req, res) { function reqParams() { self.req2params(req, this); }, - function getTemplate(err) { + function prepareImageOptions(err) { assert.ifError(err); namedMapProvider = self.namedMapProviderCache.get( cdbUser, @@ -129,42 +129,7 @@ NamedMapsController.prototype.staticMap = function(req, res) { req.query.auth_token, req.params ); - namedMapProvider.getTemplate(this); - }, - function prepareStaticImageOptions(err, template) { - assert.ifError(err); - getStaticImageOptions(template, this); - }, - function estimateBounds(err, imageOpts) { - assert.ifError(err); - if (imageOpts) { - return imageOpts; - } - - var defaultZoomCenter = { - zoom: 1, - center: { - lng: 0, - lat: 0 - } - }; - - var cacheChannel = ''; - - var dbTables = cacheChannel.split(':'); - if (dbTables.length <= 1 || dbTables[1].length === 0) { - return defaultZoomCenter; - } - - var tableNames = dbTables[1].split(','); - if (tableNames.length === 0) { - return defaultZoomCenter; - } - - var next = this; - self.tablesExtentApi.getBounds(cdbUser, tableNames, function(err, result) { - next(null, result || defaultZoomCenter); - }); + self.getStaticImageOptions(cdbUser, namedMapProvider, this); }, function getImage(err, imageOpts) { assert.ifError(err); @@ -195,20 +160,66 @@ NamedMapsController.prototype.staticMap = function(req, res) { ); }; -function getStaticImageOptions(template, callback) { - if (template.view) { - var zoomCenter = templateZoomCenter(template.view); - if (zoomCenter) { - return callback(null, zoomCenter); - } - - var bounds = templateBounds(template.view); - if (bounds) { - return callback(null, bounds); - } +var DEFAULT_ZOOM_CENTER = { + zoom: 1, + center: { + lng: 0, + lat: 0 } - return callback(null, null); -} +}; + +NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, namedMapProvider, callback) { + var self = this; + + step( + function getTemplate() { + namedMapProvider.getTemplate(this); + }, + function handleTemplateView(err, template) { + assert.ifError(err); + + if (template.view) { + var zoomCenter = templateZoomCenter(template.view); + if (zoomCenter) { + return zoomCenter; + } + + var bounds = templateBounds(template.view); + if (bounds) { + return bounds; + } + } + + return false; + }, + function estimateBoundsIfNoImageOpts(err, imageOpts) { + if (imageOpts) { + return imageOpts; + } + + var next = this; + namedMapProvider.getAffectedTablesAndLastUpdatedTime(function(err, affectedTablesAndLastUpdate) { + if (err) { + return next(null); + } + + var affectedTables = affectedTablesAndLastUpdate.affectedTables || []; + + if (affectedTables.length === 0) { + return next(null); + } + + self.tablesExtentApi.getBounds(cdbUser, affectedTables, function(err, result) { + return next(null, result); + }); + }); + + }, + function returnCallback(err, imageOpts) { + return callback(err, imageOpts || DEFAULT_ZOOM_CENTER); + } + ); +}; function templateZoomCenter(view) { if (!_.isUndefined(view.zoom) && view.center) { @@ -223,7 +234,7 @@ function templateZoomCenter(view) { function templateBounds(view) { if (view.bounds) { var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) { - return !!view.bounds[prop]; + return Number.isFinite(view.bounds[prop]); }); if (hasAllBounds) { return { diff --git a/test/acceptance/named_maps_static_view.js b/test/acceptance/named_maps_static_view.js new file mode 100644 index 00000000..7cab6e1b --- /dev/null +++ b/test/acceptance/named_maps_static_view.js @@ -0,0 +1,164 @@ +require('../support/test_helper'); +var RedisPool = require('redis-mpool'); + +var assert = require('../support/assert'); +var mapnik = require('windshaft').mapnik; +var CartodbWindshaft = require('../../lib/cartodb/server'); +var serverOptions = require('../../lib/cartodb/server_options'); +var TemplateMaps = require('../../lib/cartodb/backends/template_maps.js'); + +describe('named maps authentication', function() { + // configure redis pool instance to use in tests + var redisPool = new RedisPool(global.environment.redis); + + var templateMaps = new TemplateMaps(redisPool, { + max_user_templates: global.environment.maxUserTemplates + }); + + var username = 'localhost'; + var templateName = 'template_with_view'; + + var IMAGE_TOLERANCE = 20; + + function createTemplate(view) { + return { + version: '0.0.1', + name: templateName, + auth: { + method: 'open' + }, + placeholders: { + color: { + type: "css_color", + default: "#cc3300" + } + }, + view: view, + layergroup: { + layers: [ + { + type: 'mapnik', + options: { + sql: 'select * from populated_places_simple_reduced', + cartocss: '#layer { marker-fill: <%= color %>; }', + cartocss_version: '2.3.0' + } + } + ] + } + }; + } + + afterEach(function (done) { + templateMaps.delTemplate(username, templateName, done); + }); + + function getStaticMap(callback) { + + var url = '/api/v1/map/static/named/' + templateName + '/640/480.png'; + + var requestOptions = { + url: url, + method: 'GET', + headers: { + host: username + }, + encoding: 'binary' + }; + + var expectedResponse = { + status: 200, + headers: { + 'Content-Type': 'image/png' + } + }; + + // this could be removed once named maps are invalidated, otherwise you hits the cache + var server = new CartodbWindshaft(serverOptions); + + assert.response(server, requestOptions, expectedResponse, function (res, err) { + return callback(err, mapnik.Image.fromBytes(new Buffer(res.body, 'binary'))); + }); + } + + function previewFixture(version) { + return './test/fixtures/previews/populated_places_simple_reduced-' + version + '.png'; + } + + it('should return an image estimating its bounds based on dataset', function (done) { + templateMaps.addTemplate(username, createTemplate(), function (err) { + if (err) { + return done(err); + } + getStaticMap(function(err, img) { + assert.ok(!err); + assert.imageIsSimilarToFile(img, previewFixture('estimated'), IMAGE_TOLERANCE, done); + }); + }); + }); + + it('should return an image using view zoom + center', function (done) { + var view = { + zoom: 4, + center: { + lng: 40, + lat: 20 + } + }; + templateMaps.addTemplate(username, createTemplate(view), function (err) { + if (err) { + return done(err); + } + getStaticMap(function(err, img) { + assert.ok(!err); + assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), IMAGE_TOLERANCE, done); + }); + }); + }); + + it('should return an image using view bounds', function (done) { + var view = { + bounds: { + west: 0, + south: 0, + east: 45, + north: 45 + } + }; + templateMaps.addTemplate(username, createTemplate(view), function (err) { + if (err) { + return done(err); + } + getStaticMap(function(err, img) { + assert.ok(!err); + assert.imageIsSimilarToFile(img, previewFixture('bounds'), IMAGE_TOLERANCE, done); + }); + }); + }); + + it('should return an image using view zoom + center when bounds are also present', function (done) { + var view = { + bounds: { + west: 0, + south: 0, + east: 45, + north: 45 + }, + zoom: 4, + center: { + lng: 40, + lat: 20 + } + }; + templateMaps.addTemplate(username, createTemplate(view), function (err) { + if (err) { + return done(err); + } + getStaticMap(function(err, img) { + assert.ok(!err); + assert.imageIsSimilarToFile(img, previewFixture('zoom-center'), IMAGE_TOLERANCE, done); + }); + }); + }); + +}); diff --git a/test/fixtures/previews/populated_places_simple_reduced-bounds.png b/test/fixtures/previews/populated_places_simple_reduced-bounds.png new file mode 100644 index 00000000..ba882b27 Binary files /dev/null and b/test/fixtures/previews/populated_places_simple_reduced-bounds.png differ diff --git a/test/fixtures/previews/populated_places_simple_reduced-estimated.png b/test/fixtures/previews/populated_places_simple_reduced-estimated.png new file mode 100644 index 00000000..7171e74b Binary files /dev/null and b/test/fixtures/previews/populated_places_simple_reduced-estimated.png differ diff --git a/test/fixtures/previews/populated_places_simple_reduced-zoom-center.png b/test/fixtures/previews/populated_places_simple_reduced-zoom-center.png new file mode 100644 index 00000000..36c6cac0 Binary files /dev/null and b/test/fixtures/previews/populated_places_simple_reduced-zoom-center.png differ