diff --git a/lib/cartodb/models/aggregation/aggregation-query.js b/lib/cartodb/models/aggregation/aggregation-query.js index 2c3026a3..f55c9e75 100644 --- a/lib/cartodb/models/aggregation/aggregation-query.js +++ b/lib/cartodb/models/aggregation/aggregation-query.js @@ -310,26 +310,39 @@ const gridResolution = ctx => { }; // SQL query to extra the boundaries of the area to be aggregated and the grid resolution +// cdb_{x-y}{min_max} return the limits of the tile. Aggregations do [min, max) in both axis +// cdb_res: Aggregation resolution (as specified by gridResolution) +// cdb_point_bbox: Tile bounding box [min, max] const gridInfoQuery = ctx => { return ` SELECT - res, - CEIL (ST_XMIN(cdb_full_bbox) / res) * res AS cdb_xmin, - FLOOR(ST_XMAX(cdb_full_bbox) / res) * res AS cdb_xmax, - CEIL (ST_YMIN(cdb_full_bbox) / res) * res AS cdb_ymin, - FLOOR(ST_YMAX(cdb_full_bbox) / res) * res AS cdb_ymax + cdb_xmin, + cdb_ymin, + cdb_xmax, + cdb_ymax, + cdb_res, + ST_MakeEnvelope(cdb_xmin, cdb_ymin, cdb_xmax, cdb_ymax, 3857) AS cdb_point_bbox FROM ( SELECT - ${gridResolution(ctx)} AS res, - !bbox! cdb_full_bbox - OFFSET 0 - ) _cdb_input_resources + cdb_res, + CEIL (ST_XMIN(cdb_full_bbox) / cdb_res) * cdb_res AS cdb_xmin, + FLOOR(ST_XMAX(cdb_full_bbox) / cdb_res) * cdb_res AS cdb_xmax, + CEIL (ST_YMIN(cdb_full_bbox) / cdb_res) * cdb_res AS cdb_ymin, + FLOOR(ST_YMAX(cdb_full_bbox) / cdb_res) * cdb_res AS cdb_ymax + FROM + ( + SELECT + ${gridResolution(ctx)} AS cdb_res, + !bbox! cdb_full_bbox + ) _cdb_input_resources + ) _cdb_grid_bbox_margins `; }; -// Function to generate the resulting point for a cell from the aggregated +// Function to generate the resulting point for a cell from the aggregated data +// Point sample joins the query with itself to get the data from the lowest id const aggregatedPoint = (ctx, aggregated) => { const placement = ctx.placement || DEFAULT_PLACEMENT; switch (placement) { @@ -337,15 +350,13 @@ const aggregatedPoint = (ctx, aggregated) => { // For centroid, we return the average of the cell case `centroid`: return aggregated ? - `, ST_SetSRID(ST_MakePoint(AVG(ST_X(the_geom_webmercator)), AVG(ST_Y(the_geom_webmercator))), 3857) AS the_geom_webmercator` : + `, ST_SetSRID(ST_MakePoint(AVG(cdb_x), AVG(cdb_y)), 3857) AS the_geom_webmercator` : ``; // Middle point of the cell case `point-grid`: return aggregated ? - `, ST_SetSRID(ST_MakePoint(cdb_xmin + (cdb_pos_grid_x + 0.5) * res, - cdb_ymin + (cdb_pos_grid_y + 0.5) * res), - 3857) AS the_geom_webmercator`: + `, ST_SetSRID(ST_MakePoint(cdb_pos_grid_x, cdb_pos_grid_y), 3857) AS the_geom_webmercator`: ``; // For point-sample we'll get a single point directly from the source @@ -368,19 +379,29 @@ const aggregatedPoint = (ctx, aggregated) => { } }; -// 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. -// * bbox coordinates can have an error in the last digits; we apply a small correction before -// applying CEIL or FLOOR to compensate for this, so that coordinates closer than a small (`eps`) -// fraction of the cell size to a cell limit are moved to the exact limit. +// Function to generate the values common to all points in a cell +// By default we use the cell number (which is fast), but for point-grid we +// get the coordinates of the mid point so we don't need to calculate them later +// which requires extra data in the group by clause +const aggregatedPosCoordinate = (ctx, coordinate) => { + const placement = ctx.placement || DEFAULT_PLACEMENT; + switch (placement) { + // For point-grid we return the coordinate of the middle point of the grid + case `point-grid`: + return `(FLOOR(cdb_${coordinate} / __cdb_grid_params.cdb_res) + 0.5) * __cdb_grid_params.cdb_res`; + + // For other, we return the cell position (relative to the world) + default: + return `FLOOR(cdb_${coordinate} / __cdb_grid_params.cdb_res)`; + } +}; const defaultAggregationQueryTemplate = ctx => ` +WITH __cdb_grid_params AS +( + ${gridInfoQuery(ctx)} +) SELECT * FROM ( SELECT @@ -391,29 +412,25 @@ SELECT * FROM FROM ( SELECT - __cdb_src_query.*, - __cdb_src_params.*, - FLOOR((ST_X(the_geom_webmercator) - __cdb_src_params.cdb_xmin) / __cdb_src_params.res) as cdb_pos_grid_x, - FLOOR((ST_Y(the_geom_webmercator) - __cdb_src_params.cdb_ymin) / __cdb_src_params.res) as cdb_pos_grid_y + *, + ${aggregatedPosCoordinate(ctx, 'x')} as cdb_pos_grid_x, + ${aggregatedPosCoordinate(ctx, 'y')} as cdb_pos_grid_y FROM - ( - ${ctx.sourceQuery} - ) __cdb_src_query, ( SELECT - _cdb_grid_bbox_margins.*, - ST_MakeEnvelope(cdb_xmin, cdb_ymin, cdb_xmax, cdb_ymax, 3857) AS cdb_point_bbox, - ((cdb_xmax - cdb_xmin) / res)::int as cdb_limit_x, - ((cdb_ymax - cdb_ymin) / res)::int as cdb_limit_y + __cdb_src_query.*, + ST_X(the_geom_webmercator) cdb_x, + ST_Y(the_geom_webmercator) cdb_y FROM ( - ${gridInfoQuery(ctx)} - ) _cdb_grid_bbox_margins OFFSET 0 - ) __cdb_src_params - WHERE the_geom_webmercator && cdb_point_bbox OFFSET 0 - ) __cdb_srd_grid - WHERE cdb_pos_grid_x < cdb_limit_x AND cdb_pos_grid_y < cdb_limit_y - GROUP BY cdb_pos_grid_x, cdb_pos_grid_y, __cdb_srd_grid.cdb_xmin, __cdb_srd_grid.cdb_ymin, __cdb_srd_grid.res ${dimensionNames(ctx)} + ${ctx.sourceQuery} + ) __cdb_src_query, __cdb_grid_params + WHERE the_geom_webmercator && cdb_point_bbox + OFFSET 0 + ) __cdb_src_get_x_y, __cdb_grid_params + WHERE cdb_x < __cdb_grid_params.cdb_xmax AND cdb_y < __cdb_grid_params.cdb_ymax + ) __cdb_src_gridded, __cdb_grid_params + GROUP BY cdb_pos_grid_x, cdb_pos_grid_y ${dimensionNames(ctx)} ${havingClause(ctx)} ) __cdb_aggregation_src ${aggregatedPoint(ctx, false)}