2015-03-16 07:21:55 +08:00
|
|
|
var step = require('step');
|
2015-04-23 17:29:55 +08:00
|
|
|
var assert = require('assert');
|
2015-01-20 23:56:06 +08:00
|
|
|
var _ = require('underscore');
|
2015-04-27 20:52:36 +08:00
|
|
|
var NamedMapsCacheEntry = require('../cache/model/named_maps_entry');
|
2015-09-16 22:18:26 +08:00
|
|
|
|
|
|
|
var util = require('util');
|
|
|
|
var BaseController = require('./base');
|
|
|
|
|
2015-07-08 19:27:56 +08:00
|
|
|
var cors = require('../middleware/cors');
|
2015-09-30 23:17:01 +08:00
|
|
|
var userMiddleware = require('../middleware/user');
|
2017-03-31 02:12:55 +08:00
|
|
|
var allowQueryParams = require('../middleware/allow-query-params');
|
2015-01-20 23:56:06 +08:00
|
|
|
|
2015-10-01 00:00:54 +08:00
|
|
|
function NamedMapsController(authApi, pgConnection, namedMapProviderCache, tileBackend, previewBackend,
|
2016-09-01 22:31:30 +08:00
|
|
|
surrogateKeysCache, tablesExtentApi, metadataBackend) {
|
2015-09-16 22:18:26 +08:00
|
|
|
BaseController.call(this, authApi, pgConnection);
|
|
|
|
|
2015-07-15 02:53:06 +08:00
|
|
|
this.namedMapProviderCache = namedMapProviderCache;
|
2015-07-09 19:37:00 +08:00
|
|
|
this.tileBackend = tileBackend;
|
|
|
|
this.previewBackend = previewBackend;
|
2015-01-23 23:37:38 +08:00
|
|
|
this.surrogateKeysCache = surrogateKeysCache;
|
2015-07-08 21:50:59 +08:00
|
|
|
this.tablesExtentApi = tablesExtentApi;
|
2015-09-29 18:21:11 +08:00
|
|
|
this.metadataBackend = metadataBackend;
|
2015-01-20 23:56:06 +08:00
|
|
|
}
|
|
|
|
|
2015-09-16 22:18:26 +08:00
|
|
|
util.inherits(NamedMapsController, BaseController);
|
|
|
|
|
2015-04-27 21:01:49 +08:00
|
|
|
module.exports = NamedMapsController;
|
2015-01-20 23:56:06 +08:00
|
|
|
|
2015-04-27 21:01:49 +08:00
|
|
|
NamedMapsController.prototype.register = function(app) {
|
2015-09-30 23:17:01 +08:00
|
|
|
app.get(app.base_url_templated +
|
|
|
|
'/:template_id/:layer/:z/:x/:y.(:format)', cors(), userMiddleware,
|
|
|
|
this.tile.bind(this));
|
|
|
|
|
|
|
|
app.get(app.base_url_mapconfig +
|
2017-03-31 02:12:55 +08:00
|
|
|
'/static/named/:template_id/:width/:height.:format', cors(), userMiddleware, allowQueryParams(['layer']),
|
2015-09-30 23:17:01 +08:00
|
|
|
this.staticMap.bind(this));
|
2015-07-09 19:37:00 +08:00
|
|
|
};
|
|
|
|
|
2015-07-14 23:34:05 +08:00
|
|
|
NamedMapsController.prototype.sendResponse = function(req, res, resource, headers, namedMapProvider) {
|
|
|
|
this.surrogateKeysCache.tag(res, new NamedMapsCacheEntry(req.context.user, namedMapProvider.getTemplateName()));
|
2015-09-17 08:03:09 +08:00
|
|
|
res.set('Content-Type', headers['content-type'] || headers['Content-Type'] || 'image/png');
|
|
|
|
res.set('Cache-Control', 'public,max-age=7200,must-revalidate');
|
2015-07-15 02:10:55 +08:00
|
|
|
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
step(
|
|
|
|
function getAffectedTablesAndLastUpdatedTime() {
|
2015-07-15 02:53:06 +08:00
|
|
|
namedMapProvider.getAffectedTablesAndLastUpdatedTime(this);
|
2015-07-15 02:10:55 +08:00
|
|
|
},
|
|
|
|
function sendResponse(err, result) {
|
|
|
|
req.profiler.done('affectedTables');
|
|
|
|
if (err) {
|
2015-09-18 23:12:53 +08:00
|
|
|
global.logger.log('ERROR generating cache channel: ' + err);
|
2015-07-15 02:10:55 +08:00
|
|
|
}
|
2016-02-22 18:40:25 +08:00
|
|
|
if (!result || !!result.tables) {
|
2015-07-15 02:10:55 +08:00
|
|
|
// we increase cache control as we can invalidate it
|
2015-09-17 08:03:09 +08:00
|
|
|
res.set('Cache-Control', 'public,max-age=31536000');
|
2015-07-15 02:10:55 +08:00
|
|
|
|
|
|
|
var lastModifiedDate;
|
|
|
|
if (Number.isFinite(result.lastUpdatedTime)) {
|
2016-02-22 18:40:25 +08:00
|
|
|
lastModifiedDate = new Date(result.getLastUpdatedAt());
|
2015-07-15 02:10:55 +08:00
|
|
|
} else {
|
|
|
|
lastModifiedDate = new Date();
|
|
|
|
}
|
2015-09-17 08:03:09 +08:00
|
|
|
res.set('Last-Modified', lastModifiedDate.toUTCString());
|
2015-07-15 02:10:55 +08:00
|
|
|
|
2016-02-22 18:40:25 +08:00
|
|
|
res.set('X-Cache-Channel', result.getCacheChannel());
|
|
|
|
if (result.tables.length > 0) {
|
|
|
|
self.surrogateKeysCache.tag(res, result);
|
2015-07-15 02:53:26 +08:00
|
|
|
}
|
2015-07-15 02:10:55 +08:00
|
|
|
}
|
2015-09-17 08:05:25 +08:00
|
|
|
self.send(req, res, resource, 200);
|
2015-07-15 02:10:55 +08:00
|
|
|
}
|
|
|
|
);
|
2015-07-14 23:34:05 +08:00
|
|
|
};
|
|
|
|
|
2015-06-29 22:39:35 +08:00
|
|
|
NamedMapsController.prototype.tile = function(req, res) {
|
2015-06-30 21:41:57 +08:00
|
|
|
var self = this;
|
|
|
|
|
2015-07-08 21:34:46 +08:00
|
|
|
var cdbUser = req.context.user;
|
2015-07-02 08:02:22 +08:00
|
|
|
|
2015-07-10 00:47:21 +08:00
|
|
|
var namedMapProvider;
|
2015-06-30 21:41:57 +08:00
|
|
|
step(
|
|
|
|
function reqParams() {
|
2015-09-16 22:18:26 +08:00
|
|
|
self.req2params(req, this);
|
2015-06-30 21:41:57 +08:00
|
|
|
},
|
2015-09-23 20:32:26 +08:00
|
|
|
function getNamedMapProvider(err) {
|
|
|
|
assert.ifError(err);
|
|
|
|
self.namedMapProviderCache.get(
|
2015-07-10 00:47:21 +08:00
|
|
|
cdbUser,
|
|
|
|
req.params.template_id,
|
|
|
|
req.query.config,
|
|
|
|
req.query.auth_token,
|
2015-09-23 20:32:26 +08:00
|
|
|
req.params,
|
|
|
|
this
|
2015-07-10 00:47:21 +08:00
|
|
|
);
|
2015-09-23 20:32:26 +08:00
|
|
|
},
|
|
|
|
function getTile(err, _namedMapProvider) {
|
|
|
|
assert.ifError(err);
|
|
|
|
namedMapProvider = _namedMapProvider;
|
2015-07-10 00:47:21 +08:00
|
|
|
self.tileBackend.getTile(namedMapProvider, req.params, this);
|
2015-06-30 21:41:57 +08:00
|
|
|
},
|
2015-07-02 08:02:22 +08:00
|
|
|
function handleImage(err, tile, headers, stats) {
|
2017-03-31 02:31:53 +08:00
|
|
|
req.profiler.add(stats);
|
2015-06-30 21:41:57 +08:00
|
|
|
if (err) {
|
2015-09-17 03:54:56 +08:00
|
|
|
self.sendError(req, res, err, 'NAMED_MAP_TILE');
|
2015-06-30 21:41:57 +08:00
|
|
|
} else {
|
2015-07-14 23:34:05 +08:00
|
|
|
self.sendResponse(req, res, tile, headers, namedMapProvider);
|
2015-06-30 21:41:57 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
2015-06-29 22:39:35 +08:00
|
|
|
};
|
|
|
|
|
2015-07-08 21:50:59 +08:00
|
|
|
NamedMapsController.prototype.staticMap = function(req, res) {
|
|
|
|
var self = this;
|
|
|
|
|
|
|
|
var cdbUser = req.context.user;
|
|
|
|
|
|
|
|
var format = req.params.format === 'jpg' ? 'jpeg' : 'png';
|
2015-07-10 00:47:21 +08:00
|
|
|
req.params.format = 'png';
|
|
|
|
req.params.layer = 'all';
|
2015-07-08 21:50:59 +08:00
|
|
|
|
2015-07-10 00:47:21 +08:00
|
|
|
var namedMapProvider;
|
2015-07-08 21:50:59 +08:00
|
|
|
step(
|
|
|
|
function reqParams() {
|
2015-09-16 22:18:26 +08:00
|
|
|
self.req2params(req, this);
|
2015-07-08 21:50:59 +08:00
|
|
|
},
|
2015-09-23 20:32:26 +08:00
|
|
|
function getNamedMapProvider(err) {
|
2015-07-08 21:50:59 +08:00
|
|
|
assert.ifError(err);
|
2015-09-23 20:32:26 +08:00
|
|
|
self.namedMapProviderCache.get(
|
2015-07-10 00:47:21 +08:00
|
|
|
cdbUser,
|
|
|
|
req.params.template_id,
|
|
|
|
req.query.config,
|
|
|
|
req.query.auth_token,
|
2015-09-23 20:32:26 +08:00
|
|
|
req.params,
|
|
|
|
this
|
2015-07-10 00:47:21 +08:00
|
|
|
);
|
2015-09-23 20:32:26 +08:00
|
|
|
},
|
2016-09-01 22:31:30 +08:00
|
|
|
function prepareLayerVisibility(err, _namedMapProvider) {
|
2016-09-01 01:40:00 +08:00
|
|
|
assert.ifError(err);
|
2016-09-01 22:31:30 +08:00
|
|
|
|
2016-09-01 18:37:04 +08:00
|
|
|
namedMapProvider = _namedMapProvider;
|
2016-09-01 22:31:30 +08:00
|
|
|
|
2016-09-01 22:42:06 +08:00
|
|
|
self.prepareLayerFilterFromPreviewLayers(cdbUser, req, namedMapProvider, this);
|
2016-09-01 22:31:30 +08:00
|
|
|
},
|
|
|
|
function prepareImageOptions(err) {
|
|
|
|
assert.ifError(err);
|
2016-05-10 03:13:13 +08:00
|
|
|
self.getStaticImageOptions(cdbUser, req.params, namedMapProvider, this);
|
2015-07-08 21:50:59 +08:00
|
|
|
},
|
|
|
|
function getImage(err, imageOpts) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var width = +req.params.width;
|
|
|
|
var height = +req.params.height;
|
|
|
|
|
|
|
|
if (!_.isUndefined(imageOpts.zoom) && imageOpts.center) {
|
2015-07-09 19:37:00 +08:00
|
|
|
self.previewBackend.getImage(
|
2015-07-10 00:47:21 +08:00
|
|
|
namedMapProvider, format, width, height, imageOpts.zoom, imageOpts.center, this);
|
2015-07-08 21:50:59 +08:00
|
|
|
} else {
|
2015-07-09 19:37:00 +08:00
|
|
|
self.previewBackend.getImage(
|
2015-07-10 00:47:21 +08:00
|
|
|
namedMapProvider, format, width, height, imageOpts.bounds, this);
|
2015-07-08 21:50:59 +08:00
|
|
|
}
|
|
|
|
},
|
2015-09-29 18:21:11 +08:00
|
|
|
function incrementMapViews(err, image, headers, stats) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
var next = this;
|
|
|
|
namedMapProvider.getMapConfig(function(mapConfigErr, mapConfig) {
|
|
|
|
self.metadataBackend.incMapviewCount(cdbUser, mapConfig.obj().stat_tag, function(sErr) {
|
|
|
|
if (err) {
|
|
|
|
global.logger.log("ERROR: failed to increment mapview count for user '%s': %s", cdbUser, sErr);
|
|
|
|
}
|
|
|
|
next(err, image, headers, stats);
|
|
|
|
});
|
|
|
|
});
|
|
|
|
},
|
2015-07-08 21:50:59 +08:00
|
|
|
function handleImage(err, image, headers, stats) {
|
2017-03-31 02:31:53 +08:00
|
|
|
req.profiler.done('render-' + format);
|
|
|
|
req.profiler.add(stats || {});
|
2015-07-08 21:50:59 +08:00
|
|
|
|
|
|
|
if (err) {
|
2015-09-17 03:54:56 +08:00
|
|
|
self.sendError(req, res, err, 'STATIC_VIZ_MAP');
|
2015-07-08 21:50:59 +08:00
|
|
|
} else {
|
2015-07-14 23:34:05 +08:00
|
|
|
self.sendResponse(req, res, image, headers, namedMapProvider);
|
2015-07-08 21:50:59 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2016-09-01 22:53:10 +08:00
|
|
|
NamedMapsController.prototype.prepareLayerFilterFromPreviewLayers = function (user, req, namedMapProvider, callback) {
|
2016-09-01 22:42:06 +08:00
|
|
|
var self = this;
|
2016-09-01 22:31:30 +08:00
|
|
|
namedMapProvider.getTemplate(function (err, template) {
|
|
|
|
if (err) {
|
|
|
|
return callback(err);
|
|
|
|
}
|
2016-09-01 01:40:00 +08:00
|
|
|
|
2016-09-01 22:31:30 +08:00
|
|
|
if (!template || !template.view || !template.view.preview_layers) {
|
|
|
|
return callback();
|
|
|
|
}
|
2016-09-01 01:40:00 +08:00
|
|
|
|
2016-09-01 22:31:30 +08:00
|
|
|
var previewLayers = template.view.preview_layers;
|
|
|
|
var layerVisibilityFilter = [];
|
2016-09-01 01:40:00 +08:00
|
|
|
|
2016-09-01 22:31:30 +08:00
|
|
|
template.layergroup.layers.forEach(function (layer, index) {
|
|
|
|
if (previewLayers[''+index] !== false && previewLayers[layer.id] !== false) {
|
|
|
|
layerVisibilityFilter.push(''+index);
|
2016-09-01 18:37:04 +08:00
|
|
|
}
|
2016-09-01 22:31:30 +08:00
|
|
|
});
|
2016-09-01 01:40:00 +08:00
|
|
|
|
2016-09-01 22:42:06 +08:00
|
|
|
if (!layerVisibilityFilter.length) {
|
|
|
|
return callback();
|
2016-09-01 18:37:04 +08:00
|
|
|
}
|
2016-09-01 22:31:30 +08:00
|
|
|
|
2016-09-01 22:42:06 +08:00
|
|
|
// overwrites 'all' default filter
|
|
|
|
req.params.layer = layerVisibilityFilter.join(',');
|
|
|
|
|
|
|
|
// recreates the provider
|
|
|
|
self.namedMapProviderCache.get(
|
2016-09-01 22:53:10 +08:00
|
|
|
user,
|
2016-09-01 22:42:06 +08:00
|
|
|
req.params.template_id,
|
|
|
|
req.query.config,
|
|
|
|
req.query.auth_token,
|
|
|
|
req.params,
|
|
|
|
callback
|
|
|
|
);
|
2016-09-01 22:31:30 +08:00
|
|
|
});
|
2016-09-01 18:37:04 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
var DEFAULT_ZOOM_CENTER = {
|
|
|
|
zoom: 1,
|
|
|
|
center: {
|
|
|
|
lng: 0,
|
|
|
|
lat: 0
|
|
|
|
}
|
|
|
|
};
|
2016-09-01 01:40:00 +08:00
|
|
|
|
2016-09-01 18:37:04 +08:00
|
|
|
function numMapper(n) {
|
|
|
|
return +n;
|
2016-09-01 01:40:00 +08:00
|
|
|
}
|
|
|
|
|
2016-05-10 03:13:13 +08:00
|
|
|
NamedMapsController.prototype.getStaticImageOptions = function(cdbUser, params, namedMapProvider, callback) {
|
2015-09-23 19:04:46 +08:00
|
|
|
var self = this;
|
2015-07-08 21:50:59 +08:00
|
|
|
|
2016-05-10 03:13:13 +08:00
|
|
|
if ([params.zoom, params.lon, params.lat].map(numMapper).every(Number.isFinite)) {
|
|
|
|
return callback(null, {
|
|
|
|
zoom: params.zoom,
|
|
|
|
center: {
|
|
|
|
lng: params.lon,
|
|
|
|
lat: params.lat
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
|
|
|
|
if (params.bbox) {
|
|
|
|
var bbox = params.bbox.split(',').map(numMapper);
|
|
|
|
if (bbox.length === 4 && bbox.every(Number.isFinite)) {
|
|
|
|
return callback(null, {
|
|
|
|
bounds: {
|
|
|
|
west: bbox[0],
|
|
|
|
south: bbox[1],
|
|
|
|
east: bbox[2],
|
|
|
|
north: bbox[3]
|
|
|
|
}
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-09-23 19:04:46 +08:00
|
|
|
step(
|
|
|
|
function getTemplate() {
|
|
|
|
namedMapProvider.getTemplate(this);
|
|
|
|
},
|
|
|
|
function handleTemplateView(err, template) {
|
|
|
|
assert.ifError(err);
|
|
|
|
|
|
|
|
if (template.view) {
|
|
|
|
var zoomCenter = templateZoomCenter(template.view);
|
|
|
|
if (zoomCenter) {
|
2016-05-10 03:13:13 +08:00
|
|
|
if (Number.isFinite(+params.zoom)) {
|
|
|
|
zoomCenter.zoom = +params.zoom;
|
|
|
|
}
|
2015-09-23 19:04:46 +08:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2016-02-22 18:40:25 +08:00
|
|
|
var affectedTables = affectedTablesAndLastUpdate.tables || [];
|
2015-09-23 19:04:46 +08:00
|
|
|
|
|
|
|
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);
|
2015-07-08 21:50:59 +08:00
|
|
|
}
|
2015-09-23 19:04:46 +08:00
|
|
|
);
|
|
|
|
};
|
2015-07-08 21:50:59 +08:00
|
|
|
|
|
|
|
function templateZoomCenter(view) {
|
|
|
|
if (!_.isUndefined(view.zoom) && view.center) {
|
|
|
|
return {
|
|
|
|
zoom: view.zoom,
|
|
|
|
center: view.center
|
|
|
|
};
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
function templateBounds(view) {
|
|
|
|
if (view.bounds) {
|
|
|
|
var hasAllBounds = _.every(['west', 'south', 'east', 'north'], function(prop) {
|
2015-09-23 19:04:46 +08:00
|
|
|
return Number.isFinite(view.bounds[prop]);
|
2015-07-08 21:50:59 +08:00
|
|
|
});
|
|
|
|
if (hasAllBounds) {
|
|
|
|
return {
|
|
|
|
bounds: {
|
|
|
|
west: view.bounds.west,
|
|
|
|
south: view.bounds.south,
|
|
|
|
east: view.bounds.east,
|
|
|
|
north: view.bounds.north
|
|
|
|
}
|
|
|
|
};
|
|
|
|
} else {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false;
|
|
|
|
}
|