diff --git a/src/sources/types.ts b/src/sources/types.ts index f61ec7d..f3ea647 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -149,6 +149,15 @@ export type QuerySourceOptions = { * ``` */ queryParameters?: QueryParameters; + + /** + * Comma-separated aggregation expressions. If assigned on a vector source, source is grouped by geometry and then aggregated. + * + * Example: + * + * 1 as value, avg(rev) as average_revenue + */ + aggregationExp?: string; }; export type TableSourceOptions = { @@ -178,6 +187,15 @@ export type TableSourceOptions = { * - 4: 4096x4096 */ tileResolution?: TileResolution; + + /** + * Comma-separated aggregation expressions. If assigned on a vector source, source is grouped by geometry and then aggregated. + * + * Example: + * + * 1 as value, avg(rev) as average_revenue + */ + aggregationExp?: string; }; export type TilesetSourceOptions = { diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 202095c..6cd17ce 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -31,6 +31,7 @@ type UrlParameters = { tileResolution?: string; q: string; queryParameters?: Record | unknown[]; + aggregationExp?: string; }; export type VectorQuerySourceResponse = TilejsonResult & @@ -46,6 +47,7 @@ export const vectorQuerySource = async function ( sqlQuery, tileResolution = DEFAULT_TILE_RESOLUTION, queryParameters, + aggregationExp, } = options; const urlParameters: UrlParameters = { @@ -64,6 +66,9 @@ export const vectorQuerySource = async function ( if (queryParameters) { urlParameters.queryParameters = queryParameters; } + if (aggregationExp) { + urlParameters.aggregationExp = aggregationExp; + } return baseSource('query', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 62707ef..541cde1 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -22,6 +22,7 @@ export type VectorTableSourceOptions = SourceOptions & TableSourceOptions & FilterOptions & ColumnsOption; + type UrlParameters = { columns?: string; filters?: Record; @@ -29,6 +30,7 @@ type UrlParameters = { spatialDataColumn?: string; tileResolution?: string; name: string; + aggregationExp?: string; }; export type VectorTableSourceResponse = TilejsonResult & @@ -43,6 +45,7 @@ export const vectorTableSource = async function ( spatialDataColumn = 'geom', tableName, tileResolution = DEFAULT_TILE_RESOLUTION, + aggregationExp, } = options; const urlParameters: UrlParameters = { @@ -58,6 +61,9 @@ export const vectorTableSource = async function ( if (filters) { urlParameters.filters = filters; } + if (aggregationExp) { + urlParameters.aggregationExp = aggregationExp; + } return baseSource('table', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), diff --git a/test/sources/vector-query-source.test.ts b/test/sources/vector-query-source.test.ts index 4e18e47..b3c6bd0 100644 --- a/test/sources/vector-query-source.test.ts +++ b/test/sources/vector-query-source.test.ts @@ -40,6 +40,7 @@ describe('vectorQuerySource', () => { columns: ['a', 'b'], spatialDataColumn: 'mygeom', queryParameters: {type: 'Supermarket', minRevenue: 1000000}, + aggregationExp: 'SUM(revenue)', }); expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); @@ -51,6 +52,7 @@ describe('vectorQuerySource', () => { expect(initURL).toMatch(/columns=a%2Cb/); expect(initURL).toMatch(/spatialDataColumn=mygeom/); expect(initURL).toMatch(/spatialDataType=geo/); + expect(initURL).toMatch(/aggregationExp=SUM%28revenue%29/); expect(initURL).toMatch( /queryParameters=%7B%22type%22%3A%22Supermarket%22%2C%22minRevenue%22%3A1000000%7D/ ); @@ -66,6 +68,20 @@ describe('vectorQuerySource', () => { expect(tilejson.accessToken).toBe(''); }); + test('when aggregationExp is not provided', async () => { + await vectorQuerySource({ + connectionName: 'carto_dw', + accessToken: '', + sqlQuery: 'SELECT * FROM a.b.vector_table', + columns: ['a', 'b'], + spatialDataColumn: 'mygeom', + queryParameters: {type: 'Supermarket', minRevenue: 1000000}, + }); + + const [[initURL]] = vi.mocked(fetch).mock.calls; + expect(initURL).not.toContain('aggregationExp'); + }); + test('widgetSource', async () => { const {widgetSource} = await vectorQuerySource({ accessToken: '', diff --git a/test/sources/vector-table-source.test.ts b/test/sources/vector-table-source.test.ts index 45665aa..151e508 100644 --- a/test/sources/vector-table-source.test.ts +++ b/test/sources/vector-table-source.test.ts @@ -39,6 +39,7 @@ describe('vectorTableSource', () => { tableName: 'a.b.vector_table', columns: ['a', 'b'], spatialDataColumn: 'mygeom', + aggregationExp: 'SUM(revenue)', }); expect(vi.mocked(fetch)).toHaveBeenCalledTimes(2); @@ -50,7 +51,7 @@ describe('vectorTableSource', () => { expect(initURL).toMatch(/columns=a%2Cb/); expect(initURL).toMatch(/spatialDataColumn=mygeom/); expect(initURL).toMatch(/spatialDataType=geo/); - + expect(initURL).toMatch(/aggregationExp=SUM%28revenue%29/); expect(tilesetURL).toMatch( /^https:\/\/xyz\.com\/\?format\=tilejson\&cache\=/ ); @@ -62,6 +63,19 @@ describe('vectorTableSource', () => { expect(tilejson.accessToken).toBe(''); }); + test('when aggregationExp is not provided', async () => { + await vectorTableSource({ + connectionName: 'carto_dw', + accessToken: '', + tableName: 'a.b.vector_table', + columns: ['a', 'b'], + spatialDataColumn: 'mygeom', + }); + + const [[initURL]] = vi.mocked(fetch).mock.calls; + expect(initURL).not.toContain('aggregationExp'); + }); + test('widgetSource', async () => { const {widgetSource} = await vectorTableSource({ accessToken: '',