diff --git a/lib/cartodb/models/aggregation/aggregation-query.js b/lib/cartodb/models/aggregation/aggregation-query.js index 23028f1b..4c2c34bf 100644 --- a/lib/cartodb/models/aggregation/aggregation-query.js +++ b/lib/cartodb/models/aggregation/aggregation-query.js @@ -248,28 +248,58 @@ const gridResolution = ctx => { return `GREATEST(${256/ctx.res}*CDB_XYZ_Resolution(CDB_ZoomFromScale(!scale_denominator!)), ${minimumResolution})::double precision`; }; +// Each aggregation cell is defined by the cell coordinates Floor(x/res), Floor(y/res), +// i.e. they include the West and South borders but not the East and North ones. +// So, to avoid picking points that don't belong to cells in the tile, given the tile +// limits Xmin, Ymin, Xmax, Ymax (bbox), we should select points that satisfy +// Xmin <= x < Xmax and Ymin <= y < Ymax (with x, y from the_geom_webmercator) +// On the other hand we can efficiently filter spatially (relying on spatial indexing) +// with `the_geom_webmercator && bbox` which is equivalent to +// Xmin <= x <= Xmax and Ymin <= y <= Ymax +// So, in order to be both efficient and accurate we will need to use both +// conditions for spatial filtering. +const spatialFilter = ` + (_cdb_query.the_geom_webmercator && _cdb_params.bbox) AND + ST_X(_cdb_query.the_geom_webmercator) >= _cdb_params.xmin AND ST_X(_cdb_query.the_geom_webmercator) < _cdb_params.xmax AND + ST_Y(_cdb_query.the_geom_webmercator) >= _cdb_params.ymin AND ST_Y(_cdb_query.the_geom_webmercator) < _cdb_params.ymax +`; + // Notes: // * 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. // * This queries are used for rendering and the_geom is omitted in the results for better performance +// * If the MVT extent or tile buffer was 0 or a multiple of the resolution we could use directly +// the bbox for them, but in general we need to find the nearest cell limits inside the bbox. +const sqlParams = (ctx) => ` +_cdb_res AS ( + SELECT + ${gridResolution(ctx)} AS res, + !bbox! AS bbox +), +_cdb_params AS ( + SELECT + res, + bbox, + CEIL(ST_XMIN(bbox)/res)*res AS xmin, + FLOOR(ST_XMAX(bbox)/res)*res AS xmax, + CEIL(ST_YMIN(bbox)/res)*res AS ymin, + FLOOR(ST_YMAX(bbox)/res)*res AS ymax + FROM _cdb_res +) +`; // The special default aggregation includes all the columns of a sample row per grid cell and // the count (_cdb_feature_count) of the aggregated rows. const defaultAggregationQueryTemplate = ctx => ` - WITH - _cdb_params AS ( - SELECT - ${gridResolution(ctx)} AS res, - !bbox! AS bbox - ), + WITH ${sqlParams(ctx)}, _cdb_clusters AS ( SELECT MIN(cartodb_id) AS cartodb_id ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params - WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox + WHERE ${spatialFilter} GROUP BY Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res), Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res) @@ -284,12 +314,7 @@ const defaultAggregationQueryTemplate = ctx => ` const aggregationQueryTemplates = { 'centroid': ctx => ` - WITH - _cdb_params AS ( - SELECT - ${gridResolution(ctx)} AS res, - !bbox! AS bbox - ) + WITH ${sqlParams(ctx)} SELECT MIN(_cdb_query.cartodb_id) AS cartodb_id, ST_SetSRID( @@ -301,7 +326,7 @@ const aggregationQueryTemplates = { ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params - WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox + WHERE ${spatialFilter} GROUP BY Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res), Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res) @@ -310,12 +335,7 @@ const aggregationQueryTemplates = { `, 'point-grid': ctx => ` - WITH - _cdb_params AS ( - SELECT - ${gridResolution(ctx)} AS res, - !bbox! AS bbox - ), + WITH ${sqlParams(ctx)}, _cdb_clusters AS ( SELECT MIN(_cdb_query.cartodb_id) AS cartodb_id, @@ -324,7 +344,7 @@ const aggregationQueryTemplates = { ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params - WHERE the_geom_webmercator && _cdb_params.bbox + WHERE ${spatialFilter} GROUP BY _cdb_gx, _cdb_gy ${dimensionNames(ctx)} ${havingClause(ctx)} ) @@ -337,19 +357,14 @@ const aggregationQueryTemplates = { `, 'point-sample': ctx => ` - WITH - _cdb_params AS ( - SELECT - ${gridResolution(ctx)} AS res, - !bbox! AS bbox - ), + WITH ${sqlParams(ctx)}, _cdb_clusters AS ( SELECT MIN(cartodb_id) AS cartodb_id ${dimensionDefs(ctx)} ${aggregateColumnDefs(ctx)} FROM (${ctx.sourceQuery}) _cdb_query, _cdb_params - WHERE _cdb_query.the_geom_webmercator && _cdb_params.bbox + WHERE ${spatialFilter} GROUP BY Floor(ST_X(_cdb_query.the_geom_webmercator)/_cdb_params.res), Floor(ST_Y(_cdb_query.the_geom_webmercator)/_cdb_params.res)