Splits controllers and supports after layergroup creation actions
This commit is contained in:
parent
6e0678e084
commit
9bece712a9
@ -1,6 +1,7 @@
|
||||
module.exports = {
|
||||
Layergroup: require('./layergroup'),
|
||||
Map: require('./map'),
|
||||
NamedMaps: require('./named_maps'),
|
||||
NamedMapsAdmin: require('./named_maps_admin'),
|
||||
ServerInfo: require('./server_info')
|
||||
};
|
||||
};
|
||||
|
205
lib/cartodb/controllers/layergroup.js
Normal file
205
lib/cartodb/controllers/layergroup.js
Normal file
@ -0,0 +1,205 @@
|
||||
var assert = require('assert');
|
||||
var step = require('step');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
|
||||
var windshaft = require('windshaft');
|
||||
var MapStoreMapConfigProvider = windshaft.model.provider.MapStoreMapConfig;
|
||||
|
||||
/**
|
||||
* @param app
|
||||
* @param {MapStore} mapStore
|
||||
* @param {TileBackend} tileBackend
|
||||
* @param {PreviewBackend} previewBackend
|
||||
* @param {AttributesBackend} attributesBackend
|
||||
* @constructor
|
||||
*/
|
||||
function LayergroupController(app, mapStore, tileBackend, previewBackend, attributesBackend) {
|
||||
this.app = app;
|
||||
this.mapStore = mapStore;
|
||||
this.tileBackend = tileBackend;
|
||||
this.previewBackend = previewBackend;
|
||||
this.attributesBackend = attributesBackend;
|
||||
}
|
||||
|
||||
module.exports = LayergroupController;
|
||||
|
||||
|
||||
LayergroupController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format', cors(), this.tile.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y.:format', cors(), this.tile.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)', cors(), this.layer.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:layer/attributes/:fid', cors(), this.attributes.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(),
|
||||
this.center.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(),
|
||||
this.bbox.bind(this));
|
||||
};
|
||||
|
||||
LayergroupController.prototype.attributes = function(req, res) {
|
||||
var self = this;
|
||||
|
||||
req.profiler.start('windshaft.maplayer_attribute');
|
||||
|
||||
step(
|
||||
function setupParams() {
|
||||
self.app.req2params(req, this);
|
||||
},
|
||||
function retrieveFeatureAttributes(err) {
|
||||
req.profiler.done('req2params');
|
||||
|
||||
assert.ifError(err);
|
||||
|
||||
self.attributesBackend.getFeatureAttributes(req.params, false, this);
|
||||
},
|
||||
function finish(err, tile, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
var statusCode = self.app.findStatusCode(err);
|
||||
self.app.sendError(res, { errors: [errMsg] }, statusCode, 'GET ATTRIBUTES', err);
|
||||
} else {
|
||||
self.app.sendResponse(res, [tile, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
};
|
||||
|
||||
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
|
||||
LayergroupController.prototype.tile = function(req, res) {
|
||||
req.profiler.start('windshaft.map_tile');
|
||||
this.tileOrLayer(req, res);
|
||||
};
|
||||
|
||||
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
|
||||
LayergroupController.prototype.layer = function(req, res, next) {
|
||||
if (req.params.token === 'static') {
|
||||
return next();
|
||||
}
|
||||
req.profiler.start('windshaft.maplayer_tile');
|
||||
this.tileOrLayer(req, res);
|
||||
};
|
||||
|
||||
LayergroupController.prototype.tileOrLayer = function (req, res) {
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function mapController$prepareParams() {
|
||||
self.app.req2params(req, this);
|
||||
},
|
||||
function mapController$getTileOrGrid(err) {
|
||||
req.profiler.done('req2params');
|
||||
if ( err ) {
|
||||
throw err;
|
||||
}
|
||||
self.tileBackend.getTile(new MapStoreMapConfigProvider(self.mapStore, req.params), req.params, this);
|
||||
},
|
||||
function mapController$finalize(err, tile, headers, stats) {
|
||||
req.profiler.add(stats);
|
||||
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) {
|
||||
console.error("windshaft.tiles: " + err);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// This function is meant for being called as the very last
|
||||
// step by all endpoints serving tiles or grids
|
||||
LayergroupController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
|
||||
var supportedFormats = {
|
||||
grid_json: true,
|
||||
json_torque: true,
|
||||
torque_json: true,
|
||||
png: true
|
||||
};
|
||||
|
||||
var formatStat = 'invalid';
|
||||
if (req.params.format) {
|
||||
var format = req.params.format.replace('.', '_');
|
||||
if (supportedFormats[format]) {
|
||||
formatStat = format;
|
||||
}
|
||||
}
|
||||
|
||||
if (err){
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
var statusCode = this.app.findStatusCode(err);
|
||||
|
||||
// Rewrite mapnik parsing errors to start with layer number
|
||||
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
|
||||
if (matches) {
|
||||
errMsg = 'style'+matches[2]+': ' + matches[1];
|
||||
}
|
||||
|
||||
this.app.sendError(res, { errors: ['' + errMsg] }, statusCode, 'TILE RENDER', err);
|
||||
global.statsClient.increment('windshaft.tiles.error');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
|
||||
} else {
|
||||
this.app.sendWithHeaders(res, tile, 200, headers);
|
||||
global.statsClient.increment('windshaft.tiles.success');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
|
||||
}
|
||||
};
|
||||
|
||||
LayergroupController.prototype.bbox = function(req, res) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, {
|
||||
west: +req.params.west,
|
||||
north: +req.params.north,
|
||||
east: +req.params.east,
|
||||
south: +req.params.south
|
||||
});
|
||||
};
|
||||
|
||||
LayergroupController.prototype.center = function(req, res) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
|
||||
lng: +req.params.lng,
|
||||
lat: +req.params.lat
|
||||
});
|
||||
};
|
||||
|
||||
LayergroupController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
req.params.layer = 'all';
|
||||
req.params.format = 'png';
|
||||
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function() {
|
||||
self.app.req2params(req, this);
|
||||
},
|
||||
function(err) {
|
||||
req.profiler.done('req2params');
|
||||
assert.ifError(err);
|
||||
if (center) {
|
||||
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
|
||||
format, width, height, zoom, center, this);
|
||||
} else {
|
||||
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
|
||||
format, width, height, zoom /* bounds */, this);
|
||||
}
|
||||
},
|
||||
function handleImage(err, image, headers, stats) {
|
||||
req.profiler.done('render-' + format);
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
if (!err.error) {
|
||||
err.error = err.message;
|
||||
}
|
||||
self.app.sendError(res, {errors: ['' + err] }, self.app.findStatusCode(err), 'STATIC_MAP', err);
|
||||
} else {
|
||||
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
|
||||
self.app.sendResponse(res, [image, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
@ -1,37 +1,37 @@
|
||||
var _ = require('underscore');
|
||||
var assert = require('assert');
|
||||
|
||||
var step = require('step');
|
||||
var windshaft = require('windshaft');
|
||||
|
||||
var cors = require('../middleware/cors');
|
||||
|
||||
var windshaft = require('windshaft');
|
||||
var MapStoreMapConfigProvider = windshaft.model.provider.MapStoreMapConfig;
|
||||
var MapConfig = windshaft.model.MapConfig;
|
||||
var Datasource = windshaft.model.Datasource;
|
||||
|
||||
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
||||
|
||||
var MapConfigNamedLayersAdapter = require('../models/mapconfig_named_layers_adapter');
|
||||
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
|
||||
|
||||
/**
|
||||
* @param app
|
||||
* @param {PgConnection} pgConnection
|
||||
* @param {MapStore} mapStore
|
||||
* @param {TemplateMaps} templateMaps
|
||||
* @param {MapBackend} mapBackend
|
||||
* @param {TileBackend} tileBackend
|
||||
* @param {PreviewBackend} previewBackend
|
||||
* @param {AttributesBackend} attributesBackend
|
||||
* @param metadataBackend
|
||||
* @param {QueryTablesApi} queryTablesApi
|
||||
* @param {SurrogateKeysCache} surrogateKeysCache
|
||||
* @constructor
|
||||
*/
|
||||
function MapController(app, pgConnection, mapStore, templateMaps, mapBackend, tileBackend, previewBackend,
|
||||
attributesBackend) {
|
||||
this._app = app;
|
||||
function MapController(app, pgConnection, templateMaps, mapBackend, metadataBackend, queryTablesApi,
|
||||
surrogateKeysCache) {
|
||||
this.app = app;
|
||||
this.pgConnection = pgConnection;
|
||||
this.mapStore = mapStore;
|
||||
this.templateMaps = templateMaps;
|
||||
this.mapBackend = mapBackend;
|
||||
this.tileBackend = tileBackend;
|
||||
this.previewBackend = previewBackend;
|
||||
this.attributesBackend = attributesBackend;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.queryTablesApi = queryTablesApi;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.namedLayersAdapter = new MapConfigNamedLayersAdapter(templateMaps);
|
||||
}
|
||||
|
||||
@ -39,57 +39,82 @@ module.exports = MapController;
|
||||
|
||||
|
||||
MapController.prototype.register = function(app) {
|
||||
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y@:scale_factor?x.:format', cors(), this.tile.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:z/:x/:y.:format', cors(), this.tile.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:layer/:z/:x/:y.(:format)', cors(), this.layer.bind(this));
|
||||
app.options(app.base_url_mapconfig, cors('Content-Type'));
|
||||
app.get(app.base_url_mapconfig, cors(), this.createGet.bind(this));
|
||||
app.post(app.base_url_mapconfig, cors(), this.createPost.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/:token/:layer/attributes/:fid', cors(), this.attributes.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/static/center/:token/:z/:lat/:lng/:width/:height.:format', cors(),
|
||||
this.center.bind(this));
|
||||
app.get(app.base_url_mapconfig + '/static/bbox/:token/:west,:south,:east,:north/:width/:height.:format', cors(),
|
||||
this.bbox.bind(this));
|
||||
app.get(app.base_url_templated + '/:template_id/jsonp', cors(), this.jsonp.bind(this));
|
||||
app.post(app.base_url_templated + '/:template_id', cors(), this.instantiate.bind(this));
|
||||
app.options(app.base_url_mapconfig, cors('Content-Type'));
|
||||
};
|
||||
|
||||
MapController.prototype.attributes = function(req, res) {
|
||||
var self = this;
|
||||
MapController.prototype.createGet = function(req, res){
|
||||
req.profiler.start('windshaft.createmap_get');
|
||||
|
||||
req.profiler.start('windshaft.maplayer_attribute');
|
||||
this.create(req, res, function createGet$prepareConfig(err, req) {
|
||||
assert.ifError(err);
|
||||
if ( ! req.params.config ) {
|
||||
throw new Error('layergroup GET needs a "config" parameter');
|
||||
}
|
||||
return JSON.parse(req.params.config);
|
||||
});
|
||||
};
|
||||
|
||||
step(
|
||||
function setupParams() {
|
||||
self._app.req2params(req, this);
|
||||
},
|
||||
function retrieveFeatureAttributes(err) {
|
||||
req.profiler.done('req2params');
|
||||
MapController.prototype.createPost = function(req, res) {
|
||||
req.profiler.start('windshaft.createmap_post');
|
||||
|
||||
assert.ifError(err);
|
||||
this.create(req, res, function createPost$prepareConfig(err, req) {
|
||||
assert.ifError(err);
|
||||
if (!req.is('application/json')) {
|
||||
throw new Error('layergroup POST data must be of type application/json');
|
||||
}
|
||||
return req.body;
|
||||
});
|
||||
};
|
||||
|
||||
self.attributesBackend.getFeatureAttributes(req.params, false, this);
|
||||
},
|
||||
function finish(err, tile, stats) {
|
||||
req.profiler.add(stats || {});
|
||||
MapController.prototype.instantiate = function(req, res) {
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
}
|
||||
|
||||
if (err) {
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
var statusCode = self._app.findStatusCode(err);
|
||||
self._app.sendError(res, { errors: [errMsg] }, statusCode, 'GET ATTRIBUTES', err);
|
||||
} else {
|
||||
self._app.sendResponse(res, [tile, 200]);
|
||||
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
|
||||
if (!req.is('application/json')) {
|
||||
return callback(new Error('Template POST data must be of type application/json'));
|
||||
}
|
||||
return callback(null, req.body);
|
||||
});
|
||||
};
|
||||
|
||||
MapController.prototype.jsonp = function(req, res) {
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
}
|
||||
|
||||
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
|
||||
var err = null;
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
err = new Error('callback parameter should be present and be a function name');
|
||||
}
|
||||
|
||||
var templateParams = {};
|
||||
if (req.query.config) {
|
||||
try {
|
||||
templateParams = JSON.parse(req.query.config);
|
||||
} catch(e) {
|
||||
err = new Error('Invalid config parameter, should be a valid JSON');
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return callback(err, templateParams);
|
||||
});
|
||||
};
|
||||
|
||||
MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
var self = this;
|
||||
|
||||
var mapConfig;
|
||||
|
||||
step(
|
||||
function setupParams(){
|
||||
self._app.req2params(req, this);
|
||||
self.app.req2params(req, this);
|
||||
},
|
||||
prepareConfigFn,
|
||||
function beforeLayergroupCreate(err, requestMapConfig) {
|
||||
@ -110,178 +135,151 @@ MapController.prototype.create = function(req, res, prepareConfigFn) {
|
||||
},
|
||||
function createLayergroup(err, requestMapConfig, datasource) {
|
||||
assert.ifError(err);
|
||||
self.mapBackend.createLayergroup(
|
||||
new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource()), req.params, this
|
||||
);
|
||||
mapConfig = new MapConfig(requestMapConfig, datasource || Datasource.EmptyDatasource());
|
||||
self.mapBackend.createLayergroup(mapConfig, req.params, this);
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, mapConfig, layergroup, this);
|
||||
},
|
||||
function finish(err, layergroup) {
|
||||
if (err) {
|
||||
var statusCode = self._app.findStatusCode(err);
|
||||
self._app.sendError(res, { errors: [ err.message ] }, statusCode, 'ANONYMOUS LAYERGROUP', err);
|
||||
var statusCode = self.app.findStatusCode(err);
|
||||
self.app.sendError(res, { errors: [ err.message ] }, statusCode, 'ANONYMOUS LAYERGROUP', err);
|
||||
} else {
|
||||
self._app.sendResponse(res, [layergroup, 200]);
|
||||
self.app.sendResponse(res, [layergroup, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
MapController.prototype.createGet = function(req, res){
|
||||
req.profiler.start('windshaft.createmap_get');
|
||||
|
||||
this.create(req, res, function createGet$prepareConfig(err, req) {
|
||||
assert.ifError(err);
|
||||
if ( ! req.params.config ) {
|
||||
throw new Error('layergroup GET needs a "config" parameter');
|
||||
}
|
||||
return JSON.parse(req.params.config);
|
||||
});
|
||||
};
|
||||
|
||||
// TODO rewrite this so it is possible to share code with `MapController::create` method
|
||||
MapController.prototype.createPost = function(req, res) {
|
||||
req.profiler.start('windshaft.createmap_post');
|
||||
|
||||
this.create(req, res, function createPost$prepareConfig(err, req) {
|
||||
assert.ifError(err);
|
||||
if (!req.is('application/json')) {
|
||||
throw new Error('layergroup POST data must be of type application/json');
|
||||
}
|
||||
return req.body;
|
||||
});
|
||||
};
|
||||
|
||||
// Gets a tile for a given token and set of tile ZXY coords. (OSM style)
|
||||
MapController.prototype.tile = function(req, res) {
|
||||
req.profiler.start('windshaft.map_tile');
|
||||
this.tileOrLayer(req, res);
|
||||
};
|
||||
|
||||
// Gets a tile for a given token, layer set of tile ZXY coords. (OSM style)
|
||||
MapController.prototype.layer = function(req, res, next) {
|
||||
if (req.params.token === 'static') {
|
||||
return next();
|
||||
}
|
||||
req.profiler.start('windshaft.maplayer_tile');
|
||||
this.tileOrLayer(req, res);
|
||||
};
|
||||
|
||||
MapController.prototype.tileOrLayer = function (req, res) {
|
||||
MapController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
|
||||
var self = this;
|
||||
|
||||
var cdbuser = req.context.user;
|
||||
|
||||
var mapConfigProvider;
|
||||
var mapConfig;
|
||||
|
||||
step(
|
||||
function mapController$prepareParams() {
|
||||
self._app.req2params(req, this);
|
||||
function getTemplateParams() {
|
||||
prepareParamsFn(this);
|
||||
},
|
||||
function mapController$getTileOrGrid(err) {
|
||||
req.profiler.done('req2params');
|
||||
if ( err ) {
|
||||
throw err;
|
||||
function getTemplate(err, templateParams) {
|
||||
assert.ifError(err);
|
||||
mapConfigProvider = new NamedMapMapConfigProvider(
|
||||
self.templateMaps,
|
||||
self.pgConnection,
|
||||
cdbuser,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
req.query.auth_token,
|
||||
req.params
|
||||
);
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function createLayergroup(err, mapConfig_, rendererParams/*, context*/) {
|
||||
assert.ifError(err);
|
||||
mapConfig = mapConfig_;
|
||||
self.mapBackend.createLayergroup(mapConfig, rendererParams, this);
|
||||
},
|
||||
function afterLayergroupCreate(err, layergroup) {
|
||||
assert.ifError(err);
|
||||
self.afterLayergroupCreate(req, mapConfig, layergroup, this);
|
||||
},
|
||||
function finishTemplateInstantiation(err, layergroup) {
|
||||
if (err) {
|
||||
var statusCode = self.app.findStatusCode(err);
|
||||
self.app.sendError(res, { errors: [ err.message ] }, statusCode, 'NAMED MAP LAYERGROUP', err);
|
||||
} else {
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
res.header('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
|
||||
self.app.sendResponse(res, [layergroup, 200]);
|
||||
}
|
||||
self.tileBackend.getTile(new MapStoreMapConfigProvider(self.mapStore, req.params), req.params, this);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
MapController.prototype.afterLayergroupCreate = function(req, mapconfig, layergroup, callback) {
|
||||
var self = this;
|
||||
|
||||
var username = req.context.user;
|
||||
|
||||
var tasksleft = 2; // redis key and affectedTables
|
||||
var errors = [];
|
||||
|
||||
var done = function(err) {
|
||||
if ( err ) {
|
||||
errors.push('' + err);
|
||||
}
|
||||
if ( ! --tasksleft ) {
|
||||
err = errors.length ? new Error(errors.join('\n')) : null;
|
||||
callback(err, layergroup);
|
||||
}
|
||||
};
|
||||
|
||||
// include in layergroup response the variables in serverMedata
|
||||
// those variables are useful to send to the client information
|
||||
// about how to reach this server or information about it
|
||||
_.extend(layergroup, global.environment.serverMetadata);
|
||||
|
||||
// Don't wait for the mapview count increment to
|
||||
// take place before proceeding. Error will be logged
|
||||
// asynchronously
|
||||
this.metadataBackend.incMapviewCount(username, mapconfig.obj().stat_tag, function(err) {
|
||||
if (req.profiler) {
|
||||
req.profiler.done('incMapviewCount');
|
||||
}
|
||||
if ( err ) {
|
||||
console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
||||
}
|
||||
done();
|
||||
});
|
||||
|
||||
var sql = mapconfig.getLayers().map(function(layer) {
|
||||
return layer.options.sql;
|
||||
}).join(';');
|
||||
|
||||
var dbName = req.params.dbname;
|
||||
var cacheKey = dbName + ':' + layergroup.layergroupid;
|
||||
|
||||
step(
|
||||
function getAffectedTablesAndLastUpdatedTime() {
|
||||
self.queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
|
||||
},
|
||||
function mapController$finalize(err, tile, headers, stats) {
|
||||
req.profiler.add(stats);
|
||||
self.finalizeGetTileOrGrid(err, req, res, tile, headers);
|
||||
function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||
if (req.profiler) {
|
||||
req.profiler.done('queryTablesAndLastUpdated');
|
||||
}
|
||||
assert.ifError(err);
|
||||
var cacheChannel = self.app.buildCacheChannel(dbName, result.affectedTables);
|
||||
self.app.channelCache[cacheKey] = cacheChannel;
|
||||
|
||||
// last update for layergroup cache buster
|
||||
layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
|
||||
layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
|
||||
|
||||
var res = req.res;
|
||||
if (res) {
|
||||
if (req.method === 'GET') {
|
||||
var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
res.header('Last-Modified', (new Date()).toUTCString());
|
||||
res.header('X-Cache-Channel', cacheChannel);
|
||||
}
|
||||
|
||||
res.header('X-Layergroup-Id', layergroup.layergroupid);
|
||||
}
|
||||
|
||||
return null;
|
||||
},
|
||||
function finish(err) {
|
||||
if ( err ) {
|
||||
console.error("windshaft.tiles: " + err);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
// This function is meant for being called as the very last
|
||||
// step by all endpoints serving tiles or grids
|
||||
MapController.prototype.finalizeGetTileOrGrid = function(err, req, res, tile, headers) {
|
||||
var supportedFormats = {
|
||||
grid_json: true,
|
||||
json_torque: true,
|
||||
torque_json: true,
|
||||
png: true
|
||||
};
|
||||
|
||||
var formatStat = 'invalid';
|
||||
if (req.params.format) {
|
||||
var format = req.params.format.replace('.', '_');
|
||||
if (supportedFormats[format]) {
|
||||
formatStat = format;
|
||||
}
|
||||
}
|
||||
|
||||
if (err){
|
||||
// See https://github.com/Vizzuality/Windshaft-cartodb/issues/68
|
||||
var errMsg = err.message ? ( '' + err.message ) : ( '' + err );
|
||||
var statusCode = this._app.findStatusCode(err);
|
||||
|
||||
// Rewrite mapnik parsing errors to start with layer number
|
||||
var matches = errMsg.match("(.*) in style 'layer([0-9]+)'");
|
||||
if (matches) {
|
||||
errMsg = 'style'+matches[2]+': ' + matches[1];
|
||||
}
|
||||
|
||||
this._app.sendError(res, { errors: ['' + errMsg] }, statusCode, 'TILE RENDER', err);
|
||||
global.statsClient.increment('windshaft.tiles.error');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.error');
|
||||
} else {
|
||||
this._app.sendWithHeaders(res, tile, 200, headers);
|
||||
global.statsClient.increment('windshaft.tiles.success');
|
||||
global.statsClient.increment('windshaft.tiles.' + formatStat + '.success');
|
||||
}
|
||||
};
|
||||
|
||||
MapController.prototype.bbox = function(req, res) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, {
|
||||
west: +req.params.west,
|
||||
north: +req.params.north,
|
||||
east: +req.params.east,
|
||||
south: +req.params.south
|
||||
});
|
||||
};
|
||||
|
||||
MapController.prototype.center = function(req, res) {
|
||||
this.staticMap(req, res, +req.params.width, +req.params.height, +req.params.z, {
|
||||
lng: +req.params.lng,
|
||||
lat: +req.params.lat
|
||||
});
|
||||
};
|
||||
|
||||
MapController.prototype.staticMap = function(req, res, width, height, zoom /* bounds */, center) {
|
||||
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
||||
req.params.layer = 'all';
|
||||
req.params.format = 'png';
|
||||
|
||||
var self = this;
|
||||
|
||||
step(
|
||||
function() {
|
||||
self._app.req2params(req, this);
|
||||
},
|
||||
function(err) {
|
||||
req.profiler.done('req2params');
|
||||
assert.ifError(err);
|
||||
if (center) {
|
||||
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
|
||||
format, width, height, zoom, center, this);
|
||||
} else {
|
||||
self.previewBackend.getImage(new MapStoreMapConfigProvider(self.mapStore, req.params),
|
||||
format, width, height, zoom /* bounds */, this);
|
||||
}
|
||||
},
|
||||
function handleImage(err, image, headers, stats) {
|
||||
req.profiler.done('render-' + format);
|
||||
req.profiler.add(stats || {});
|
||||
|
||||
if (err) {
|
||||
if (!err.error) {
|
||||
err.error = err.message;
|
||||
}
|
||||
self._app.sendError(res, {errors: ['' + err] }, self._app.findStatusCode(err), 'STATIC_MAP', err);
|
||||
} else {
|
||||
res.setHeader('Content-Type', headers['Content-Type'] || 'image/' + format);
|
||||
self._app.sendResponse(res, [image, 200]);
|
||||
}
|
||||
done(err);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
@ -6,17 +6,13 @@ var cors = require('../middleware/cors');
|
||||
|
||||
var NamedMapMapConfigProvider = require('../models/mapconfig/named_map_provider');
|
||||
|
||||
function NamedMapsController(app, pgConnection, mapStore, templateMaps, metadataBackend, mapBackend, tileBackend,
|
||||
previewBackend, templateBaseUrl, surrogateKeysCache, tablesExtentApi) {
|
||||
function NamedMapsController(app, pgConnection, templateMaps, tileBackend, previewBackend, surrogateKeysCache,
|
||||
tablesExtentApi) {
|
||||
this.app = app;
|
||||
this.mapStore = mapStore;
|
||||
this.pgConnection = pgConnection;
|
||||
this.templateMaps = templateMaps;
|
||||
this.metadataBackend = metadataBackend;
|
||||
this.mapBackend = mapBackend;
|
||||
this.tileBackend = tileBackend;
|
||||
this.previewBackend = previewBackend;
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
this.surrogateKeysCache = surrogateKeysCache;
|
||||
this.tablesExtentApi = tablesExtentApi;
|
||||
}
|
||||
@ -24,49 +20,10 @@ function NamedMapsController(app, pgConnection, mapStore, templateMaps, metadata
|
||||
module.exports = NamedMapsController;
|
||||
|
||||
NamedMapsController.prototype.register = function(app) {
|
||||
app.get(this.templateBaseUrl + '/:template_id/:layer/:z/:x/:y.(:format)', cors(), this.tile.bind(this));
|
||||
app.get(this.templateBaseUrl + '/:template_id/jsonp', cors(), this.jsonp.bind(this));
|
||||
app.get(app.base_url_templated + '/:template_id/:layer/:z/:x/:y.(:format)', cors(), this.tile.bind(this));
|
||||
app.get(
|
||||
app.base_url_mapconfig + '/static/named/:template_id/:width/:height.:format', cors(), this.staticMap.bind(this)
|
||||
);
|
||||
app.post(this.templateBaseUrl + '/:template_id', cors(), this.instantiate.bind(this));
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.instantiate = function(req, res) {
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_post');
|
||||
}
|
||||
|
||||
this.instantiateTemplate(req, res, function prepareTemplateParams(callback) {
|
||||
if (!req.is('application/json')) {
|
||||
return callback(new Error('Template POST data must be of type application/json'));
|
||||
}
|
||||
return callback(null, req.body);
|
||||
});
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.jsonp = function(req, res) {
|
||||
if (req.profiler) {
|
||||
req.profiler.start('windshaft-cartodb.instance_template_get');
|
||||
}
|
||||
|
||||
this.instantiateTemplate(req, res, function prepareJsonTemplateParams(callback) {
|
||||
var err = null;
|
||||
if ( req.query.callback === undefined || req.query.callback.length === 0) {
|
||||
err = new Error('callback parameter should be present and be a function name');
|
||||
}
|
||||
|
||||
var templateParams = {};
|
||||
if (req.query.config) {
|
||||
try {
|
||||
templateParams = JSON.parse(req.query.config);
|
||||
} catch(e) {
|
||||
err = new Error('Invalid config parameter, should be a valid JSON');
|
||||
}
|
||||
}
|
||||
|
||||
return callback(err, templateParams);
|
||||
});
|
||||
};
|
||||
|
||||
NamedMapsController.prototype.tile = function(req, res) {
|
||||
@ -207,53 +164,6 @@ NamedMapsController.prototype.staticMap = function(req, res) {
|
||||
);
|
||||
};
|
||||
|
||||
|
||||
// Instantiate a template
|
||||
NamedMapsController.prototype.instantiateTemplate = function(req, res, prepareParamsFn) {
|
||||
var self = this;
|
||||
|
||||
var cdbuser = req.context.user;
|
||||
|
||||
var mapConfigProvider;
|
||||
|
||||
step(
|
||||
function getTemplateParams() {
|
||||
prepareParamsFn(this);
|
||||
},
|
||||
function getTemplate(err, templateParams) {
|
||||
assert.ifError(err);
|
||||
mapConfigProvider = new NamedMapMapConfigProvider(
|
||||
this.templateMaps,
|
||||
this.pgConnection,
|
||||
cdbuser,
|
||||
req.params.template_id,
|
||||
templateParams,
|
||||
req.query.auth_token,
|
||||
req.params
|
||||
);
|
||||
mapConfigProvider.getMapConfig(this);
|
||||
},
|
||||
function createLayergroup(err, mapConfig, rendererParams/*, context*/) {
|
||||
assert.ifError(err);
|
||||
self.mapBackend.createLayergroup(mapConfig, rendererParams, this);
|
||||
},
|
||||
function finishTemplateInstantiation(err, layergroup) {
|
||||
if (err) {
|
||||
var statusCode = this._app.findStatusCode(err);
|
||||
this.app.sendError(res, { errors: [ err.message ] }, statusCode, 'NAMED MAP LAYERGROUP', err);
|
||||
} else {
|
||||
var templateHash = self.templateMaps.fingerPrint(mapConfigProvider.template).substring(0, 8);
|
||||
layergroup.layergroupid = cdbuser + '@' + templateHash + '@' + layergroup.layergroupid;
|
||||
|
||||
res.header('X-Layergroup-Id', layergroup.layergroupid);
|
||||
self.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(cdbuser, mapConfigProvider.getTemplateName()));
|
||||
|
||||
this.app.sendResponse(res, [layergroup, 200]);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
function getStaticImageOptions(template, callback) {
|
||||
if (template.view) {
|
||||
var zoomCenter = templateZoomCenter(template.view);
|
||||
|
@ -5,21 +5,20 @@ var templateName = require('../backends/template_maps').templateName;
|
||||
var cors = require('../middleware/cors');
|
||||
|
||||
|
||||
function NamedMapsAdminController(app, templateMaps, templateBaseUrl) {
|
||||
function NamedMapsAdminController(app, templateMaps) {
|
||||
this.app = app;
|
||||
this.templateMaps = templateMaps;
|
||||
this.templateBaseUrl = templateBaseUrl;
|
||||
}
|
||||
|
||||
module.exports = NamedMapsAdminController;
|
||||
|
||||
NamedMapsAdminController.prototype.register = function(app) {
|
||||
app.post(this.templateBaseUrl, cors(), this.create.bind(this));
|
||||
app.put(this.templateBaseUrl + '/:template_id', cors(), this.update.bind(this));
|
||||
app.get(this.templateBaseUrl + '/:template_id', cors(), this.retrieve.bind(this));
|
||||
app.del(this.templateBaseUrl + '/:template_id', cors(), this.destroy.bind(this));
|
||||
app.get(this.templateBaseUrl, cors(), this.list.bind(this));
|
||||
app.options(this.templateBaseUrl + '/:template_id', cors('Content-Type'));
|
||||
app.post(app.base_url_templated, cors(), this.create.bind(this));
|
||||
app.put(app.base_url_templated + '/:template_id', cors(), this.update.bind(this));
|
||||
app.get(app.base_url_templated + '/:template_id', cors(), this.retrieve.bind(this));
|
||||
app.del(app.base_url_templated + '/:template_id', cors(), this.destroy.bind(this));
|
||||
app.get(app.base_url_templated, cors(), this.list.bind(this));
|
||||
app.options(app.base_url_templated + '/:template_id', cors('Content-Type'));
|
||||
};
|
||||
|
||||
NamedMapsAdminController.prototype.create = function(req, res) {
|
||||
|
@ -195,90 +195,6 @@ module.exports = function(serverOptions) {
|
||||
return statusCode;
|
||||
};
|
||||
|
||||
// var layergroupRequestDecorator = {
|
||||
// afterLayergroupCreate: function(req, mapconfig, layergroup, callback) {
|
||||
// var token = layergroup.layergroupid;
|
||||
//
|
||||
// var username = req.context.user;
|
||||
//
|
||||
// var tasksleft = 2; // redis key and affectedTables
|
||||
// var errors = [];
|
||||
//
|
||||
// var done = function(err) {
|
||||
// if ( err ) {
|
||||
// errors.push('' + err);
|
||||
// }
|
||||
// if ( ! --tasksleft ) {
|
||||
// err = errors.length ? new Error(errors.join('\n')) : null;
|
||||
// callback(err);
|
||||
// }
|
||||
// };
|
||||
//
|
||||
// // include in layergroup response the variables in serverMedata
|
||||
// // those variables are useful to send to the client information
|
||||
// // about how to reach this server or information about it
|
||||
// var serverMetadata = global.environment.serverMetadata;
|
||||
// if (serverMetadata) {
|
||||
// _.extend(layergroup, serverMetadata);
|
||||
// }
|
||||
//
|
||||
// // Don't wait for the mapview count increment to
|
||||
// // take place before proceeding. Error will be logged
|
||||
// // asyncronously
|
||||
// metadataBackend.incMapviewCount(username, mapconfig.stat_tag, function(err) {
|
||||
// if (req.profiler) {
|
||||
// req.profiler.done('incMapviewCount');
|
||||
// }
|
||||
// if ( err ) {
|
||||
// console.log("ERROR: failed to increment mapview count for user '" + username + "': " + err);
|
||||
// }
|
||||
// done();
|
||||
// });
|
||||
//
|
||||
// var sql = mapconfig.layers.map(function(layer) {
|
||||
// return layer.options.sql;
|
||||
// }).join(';');
|
||||
//
|
||||
// var dbName = req.params.dbname;
|
||||
// var cacheKey = dbName + ':' + token;
|
||||
//
|
||||
// step(
|
||||
// function getAffectedTablesAndLastUpdatedTime() {
|
||||
// queryTablesApi.getAffectedTablesAndLastUpdatedTime(username, sql, this);
|
||||
// },
|
||||
// function handleAffectedTablesAndLastUpdatedTime(err, result) {
|
||||
// if (req.profiler) {
|
||||
// req.profiler.done('queryTablesAndLastUpdated');
|
||||
// }
|
||||
// assert.ifError(err);
|
||||
// var cacheChannel = app.buildCacheChannel(dbName, result.affectedTables);
|
||||
// app.channelCache[cacheKey] = cacheChannel;
|
||||
//
|
||||
// // last update for layergroup cache buster
|
||||
// layergroup.layergroupid = layergroup.layergroupid + ':' + result.lastUpdatedTime;
|
||||
// layergroup.last_updated = new Date(result.lastUpdatedTime).toISOString();
|
||||
//
|
||||
// var res = req.res;
|
||||
// if (res) {
|
||||
// if (req.method === 'GET') {
|
||||
// var ttl = global.environment.varnish.layergroupTtl || 86400;
|
||||
// res.header('Cache-Control', 'public,max-age='+ttl+',must-revalidate');
|
||||
// res.header('Last-Modified', (new Date()).toUTCString());
|
||||
// res.header('X-Cache-Channel', cacheChannel);
|
||||
// }
|
||||
//
|
||||
// res.header('X-Layergroup-Id', layergroup.layergroupid);
|
||||
// }
|
||||
//
|
||||
// return null;
|
||||
// },
|
||||
// function finish(err) {
|
||||
// done(err);
|
||||
// }
|
||||
// );
|
||||
// }
|
||||
// };
|
||||
|
||||
var TablesExtentApi = require('./api/tables_extent_api');
|
||||
var tablesExtentApi = new TablesExtentApi(pgQueryRunner);
|
||||
|
||||
@ -291,32 +207,35 @@ module.exports = function(serverOptions) {
|
||||
next();
|
||||
});
|
||||
|
||||
new controller.Map(
|
||||
new controller.Layergroup(
|
||||
app,
|
||||
pgConnection,
|
||||
mapStore,
|
||||
templateMaps,
|
||||
mapBackend,
|
||||
tileBackend,
|
||||
previewBackend,
|
||||
attributesBackend
|
||||
).register(app);
|
||||
|
||||
new controller.Map(
|
||||
app,
|
||||
pgConnection,
|
||||
templateMaps,
|
||||
mapBackend,
|
||||
metadataBackend,
|
||||
queryTablesApi,
|
||||
surrogateKeysCache
|
||||
).register(app);
|
||||
|
||||
new controller.NamedMaps(
|
||||
app,
|
||||
pgConnection,
|
||||
mapStore,
|
||||
templateMaps,
|
||||
metadataBackend,
|
||||
mapBackend,
|
||||
tileBackend,
|
||||
previewBackend,
|
||||
template_baseurl,
|
||||
surrogateKeysCache,
|
||||
tablesExtentApi
|
||||
).register(app);
|
||||
|
||||
new controller.NamedMapsAdmin(app, templateMaps, template_baseurl).register(app);
|
||||
new controller.NamedMapsAdmin(app, templateMaps).register(app);
|
||||
|
||||
new controller.ServerInfo().register(app);
|
||||
|
||||
@ -857,8 +776,8 @@ module.exports = function(serverOptions) {
|
||||
};
|
||||
|
||||
function validateOptions(opts) {
|
||||
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig)) {
|
||||
throw new Error("Must initialise Windshaft with: 'base_url'/'base_url_mapconfig' URLs");
|
||||
if (!_.isString(opts.base_url) || !_.isString(opts.base_url_mapconfig) || !_.isString(opts.base_url_templated)) {
|
||||
throw new Error("Must initialise server with: 'base_url'/'base_url_mapconfig'/'base_url_templated' URLs");
|
||||
}
|
||||
|
||||
// Be nice and warn if configured mapnik version is != instaled mapnik version
|
||||
|
@ -42,6 +42,8 @@ module.exports = {
|
||||
//
|
||||
base_url_mapconfig: global.environment.base_url_detached || '(?:/maps|/tiles/layergroup)',
|
||||
|
||||
base_url_templated: global.environment.base_url_templated || '(?:/maps/named|/tiles/template)',
|
||||
|
||||
grainstore: {
|
||||
map: {
|
||||
// TODO: allow to specify in configuration
|
||||
|
@ -5,7 +5,7 @@ var cartodbServer = require('../../../../lib/cartodb/server');
|
||||
var serverOptions = require('../../../../lib/cartodb/server_options');
|
||||
var StatsClient = require('windshaft').stats.Client;
|
||||
|
||||
var MapController = require('../../../../lib/cartodb/controllers/map');
|
||||
var LayergroupController = require('../../../../lib/cartodb/controllers/layergroup');
|
||||
|
||||
describe('tile stats', function() {
|
||||
|
||||
@ -31,14 +31,14 @@ describe('tile stats', function() {
|
||||
var ws = cartodbServer(serverOptions);
|
||||
ws.sendError = function(){};
|
||||
|
||||
var mapController = new MapController(ws, null);
|
||||
var layergroupController = new LayergroupController(ws, null);
|
||||
|
||||
var reqMock = {
|
||||
params: {
|
||||
format: invalidFormat
|
||||
}
|
||||
};
|
||||
mapController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, {}, null, null);
|
||||
layergroupController.finalizeGetTileOrGrid('Unsupported format png2', reqMock, {}, null, null);
|
||||
|
||||
assert.ok(formatMatched, 'Format was never matched in increment method');
|
||||
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
|
||||
@ -64,9 +64,9 @@ describe('tile stats', function() {
|
||||
var ws = cartodbServer(serverOptions);
|
||||
ws.sendError = function(){};
|
||||
|
||||
var mapController = new MapController(ws, null);
|
||||
var layergroupController = new LayergroupController(ws, null);
|
||||
|
||||
mapController.finalizeGetTileOrGrid('Another error happened', reqMock, {}, null, null);
|
||||
layergroupController.finalizeGetTileOrGrid('Another error happened', reqMock, {}, null, null);
|
||||
|
||||
assert.ok(formatMatched, 'Format was never matched in increment method');
|
||||
assert.equal(expectedCalls, 0, 'Unexpected number of calls to increment method');
|
||||
|
Loading…
Reference in New Issue
Block a user