165 lines
5.5 KiB
JavaScript
165 lines
5.5 KiB
JavaScript
'use strict';
|
|
|
|
var dot = require('dot');
|
|
dot.templateSettings.strip = false;
|
|
var queue = require('queue-async');
|
|
var PSQL = require('cartodb-psql');
|
|
var turboCarto = require('turbo-carto');
|
|
|
|
const SubstitutionTokens = require('cartodb-query-tables').utils.substitutionTokens;
|
|
var PostgresDatasource = require('../../../backends/turbo-carto-postgres-datasource');
|
|
|
|
var MapConfig = require('windshaft').model.MapConfig;
|
|
|
|
const dbParamsFromReqParams = require('../../../utils/database-params');
|
|
|
|
function TurboCartoAdapter () {
|
|
}
|
|
|
|
module.exports = TurboCartoAdapter;
|
|
|
|
TurboCartoAdapter.prototype.getMapConfig = function (user, requestMapConfig, params, context, callback) {
|
|
var self = this;
|
|
|
|
var layers = requestMapConfig.layers;
|
|
|
|
if (!layers || layers.length === 0) {
|
|
return callback(null, requestMapConfig);
|
|
}
|
|
|
|
var parseCartoQueue = queue(layers.length);
|
|
|
|
layers.forEach(function (layer, index) {
|
|
var layerId = MapConfig.getLayerId(requestMapConfig, index);
|
|
parseCartoQueue.defer(self._parseCartoCss.bind(self), user, params, layer, index, layerId);
|
|
});
|
|
|
|
parseCartoQueue.awaitAll(function (err, results) {
|
|
if (err) {
|
|
return callback(err);
|
|
}
|
|
|
|
var errors = results.reduce(function (errors, result) {
|
|
if (result.error) {
|
|
errors.push(result.error);
|
|
}
|
|
return errors;
|
|
}, []);
|
|
if (errors.length > 0) {
|
|
return callback(errors);
|
|
}
|
|
|
|
requestMapConfig.layers = results.map(function (result) { return result.layer; });
|
|
context.turboCarto = {
|
|
layers: results.map(function (result) {
|
|
return result.meta;
|
|
})
|
|
};
|
|
|
|
return callback(null, requestMapConfig);
|
|
});
|
|
};
|
|
|
|
var tokensQueryTpl = dot.template([
|
|
'WITH input_query AS (',
|
|
' {{=it._sql}}',
|
|
'),',
|
|
'bbox_query AS (',
|
|
' SELECT ST_SetSRID(ST_Extent(the_geom_webmercator), 3857) as bbox from input_query',
|
|
'),',
|
|
'zoom_query as (',
|
|
' SELECT GREATEST(',
|
|
' ceil(log(40075017000 / 256 / GREATEST(ST_XMax(bbox) - ST_XMin(bbox), ST_YMax(bbox) - ST_YMin(bbox)))/log(2)),',
|
|
' 0) as zoom',
|
|
' FROM bbox_query',
|
|
'),',
|
|
'pixel_size_query as (',
|
|
' SELECT 40075017 * cos(radians(ST_Y(ST_Transform(ST_Centroid(bbox), 4326)))) / 2 ^ ((zoom) + 8) as pixel_size',
|
|
' FROM bbox_query, zoom_query',
|
|
'),',
|
|
'scale_denominator_query as (',
|
|
' SELECT (pixel_size / 0.00028)::numeric as scale_denominator',
|
|
' FROM pixel_size_query',
|
|
')',
|
|
'select ST_AsText(bbox) bbox, pixel_size, scale_denominator, zoom',
|
|
'from bbox_query, pixel_size_query, scale_denominator_query, zoom_query'
|
|
].join('\n'));
|
|
|
|
TurboCartoAdapter.prototype._parseCartoCss = function (username, params, layer, layerIndex, layerId, callback) {
|
|
if (!shouldParseLayerCartocss(layer)) {
|
|
return callback(null, { layer: layer });
|
|
}
|
|
|
|
var pg = new PSQL(dbParamsFromReqParams(params));
|
|
function processCallback (err, cartocss, meta) {
|
|
// Only return turbo-carto errors
|
|
if (err && err.name === 'TurboCartoError') {
|
|
var error = new Error(err.message);
|
|
error.http_status = 400;
|
|
error.type = 'layer';
|
|
error.subtype = 'turbo-carto';
|
|
error.layer = {
|
|
id: layerId,
|
|
index: layerIndex,
|
|
type: layer.type,
|
|
context: err.context
|
|
};
|
|
|
|
return callback(null, { error: error });
|
|
}
|
|
|
|
// Try to continue in the rest of the cases
|
|
if (cartocss) {
|
|
layer.options.cartocss = cartocss;
|
|
}
|
|
return callback(null, { layer: layer, meta: meta });
|
|
}
|
|
|
|
var layerSql = layer.options.sql;
|
|
var layerRawSql = layer.options.sql_raw;
|
|
if (SubstitutionTokens.hasTokens(layerSql) && layerRawSql && layer.options.sql_wrap) {
|
|
// For wrapped queries we'll derive the tokens from the data extent
|
|
// instead of the whole Earth/root tile.
|
|
var self = this;
|
|
var tokensQuery = tokensQueryTpl({ _sql: layerRawSql });
|
|
return pg.query(tokensQuery, function (err, resultSet) {
|
|
if (err) {
|
|
return processCallback(err);
|
|
}
|
|
|
|
resultSet = resultSet || {};
|
|
var rows = resultSet.rows || [];
|
|
var result = rows[0] || {};
|
|
|
|
var tokens = {
|
|
bbox: 'ST_SetSRID(ST_GeomFromText(\'' + result.bbox + '\'), 3857)',
|
|
scale_denominator: result.scale_denominator,
|
|
pixel_width: result.pixel_size,
|
|
pixel_height: result.pixel_size
|
|
};
|
|
|
|
var sql = SubstitutionTokens.replace(layerSql, tokens);
|
|
self.process(pg, layer.options.cartocss, sql, processCallback);
|
|
}, true); // use read-only transaction
|
|
}
|
|
|
|
var tokens = {
|
|
bbox: 'ST_MakeEnvelope(-20037508.34,-20037508.34,20037508.34,20037508.34,3857)',
|
|
scale_denominator: '500000001',
|
|
pixel_width: '156412',
|
|
pixel_height: '156412'
|
|
};
|
|
|
|
var sql = SubstitutionTokens.replace(layerSql, tokens);
|
|
this.process(pg, layer.options.cartocss, sql, processCallback);
|
|
};
|
|
|
|
TurboCartoAdapter.prototype.process = function (psql, cartocss, sql, callback) {
|
|
var datasource = new PostgresDatasource(psql, sql);
|
|
turboCarto(cartocss, datasource, callback);
|
|
};
|
|
|
|
function shouldParseLayerCartocss (layer) {
|
|
return layer && layer.options && layer.options.cartocss && layer.options.sql;
|
|
}
|