2017-12-12 01:33:06 +08:00
|
|
|
/**
|
2017-12-13 19:35:17 +08:00
|
|
|
* Returns a template function (function that accepts template parameters and returns a string)
|
|
|
|
* to generate an aggregation query.
|
|
|
|
* Valid options to define the query template are:
|
|
|
|
* - placement
|
|
|
|
* The query template parameters taken by the result template function are:
|
|
|
|
* - sourceQuery
|
|
|
|
* - res
|
|
|
|
* - columns
|
2017-12-12 01:33:06 +08:00
|
|
|
*/
|
2017-12-13 19:35:17 +08:00
|
|
|
const templateForOptions = (options) => {
|
2017-12-12 01:33:06 +08:00
|
|
|
let templateFn = aggregationQueryTemplates[options.placement];
|
|
|
|
if (!templateFn) {
|
|
|
|
throw new Error("Invalid Aggregation placement: '" + options.placement + "'");
|
|
|
|
}
|
|
|
|
return templateFn;
|
|
|
|
};
|
|
|
|
|
2017-12-13 19:35:17 +08:00
|
|
|
/**
|
|
|
|
* Generates an aggregation query given the aggregation options:
|
|
|
|
* - query
|
|
|
|
* - resolution
|
|
|
|
* - columns
|
|
|
|
* - placement
|
|
|
|
*/
|
|
|
|
const queryForOptions = (options) => templateForOptions(options)({
|
|
|
|
sourceQuery: options.query,
|
|
|
|
res: options.resolution,
|
|
|
|
columns: options.columns
|
|
|
|
});
|
|
|
|
|
|
|
|
module.exports = queryForOptions;
|
|
|
|
|
2017-12-12 23:17:42 +08:00
|
|
|
const SUPPORTED_AGGREGATE_FUNCTIONS = {
|
|
|
|
'count': {
|
|
|
|
sql: (column_name, params) => `count(${params.aggregated_column || '*'})`
|
|
|
|
},
|
|
|
|
'avg': {
|
|
|
|
sql: (column_name, params) => `avg(${params.aggregated_column || column_name})`
|
|
|
|
},
|
|
|
|
'sum': {
|
|
|
|
sql: (column_name, params) => `sum(${params.aggregated_column || column_name})`
|
|
|
|
},
|
|
|
|
'min': {
|
|
|
|
sql: (column_name, params) => `min(${params.aggregated_column || column_name})`
|
|
|
|
},
|
|
|
|
'max': {
|
|
|
|
sql: (column_name, params) => `max(${params.aggregated_column || column_name})`
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
const aggregateColumns = ctx => {
|
2017-12-14 19:12:43 +08:00
|
|
|
// TODO: always add count
|
2017-12-12 18:18:18 +08:00
|
|
|
let columns = ctx.columns || {};
|
2017-12-12 22:53:35 +08:00
|
|
|
if (Object.keys(columns).length === 0) {
|
2017-12-12 18:18:18 +08:00
|
|
|
// default aggregation
|
|
|
|
columns = {
|
|
|
|
_cdb_feature_count: {
|
|
|
|
aggregate_function: 'count'
|
|
|
|
}
|
2017-12-12 22:53:35 +08:00
|
|
|
};
|
2017-12-12 18:18:18 +08:00
|
|
|
}
|
|
|
|
return Object.keys(columns).map(column_name => {
|
2017-12-12 23:17:42 +08:00
|
|
|
const aggregate_function = columns[column_name].aggregate_function || 'count';
|
|
|
|
const aggregate_definition = SUPPORTED_AGGREGATE_FUNCTIONS[aggregate_function];
|
|
|
|
if (!aggregate_definition) {
|
|
|
|
throw new Error("Invalid Aggregate function: '" + aggregate_function + "'");
|
2017-12-12 18:18:18 +08:00
|
|
|
}
|
2017-12-12 23:17:42 +08:00
|
|
|
const aggregate_expression = aggregate_definition.sql(column_name, columns[column_name]);
|
2017-12-12 18:18:18 +08:00
|
|
|
return `${aggregate_expression} AS ${column_name}`;
|
|
|
|
}).join(', ');
|
|
|
|
};
|
|
|
|
|
2017-12-12 01:33:06 +08:00
|
|
|
// Notes:
|
2017-12-14 19:23:02 +08:00
|
|
|
// * ${256*0.00028/ctx.res}*!scale_denominator! is equivalent to
|
|
|
|
// ${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!))
|
2017-12-12 23:17:42 +08:00
|
|
|
// * We need to filter spatially using !bbox! to make the queries efficient because
|
|
|
|
// the filter added by Mapnik (wrapping the query)
|
|
|
|
// is only applied after the aggregation.
|
2017-12-12 01:33:06 +08:00
|
|
|
// * This queries are used for rendering and the_geom is omitted in the results for better performance
|
|
|
|
|
|
|
|
const aggregationQueryTemplates = {
|
2017-12-12 22:53:35 +08:00
|
|
|
'centroid': ctx => `
|
|
|
|
WITH _cdb_params AS (
|
|
|
|
SELECT
|
2017-12-14 19:23:02 +08:00
|
|
|
(${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
|
2017-12-12 22:53:35 +08:00
|
|
|
!bbox! AS bbox
|
|
|
|
)
|
|
|
|
SELECT
|
|
|
|
row_number() over() AS cartodb_id,
|
|
|
|
ST_SetSRID(
|
|
|
|
ST_MakePoint(
|
|
|
|
AVG(ST_X(_cdb_query.the_geom_webmercator)),
|
|
|
|
AVG(ST_Y(_cdb_query.the_geom_webmercator))
|
|
|
|
), 3857
|
|
|
|
) AS the_geom_webmercator,
|
2017-12-12 23:17:42 +08:00
|
|
|
${aggregateColumns(ctx)}
|
2017-12-12 22:53:35 +08:00
|
|
|
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)
|
|
|
|
`,
|
2017-12-12 01:33:06 +08:00
|
|
|
|
2017-12-12 22:53:35 +08:00
|
|
|
'point-grid': ctx => `
|
2017-12-13 00:38:39 +08:00
|
|
|
WITH _cdb_params AS (
|
2017-12-12 22:53:35 +08:00
|
|
|
SELECT
|
2017-12-14 19:23:02 +08:00
|
|
|
(${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
|
2017-12-13 00:38:39 +08:00
|
|
|
!bbox! AS bbox
|
|
|
|
),
|
|
|
|
_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,
|
|
|
|
${aggregateColumns(ctx)}
|
|
|
|
FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params
|
|
|
|
WHERE the_geom_webmercator && _cdb_params.bbox
|
|
|
|
GROUP BY _cdb_gx, _cdb_gy
|
|
|
|
)
|
|
|
|
SELECT
|
2017-12-14 19:12:43 +08:00
|
|
|
ST_SetSRID(ST_MakePoint(_cdb_gx*(res+0.5), _cdb_gy*(res+0.5)), 3857) AS the_geom_webmercator,
|
2017-12-13 00:38:39 +08:00
|
|
|
_cdb_feature_count
|
|
|
|
FROM _cdb_clusters, _cdb_params
|
2017-12-12 22:53:35 +08:00
|
|
|
`,
|
2017-12-12 01:33:06 +08:00
|
|
|
|
2017-12-12 22:54:36 +08:00
|
|
|
'point-sample': ctx => `
|
2017-12-12 22:53:35 +08:00
|
|
|
WITH _cdb_params AS (
|
2017-12-12 01:33:06 +08:00
|
|
|
SELECT
|
|
|
|
|
2017-12-14 19:23:02 +08:00
|
|
|
(${256*0.00028/ctx.res}*!scale_denominator!)::double precision AS res,
|
2017-12-12 22:53:35 +08:00
|
|
|
!bbox! AS bbox
|
|
|
|
), _cdb_clusters AS (
|
|
|
|
SELECT
|
|
|
|
MIN(cartodb_id) AS cartodb_id,
|
2017-12-12 23:17:42 +08:00
|
|
|
${aggregateColumns(ctx)}
|
2017-12-12 22:53:35 +08:00
|
|
|
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)
|
|
|
|
) SELECT
|
|
|
|
_cdb_clusters.cartodb_id,
|
|
|
|
the_geom, the_geom_webmercator,
|
|
|
|
_cdb_feature_count
|
|
|
|
FROM
|
|
|
|
_cdb_clusters INNER JOIN (${ctx.sourceQuery}) _cdb_query
|
|
|
|
ON (_cdb_clusters.cartodb_id = _cdb_query.cartodb_id)
|
|
|
|
`
|
|
|
|
};
|