From 608d5f28af18247b9c442b3be990de08bb4f4a56 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 14:17:24 +0100 Subject: [PATCH 01/36] feat(source): extend source options in widget constructor with spatialDataColumn and spatialDataType --- src/sources/h3-query-source.ts | 12 ++++++++++-- src/sources/h3-table-source.ts | 11 +++++++++-- src/sources/quadbin-query-source.ts | 11 +++++++++-- src/sources/quadbin-table-source.ts | 11 +++++++++-- src/sources/vector-query-source.ts | 4 +++- src/sources/vector-table-source.ts | 5 ++++- 6 files changed, 44 insertions(+), 10 deletions(-) diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts index 230b357..7daff86 100644 --- a/src/sources/h3-query-source.ts +++ b/src/sources/h3-query-source.ts @@ -18,7 +18,10 @@ import type { export type H3QuerySourceOptions = SourceOptions & QuerySourceOptions & AggregationOptions & - FilterOptions; + FilterOptions & { + spatialDataType: 'h3'; + }; + type UrlParameters = { aggregationExp: string; aggregationResLevel?: string; @@ -59,7 +62,12 @@ export const h3QuerySource = async function ( return baseSource('query', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetQuerySource(options), + widgetSource: new WidgetQuerySource({ + ...options, + // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3' + spatialDataColumn, + spatialDataType: 'h3', + }), }) ); }; diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts index 6bad8a9..0941d27 100644 --- a/src/sources/h3-table-source.ts +++ b/src/sources/h3-table-source.ts @@ -18,7 +18,9 @@ import type { export type H3TableSourceOptions = SourceOptions & TableSourceOptions & AggregationOptions & - FilterOptions; + FilterOptions & { + spatialDataType: 'h3'; + }; type UrlParameters = { aggregationExp: string; @@ -55,7 +57,12 @@ export const h3TableSource = async function ( return baseSource('table', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetTableSource(options), + widgetSource: new WidgetTableSource({ + ...options, + // NOTE: passing redundant spatialDataColumn here to apply the default value 'h3' + spatialDataColumn, + spatialDataType: 'h3', + }), }) ); }; diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts index 0e6ea7f..14e4e52 100644 --- a/src/sources/quadbin-query-source.ts +++ b/src/sources/quadbin-query-source.ts @@ -18,7 +18,9 @@ import type { export type QuadbinQuerySourceOptions = SourceOptions & QuerySourceOptions & AggregationOptions & - FilterOptions; + FilterOptions & { + spatialDataType: 'quadbin'; + }; type UrlParameters = { aggregationExp: string; @@ -60,7 +62,12 @@ export const quadbinQuerySource = async function ( return baseSource('query', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetQuerySource(options), + widgetSource: new WidgetQuerySource({ + ...options, + // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin' + spatialDataColumn, + spatialDataType: 'quadbin', + }), }) ); }; diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index 91b7a1c..4ed4190 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -18,7 +18,9 @@ import type { export type QuadbinTableSourceOptions = SourceOptions & TableSourceOptions & AggregationOptions & - FilterOptions; + FilterOptions & { + spatialDataType: 'quadbin'; + }; type UrlParameters = { aggregationExp: string; @@ -56,7 +58,12 @@ export const quadbinTableSource = async function ( return baseSource('table', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetTableSource(options), + widgetSource: new WidgetTableSource({ + ...options, + // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin' + spatialDataColumn, + spatialDataType: 'quadbin' + }), }) ); }; diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 93db9a4..a3d3515 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -21,7 +21,9 @@ import type { export type VectorQuerySourceOptions = SourceOptions & QuerySourceOptions & FilterOptions & - ColumnsOption; + ColumnsOption & { + spatialDataType: 'geo' + }; type UrlParameters = { columns?: string; diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 85dc4db..ada5705 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -21,7 +21,10 @@ import type { export type VectorTableSourceOptions = SourceOptions & TableSourceOptions & FilterOptions & - ColumnsOption; + ColumnsOption & { + spatialDataType: 'geo'; + }; + type UrlParameters = { columns?: string; filters?: Record; From e317a9b4bf150c0fa2f8645fab1252059a7a618e Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 14:17:50 +0100 Subject: [PATCH 02/36] feat(widgets): read spatialDataColumn and spatialDataType from props in getModelSource --- src/models/model.ts | 3 +++ src/widget-sources/widget-query-source.ts | 2 ++ src/widget-sources/widget-table-source.ts | 2 ++ 3 files changed, 7 insertions(+) diff --git a/src/models/model.ts b/src/models/model.ts index cfd6057..3557b42 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -10,6 +10,7 @@ import {$TODO} from '../types-internal.js'; import {assert} from '../utils.js'; import {ModelRequestOptions, makeCall} from './common.js'; import {ApiVersion} from '../constants.js'; +import { SpatialDataType } from '../sources/types.js'; /** @internalRemarks Source: @carto/react-api */ const AVAILABLE_MODELS = [ @@ -38,6 +39,8 @@ export interface ModelSource { geoColumn?: string; spatialFilter?: SpatialFilter; queryParameters?: QueryParameters; + spatialDataColumn?: string; + spatialDataType?: SpatialDataType; } const {V3} = ApiVersion; diff --git a/src/widget-sources/widget-query-source.ts b/src/widget-sources/widget-query-source.ts index 9c82f24..36d7cbf 100644 --- a/src/widget-sources/widget-query-source.ts +++ b/src/widget-sources/widget-query-source.ts @@ -44,6 +44,8 @@ export class WidgetQuerySource extends WidgetBaseSource< type: 'query', data: this.props.sqlQuery, queryParameters: this.props.queryParameters, + spatialDataColumn: this.props.spatialDataColumn, + spatialDataType: this.props.spatialDataType, }; } } diff --git a/src/widget-sources/widget-table-source.ts b/src/widget-sources/widget-table-source.ts index 28dc5d1..51ec478 100644 --- a/src/widget-sources/widget-table-source.ts +++ b/src/widget-sources/widget-table-source.ts @@ -43,6 +43,8 @@ export class WidgetTableSource extends WidgetBaseSource< ...super._getModelSource(owner), type: 'table', data: this.props.tableName, + spatialDataColumn: this.props.spatialDataColumn, + spatialDataType: this.props.spatialDataType, }; } } From 6932b37de8054c950d363ee0a6be5cdbc28275b2 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 14:53:20 +0100 Subject: [PATCH 03/36] feat(widgets): allow passing spatialFiltersResolution to all model calls --- src/models/model.ts | 1 + src/widget-sources/types.ts | 1 + src/widget-sources/widget-base-source.ts | 63 ++++++++++++++++++------ 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index 3557b42..2f5904b 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -41,6 +41,7 @@ export interface ModelSource { queryParameters?: QueryParameters; spatialDataColumn?: string; spatialDataType?: SpatialDataType; + spatialFiltersResolution?: number; } const {V3} = ApiVersion; diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index 12f2e9e..6fbe07b 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -13,6 +13,7 @@ import { /** Common options for {@link WidgetBaseSource} requests. */ interface BaseRequestOptions { spatialFilter?: SpatialFilter; + spatialFiltersResolution?: number; abortController?: AbortController; filterOwner?: string; } diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 3768aba..adcdddc 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -93,14 +93,18 @@ export abstract class WidgetBaseSource { async getCategories( options: CategoryRequestOptions ): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {column, operation, operationColumn} = params; type CategoriesModelResponse = {rows: {name: string; value: number}[]}; return executeModel({ model: 'category', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFilter, + spatialFiltersResolution, + }, params: { column, operation, @@ -125,14 +129,18 @@ export abstract class WidgetBaseSource { async getFeatures( options: FeaturesRequestOptions ): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; type FeaturesModelResponse = {rows: Record[]}; return executeModel({ model: 'pick', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: { columns, dataType, @@ -159,6 +167,7 @@ export abstract class WidgetBaseSource { const { filterOwner, spatialFilter, + spatialFiltersResolution, abortController, operationExp, ...params @@ -169,7 +178,11 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'formula', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: {column: column ?? '*', operation, operationExp}, opts: {abortController}, }).then((res: FormulaModelResponse) => normalizeObjectKeys(res.rows[0])); @@ -186,14 +199,18 @@ export abstract class WidgetBaseSource { async getHistogram( options: HistogramRequestOptions ): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {column, operation, ticks} = params; type HistogramModelResponse = {rows: {tick: number; value: number}[]}; const data = await executeModel({ model: 'histogram', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: {column, operation, ticks}, opts: {abortController}, }).then((res: HistogramModelResponse) => normalizeObjectKeys(res.rows)); @@ -221,14 +238,18 @@ export abstract class WidgetBaseSource { * or rendering a range slider UI for filtering. */ async getRange(options: RangeRequestOptions): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {column} = params; type RangeModelResponse = {rows: {min: number; max: number}[]}; return executeModel({ model: 'range', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: {column}, opts: {abortController}, }).then((res: RangeModelResponse) => normalizeObjectKeys(res.rows[0])); @@ -243,7 +264,7 @@ export abstract class WidgetBaseSource { * values. Suitable for rendering scatter plots. */ async getScatter(options: ScatterRequestOptions): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {xAxisColumn, xAxisJoinOperation, yAxisColumn, yAxisJoinOperation} = params; @@ -254,7 +275,11 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'scatterplot', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: { xAxisColumn, xAxisJoinOperation, @@ -277,7 +302,7 @@ export abstract class WidgetBaseSource { * sorting. Suitable for displaying tables and lists. */ async getTable(options: TableRequestOptions): Promise { - const {filterOwner, spatialFilter, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; type TableModelResponse = { @@ -287,7 +312,11 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'table', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: { column: columns, sortBy, @@ -314,7 +343,7 @@ export abstract class WidgetBaseSource { async getTimeSeries( options: TimeSeriesRequestOptions ): Promise { - const {filterOwner, abortController, spatialFilter, ...params} = options; + const {filterOwner, abortController, spatialFilter, spatialFiltersResolution, ...params} = options; const { column, operationColumn, @@ -334,7 +363,11 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'timeseries', - source: {...this.getModelSource(filterOwner), spatialFilter}, + source: { + ...this.getModelSource(filterOwner), + spatialFiltersResolution, + spatialFilter + }, params: { column, stepSize, From 51622c2a71e9361d49e0ba78277c336bb4f83be6 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 15:28:59 +0100 Subject: [PATCH 04/36] feat(widgets): allow passing spatialFiltersMode to all model calls --- src/models/model.ts | 3 ++- src/sources/types.ts | 8 ++++++++ src/widget-sources/types.ts | 3 ++- src/widget-sources/widget-base-source.ts | 25 ++++++++++++++++-------- 4 files changed, 29 insertions(+), 10 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index 2f5904b..324d84b 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -10,7 +10,7 @@ import {$TODO} from '../types-internal.js'; import {assert} from '../utils.js'; import {ModelRequestOptions, makeCall} from './common.js'; import {ApiVersion} from '../constants.js'; -import { SpatialDataType } from '../sources/types.js'; +import { SpatialDataType, SpatialFilterPolyfillMode } from '../sources/types.js'; /** @internalRemarks Source: @carto/react-api */ const AVAILABLE_MODELS = [ @@ -42,6 +42,7 @@ export interface ModelSource { spatialDataColumn?: string; spatialDataType?: SpatialDataType; spatialFiltersResolution?: number; + spatialFiltersMode?: SpatialFilterPolyfillMode; } const {V3} = ApiVersion; diff --git a/src/sources/types.ts b/src/sources/types.ts index b039d18..f90b5d7 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -177,6 +177,14 @@ export type ColumnsOption = { export type SpatialDataType = 'geo' | 'h3' | 'quadbin'; +/** + * Strategy used for covering spatial filter geometry with spatial indexes. + * See https://docs.carto.com/data-and-analysis/analytics-toolbox-for-bigquery/sql-reference/quadbin#quadbin_polyfill_mode + * or https://docs.carto.com/data-and-analysis/analytics-toolbox-for-bigquery/sql-reference/h3#h3_polyfill_mode for more information. + * @internalRemarks Source: cloud-native maps-api + * */ +export type SpatialFilterPolyfillMode = 'center' | 'intersects' | 'contains'; + export type TilejsonMapInstantiation = MapInstantiation & { tilejson: {url: string[]}; }; diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index 6fbe07b..e921583 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -1,4 +1,4 @@ -import {TileResolution} from '../sources/types'; +import {SpatialFilterPolyfillMode, TileResolution} from '../sources/types'; import { GroupDateType, SortColumnType, @@ -14,6 +14,7 @@ import { interface BaseRequestOptions { spatialFilter?: SpatialFilter; spatialFiltersResolution?: number; + spatialFiltersMode?: SpatialFilterPolyfillMode; abortController?: AbortController; filterOwner?: string; } diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index adcdddc..0ffa126 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -93,7 +93,7 @@ export abstract class WidgetBaseSource { async getCategories( options: CategoryRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {column, operation, operationColumn} = params; type CategoriesModelResponse = {rows: {name: string; value: number}[]}; @@ -102,8 +102,9 @@ export abstract class WidgetBaseSource { model: 'category', source: { ...this.getModelSource(filterOwner), - spatialFilter, spatialFiltersResolution, + spatialFiltersMode, + spatialFilter, }, params: { column, @@ -129,7 +130,7 @@ export abstract class WidgetBaseSource { async getFeatures( options: FeaturesRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; type FeaturesModelResponse = {rows: Record[]}; @@ -139,6 +140,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: { @@ -167,6 +169,7 @@ export abstract class WidgetBaseSource { const { filterOwner, spatialFilter, + spatialFiltersMode, spatialFiltersResolution, abortController, operationExp, @@ -181,6 +184,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: {column: column ?? '*', operation, operationExp}, @@ -199,7 +203,7 @@ export abstract class WidgetBaseSource { async getHistogram( options: HistogramRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {column, operation, ticks} = params; type HistogramModelResponse = {rows: {tick: number; value: number}[]}; @@ -209,6 +213,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: {column, operation, ticks}, @@ -238,7 +243,7 @@ export abstract class WidgetBaseSource { * or rendering a range slider UI for filtering. */ async getRange(options: RangeRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {column} = params; type RangeModelResponse = {rows: {min: number; max: number}[]}; @@ -248,6 +253,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: {column}, @@ -264,7 +270,7 @@ export abstract class WidgetBaseSource { * values. Suitable for rendering scatter plots. */ async getScatter(options: ScatterRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {xAxisColumn, xAxisJoinOperation, yAxisColumn, yAxisJoinOperation} = params; @@ -278,6 +284,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: { @@ -302,7 +309,7 @@ export abstract class WidgetBaseSource { * sorting. Suitable for displaying tables and lists. */ async getTable(options: TableRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersResolution, abortController, ...params} = options; + const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; type TableModelResponse = { @@ -315,6 +322,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: { @@ -343,7 +351,7 @@ export abstract class WidgetBaseSource { async getTimeSeries( options: TimeSeriesRequestOptions ): Promise { - const {filterOwner, abortController, spatialFilter, spatialFiltersResolution, ...params} = options; + const {filterOwner, abortController, spatialFilter, spatialFiltersMode, spatialFiltersResolution, ...params} = options; const { column, operationColumn, @@ -366,6 +374,7 @@ export abstract class WidgetBaseSource { source: { ...this.getModelSource(filterOwner), spatialFiltersResolution, + spatialFiltersMode, spatialFilter }, params: { From 341e691f2f94ae339c68ad6e55dcdbca4dd98537 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 15:53:32 +0100 Subject: [PATCH 05/36] fix(source): fix source input types --- src/sources/h3-query-source.ts | 2 +- src/sources/h3-table-source.ts | 2 +- src/sources/quadbin-query-source.ts | 2 +- src/sources/quadbin-table-source.ts | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts index 7daff86..8aef59c 100644 --- a/src/sources/h3-query-source.ts +++ b/src/sources/h3-query-source.ts @@ -33,7 +33,7 @@ type UrlParameters = { }; export const h3QuerySource = async function ( - options: H3QuerySourceOptions + options: Omit ): Promise { const { aggregationExp, diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts index 0941d27..1e7180c 100644 --- a/src/sources/h3-table-source.ts +++ b/src/sources/h3-table-source.ts @@ -32,7 +32,7 @@ type UrlParameters = { }; export const h3TableSource = async function ( - options: H3TableSourceOptions + options: Omit ): Promise { const { aggregationExp, diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts index 14e4e52..74c9671 100644 --- a/src/sources/quadbin-query-source.ts +++ b/src/sources/quadbin-query-source.ts @@ -33,7 +33,7 @@ type UrlParameters = { }; export const quadbinQuerySource = async function ( - options: QuadbinQuerySourceOptions + options: Omit ): Promise { const { aggregationExp, diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index 4ed4190..2a9a66a 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -32,7 +32,7 @@ type UrlParameters = { }; export const quadbinTableSource = async function ( - options: QuadbinTableSourceOptions + options: Omit ): Promise { const { aggregationExp, From 76fde7aad1c15ed7c38845589057954e4ea5ab28 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 15:58:22 +0100 Subject: [PATCH 06/36] feat(model): add dataResolution prop --- src/models/model.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/models/model.ts b/src/models/model.ts index 324d84b..50dbb5d 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -43,6 +43,8 @@ export interface ModelSource { spatialDataType?: SpatialDataType; spatialFiltersResolution?: number; spatialFiltersMode?: SpatialFilterPolyfillMode; + /** original resolution of the spatial index data as stored in the DW */ + dataResolution?: number; } const {V3} = ApiVersion; From aceef3399aa138286b6b33ad7cd3986cb1352ec6 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Fri, 29 Nov 2024 15:59:01 +0100 Subject: [PATCH 07/36] feat(utils): add getSpatialFiltersResolution util --- src/spatial-index.ts | 106 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 106 insertions(+) create mode 100644 src/spatial-index.ts diff --git a/src/spatial-index.ts b/src/spatial-index.ts new file mode 100644 index 0000000..6fc5c04 --- /dev/null +++ b/src/spatial-index.ts @@ -0,0 +1,106 @@ +import { DEFAULT_AGGREGATION_RES_LEVEL_H3, DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN } from "./constants-internal"; +import { ModelSource } from "./models/model"; +import { AggregationOptions } from "./sources/types"; + +const DEFAULT_TILE_SIZE = 512; +const QUADBIN_ZOOM_MAX_OFFSET = 4; + +export function getSpatialFiltersResolution({ source, viewState }: { + source: ModelSource & AggregationOptions; + viewState: { + zoom: number; + latitude: number; + longitude: number; + } +}) { + if (source.spatialDataType === 'geo') { + return undefined; + } + + const currentZoom = viewState.zoom ?? 1; + + const dataResolution = source.dataResolution ?? Number.MAX_VALUE; + + const aggregationResLevel = + source.aggregationResLevel ?? + (source.spatialDataType === 'h3' + ? DEFAULT_AGGREGATION_RES_LEVEL_H3 + : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN); + + const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel)); + + const currentZoomInt = Math.ceil(currentZoom); + if (source.spatialDataType === 'h3') { + const tileSize = DEFAULT_TILE_SIZE; + const maxResolutionForZoom = + maxH3SpatialFiltersResolutions.find(([zoom]) => zoom === currentZoomInt)?.[1] ?? + Math.max(0, currentZoomInt - 3); + + const maxSpatialFiltersResolution = maxResolutionForZoom + ? Math.min(dataResolution, maxResolutionForZoom) + : dataResolution; + + const hexagonResolution = + getHexagonResolution( + { zoom: currentZoom, latitude: viewState.latitude }, + tileSize + ) + aggregationResLevelOffset; + + return Math.min(hexagonResolution, maxSpatialFiltersResolution); + } + + if (source.spatialDataType === 'quadbin') { + const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET; + const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom); + + const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset; + return Math.min(quadsResolution, maxSpatialFiltersResolution); + } + + return undefined; +} + +const maxH3SpatialFiltersResolutions = [ + [20, 14], + [19, 13], + [18, 12], + [17, 11], + [16, 10], + [15, 9], + [14, 8], + [13, 7], + [12, 7], + [11, 7], + [10, 6], + [9, 6], + [8, 5], + [7, 4], + [6, 4], + [5, 3], + [4, 2], + [3, 1], + [2, 1], + [1, 0] +]; + +// stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts + +// Relative scale factor (0 = no biasing, 2 = a few hexagons cover view) +const BIAS = 2; + +// Resolution conversion function. Takes a WebMercatorViewport and returns +// a H3 resolution such that the screen space size of the hexagons is +// similar +export function getHexagonResolution( + viewport: {zoom: number; latitude: number}, + tileSize: number +): number { + // Difference in given tile size compared to deck's internal 512px tile size, + // expressed as an offset to the viewport zoom. + const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE); + const hexagonScaleFactor = (2 / 3) * (viewport.zoom - zoomOffset); + const latitudeScaleFactor = Math.log(1 / Math.cos((Math.PI * viewport.latitude) / 180)); + + // Clip and bias + return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS)); +} From c4d7332ec95e504e2703879d739919448aa41b08 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Mon, 2 Dec 2024 16:52:36 +0100 Subject: [PATCH 08/36] fix vector source types --- src/sources/vector-query-source.ts | 7 +++++-- src/sources/vector-table-source.ts | 7 +++++-- 2 files changed, 10 insertions(+), 4 deletions(-) diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index a3d3515..264f270 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -36,7 +36,7 @@ type UrlParameters = { }; export const vectorQuerySource = async function ( - options: VectorQuerySourceOptions + options: Omit ): Promise { const { columns, @@ -66,7 +66,10 @@ export const vectorQuerySource = async function ( return baseSource('query', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetQuerySource(options), + widgetSource: new WidgetQuerySource({ + ...options, + spatialDataType: 'geo', + }), }) ); }; diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index ada5705..7d672b1 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -35,7 +35,7 @@ type UrlParameters = { }; export const vectorTableSource = async function ( - options: VectorTableSourceOptions + options: Omit ): Promise { const { columns, @@ -61,7 +61,10 @@ export const vectorTableSource = async function ( return baseSource('table', options, urlParameters).then( (result) => ({ ...(result as TilejsonResult), - widgetSource: new WidgetTableSource(options), + widgetSource: new WidgetTableSource({ + ...options, + spatialDataType: 'geo', + }), }) ); }; From 7c74ae2443edd42c35dc8a30682c06a2eb12e449 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Mon, 2 Dec 2024 16:52:49 +0100 Subject: [PATCH 09/36] export spatial index utils --- src/index.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/index.ts b/src/index.ts index dda6dec..8026617 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,6 +4,7 @@ export * from './filters.js'; export * from './geo.js'; export * from './widget-sources/index.js'; export * from './types.js'; +export * from './spatial-index.js' export { APIErrorContext, From 25ae33df518e6facc512813fc61677eb393b1ce3 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Mon, 2 Dec 2024 16:55:51 +0100 Subject: [PATCH 10/36] prettier format --- src/index.ts | 2 +- src/models/model.ts | 2 +- src/sources/quadbin-table-source.ts | 2 +- src/sources/vector-query-source.ts | 2 +- src/spatial-index.ts | 47 ++++++++++----- src/widget-sources/widget-base-source.ts | 77 +++++++++++++++++++----- 6 files changed, 100 insertions(+), 32 deletions(-) diff --git a/src/index.ts b/src/index.ts index 8026617..3badd79 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,7 @@ export * from './filters.js'; export * from './geo.js'; export * from './widget-sources/index.js'; export * from './types.js'; -export * from './spatial-index.js' +export * from './spatial-index.js'; export { APIErrorContext, diff --git a/src/models/model.ts b/src/models/model.ts index 50dbb5d..ffdce14 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -10,7 +10,7 @@ import {$TODO} from '../types-internal.js'; import {assert} from '../utils.js'; import {ModelRequestOptions, makeCall} from './common.js'; import {ApiVersion} from '../constants.js'; -import { SpatialDataType, SpatialFilterPolyfillMode } from '../sources/types.js'; +import {SpatialDataType, SpatialFilterPolyfillMode} from '../sources/types.js'; /** @internalRemarks Source: @carto/react-api */ const AVAILABLE_MODELS = [ diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index 2a9a66a..b7deaa4 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -62,7 +62,7 @@ export const quadbinTableSource = async function ( ...options, // NOTE: passing redundant spatialDataColumn here to apply the default value 'quadbin' spatialDataColumn, - spatialDataType: 'quadbin' + spatialDataType: 'quadbin', }), }) ); diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 264f270..44f1bf4 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -22,7 +22,7 @@ export type VectorQuerySourceOptions = SourceOptions & QuerySourceOptions & FilterOptions & ColumnsOption & { - spatialDataType: 'geo' + spatialDataType: 'geo'; }; type UrlParameters = { diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 6fc5c04..3121785 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -1,17 +1,23 @@ -import { DEFAULT_AGGREGATION_RES_LEVEL_H3, DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN } from "./constants-internal"; -import { ModelSource } from "./models/model"; -import { AggregationOptions } from "./sources/types"; +import { + DEFAULT_AGGREGATION_RES_LEVEL_H3, + DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, +} from './constants-internal'; +import {ModelSource} from './models/model'; +import {AggregationOptions} from './sources/types'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; -export function getSpatialFiltersResolution({ source, viewState }: { +export function getSpatialFiltersResolution({ + source, + viewState, +}: { source: ModelSource & AggregationOptions; viewState: { zoom: number; latitude: number; longitude: number; - } + }; }) { if (source.spatialDataType === 'geo') { return undefined; @@ -27,14 +33,18 @@ export function getSpatialFiltersResolution({ source, viewState }: { ? DEFAULT_AGGREGATION_RES_LEVEL_H3 : DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN); - const aggregationResLevelOffset = Math.max(0, Math.floor(aggregationResLevel)); + const aggregationResLevelOffset = Math.max( + 0, + Math.floor(aggregationResLevel) + ); const currentZoomInt = Math.ceil(currentZoom); if (source.spatialDataType === 'h3') { const tileSize = DEFAULT_TILE_SIZE; const maxResolutionForZoom = - maxH3SpatialFiltersResolutions.find(([zoom]) => zoom === currentZoomInt)?.[1] ?? - Math.max(0, currentZoomInt - 3); + maxH3SpatialFiltersResolutions.find( + ([zoom]) => zoom === currentZoomInt + )?.[1] ?? Math.max(0, currentZoomInt - 3); const maxSpatialFiltersResolution = maxResolutionForZoom ? Math.min(dataResolution, maxResolutionForZoom) @@ -42,7 +52,7 @@ export function getSpatialFiltersResolution({ source, viewState }: { const hexagonResolution = getHexagonResolution( - { zoom: currentZoom, latitude: viewState.latitude }, + {zoom: currentZoom, latitude: viewState.latitude}, tileSize ) + aggregationResLevelOffset; @@ -51,9 +61,13 @@ export function getSpatialFiltersResolution({ source, viewState }: { if (source.spatialDataType === 'quadbin') { const maxResolutionForZoom = currentZoomInt + QUADBIN_ZOOM_MAX_OFFSET; - const maxSpatialFiltersResolution = Math.min(dataResolution, maxResolutionForZoom); + const maxSpatialFiltersResolution = Math.min( + dataResolution, + maxResolutionForZoom + ); - const quadsResolution = Math.floor(viewState.zoom) + aggregationResLevelOffset; + const quadsResolution = + Math.floor(viewState.zoom) + aggregationResLevelOffset; return Math.min(quadsResolution, maxSpatialFiltersResolution); } @@ -80,7 +94,7 @@ const maxH3SpatialFiltersResolutions = [ [4, 2], [3, 1], [2, 1], - [1, 0] + [1, 0], ]; // stolen from https://github.com/visgl/deck.gl/blob/master/modules/carto/src/layers/h3-tileset-2d.ts @@ -99,8 +113,13 @@ export function getHexagonResolution( // expressed as an offset to the viewport zoom. const zoomOffset = Math.log2(tileSize / DEFAULT_TILE_SIZE); const hexagonScaleFactor = (2 / 3) * (viewport.zoom - zoomOffset); - const latitudeScaleFactor = Math.log(1 / Math.cos((Math.PI * viewport.latitude) / 180)); + const latitudeScaleFactor = Math.log( + 1 / Math.cos((Math.PI * viewport.latitude) / 180) + ); // Clip and bias - return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS)); + return Math.max( + 0, + Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS) + ); } diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 0ffa126..f8e9bba 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -93,7 +93,14 @@ export abstract class WidgetBaseSource { async getCategories( options: CategoryRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {column, operation, operationColumn} = params; type CategoriesModelResponse = {rows: {name: string; value: number}[]}; @@ -130,7 +137,14 @@ export abstract class WidgetBaseSource { async getFeatures( options: FeaturesRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; type FeaturesModelResponse = {rows: Record[]}; @@ -141,7 +155,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: { columns, @@ -185,7 +199,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: {column: column ?? '*', operation, operationExp}, opts: {abortController}, @@ -203,7 +217,14 @@ export abstract class WidgetBaseSource { async getHistogram( options: HistogramRequestOptions ): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {column, operation, ticks} = params; type HistogramModelResponse = {rows: {tick: number; value: number}[]}; @@ -214,7 +235,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: {column, operation, ticks}, opts: {abortController}, @@ -243,7 +264,14 @@ export abstract class WidgetBaseSource { * or rendering a range slider UI for filtering. */ async getRange(options: RangeRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {column} = params; type RangeModelResponse = {rows: {min: number; max: number}[]}; @@ -254,7 +282,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: {column}, opts: {abortController}, @@ -270,7 +298,14 @@ export abstract class WidgetBaseSource { * values. Suitable for rendering scatter plots. */ async getScatter(options: ScatterRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {xAxisColumn, xAxisJoinOperation, yAxisColumn, yAxisJoinOperation} = params; @@ -285,7 +320,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: { xAxisColumn, @@ -309,7 +344,14 @@ export abstract class WidgetBaseSource { * sorting. Suitable for displaying tables and lists. */ async getTable(options: TableRequestOptions): Promise { - const {filterOwner, spatialFilter, spatialFiltersMode, spatialFiltersResolution, abortController, ...params} = options; + const { + filterOwner, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + abortController, + ...params + } = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; type TableModelResponse = { @@ -323,7 +365,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: { column: columns, @@ -351,7 +393,14 @@ export abstract class WidgetBaseSource { async getTimeSeries( options: TimeSeriesRequestOptions ): Promise { - const {filterOwner, abortController, spatialFilter, spatialFiltersMode, spatialFiltersResolution, ...params} = options; + const { + filterOwner, + abortController, + spatialFilter, + spatialFiltersMode, + spatialFiltersResolution, + ...params + } = options; const { column, operationColumn, @@ -375,7 +424,7 @@ export abstract class WidgetBaseSource { ...this.getModelSource(filterOwner), spatialFiltersResolution, spatialFiltersMode, - spatialFilter + spatialFilter, }, params: { column, From d50d904fee044d8bd85b20932fa13489d6d87061 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 12:30:47 +0100 Subject: [PATCH 11/36] Spatial index widgets second proposal (#35) --- src/api/query.ts | 3 +- src/sources/h3-query-source.ts | 6 +- src/sources/h3-table-source.ts | 6 +- src/sources/quadbin-query-source.ts | 6 +- src/sources/quadbin-table-source.ts | 6 +- src/sources/types.ts | 77 +++++++++----------- src/sources/vector-query-source.ts | 6 +- src/sources/vector-table-source.ts | 6 +- src/spatial-index.ts | 19 ++--- src/utils.ts | 2 +- src/widget-sources/types.ts | 7 ++ src/widget-sources/widget-base-source.ts | 88 ++++++++++++++++++----- src/widget-sources/widget-query-source.ts | 2 - src/widget-sources/widget-table-source.ts | 2 - 14 files changed, 135 insertions(+), 101 deletions(-) diff --git a/src/api/query.ts b/src/api/query.ts index 3678038..fde0363 100644 --- a/src/api/query.ts +++ b/src/api/query.ts @@ -12,8 +12,7 @@ import {buildQueryUrl} from './endpoints'; import {requestWithParameters} from './request-with-parameters'; import {APIErrorContext} from './carto-api-error'; -export type QueryOptions = SourceOptions & - Omit; +export type QueryOptions = SourceOptions & QuerySourceOptions; type UrlParameters = {q: string; queryParameters?: string}; export const query = async function ( diff --git a/src/sources/h3-query-source.ts b/src/sources/h3-query-source.ts index 8aef59c..3d5588d 100644 --- a/src/sources/h3-query-source.ts +++ b/src/sources/h3-query-source.ts @@ -18,9 +18,7 @@ import type { export type H3QuerySourceOptions = SourceOptions & QuerySourceOptions & AggregationOptions & - FilterOptions & { - spatialDataType: 'h3'; - }; + FilterOptions; type UrlParameters = { aggregationExp: string; @@ -33,7 +31,7 @@ type UrlParameters = { }; export const h3QuerySource = async function ( - options: Omit + options: H3QuerySourceOptions ): Promise { const { aggregationExp, diff --git a/src/sources/h3-table-source.ts b/src/sources/h3-table-source.ts index 1e7180c..afc9943 100644 --- a/src/sources/h3-table-source.ts +++ b/src/sources/h3-table-source.ts @@ -18,9 +18,7 @@ import type { export type H3TableSourceOptions = SourceOptions & TableSourceOptions & AggregationOptions & - FilterOptions & { - spatialDataType: 'h3'; - }; + FilterOptions; type UrlParameters = { aggregationExp: string; @@ -32,7 +30,7 @@ type UrlParameters = { }; export const h3TableSource = async function ( - options: Omit + options: H3TableSourceOptions ): Promise { const { aggregationExp, diff --git a/src/sources/quadbin-query-source.ts b/src/sources/quadbin-query-source.ts index 74c9671..f782c1c 100644 --- a/src/sources/quadbin-query-source.ts +++ b/src/sources/quadbin-query-source.ts @@ -18,9 +18,7 @@ import type { export type QuadbinQuerySourceOptions = SourceOptions & QuerySourceOptions & AggregationOptions & - FilterOptions & { - spatialDataType: 'quadbin'; - }; + FilterOptions; type UrlParameters = { aggregationExp: string; @@ -33,7 +31,7 @@ type UrlParameters = { }; export const quadbinQuerySource = async function ( - options: Omit + options: QuadbinQuerySourceOptions ): Promise { const { aggregationExp, diff --git a/src/sources/quadbin-table-source.ts b/src/sources/quadbin-table-source.ts index b7deaa4..983378a 100644 --- a/src/sources/quadbin-table-source.ts +++ b/src/sources/quadbin-table-source.ts @@ -18,9 +18,7 @@ import type { export type QuadbinTableSourceOptions = SourceOptions & TableSourceOptions & AggregationOptions & - FilterOptions & { - spatialDataType: 'quadbin'; - }; + FilterOptions; type UrlParameters = { aggregationExp: string; @@ -32,7 +30,7 @@ type UrlParameters = { }; export const quadbinTableSource = async function ( - options: Omit + options: QuadbinTableSourceOptions ): Promise { const { aggregationExp, diff --git a/src/sources/types.ts b/src/sources/types.ts index f90b5d7..f7c7a47 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -47,6 +47,33 @@ export type SourceOptionalOptions = { * @default {@link DEFAULT_MAX_LENGTH_URL} */ maxLengthURL?: number; + + /** + * The column name and the type of geospatial support. + * + * If not present, defaults to `'geom'` for generic queries, `'quadbin'` for Quadbin sources and `'h3'` for H3 sources. + */ + spatialDataColumn?: string; + + /** + * The type of geospatial support. Defaults to `'geo'`. + */ + spatialDataType?: SpatialDataType; + + /** + * Relative resolution of a tile. Higher values increase density and data size. At `tileResolution = 1`, tile geometry is + * quantized to a 1024x1024 grid. Increasing or decreasing the resolution will increase or decrease the dimensions of + * the quantization grid proportionately. + * + * Supported `tileResolution` values, with corresponding grid sizes: + * + * - 0.25: 256x256 + * - 0.5: 512x512 + * - 1: 1024x1024 + * - 2: 2048x2048 + * - 4: 4096x4096 + */ + tileResolution?: TileResolution; }; export type SourceOptions = SourceRequiredOptions & @@ -68,6 +95,11 @@ export type AggregationOptions = { * @default 6 for quadbin and 4 for h3 sources */ aggregationResLevel?: number; + + /** + * Original resolution of the spatial index data as stored in the DW + */ + dataResolution?: number; }; export type FilterOptions = { @@ -78,31 +110,10 @@ export type FilterOptions = { }; export type QuerySourceOptions = { - /** - * The column name and the type of geospatial support. - * - * If not present, defaults to `'geom'` for generic queries, `'quadbin'` for Quadbin sources and `'h3'` for H3 sources. - */ - spatialDataColumn?: string; - /** SQL query. */ + /** Full SQL query with query paremeter placeholders (if any). */ sqlQuery: string; - /** - * Relative resolution of a tile. Higher values increase density and data size. At `tileResolution = 1`, tile geometry is - * quantized to a 1024x1024 grid. Increasing or decreasing the resolution will increase or decrease the dimensions of - * the quantization grid proportionately. - * - * Supported `tileResolution` values, with corresponding grid sizes: - * - * - 0.25: 256x256 - * - 0.5: 512x512 - * - 1: 1024x1024 - * - 2: 2048x2048 - * - 4: 4096x4096 - */ - tileResolution?: TileResolution; - /** * Values for named or positional paramteres in the query. * @@ -135,28 +146,6 @@ export type TableSourceOptions = { * Fully qualified name of table. */ tableName: string; - - /** - * The column name and the type of geospatial support. - * - * If not present, defaults to `'geom'` for generic tables, `'quadbin'` for Quadbin sources and `'h3'` for H3 sources. - */ - spatialDataColumn?: string; - - /** - * Relative resolution of a tile. Higher values increase density and data size. At `tileResolution = 1`, tile geometry is - * quantized to a 1024x1024 grid. Increasing or decreasing the resolution will increase or decrease the dimensions of - * the quantization grid proportionately. - * - * Supported `tileResolution` values, with corresponding grid sizes: - * - * - 0.25: 256x256 - * - 0.5: 512x512 - * - 1: 1024x1024 - * - 2: 2048x2048 - * - 4: 4096x4096 - */ - tileResolution?: TileResolution; }; export type TilesetSourceOptions = { diff --git a/src/sources/vector-query-source.ts b/src/sources/vector-query-source.ts index 44f1bf4..b43c71b 100644 --- a/src/sources/vector-query-source.ts +++ b/src/sources/vector-query-source.ts @@ -21,9 +21,7 @@ import type { export type VectorQuerySourceOptions = SourceOptions & QuerySourceOptions & FilterOptions & - ColumnsOption & { - spatialDataType: 'geo'; - }; + ColumnsOption; type UrlParameters = { columns?: string; @@ -36,7 +34,7 @@ type UrlParameters = { }; export const vectorQuerySource = async function ( - options: Omit + options: VectorQuerySourceOptions ): Promise { const { columns, diff --git a/src/sources/vector-table-source.ts b/src/sources/vector-table-source.ts index 7d672b1..27e48cf 100644 --- a/src/sources/vector-table-source.ts +++ b/src/sources/vector-table-source.ts @@ -21,9 +21,7 @@ import type { export type VectorTableSourceOptions = SourceOptions & TableSourceOptions & FilterOptions & - ColumnsOption & { - spatialDataType: 'geo'; - }; + ColumnsOption; type UrlParameters = { columns?: string; @@ -35,7 +33,7 @@ type UrlParameters = { }; export const vectorTableSource = async function ( - options: Omit + options: VectorTableSourceOptions ): Promise { const { columns, diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 3121785..0de3efd 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -4,6 +4,7 @@ import { } from './constants-internal'; import {ModelSource} from './models/model'; import {AggregationOptions} from './sources/types'; +import { assert } from './utils'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; @@ -12,8 +13,12 @@ export function getSpatialFiltersResolution({ source, viewState, }: { - source: ModelSource & AggregationOptions; - viewState: { + source: { + spatialDataType?: ModelSource['spatialDataType']; + dataResolution?: ModelSource['dataResolution']; + aggregationResLevel?: AggregationOptions['aggregationResLevel'] + }; + viewState?: { zoom: number; latitude: number; longitude: number; @@ -23,7 +28,7 @@ export function getSpatialFiltersResolution({ return undefined; } - const currentZoom = viewState.zoom ?? 1; + assert(viewState, "viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error") const dataResolution = source.dataResolution ?? Number.MAX_VALUE; @@ -38,7 +43,7 @@ export function getSpatialFiltersResolution({ Math.floor(aggregationResLevel) ); - const currentZoomInt = Math.ceil(currentZoom); + const currentZoomInt = Math.ceil(viewState.zoom); if (source.spatialDataType === 'h3') { const tileSize = DEFAULT_TILE_SIZE; const maxResolutionForZoom = @@ -50,11 +55,7 @@ export function getSpatialFiltersResolution({ ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution; - const hexagonResolution = - getHexagonResolution( - {zoom: currentZoom, latitude: viewState.latitude}, - tileSize - ) + aggregationResLevelOffset; + const hexagonResolution = getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset; return Math.min(hexagonResolution, maxSpatialFiltersResolution); } diff --git a/src/utils.ts b/src/utils.ts index e4048fc..7f6f927 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -57,7 +57,7 @@ export function normalizeObjectKeys>(el: R): R { } /** @internalRemarks Source: @carto/react-core */ -export function assert(condition: unknown, message: string) { +export function assert(condition: unknown, message: string): asserts condition { if (!condition) { throw new Error(message); } diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index e921583..98b35f9 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -10,6 +10,12 @@ import { * WIDGET API REQUESTS */ +export interface ViewState { + zoom: number; + latitude: number; + longitude: number; +}; + /** Common options for {@link WidgetBaseSource} requests. */ interface BaseRequestOptions { spatialFilter?: SpatialFilter; @@ -17,6 +23,7 @@ interface BaseRequestOptions { spatialFiltersMode?: SpatialFilterPolyfillMode; abortController?: AbortController; filterOwner?: string; + viewState?: ViewState; } /** Options for {@link WidgetBaseSource#getCategories}. */ diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index f8e9bba..fb933f1 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -16,9 +16,10 @@ import { TableResponse, TimeSeriesRequestOptions, TimeSeriesResponse, + ViewState, } from './types.js'; import {FilterLogicalOperator, Filter} from '../types.js'; -import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; +import {assert, getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; @@ -27,6 +28,7 @@ import { DEFAULT_GEO_COLUMN, DEFAULT_TILE_RESOLUTION, } from '../constants-internal.js'; +import { getSpatialFiltersResolution } from '../spatial-index.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; @@ -79,6 +81,8 @@ export abstract class WidgetBaseSource { filters: getApplicableFilters(owner, props.filters), filtersLogicalOperator: props.filtersLogicalOperator, geoColumn: props.geoColumn, + spatialDataType: props.spatialDataType, + spatialDataColumn: props.spatialDataColumn, }; } @@ -97,18 +101,24 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {column, operation, operationColumn} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type CategoriesModelResponse = {rows: {name: string; value: number}[]}; return executeModel({ model: 'category', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -141,18 +151,24 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type FeaturesModelResponse = {rows: Record[]}; return executeModel({ model: 'pick', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -184,19 +200,25 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, operationExp, + viewState, ...params } = options; const {column, operation} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type FormulaModelResponse = {rows: {value: number}[]}; return executeModel({ model: 'formula', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -221,18 +243,24 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {column, operation, ticks} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type HistogramModelResponse = {rows: {tick: number; value: number}[]}; const data = await executeModel({ model: 'histogram', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -268,18 +296,24 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {column} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type RangeModelResponse = {rows: {min: number; max: number}[]}; return executeModel({ model: 'range', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -302,13 +336,20 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {xAxisColumn, xAxisJoinOperation, yAxisColumn, yAxisJoinOperation} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } + // Make sure this is sync with the same constant in cloud-native/maps-api const HARD_LIMIT = 500; @@ -317,7 +358,7 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'scatterplot', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -348,11 +389,17 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, abortController, + viewState, ...params } = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } type TableModelResponse = { rows: Record[]; @@ -362,7 +409,7 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'table', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, @@ -398,7 +445,7 @@ export abstract class WidgetBaseSource { abortController, spatialFilter, spatialFiltersMode, - spatialFiltersResolution, + viewState, ...params } = options; const { @@ -413,6 +460,13 @@ export abstract class WidgetBaseSource { splitByCategoryValues, } = params; + const source = this.getModelSource(filterOwner); + + let spatialFiltersResolution = options.spatialFiltersResolution; + if (spatialFilter && !spatialFiltersResolution) { + spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + } + type TimeSeriesModelResponse = { rows: {name: string; value: number}[]; metadata: {categories: string[]}; @@ -421,7 +475,7 @@ export abstract class WidgetBaseSource { return executeModel({ model: 'timeseries', source: { - ...this.getModelSource(filterOwner), + ...source, spatialFiltersResolution, spatialFiltersMode, spatialFilter, diff --git a/src/widget-sources/widget-query-source.ts b/src/widget-sources/widget-query-source.ts index 36d7cbf..9c82f24 100644 --- a/src/widget-sources/widget-query-source.ts +++ b/src/widget-sources/widget-query-source.ts @@ -44,8 +44,6 @@ export class WidgetQuerySource extends WidgetBaseSource< type: 'query', data: this.props.sqlQuery, queryParameters: this.props.queryParameters, - spatialDataColumn: this.props.spatialDataColumn, - spatialDataType: this.props.spatialDataType, }; } } diff --git a/src/widget-sources/widget-table-source.ts b/src/widget-sources/widget-table-source.ts index 51ec478..28dc5d1 100644 --- a/src/widget-sources/widget-table-source.ts +++ b/src/widget-sources/widget-table-source.ts @@ -43,8 +43,6 @@ export class WidgetTableSource extends WidgetBaseSource< ...super._getModelSource(owner), type: 'table', data: this.props.tableName, - spatialDataColumn: this.props.spatialDataColumn, - spatialDataType: this.props.spatialDataType, }; } } From cbe99fcbabb6ba57f91c359a1fda28c262c2a2d2 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 12:36:48 +0100 Subject: [PATCH 12/36] chore: run prettier --- src/sources/types.ts | 3 +- src/spatial-index.ts | 12 +++-- src/widget-sources/types.ts | 2 +- src/widget-sources/widget-base-source.ts | 56 +++++++++++++++++------- 4 files changed, 50 insertions(+), 23 deletions(-) diff --git a/src/sources/types.ts b/src/sources/types.ts index f7c7a47..1420d3c 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -96,7 +96,7 @@ export type AggregationOptions = { */ aggregationResLevel?: number; - /** + /** * Original resolution of the spatial index data as stored in the DW */ dataResolution?: number; @@ -110,7 +110,6 @@ export type FilterOptions = { }; export type QuerySourceOptions = { - /** Full SQL query with query paremeter placeholders (if any). */ sqlQuery: string; diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 0de3efd..d06cd09 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -4,7 +4,7 @@ import { } from './constants-internal'; import {ModelSource} from './models/model'; import {AggregationOptions} from './sources/types'; -import { assert } from './utils'; +import {assert} from './utils'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; @@ -16,7 +16,7 @@ export function getSpatialFiltersResolution({ source: { spatialDataType?: ModelSource['spatialDataType']; dataResolution?: ModelSource['dataResolution']; - aggregationResLevel?: AggregationOptions['aggregationResLevel'] + aggregationResLevel?: AggregationOptions['aggregationResLevel']; }; viewState?: { zoom: number; @@ -28,7 +28,10 @@ export function getSpatialFiltersResolution({ return undefined; } - assert(viewState, "viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error") + assert( + viewState, + 'viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error' + ); const dataResolution = source.dataResolution ?? Number.MAX_VALUE; @@ -55,7 +58,8 @@ export function getSpatialFiltersResolution({ ? Math.min(dataResolution, maxResolutionForZoom) : dataResolution; - const hexagonResolution = getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset; + const hexagonResolution = + getHexagonResolution(viewState, tileSize) + aggregationResLevelOffset; return Math.min(hexagonResolution, maxSpatialFiltersResolution); } diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index 98b35f9..d4d61b0 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -14,7 +14,7 @@ export interface ViewState { zoom: number; latitude: number; longitude: number; -}; +} /** Common options for {@link WidgetBaseSource} requests. */ interface BaseRequestOptions { diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index fb933f1..f9d65b6 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -28,7 +28,7 @@ import { DEFAULT_GEO_COLUMN, DEFAULT_TILE_RESOLUTION, } from '../constants-internal.js'; -import { getSpatialFiltersResolution } from '../spatial-index.js'; +import {getSpatialFiltersResolution} from '../spatial-index.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; @@ -110,7 +110,10 @@ export abstract class WidgetBaseSource { let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type CategoriesModelResponse = {rows: {name: string; value: number}[]}; @@ -157,10 +160,13 @@ export abstract class WidgetBaseSource { } = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type FeaturesModelResponse = {rows: Record[]}; @@ -207,10 +213,13 @@ export abstract class WidgetBaseSource { } = options; const {column, operation} = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type FormulaModelResponse = {rows: {value: number}[]}; @@ -249,10 +258,13 @@ export abstract class WidgetBaseSource { } = options; const {column, operation, ticks} = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type HistogramModelResponse = {rows: {tick: number; value: number}[]}; @@ -302,10 +314,13 @@ export abstract class WidgetBaseSource { } = options; const {column} = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type RangeModelResponse = {rows: {min: number; max: number}[]}; @@ -344,10 +359,13 @@ export abstract class WidgetBaseSource { params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } // Make sure this is sync with the same constant in cloud-native/maps-api @@ -395,10 +413,13 @@ export abstract class WidgetBaseSource { } = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type TableModelResponse = { @@ -461,10 +482,13 @@ export abstract class WidgetBaseSource { } = params; const source = this.getModelSource(filterOwner); - + let spatialFiltersResolution = options.spatialFiltersResolution; if (spatialFilter && !spatialFiltersResolution) { - spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState }) + spatialFiltersResolution = getSpatialFiltersResolution({ + source, + viewState, + }); } type TimeSeriesModelResponse = { From f85f818305e41218c9c2e597ee74e852c0977e05 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 13:16:38 +0100 Subject: [PATCH 13/36] dont export functions from spatial-index util file --- src/index.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/index.ts b/src/index.ts index 3badd79..dda6dec 100644 --- a/src/index.ts +++ b/src/index.ts @@ -4,7 +4,6 @@ export * from './filters.js'; export * from './geo.js'; export * from './widget-sources/index.js'; export * from './types.js'; -export * from './spatial-index.js'; export { APIErrorContext, From a186251edf1ba4a6a2b44fa007f9a738f96a04fc Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 17:19:25 +0100 Subject: [PATCH 14/36] pass view state to model call in widgets --- examples/components/widgets/category-widget.ts | 5 +++++ examples/components/widgets/formula-widget.ts | 5 +++++ examples/components/widgets/histogram-widget.ts | 5 +++++ examples/components/widgets/scatter-widget.ts | 5 +++++ examples/components/widgets/table-widget.ts | 9 +++++++-- 5 files changed, 27 insertions(+), 2 deletions(-) diff --git a/examples/components/widgets/category-widget.ts b/examples/components/widgets/category-widget.ts index 089a4bd..070079f 100644 --- a/examples/components/widgets/category-widget.ts +++ b/examples/components/widgets/category-widget.ts @@ -57,6 +57,11 @@ export class CategoryWidget extends BaseWidget { spatialFilter: this.getSpatialFilterOrViewState(), operation, column, + viewState: this.viewState ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } : undefined, }); }, args: () => diff --git a/examples/components/widgets/formula-widget.ts b/examples/components/widgets/formula-widget.ts index a55af3e..bc247c5 100644 --- a/examples/components/widgets/formula-widget.ts +++ b/examples/components/widgets/formula-widget.ts @@ -59,6 +59,11 @@ export class FormulaWidget extends BaseWidget { operation, column, spatialFilter: this.getSpatialFilterOrViewState(), + viewState: this.viewState ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } : undefined, }); return response.value; }, diff --git a/examples/components/widgets/histogram-widget.ts b/examples/components/widgets/histogram-widget.ts index dc7af4b..f3b12e0 100644 --- a/examples/components/widgets/histogram-widget.ts +++ b/examples/components/widgets/histogram-widget.ts @@ -58,6 +58,11 @@ export class HistogramWidget extends BaseWidget { column, operation, ticks, + viewState: this.viewState ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } : undefined, }); }, args: () => diff --git a/examples/components/widgets/scatter-widget.ts b/examples/components/widgets/scatter-widget.ts index 674b04d..620607d 100644 --- a/examples/components/widgets/scatter-widget.ts +++ b/examples/components/widgets/scatter-widget.ts @@ -65,6 +65,11 @@ export class ScatterWidget extends BaseWidget { xAxisJoinOperation, yAxisColumn, yAxisJoinOperation, + viewState: this.viewState ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } : undefined, }); }, args: () => diff --git a/examples/components/widgets/table-widget.ts b/examples/components/widgets/table-widget.ts index f400101..07c68b1 100644 --- a/examples/components/widgets/table-widget.ts +++ b/examples/components/widgets/table-widget.ts @@ -69,6 +69,11 @@ export class TableWidget extends BaseWidget { ...(sortBy && {sortBy, sortDirection}), limit, spatialFilter: this.getSpatialFilterOrViewState(), + viewState: this.viewState ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } : undefined, }); }, args: () => @@ -136,11 +141,11 @@ function renderTableRow(row: unknown[]) { `; } -const _numberFormatter = new Intl.NumberFormat(); +const _numberFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2, notation: 'compact' }); function renderTableCell(value: unknown) { let formattedValue: string; if (typeof value === 'number') { - return _numberFormatter.format(value); + formattedValue = _numberFormatter.format(value); } else { formattedValue = String(value); } From 05ea3bdcc9e7cc30003b3e6ddd958f02e3db05c4 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 17:19:42 +0100 Subject: [PATCH 15/36] use new params in inner model call --- src/models/model.ts | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index ffdce14..a30ff3a 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -86,7 +86,9 @@ export function executeModel(props: { data, filters, filtersLogicalOperator = 'and', - geoColumn = DEFAULT_GEO_COLUMN, + spatialDataType = 'geo', + spatialFiltersMode = 'intersects', + spatialFiltersResolution = 0, } = source; const queryParameters = source.queryParameters @@ -103,18 +105,29 @@ export function executeModel(props: { filtersLogicalOperator, }; + const spatialDataColumn = source.spatialDataColumn || source.geoColumn || DEFAULT_GEO_COLUMN; + // Picking Model API requires 'spatialDataColumn'. if (model === 'pick') { - queryParams.spatialDataColumn = geoColumn; + queryParams.spatialDataColumn = spatialDataColumn; } // API supports multiple filters, we apply it only to geoColumn const spatialFilters = source.spatialFilter - ? {[geoColumn]: source.spatialFilter} + ? {[spatialDataColumn]: source.spatialFilter} : undefined; if (spatialFilters) { queryParams.spatialFilters = JSON.stringify(spatialFilters); + queryParams.spatialDataColumn = spatialDataColumn; + queryParams.spatialDataType = spatialDataType; + } + + if (spatialDataType !== 'geo') { + if (spatialFiltersResolution > 0) { + queryParams.spatialFiltersResolution = String(spatialFiltersResolution); + } + queryParams.spatialFiltersMode = spatialFiltersMode; } const urlWithSearchParams = From c684a72eab11afec8d7a06ec22f1c06a7cc731c3 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 17:22:49 +0100 Subject: [PATCH 16/36] example with h3 widgets --- examples/05-react-h3/app.tsx | 124 ++++++++++++++++++++++++++++++++ examples/05-react-h3/index.html | 12 ++++ examples/05-react-h3/react.tsx | 6 ++ examples/index.html | 1 + 4 files changed, 143 insertions(+) create mode 100644 examples/05-react-h3/app.tsx create mode 100644 examples/05-react-h3/index.html create mode 100644 examples/05-react-h3/react.tsx diff --git a/examples/05-react-h3/app.tsx b/examples/05-react-h3/app.tsx new file mode 100644 index 0000000..b860422 --- /dev/null +++ b/examples/05-react-h3/app.tsx @@ -0,0 +1,124 @@ +import React, {useEffect, useMemo, useState} from 'react'; +import {Map} from 'react-map-gl/maplibre'; +import DeckGL from '@deck.gl/react'; +import { h3TableSource, Filters } from '@carto/api-client'; +import { + CategoryWidget, + FormulaWidget, + HistogramWidget, + PieWidget, + ScatterWidget, + TableWidget, +} from '../components/index-react.js'; +import {MapView} from '@deck.gl/core'; +import {H3TileLayer} from '@deck.gl/carto'; +import {FilterEvent} from '../components/types.js'; + +const MAP_VIEW = new MapView({repeat: true}); +const MAP_STYLE = + 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json'; +const INITIAL_VIEW_STATE = {latitude: 37.3753636, longitude: -5.9962577, zoom: 6}; + +export function App(): JSX.Element { + const [viewState, setViewState] = useState({...INITIAL_VIEW_STATE}); + const [filters, setFilters] = useState({}); + const [attributionHTML, setAttributionHTML] = useState(''); + + // Update sources. + const data = useMemo(() => { + return h3TableSource({ + accessToken: import.meta.env.VITE_CARTO_ACCESS_TOKEN, + connectionName: 'carto_dw', + tableName: + 'carto-demo-data.demo_tables.derived_spatialfeatures_esp_h3res8_v1_yearly_v2', + filters, + aggregationExp: 'sum(population) as population', + }) + }, [filters]); + + // Update layers. + const layers = useMemo(() => { + return [ + new H3TileLayer({ + id: 'retail_stores', + data, + pointRadiusMinPixels: 4, + getFillColor: [200, 0, 80], + }), + ]; + }, [data]); + + useEffect(() => { + data?.then(({attribution}) => setAttributionHTML(attribution)); + }, [data]); + + return ( + <> +
+

React

+ ← Back +
+
+ setViewState(viewState)} + > + + +
+
+ + + setFilters((e as FilterEvent).detail.filters)} + > + setFilters((e as FilterEvent).detail.filters)} + > + + + +
+
+ + ); +} diff --git a/examples/05-react-h3/index.html b/examples/05-react-h3/index.html new file mode 100644 index 0000000..e3a4c2e --- /dev/null +++ b/examples/05-react-h3/index.html @@ -0,0 +1,12 @@ + + + + + Examples / React + + + +
+ + + diff --git a/examples/05-react-h3/react.tsx b/examples/05-react-h3/react.tsx new file mode 100644 index 0000000..bce03ce --- /dev/null +++ b/examples/05-react-h3/react.tsx @@ -0,0 +1,6 @@ +import React from 'react'; +import {createRoot} from 'react-dom/client'; +import {App} from './app'; + +const container = document.querySelector('#app')!; +createRoot(container).render(); diff --git a/examples/index.html b/examples/index.html index dba85e6..97686b7 100644 --- a/examples/index.html +++ b/examples/index.html @@ -19,6 +19,7 @@

Frameworks

  • react
  • svelte
  • vue
  • +
  • react-h3
  • angular (TODO)
  • From 6628b01ffef1deb58eab50e611e91cb6338d2bcf Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 4 Dec 2024 17:41:25 +0100 Subject: [PATCH 17/36] chore: run prettier --- examples/components/widgets/category-widget.ts | 12 +++++++----- examples/components/widgets/formula-widget.ts | 12 +++++++----- examples/components/widgets/histogram-widget.ts | 12 +++++++----- examples/components/widgets/scatter-widget.ts | 12 +++++++----- examples/components/widgets/table-widget.ts | 17 +++++++++++------ src/models/model.ts | 3 ++- 6 files changed, 41 insertions(+), 27 deletions(-) diff --git a/examples/components/widgets/category-widget.ts b/examples/components/widgets/category-widget.ts index 070079f..65b4c82 100644 --- a/examples/components/widgets/category-widget.ts +++ b/examples/components/widgets/category-widget.ts @@ -57,11 +57,13 @@ export class CategoryWidget extends BaseWidget { spatialFilter: this.getSpatialFilterOrViewState(), operation, column, - viewState: this.viewState ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } : undefined, + viewState: this.viewState + ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } + : undefined, }); }, args: () => diff --git a/examples/components/widgets/formula-widget.ts b/examples/components/widgets/formula-widget.ts index bc247c5..4727963 100644 --- a/examples/components/widgets/formula-widget.ts +++ b/examples/components/widgets/formula-widget.ts @@ -59,11 +59,13 @@ export class FormulaWidget extends BaseWidget { operation, column, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } : undefined, + viewState: this.viewState + ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } + : undefined, }); return response.value; }, diff --git a/examples/components/widgets/histogram-widget.ts b/examples/components/widgets/histogram-widget.ts index f3b12e0..46c9a40 100644 --- a/examples/components/widgets/histogram-widget.ts +++ b/examples/components/widgets/histogram-widget.ts @@ -58,11 +58,13 @@ export class HistogramWidget extends BaseWidget { column, operation, ticks, - viewState: this.viewState ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } : undefined, + viewState: this.viewState + ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } + : undefined, }); }, args: () => diff --git a/examples/components/widgets/scatter-widget.ts b/examples/components/widgets/scatter-widget.ts index 620607d..221e217 100644 --- a/examples/components/widgets/scatter-widget.ts +++ b/examples/components/widgets/scatter-widget.ts @@ -65,11 +65,13 @@ export class ScatterWidget extends BaseWidget { xAxisJoinOperation, yAxisColumn, yAxisJoinOperation, - viewState: this.viewState ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } : undefined, + viewState: this.viewState + ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } + : undefined, }); }, args: () => diff --git a/examples/components/widgets/table-widget.ts b/examples/components/widgets/table-widget.ts index 07c68b1..3e98144 100644 --- a/examples/components/widgets/table-widget.ts +++ b/examples/components/widgets/table-widget.ts @@ -69,11 +69,13 @@ export class TableWidget extends BaseWidget { ...(sortBy && {sortBy, sortDirection}), limit, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } : undefined, + viewState: this.viewState + ? { + zoom: this.viewState.zoom, + latitude: this.viewState.latitude, + longitude: this.viewState.longitude, + } + : undefined, }); }, args: () => @@ -141,7 +143,10 @@ function renderTableRow(row: unknown[]) { `; } -const _numberFormatter = new Intl.NumberFormat('en-US', { maximumFractionDigits: 2, notation: 'compact' }); +const _numberFormatter = new Intl.NumberFormat('en-US', { + maximumFractionDigits: 2, + notation: 'compact', +}); function renderTableCell(value: unknown) { let formattedValue: string; if (typeof value === 'number') { diff --git a/src/models/model.ts b/src/models/model.ts index a30ff3a..46e9f53 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -105,7 +105,8 @@ export function executeModel(props: { filtersLogicalOperator, }; - const spatialDataColumn = source.spatialDataColumn || source.geoColumn || DEFAULT_GEO_COLUMN; + const spatialDataColumn = + source.spatialDataColumn || source.geoColumn || DEFAULT_GEO_COLUMN; // Picking Model API requires 'spatialDataColumn'. if (model === 'pick') { From 046a077198ddcb1e0426f03166072648663c9460 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 12:22:26 +0100 Subject: [PATCH 18/36] add .env to .gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c49f67b..706eac5 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ custom-elements.json !.yarn/releases !.yarn/sdks !.yarn/versions +.env From 8251ceaa3ec483c21025c2b3fc7ea146d2777f95 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 13:49:08 +0100 Subject: [PATCH 19/36] simplify viewstate passing --- examples/components/widgets/category-widget.ts | 8 +------- examples/components/widgets/formula-widget.ts | 8 +------- examples/components/widgets/histogram-widget.ts | 8 +------- examples/components/widgets/scatter-widget.ts | 8 +------- examples/components/widgets/table-widget.ts | 8 +------- 5 files changed, 5 insertions(+), 35 deletions(-) diff --git a/examples/components/widgets/category-widget.ts b/examples/components/widgets/category-widget.ts index 65b4c82..04fa3f0 100644 --- a/examples/components/widgets/category-widget.ts +++ b/examples/components/widgets/category-widget.ts @@ -57,13 +57,7 @@ export class CategoryWidget extends BaseWidget { spatialFilter: this.getSpatialFilterOrViewState(), operation, column, - viewState: this.viewState - ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } - : undefined, + viewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/formula-widget.ts b/examples/components/widgets/formula-widget.ts index 4727963..c21c942 100644 --- a/examples/components/widgets/formula-widget.ts +++ b/examples/components/widgets/formula-widget.ts @@ -59,13 +59,7 @@ export class FormulaWidget extends BaseWidget { operation, column, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState - ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } - : undefined, + viewState: this.viewState ?? undefined, }); return response.value; }, diff --git a/examples/components/widgets/histogram-widget.ts b/examples/components/widgets/histogram-widget.ts index 46c9a40..8856e42 100644 --- a/examples/components/widgets/histogram-widget.ts +++ b/examples/components/widgets/histogram-widget.ts @@ -58,13 +58,7 @@ export class HistogramWidget extends BaseWidget { column, operation, ticks, - viewState: this.viewState - ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } - : undefined, + viewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/scatter-widget.ts b/examples/components/widgets/scatter-widget.ts index 221e217..803144d 100644 --- a/examples/components/widgets/scatter-widget.ts +++ b/examples/components/widgets/scatter-widget.ts @@ -65,13 +65,7 @@ export class ScatterWidget extends BaseWidget { xAxisJoinOperation, yAxisColumn, yAxisJoinOperation, - viewState: this.viewState - ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } - : undefined, + viewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/table-widget.ts b/examples/components/widgets/table-widget.ts index 3e98144..d11b38e 100644 --- a/examples/components/widgets/table-widget.ts +++ b/examples/components/widgets/table-widget.ts @@ -69,13 +69,7 @@ export class TableWidget extends BaseWidget { ...(sortBy && {sortBy, sortDirection}), limit, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState - ? { - zoom: this.viewState.zoom, - latitude: this.viewState.latitude, - longitude: this.viewState.longitude, - } - : undefined, + viewState: this.viewState ?? undefined, }); }, args: () => From c41dbe94b40f40c41f214970921e7c5945cb187e Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 13:50:03 +0100 Subject: [PATCH 20/36] simplify source prop type in getSpatialFiltersResolution --- src/spatial-index.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/src/spatial-index.ts b/src/spatial-index.ts index d06cd09..954793e 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -13,11 +13,7 @@ export function getSpatialFiltersResolution({ source, viewState, }: { - source: { - spatialDataType?: ModelSource['spatialDataType']; - dataResolution?: ModelSource['dataResolution']; - aggregationResLevel?: AggregationOptions['aggregationResLevel']; - }; + source: Partial; viewState?: { zoom: number; latitude: number; From 2f98cbc4fd1dcfbfdff86aee20527880bbf0aefc Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 13:51:33 +0100 Subject: [PATCH 21/36] simplify viewState prop type in getSpatialFiltersResolution --- src/spatial-index.ts | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 954793e..2871756 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -2,9 +2,10 @@ import { DEFAULT_AGGREGATION_RES_LEVEL_H3, DEFAULT_AGGREGATION_RES_LEVEL_QUADBIN, } from './constants-internal'; -import {ModelSource} from './models/model'; -import {AggregationOptions} from './sources/types'; +import type {ModelSource} from './models/model'; +import type {AggregationOptions} from './sources/types'; import {assert} from './utils'; +import type { ViewState } from './widget-sources'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; @@ -14,11 +15,7 @@ export function getSpatialFiltersResolution({ viewState, }: { source: Partial; - viewState?: { - zoom: number; - latitude: number; - longitude: number; - }; + viewState?: ViewState; }) { if (source.spatialDataType === 'geo') { return undefined; From adb0bf7dbbe1bb06d26feee2fd2f67e2adecfde3 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 13:59:40 +0100 Subject: [PATCH 22/36] chore: remove geoColumn --- src/models/model.ts | 6 ++---- src/widget-sources/widget-base-source.ts | 3 --- 2 files changed, 2 insertions(+), 7 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index 46e9f53..a026278 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -36,7 +36,6 @@ export interface ModelSource { data: string; filters?: Record; filtersLogicalOperator?: FilterLogicalOperator; - geoColumn?: string; spatialFilter?: SpatialFilter; queryParameters?: QueryParameters; spatialDataColumn?: string; @@ -105,15 +104,14 @@ export function executeModel(props: { filtersLogicalOperator, }; - const spatialDataColumn = - source.spatialDataColumn || source.geoColumn || DEFAULT_GEO_COLUMN; + const spatialDataColumn = source.spatialDataColumn || DEFAULT_GEO_COLUMN; // Picking Model API requires 'spatialDataColumn'. if (model === 'pick') { queryParams.spatialDataColumn = spatialDataColumn; } - // API supports multiple filters, we apply it only to geoColumn + // API supports multiple filters, we apply it only to spatialDataColumn const spatialFilters = source.spatialFilter ? {[spatialDataColumn]: source.spatialFilter} : undefined; diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index f9d65b6..fffd0ef 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -32,7 +32,6 @@ import {getSpatialFiltersResolution} from '../spatial-index.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; - geoColumn?: string; filters?: Record; filtersLogicalOperator?: FilterLogicalOperator; } @@ -53,7 +52,6 @@ export abstract class WidgetBaseSource { clientId: getClient(), filters: {}, filtersLogicalOperator: 'and', - geoColumn: DEFAULT_GEO_COLUMN, }; constructor(props: Props) { @@ -80,7 +78,6 @@ export abstract class WidgetBaseSource { connectionName: props.connectionName, filters: getApplicableFilters(owner, props.filters), filtersLogicalOperator: props.filtersLogicalOperator, - geoColumn: props.geoColumn, spatialDataType: props.spatialDataType, spatialDataColumn: props.spatialDataColumn, }; From 62c890e515a7476fa3e62c7bbe7b1113a3d6de40 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 5 Dec 2024 14:00:02 +0100 Subject: [PATCH 23/36] chore: run prettier --- src/spatial-index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 2871756..90c2543 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -5,7 +5,7 @@ import { import type {ModelSource} from './models/model'; import type {AggregationOptions} from './sources/types'; import {assert} from './utils'; -import type { ViewState } from './widget-sources'; +import type {ViewState} from './widget-sources'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; From 5703d127dd8e17df669ac2d91d747bde8e47ea69 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:03:08 +0100 Subject: [PATCH 24/36] Refactor model call query params (#41) --- src/models/model.ts | 47 ++++++++++++++---------- src/spatial-index.ts | 4 -- src/widget-sources/types.ts | 1 - src/widget-sources/widget-base-source.ts | 32 ++++++++-------- 4 files changed, 43 insertions(+), 41 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index a026278..f258e37 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -7,7 +7,7 @@ import { SpatialFilter, } from '../types.js'; import {$TODO} from '../types-internal.js'; -import {assert} from '../utils.js'; +import {assert, isPureObject} from '../utils.js'; import {ModelRequestOptions, makeCall} from './common.js'; import {ApiVersion} from '../constants.js'; import {SpatialDataType, SpatialFilterPolyfillMode} from '../sources/types.js'; @@ -90,17 +90,13 @@ export function executeModel(props: { spatialFiltersResolution = 0, } = source; - const queryParameters = source.queryParameters - ? JSON.stringify(source.queryParameters) - : ''; - - const queryParams: Record = { + const queryParams: Record = { type, client: clientId, source: data, - params: JSON.stringify(params), - queryParameters, - filters: JSON.stringify(filters), + params, + queryParameters: source.queryParameters, + filters, filtersLogicalOperator, }; @@ -117,31 +113,23 @@ export function executeModel(props: { : undefined; if (spatialFilters) { - queryParams.spatialFilters = JSON.stringify(spatialFilters); + queryParams.spatialFilters = spatialFilters; // JSON.stringify(spatialFilters); queryParams.spatialDataColumn = spatialDataColumn; queryParams.spatialDataType = spatialDataType; } if (spatialDataType !== 'geo') { if (spatialFiltersResolution > 0) { - queryParams.spatialFiltersResolution = String(spatialFiltersResolution); + queryParams.spatialFiltersResolution = spatialFiltersResolution; } queryParams.spatialFiltersMode = spatialFiltersMode; } const urlWithSearchParams = - url + '?' + new URLSearchParams(queryParams).toString(); + url + '?' + objectToURLSearchParams(queryParams).toString(); const isGet = urlWithSearchParams.length <= REQUEST_GET_MAX_URL_LENGTH; if (isGet) { url = urlWithSearchParams; - } else { - // undo the JSON.stringify, @TODO find a better pattern - queryParams.params = params as $TODO; - queryParams.filters = filters as $TODO; - queryParams.queryParameters = source.queryParameters as $TODO; - if (spatialFilters) { - queryParams.spatialFilters = spatialFilters as $TODO; - } } return makeCall({ url, @@ -153,3 +141,22 @@ export function executeModel(props: { }, }); } + +function objectToURLSearchParams(object: Record) { + const params = new URLSearchParams(); + for (const key in object) { + if (isPureObject(object[key])) { + params.append(key, JSON.stringify(object[key])); + } + else if (Array.isArray(object[key])) { + params.append(key, JSON.stringify(object[key])); + } + else if (object[key] === null) { + params.append(key, 'null'); + } + else if (object[key] !== undefined) { + params.append(key, String(object[key])); + } + } + return params; +} diff --git a/src/spatial-index.ts b/src/spatial-index.ts index 90c2543..f91d90a 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -17,10 +17,6 @@ export function getSpatialFiltersResolution({ source: Partial; viewState?: ViewState; }) { - if (source.spatialDataType === 'geo') { - return undefined; - } - assert( viewState, 'viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error' diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index d4d61b0..b868cf2 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -19,7 +19,6 @@ export interface ViewState { /** Common options for {@link WidgetBaseSource} requests. */ interface BaseRequestOptions { spatialFilter?: SpatialFilter; - spatialFiltersResolution?: number; spatialFiltersMode?: SpatialFilterPolyfillMode; abortController?: AbortController; filterOwner?: string; diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index fffd0ef..8c4bf4b 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -105,8 +105,8 @@ export abstract class WidgetBaseSource { const {column, operation, operationColumn} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -158,8 +158,8 @@ export abstract class WidgetBaseSource { const {columns, dataType, featureIds, z, limit, tileResolution} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -211,8 +211,8 @@ export abstract class WidgetBaseSource { const {column, operation} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -256,8 +256,8 @@ export abstract class WidgetBaseSource { const {column, operation, ticks} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -312,8 +312,8 @@ export abstract class WidgetBaseSource { const {column} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -357,8 +357,8 @@ export abstract class WidgetBaseSource { const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -411,8 +411,8 @@ export abstract class WidgetBaseSource { const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, @@ -480,8 +480,8 @@ export abstract class WidgetBaseSource { const source = this.getModelSource(filterOwner); - let spatialFiltersResolution = options.spatialFiltersResolution; - if (spatialFilter && !spatialFiltersResolution) { + let spatialFiltersResolution: number | undefined; + if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, viewState, From cbea41b4f760e53da06b759d1136aa885ed88d77 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:04:31 +0100 Subject: [PATCH 25/36] run prettier --- src/models/model.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/models/model.ts b/src/models/model.ts index f258e37..f5572b7 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -147,14 +147,11 @@ function objectToURLSearchParams(object: Record) { for (const key in object) { if (isPureObject(object[key])) { params.append(key, JSON.stringify(object[key])); - } - else if (Array.isArray(object[key])) { + } else if (Array.isArray(object[key])) { params.append(key, JSON.stringify(object[key])); - } - else if (object[key] === null) { + } else if (object[key] === null) { params.append(key, 'null'); - } - else if (object[key] !== undefined) { + } else if (object[key] !== undefined) { params.append(key, String(object[key])); } } From b0af864a04ce5e912c37cc65551b3a5f8c77feb1 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:11:25 +0100 Subject: [PATCH 26/36] query parameters default --- src/models/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/model.ts b/src/models/model.ts index f5572b7..385a127 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -95,7 +95,7 @@ export function executeModel(props: { client: clientId, source: data, params, - queryParameters: source.queryParameters, + queryParameters: source.queryParameters || '', filters, filtersLogicalOperator, }; From e3bbde62aa12f6a7565c780a95990d7b9db73020 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:16:18 +0100 Subject: [PATCH 27/36] chore(release): v0.4.1-alpha.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 45e4e6a..4262064 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.0-alpha.6", + "version": "0.4.1-alpha.0", "license": "MIT", "publishConfig": { "access": "public", From 125385b025b5d85f3559d533634279b019fd0457 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:48:22 +0100 Subject: [PATCH 28/36] chore(release): v0.4.1-alpha.1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index fb34f66..2cab8ab 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.1-alpha.0", + "version": "0.4.1-alpha.1", "license": "MIT", "publishConfig": { "access": "public", From 1d47f7b729df7f8b6c4e1c4b81925b2ce5aad1d2 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Tue, 10 Dec 2024 12:56:07 +0100 Subject: [PATCH 29/36] chore(release): v0.4.1-alpha.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2cab8ab..fb34f66 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.1-alpha.1", + "version": "0.4.1-alpha.0", "license": "MIT", "publishConfig": { "access": "public", From 26b89489604b73ea0d6793d9552817b75995a9dd Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 12 Dec 2024 18:22:28 +0100 Subject: [PATCH 30/36] pass dataResolution from source to widgets --- src/widget-sources/widget-base-source.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 8c4bf4b..3616840 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -29,6 +29,7 @@ import { DEFAULT_TILE_RESOLUTION, } from '../constants-internal.js'; import {getSpatialFiltersResolution} from '../spatial-index.js'; +import { AggregationOptions } from '../sources/types.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; @@ -80,6 +81,7 @@ export abstract class WidgetBaseSource { filtersLogicalOperator: props.filtersLogicalOperator, spatialDataType: props.spatialDataType, spatialDataColumn: props.spatialDataColumn, + dataResolution: (props as Partial).dataResolution, }; } From f7220a02bba0478b3bc172bc41fdae5febe012d5 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 12 Dec 2024 18:31:08 +0100 Subject: [PATCH 31/36] run prettier --- src/sources/types.ts | 2 +- src/widget-sources/widget-base-source.ts | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sources/types.ts b/src/sources/types.ts index e0234f6..e8f14da 100644 --- a/src/sources/types.ts +++ b/src/sources/types.ts @@ -74,7 +74,7 @@ export type SourceOptionalOptions = { * - 4: 4096x4096 */ tileResolution?: TileResolution; - + /** * By default, local in-memory caching is enabled. */ diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 3616840..31cd770 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -29,7 +29,7 @@ import { DEFAULT_TILE_RESOLUTION, } from '../constants-internal.js'; import {getSpatialFiltersResolution} from '../spatial-index.js'; -import { AggregationOptions } from '../sources/types.js'; +import {AggregationOptions} from '../sources/types.js'; export interface WidgetBaseSourceProps extends Omit { apiVersion?: ApiVersion; From cb8711fffdc266f7b00667f8e0b501cd2e53fe47 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Thu, 12 Dec 2024 18:33:24 +0100 Subject: [PATCH 32/36] chore(release): v0.4.2-alpha.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 0b52505..5c853f3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "repository": "github:CartoDB/carto-api-client", "author": "Don McCurdy ", "packageManager": "yarn@4.3.1", - "version": "0.4.1", + "version": "0.4.2-alpha.0", "license": "MIT", "publishConfig": { "access": "public", From 2bb52df658256affc46634f21aaa726910dd7790 Mon Sep 17 00:00:00 2001 From: "Juan D. Jara" Date: Wed, 18 Dec 2024 17:36:27 +0100 Subject: [PATCH 33/36] Update src/models/model.ts Co-authored-by: Don McCurdy --- src/models/model.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/models/model.ts b/src/models/model.ts index 385a127..c890775 100644 --- a/src/models/model.ts +++ b/src/models/model.ts @@ -113,7 +113,7 @@ export function executeModel(props: { : undefined; if (spatialFilters) { - queryParams.spatialFilters = spatialFilters; // JSON.stringify(spatialFilters); + queryParams.spatialFilters = spatialFilters; queryParams.spatialDataColumn = spatialDataColumn; queryParams.spatialDataType = spatialDataType; } From 7c7a11f1e7964899e5fe88002586558ccdc86118 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 2 Jan 2025 17:25:30 -0500 Subject: [PATCH 34/36] rename widget source request param viewState -> spatialIndexReferenceViewState --- src/widget-sources/types.ts | 2 +- src/widget-sources/widget-base-source.ts | 40 +++++++++++------------- 2 files changed, 19 insertions(+), 23 deletions(-) diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index b868cf2..b8e0e91 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -20,9 +20,9 @@ export interface ViewState { interface BaseRequestOptions { spatialFilter?: SpatialFilter; spatialFiltersMode?: SpatialFilterPolyfillMode; + spatialIndexReferenceViewState?: ViewState; abortController?: AbortController; filterOwner?: string; - viewState?: ViewState; } /** Options for {@link WidgetBaseSource#getCategories}. */ diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index a6ee895..0bf488e 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -16,18 +16,14 @@ import { TableResponse, TimeSeriesRequestOptions, TimeSeriesResponse, - ViewState, } from './types.js'; import {FilterLogicalOperator, Filter} from '../types.js'; -import {assert, getApplicableFilters, normalizeObjectKeys} from '../utils.js'; +import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; import {SourceOptions} from '../sources/index.js'; import {ApiVersion, DEFAULT_API_BASE_URL} from '../constants.js'; -import { - DEFAULT_GEO_COLUMN, - DEFAULT_TILE_RESOLUTION, -} from '../constants-internal.js'; +import {DEFAULT_TILE_RESOLUTION} from '../constants-internal.js'; import {getSpatialFiltersResolution} from '../spatial-index.js'; import {AggregationOptions} from '../sources/types.js'; @@ -100,8 +96,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {column, operation, operationColumn} = params; @@ -111,7 +107,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -153,8 +149,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; @@ -164,7 +160,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -204,9 +200,9 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, operationExp, - viewState, ...params } = options; const {column, operation} = params; @@ -216,7 +212,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -250,8 +246,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {column, operation, ticks} = params; @@ -261,7 +257,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -306,8 +302,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {column} = params; @@ -317,7 +313,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -349,8 +345,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {xAxisColumn, xAxisJoinOperation, yAxisColumn, yAxisJoinOperation} = @@ -362,7 +358,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -405,8 +401,8 @@ export abstract class WidgetBaseSource { filterOwner, spatialFilter, spatialFiltersMode, + spatialIndexReferenceViewState, abortController, - viewState, ...params } = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; @@ -416,7 +412,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } @@ -464,7 +460,7 @@ export abstract class WidgetBaseSource { abortController, spatialFilter, spatialFiltersMode, - viewState, + spatialIndexReferenceViewState, ...params } = options; const { @@ -485,7 +481,7 @@ export abstract class WidgetBaseSource { if (spatialFilter && source.spatialDataType !== 'geo') { spatialFiltersResolution = getSpatialFiltersResolution({ source, - viewState, + viewState: spatialIndexReferenceViewState, }); } From ac2e2a5861db448fa70bbfb9e3e60837b2396c53 Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 2 Jan 2025 17:36:58 -0500 Subject: [PATCH 35/36] clean up, add helper WidgetBaseSource#_getSpatialFiltersResolution --- src/spatial-index.ts | 16 +-- src/widget-sources/widget-base-source.ts | 126 +++++++++++------------ 2 files changed, 65 insertions(+), 77 deletions(-) diff --git a/src/spatial-index.ts b/src/spatial-index.ts index f91d90a..6ff33a3 100644 --- a/src/spatial-index.ts +++ b/src/spatial-index.ts @@ -10,18 +10,10 @@ import type {ViewState} from './widget-sources'; const DEFAULT_TILE_SIZE = 512; const QUADBIN_ZOOM_MAX_OFFSET = 4; -export function getSpatialFiltersResolution({ - source, - viewState, -}: { - source: Partial; - viewState?: ViewState; -}) { - assert( - viewState, - 'viewState prop is required to compute automatic spatialFiltersResolution when using spatialFilter with spatial indexes. Either pass a `spatialFiltersResolution` prop or a `viewState` prop to avoid this error' - ); - +export function getSpatialFiltersResolution( + source: Partial, + viewState: ViewState +): number | undefined { const dataResolution = source.dataResolution ?? Number.MAX_VALUE; const aggregationResLevel = diff --git a/src/widget-sources/widget-base-source.ts b/src/widget-sources/widget-base-source.ts index 0bf488e..a9c85f6 100644 --- a/src/widget-sources/widget-base-source.ts +++ b/src/widget-sources/widget-base-source.ts @@ -16,8 +16,9 @@ import { TableResponse, TimeSeriesRequestOptions, TimeSeriesResponse, + ViewState, } from './types.js'; -import {FilterLogicalOperator, Filter} from '../types.js'; +import {FilterLogicalOperator, Filter, SpatialFilter} from '../types.js'; import {getApplicableFilters, normalizeObjectKeys} from '../utils.js'; import {getClient} from '../client.js'; import {ModelSource} from '../models/model.js'; @@ -81,6 +82,25 @@ export abstract class WidgetBaseSource { }; } + protected _getSpatialFiltersResolution( + source: Omit, + spatialFilter?: SpatialFilter, + referenceViewState?: ViewState + ): number | undefined { + // spatialFiltersResolution applies only to spatial index sources. + if (!spatialFilter || source.spatialDataType === 'geo') { + return; + } + + if (!referenceViewState) { + throw new Error( + 'Missing required option, "spatialIndexReferenceViewState".' + ); + } + + return getSpatialFiltersResolution(source, referenceViewState); + } + /**************************************************************************** * CATEGORIES */ @@ -102,14 +122,11 @@ export abstract class WidgetBaseSource { } = options; const {column, operation, operationColumn} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type CategoriesModelResponse = {rows: {name: string; value: number}[]}; @@ -155,14 +172,11 @@ export abstract class WidgetBaseSource { } = options; const {columns, dataType, featureIds, z, limit, tileResolution} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type FeaturesModelResponse = {rows: Record[]}; @@ -207,14 +221,11 @@ export abstract class WidgetBaseSource { } = options; const {column, operation} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type FormulaModelResponse = {rows: {value: number}[]}; @@ -252,14 +263,11 @@ export abstract class WidgetBaseSource { } = options; const {column, operation, ticks} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type HistogramModelResponse = {rows: {tick: number; value: number}[]}; @@ -308,14 +316,11 @@ export abstract class WidgetBaseSource { } = options; const {column} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type RangeModelResponse = {rows: {min: number; max: number}[]}; @@ -353,14 +358,11 @@ export abstract class WidgetBaseSource { params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); // Make sure this is sync with the same constant in cloud-native/maps-api const HARD_LIMIT = 500; @@ -407,14 +409,11 @@ export abstract class WidgetBaseSource { } = options; const {columns, sortBy, sortDirection, offset = 0, limit = 10} = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type TableModelResponse = { rows: Record[]; @@ -476,14 +475,11 @@ export abstract class WidgetBaseSource { } = params; const source = this.getModelSource(filterOwner); - - let spatialFiltersResolution: number | undefined; - if (spatialFilter && source.spatialDataType !== 'geo') { - spatialFiltersResolution = getSpatialFiltersResolution({ - source, - viewState: spatialIndexReferenceViewState, - }); - } + const spatialFiltersResolution = this._getSpatialFiltersResolution( + source, + spatialFilter, + spatialIndexReferenceViewState + ); type TimeSeriesModelResponse = { rows: {name: string; value: number}[]; From 8d3bf7ec1321c4e47bf68099c2ede7d6f0527caf Mon Sep 17 00:00:00 2001 From: Don McCurdy Date: Thu, 2 Jan 2025 17:46:26 -0500 Subject: [PATCH 36/36] clean up examples --- examples/{05-react-h3 => 05-spatial-index}/app.tsx | 14 +++++++++----- .../{05-react-h3 => 05-spatial-index}/index.html | 2 +- .../{05-react-h3 => 05-spatial-index}/react.tsx | 0 examples/components/widgets/category-widget.ts | 2 +- examples/components/widgets/formula-widget.ts | 2 +- examples/components/widgets/histogram-widget.ts | 2 +- examples/components/widgets/scatter-widget.ts | 2 +- examples/components/widgets/table-widget.ts | 2 +- examples/index.html | 8 ++++++-- src/widget-sources/types.ts | 1 + 10 files changed, 22 insertions(+), 13 deletions(-) rename examples/{05-react-h3 => 05-spatial-index}/app.tsx (93%) rename examples/{05-react-h3 => 05-spatial-index}/index.html (84%) rename examples/{05-react-h3 => 05-spatial-index}/react.tsx (100%) diff --git a/examples/05-react-h3/app.tsx b/examples/05-spatial-index/app.tsx similarity index 93% rename from examples/05-react-h3/app.tsx rename to examples/05-spatial-index/app.tsx index b860422..55731ff 100644 --- a/examples/05-react-h3/app.tsx +++ b/examples/05-spatial-index/app.tsx @@ -1,7 +1,7 @@ import React, {useEffect, useMemo, useState} from 'react'; import {Map} from 'react-map-gl/maplibre'; import DeckGL from '@deck.gl/react'; -import { h3TableSource, Filters } from '@carto/api-client'; +import {h3TableSource, Filters} from '@carto/api-client'; import { CategoryWidget, FormulaWidget, @@ -17,7 +17,11 @@ import {FilterEvent} from '../components/types.js'; const MAP_VIEW = new MapView({repeat: true}); const MAP_STYLE = 'https://basemaps.cartocdn.com/gl/positron-nolabels-gl-style/style.json'; -const INITIAL_VIEW_STATE = {latitude: 37.3753636, longitude: -5.9962577, zoom: 6}; +const INITIAL_VIEW_STATE = { + latitude: 37.3753636, + longitude: -5.9962577, + zoom: 6, +}; export function App(): JSX.Element { const [viewState, setViewState] = useState({...INITIAL_VIEW_STATE}); @@ -33,7 +37,7 @@ export function App(): JSX.Element { 'carto-demo-data.demo_tables.derived_spatialfeatures_esp_h3res8_v1_yearly_v2', filters, aggregationExp: 'sum(population) as population', - }) + }); }, [filters]); // Update layers. @@ -55,7 +59,7 @@ export function App(): JSX.Element { return ( <>
    -

    React

    +

    Spatial Index

    ← Back
    @@ -98,7 +102,7 @@ export function App(): JSX.Element { viewState={viewState} header="Pop. Distribution" columns={['population', 'male', 'female']} - sortBy='population' + sortBy="population" > - Examples / React + Examples / Spatial Index diff --git a/examples/05-react-h3/react.tsx b/examples/05-spatial-index/react.tsx similarity index 100% rename from examples/05-react-h3/react.tsx rename to examples/05-spatial-index/react.tsx diff --git a/examples/components/widgets/category-widget.ts b/examples/components/widgets/category-widget.ts index 04fa3f0..8376fe3 100644 --- a/examples/components/widgets/category-widget.ts +++ b/examples/components/widgets/category-widget.ts @@ -57,7 +57,7 @@ export class CategoryWidget extends BaseWidget { spatialFilter: this.getSpatialFilterOrViewState(), operation, column, - viewState: this.viewState ?? undefined, + spatialIndexReferenceViewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/formula-widget.ts b/examples/components/widgets/formula-widget.ts index c21c942..7fcb1be 100644 --- a/examples/components/widgets/formula-widget.ts +++ b/examples/components/widgets/formula-widget.ts @@ -59,7 +59,7 @@ export class FormulaWidget extends BaseWidget { operation, column, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState ?? undefined, + spatialIndexReferenceViewState: this.viewState ?? undefined, }); return response.value; }, diff --git a/examples/components/widgets/histogram-widget.ts b/examples/components/widgets/histogram-widget.ts index 8856e42..097d346 100644 --- a/examples/components/widgets/histogram-widget.ts +++ b/examples/components/widgets/histogram-widget.ts @@ -58,7 +58,7 @@ export class HistogramWidget extends BaseWidget { column, operation, ticks, - viewState: this.viewState ?? undefined, + spatialIndexReferenceViewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/scatter-widget.ts b/examples/components/widgets/scatter-widget.ts index 803144d..a3f7e80 100644 --- a/examples/components/widgets/scatter-widget.ts +++ b/examples/components/widgets/scatter-widget.ts @@ -65,7 +65,7 @@ export class ScatterWidget extends BaseWidget { xAxisJoinOperation, yAxisColumn, yAxisJoinOperation, - viewState: this.viewState ?? undefined, + spatialIndexReferenceViewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/components/widgets/table-widget.ts b/examples/components/widgets/table-widget.ts index d11b38e..c70bb4b 100644 --- a/examples/components/widgets/table-widget.ts +++ b/examples/components/widgets/table-widget.ts @@ -69,7 +69,7 @@ export class TableWidget extends BaseWidget { ...(sortBy && {sortBy, sortDirection}), limit, spatialFilter: this.getSpatialFilterOrViewState(), - viewState: this.viewState ?? undefined, + spatialIndexReferenceViewState: this.viewState ?? undefined, }); }, args: () => diff --git a/examples/index.html b/examples/index.html index 97686b7..296c17a 100644 --- a/examples/index.html +++ b/examples/index.html @@ -19,8 +19,12 @@

    Frameworks

  • react
  • svelte
  • vue
  • -
  • react-h3
  • -
  • angular (TODO)
  • + +

    Features

    +
      +
    1. + spatial index +
    diff --git a/src/widget-sources/types.ts b/src/widget-sources/types.ts index b8e0e91..9d96868 100644 --- a/src/widget-sources/types.ts +++ b/src/widget-sources/types.ts @@ -20,6 +20,7 @@ export interface ViewState { interface BaseRequestOptions { spatialFilter?: SpatialFilter; spatialFiltersMode?: SpatialFilterPolyfillMode; + /** Required for table- and query-based spatial index sources (H3, Quadbin). */ spatialIndexReferenceViewState?: ViewState; abortController?: AbortController; filterOwner?: string;