diff --git a/lib/cartodb/models/aggregation/aggregation-query.js b/lib/cartodb/models/aggregation/aggregation-query.js index 89ab47ad..b42b1c57 100644 --- a/lib/cartodb/models/aggregation/aggregation-query.js +++ b/lib/cartodb/models/aggregation/aggregation-query.js @@ -7,6 +7,7 @@ * - sourceQuery * - res * - columns + * - dimensions */ const templateForOptions = (options) => { let templateFn = aggregationQueryTemplates[options.placement]; @@ -23,11 +24,13 @@ const templateForOptions = (options) => { * aggregation cell is resolution*resolution pixels, where tiles are always 256x256 pixels * - columns * - placement + * - dimensions */ const queryForOptions = (options) => templateForOptions(options)({ sourceQuery: options.query, res: 256/options.resolution, - columns: options.columns + columns: options.columns, + dimensions: options.dimensions }); module.exports = queryForOptions; @@ -53,6 +56,11 @@ const SUPPORTED_AGGREGATE_FUNCTIONS = { } }; +const sep = (list) => { + let expr = list.join(', '); + return expr ? ', ' + expr : expr; +}; + const aggregateColumns = ctx => { return Object.assign({ _cdb_feature_count: { @@ -63,12 +71,12 @@ const aggregateColumns = ctx => { const aggregateColumnNames = ctx => { let columns = aggregateColumns(ctx); - return Object.keys(columns).join(', '); + return sep(Object.keys(columns)); }; const aggregateColumnDefs = ctx => { let columns = aggregateColumns(ctx); - return Object.keys(columns).map(column_name => { + return sep(Object.keys(columns).map(column_name => { const aggregate_function = columns[column_name].aggregate_function || 'count'; const aggregate_definition = SUPPORTED_AGGREGATE_FUNCTIONS[aggregate_function]; if (!aggregate_definition) { @@ -76,10 +84,24 @@ const aggregateColumnDefs = ctx => { } const aggregate_expression = aggregate_definition.sql(column_name, columns[column_name]); return `${aggregate_expression} AS ${column_name}`; - }).join(', '); + })); }; +const aggregateDimensions = ctx => ctx.dimensions || {}; + +const dimensionNames = ctx => { + return sep(Object.keys(aggregateDimensions(ctx))); +}; + +const dimensionDefs = ctx => { + let dimensions = aggregateDimensions(ctx); + return sep(Object.keys(dimensions).map(dimension_name => { + const expression = dimensions[dimension_name]; + return `${expression} AS ${dimension_name}`; + })); +}; + // SQL expression to compute the aggregation resolution (grid cell size). // This is equivalent to `${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))` // This is defined by the ctx.res parameter, which is the number of grid cells per tile linear dimension @@ -106,13 +128,15 @@ const aggregationQueryTemplates = { AVG(ST_X(_cdb_query.the_geom_webmercator)), AVG(ST_Y(_cdb_query.the_geom_webmercator)) ), 3857 - ) AS the_geom_webmercator, + ) AS the_geom_webmercator + ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox GROUP BY Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res), Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res) + ${dimensionNames(ctx)} `, 'point-grid': ctx => ` @@ -124,14 +148,16 @@ const aggregationQueryTemplates = { _cdb_clusters AS ( SELECT Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gx, - Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gy, + Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)::int AS _cdb_gy + ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} - FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params + FROM (${ctx.sourceQuery(ctx)}) _cdb_query, _cdb_params WHERE the_geom_webmercator && _cdb_params.bbox - GROUP BY _cdb_gx, _cdb_gy + GROUP BY _cdb_gx, _cdb_gy ${dimensionNames} ) SELECT - ST_SetSRID(ST_MakePoint(_cdb_gx*(res+0.5), _cdb_gy*(res+0.5)), 3857) AS the_geom_webmercator, + ST_SetSRID(ST_MakePoint(_cdb_gx*(res+0.5), _cdb_gy*(res+0.5)), 3857) AS the_geom_webmercator + ${dimensionNames(ctx)} ${aggregateColumnNames(ctx)} FROM _cdb_clusters, _cdb_params `, @@ -143,19 +169,22 @@ const aggregationQueryTemplates = { !bbox! AS bbox ), _cdb_clusters AS ( SELECT - MIN(cartodb_id) AS cartodb_id, + MIN(cartodb_id) AS cartodb_id + ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} - FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params + FROM (${ctx.sourceQuery(ctx)}) _cdb_query, _cdb_params WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox GROUP BY Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res), Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res) + ${dimensionNames(ctx)} ) SELECT _cdb_clusters.cartodb_id, - the_geom, the_geom_webmercator, + the_geom, the_geom_webmercator + ${dimensionNames(ctx)} ${aggregateColumnNames(ctx)} FROM - _cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query + _cdb_clusters INNER JOIN (${ctx.sourceQuery(ctx)}) _cdb_query ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id) ` }; diff --git a/lib/cartodb/models/aggregation/aggregation.js b/lib/cartodb/models/aggregation/aggregation.js index d95b74cd..2642898c 100644 --- a/lib/cartodb/models/aggregation/aggregation.js +++ b/lib/cartodb/models/aggregation/aggregation.js @@ -9,7 +9,8 @@ module.exports = class Aggregation { resolution = 1, threshold = Aggregation.THRESHOLD, placement = 'centroid', - columns = {} + columns = {}, + dimensions = {} } = {}) { this.mapconfig = mapconfig; this.query = query; @@ -17,6 +18,7 @@ module.exports = class Aggregation { this.threshold = threshold; this.placement = placement; this.columns = columns; + this.dimensions = dimensions; } sql () { return aggregationQuery(this);