Windshaft-cartodb/lib/models/mapconfig/adapter/aggregation-mapconfig-adapter.js

189 lines
6.2 KiB
JavaScript
Raw Normal View History

'use strict';
2017-12-19 02:05:49 +08:00
const AggregationMapConfig = require('../../aggregation/aggregation-mapconfig');
const queryUtils = require('../../../utils/query-utils');
const unsupportedGeometryTypeErrorMessage = ctx =>
2017-12-19 01:53:44 +08:00
`Unsupported geometry type: ${ctx.geometryType}. ` +
`Aggregation is available only for geometry type: ${AggregationMapConfig.SUPPORTED_GEOMETRY_TYPES}`;
2017-12-13 02:23:21 +08:00
const invalidAggregationParamValueErrorMessage = ctx =>
`Invalid value for 'aggregation' query param: ${ctx.value}. Valid ones are 'true' or 'false'`;
module.exports = class AggregationMapConfigAdapter {
constructor (pgConnection) {
this.pgConnection = pgConnection;
}
getMapConfig (user, requestMapConfig, params, context, callback) {
2017-12-19 17:50:53 +08:00
if (!this._isValidAggregationQueryParam(params)) {
2017-12-13 02:23:21 +08:00
return callback(new Error(invalidAggregationParamValueErrorMessage({ value: params.aggregation })));
}
let mapConfig;
try {
mapConfig = new AggregationMapConfig(user, requestMapConfig, this.pgConnection);
} catch (err) {
return callback(err);
}
if (!this._shouldAdapt(mapConfig, params)) {
return callback(null, requestMapConfig);
}
this.pgConnection.getConnection(user, (err, connection) => {
if (err) {
return callback(err);
}
this._adaptLayers(connection, mapConfig, requestMapConfig, context, callback);
});
}
2017-12-19 17:50:53 +08:00
_isValidAggregationQueryParam (params) {
2017-12-13 02:23:21 +08:00
const { aggregation } = params;
return aggregation === undefined || aggregation === 'true' || aggregation === 'false';
}
_shouldAdapt (mapConfig, params) {
const { aggregation } = params;
2017-12-02 00:06:42 +08:00
if (aggregation === 'false') {
2017-12-12 17:57:50 +08:00
return false;
}
2017-12-13 02:23:21 +08:00
if (aggregation === 'true' || mapConfig.isAggregationMapConfig()) {
2017-12-12 17:57:50 +08:00
return true;
}
return false;
}
_adaptLayers (connection, mapConfig, requestMapConfig, context, callback) {
const adaptLayerPromises = requestMapConfig.layers.map((layer, index) => {
return this._adaptLayer(connection, mapConfig, layer, index);
2017-12-01 22:43:15 +08:00
});
Promise.all(adaptLayerPromises)
.then(results => {
context.aggregation = {
layers: []
};
results.forEach(({ layer, index, adapted }) => {
if (adapted) {
requestMapConfig.layers[index] = layer;
}
2018-05-12 01:57:49 +08:00
const aggregatedFormats = this._getAggregationMetadata(mapConfig, layer, adapted);
context.aggregation.layers.push(aggregatedFormats);
});
2017-12-12 02:35:59 +08:00
callback(null, requestMapConfig);
})
.catch(err => callback(err));
2017-12-01 22:43:15 +08:00
}
_adaptLayer (connection, mapConfig, layer, index) {
2017-12-12 01:34:22 +08:00
return new Promise((resolve, reject) => {
this._shouldAdaptLayer(connection, mapConfig, layer, index, (err, shouldAdapt) => {
2017-12-12 01:34:22 +08:00
if (err) {
return reject(err);
2017-12-12 01:34:22 +08:00
}
if (!shouldAdapt) {
return resolve({ layer, index, adapted: shouldAdapt });
}
const sqlQueryWrap = layer.options.sql_wrap;
2017-12-12 01:34:22 +08:00
2018-10-04 05:13:22 +08:00
let aggregationSql;
try {
aggregationSql = mapConfig.getAggregatedQuery(index);
}
catch (error) {
return reject(error);
}
if (sqlQueryWrap) {
aggregationSql = sqlQueryWrap.replace(/<%=\s*sql\s*%>/g, aggregationSql);
}
if (!layer.options.sql_raw) {
// if sql_wrap is present, the original query should already be
// in sql_raw (with sql being the wrapped query);
// otherwise we keep the now the original query in sql_raw
layer.options.sql_raw = layer.options.sql;
}
layer.options.sql = aggregationSql;
mapConfig.getLayerAggregationColumns(index, (err, columns) => {
if (err) {
return reject(err);
}
2017-12-12 01:34:22 +08:00
layer.options.columns = columns;
return resolve({ layer, index, adapted: shouldAdapt });
});
});
});
}
2017-12-12 01:34:22 +08:00
_shouldAdaptLayer (connection, mapConfig, layer, index, callback) {
if (!mapConfig.isAggregationLayer(index)) {
2017-12-19 01:55:32 +08:00
return callback(null, false);
}
2017-12-12 01:34:22 +08:00
const aggregationMetadata = queryUtils.getAggregationMetadata({
query: layer.options.sql_raw ? layer.options.sql_raw : layer.options.sql,
geometryColumn: AggregationMapConfig.getAggregationGeometryColumn()
});
2017-12-12 01:34:22 +08:00
connection.query(aggregationMetadata, (err, res) => {
if (err) {
2017-12-19 01:55:32 +08:00
return callback(null, false);
}
2017-12-12 01:34:22 +08:00
2017-12-12 19:53:29 +08:00
const result = res.rows[0] || {};
if (!mapConfig.isVectorOnlyMapConfig() && !AggregationMapConfig.supportsGeometryType(result.type)) {
2017-12-19 17:54:20 +08:00
const message = unsupportedGeometryTypeErrorMessage({ geometryType: result.type });
const error = new Error(message);
2017-12-19 02:35:12 +08:00
error.type = 'layer';
error.layer = {
id: mapConfig.getLayerId(index),
index: index,
type: mapConfig.layerType(index)
};
return callback(error);
}
if (mapConfig.isVectorOnlyMapConfig() && !AggregationMapConfig.supportsGeometryType(result.type)) {
return callback(null, false);
}
if (!mapConfig.doesLayerReachThreshold(index, result.count)) {
return callback(null, false);
}
callback(null, true);
2017-12-12 01:34:22 +08:00
});
}
_getAggregationMetadata (mapConfig, layer, adapted) {
// also: pre-aggr query, columns, ...
if (!adapted) {
return { png: false, mvt: false };
}
if (mapConfig.isVectorOnlyMapConfig()) {
return { png: false, mvt: true };
}
return { png: true, mvt: true };
}
};