'use strict'; const AggregationMapConfig = require('../../aggregation/aggregation-mapconfig'); const queryUtils = require('../../../utils/query-utils'); const unsupportedGeometryTypeErrorMessage = ctx => `Unsupported geometry type: ${ctx.geometryType}. ` + `Aggregation is available only for geometry type: ${AggregationMapConfig.SUPPORTED_GEOMETRY_TYPES}`; 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) { if (!this._isValidAggregationQueryParam(params)) { 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); }); } _isValidAggregationQueryParam (params) { const { aggregation } = params; return aggregation === undefined || aggregation === 'true' || aggregation === 'false'; } _shouldAdapt (mapConfig, params) { const { aggregation } = params; if (aggregation === 'false') { return false; } if (aggregation === 'true' || mapConfig.isAggregationMapConfig()) { return true; } return false; } _adaptLayers (connection, mapConfig, requestMapConfig, context, callback) { const adaptLayerPromises = requestMapConfig.layers.map((layer, index) => { return this._adaptLayer(connection, mapConfig, layer, index); }); Promise.all(adaptLayerPromises) .then(results => { context.aggregation = { layers: [] }; results.forEach(({ layer, index, adapted }) => { if (adapted) { requestMapConfig.layers[index] = layer; } const aggregatedFormats = this._getAggregationMetadata(mapConfig, layer, adapted); context.aggregation.layers.push(aggregatedFormats); }); callback(null, requestMapConfig); }) .catch(err => callback(err)); } _adaptLayer (connection, mapConfig, layer, index) { return new Promise((resolve, reject) => { this._shouldAdaptLayer(connection, mapConfig, layer, index, (err, shouldAdapt) => { if (err) { return reject(err); } if (!shouldAdapt) { return resolve({ layer, index, adapted: shouldAdapt }); } const sqlQueryWrap = layer.options.sql_wrap; 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); } layer.options.columns = columns; return resolve({ layer, index, adapted: shouldAdapt }); }); }); }); } _shouldAdaptLayer (connection, mapConfig, layer, index, callback) { if (!mapConfig.isAggregationLayer(index)) { return callback(null, false); } const aggregationMetadata = queryUtils.getAggregationMetadata({ query: layer.options.sql_raw ? layer.options.sql_raw : layer.options.sql, geometryColumn: AggregationMapConfig.getAggregationGeometryColumn() }); connection.query(aggregationMetadata, (err, res) => { if (err) { return callback(null, false); } const result = res.rows[0] || {}; if (!mapConfig.isVectorOnlyMapConfig() && !AggregationMapConfig.supportsGeometryType(result.type)) { const message = unsupportedGeometryTypeErrorMessage({ geometryType: result.type }); const error = new Error(message); 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); }); } _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 }; } };