@carto/api-client 0.4.5-alpha.0 → 0.4.6-0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (79) hide show
  1. package/CHANGELOG.md +5 -0
  2. package/build/api-client.cjs +2179 -216
  3. package/build/api-client.cjs.map +1 -1
  4. package/build/api-client.modern.js +2061 -226
  5. package/build/api-client.modern.js.map +1 -1
  6. package/build/constants.d.ts +22 -0
  7. package/build/filters/Filter.d.ts +13 -0
  8. package/build/filters/FilterTypes.d.ts +3 -0
  9. package/build/filters/geosjonFeatures.d.ts +8 -0
  10. package/build/filters/index.d.ts +6 -0
  11. package/build/filters/tileFeatures.d.ts +20 -0
  12. package/build/filters/tileFeaturesGeometries.d.ts +13 -0
  13. package/build/filters/tileFeaturesSpatialIndex.d.ts +10 -0
  14. package/build/index.d.ts +4 -0
  15. package/build/models/index.d.ts +1 -1
  16. package/build/operations/aggregation.d.ts +8 -0
  17. package/build/operations/applySorting.d.ts +20 -0
  18. package/build/operations/groupBy.d.ts +15 -0
  19. package/build/operations/groupByDate.d.ts +11 -0
  20. package/build/operations/histogram.d.ts +13 -0
  21. package/build/operations/index.d.ts +6 -0
  22. package/build/operations/scatterPlot.d.ts +14 -0
  23. package/build/sources/h3-tileset-source.d.ts +2 -1
  24. package/build/sources/index.d.ts +2 -2
  25. package/build/sources/quadbin-tileset-source.d.ts +2 -1
  26. package/build/sources/vector-tileset-source.d.ts +2 -1
  27. package/build/types-internal.d.ts +4 -0
  28. package/build/types.d.ts +61 -1
  29. package/build/utils/dateUtils.d.ts +10 -0
  30. package/build/utils/getTileFormat.d.ts +3 -0
  31. package/build/utils/makeIntervalComplete.d.ts +2 -0
  32. package/build/utils/transformTileCoordsToWGS84.d.ts +8 -0
  33. package/build/utils/transformToTileCoords.d.ts +9 -0
  34. package/build/widget-sources/index.d.ts +3 -1
  35. package/build/widget-sources/types.d.ts +31 -22
  36. package/build/widget-sources/widget-query-source.d.ts +2 -2
  37. package/build/widget-sources/widget-remote-source.d.ts +18 -0
  38. package/build/widget-sources/{widget-base-source.d.ts → widget-source.d.ts} +13 -38
  39. package/build/widget-sources/widget-table-source.d.ts +2 -2
  40. package/build/widget-sources/widget-tileset-source.d.ts +76 -0
  41. package/package.json +9 -3
  42. package/src/constants.ts +25 -0
  43. package/src/filters/Filter.ts +169 -0
  44. package/src/filters/FilterTypes.ts +109 -0
  45. package/src/filters/geosjonFeatures.ts +32 -0
  46. package/src/filters/index.ts +6 -0
  47. package/src/filters/tileFeatures.ts +56 -0
  48. package/src/filters/tileFeaturesGeometries.ts +444 -0
  49. package/src/filters/tileFeaturesSpatialIndex.ts +119 -0
  50. package/src/index.ts +6 -0
  51. package/src/models/index.ts +1 -1
  52. package/src/operations/aggregation.ts +154 -0
  53. package/src/operations/applySorting.ts +109 -0
  54. package/src/operations/groupBy.ts +59 -0
  55. package/src/operations/groupByDate.ts +98 -0
  56. package/src/operations/histogram.ts +66 -0
  57. package/src/operations/index.ts +6 -0
  58. package/src/operations/scatterPlot.ts +50 -0
  59. package/src/sources/h3-tileset-source.ts +18 -6
  60. package/src/sources/index.ts +3 -1
  61. package/src/sources/quadbin-tileset-source.ts +18 -6
  62. package/src/sources/raster-source.ts +1 -0
  63. package/src/sources/vector-query-source.ts +5 -2
  64. package/src/sources/vector-table-source.ts +5 -2
  65. package/src/sources/vector-tileset-source.ts +19 -6
  66. package/src/types-internal.ts +6 -0
  67. package/src/types.ts +60 -2
  68. package/src/utils/dateUtils.ts +28 -0
  69. package/src/utils/getTileFormat.ts +9 -0
  70. package/src/utils/makeIntervalComplete.ts +17 -0
  71. package/src/utils/transformTileCoordsToWGS84.ts +77 -0
  72. package/src/utils/transformToTileCoords.ts +85 -0
  73. package/src/widget-sources/index.ts +3 -1
  74. package/src/widget-sources/types.ts +32 -22
  75. package/src/widget-sources/widget-query-source.ts +6 -3
  76. package/src/widget-sources/{widget-base-source.ts → widget-remote-source.ts} +12 -147
  77. package/src/widget-sources/widget-source.ts +160 -0
  78. package/src/widget-sources/widget-table-source.ts +6 -3
  79. package/src/widget-sources/widget-tileset-source.ts +407 -0
@@ -3,41 +3,34 @@ import { FilterLogicalOperator, Filter, SpatialFilter } from '../types.js';
3
3
  import { ModelSource } from '../models/model.js';
4
4
  import { SourceOptions } from '../sources/index.js';
5
5
  import { ApiVersion } from '../constants.js';
6
- export interface WidgetBaseSourceProps extends Omit<SourceOptions, 'filters'> {
6
+ export interface WidgetSourceProps extends Omit<SourceOptions, 'filters'> {
7
7
  apiVersion?: ApiVersion;
8
8
  filters?: Record<string, Filter>;
9
9
  filtersLogicalOperator?: FilterLogicalOperator;
10
10
  }
11
- export type WidgetSource = WidgetBaseSource<WidgetBaseSourceProps>;
12
11
  /**
13
12
  * Source for Widget API requests on a data source defined by a SQL query.
14
13
  *
15
14
  * Abstract class. Use {@link WidgetQuerySource} or {@link WidgetTableSource}.
16
15
  */
17
- export declare abstract class WidgetBaseSource<Props extends WidgetBaseSourceProps> {
16
+ export declare abstract class WidgetSource<Props extends WidgetSourceProps> {
18
17
  readonly props: Props;
19
- static defaultProps: Partial<WidgetBaseSourceProps>;
18
+ static defaultProps: Partial<WidgetSourceProps>;
20
19
  constructor(props: Props);
21
20
  /**
22
- * Subclasses of {@link WidgetBaseSource} must implement this method, calling
23
- * {@link WidgetBaseSource.prototype._getModelSource} for common source
21
+ * Subclasses of {@link WidgetRemoteSource} must implement this method, calling
22
+ * {@link WidgetRemoteSource.prototype._getModelSource} for common source
24
23
  * properties, and adding additional required properties including 'type' and
25
24
  * 'data'.
26
25
  */
27
26
  protected abstract getModelSource(owner: string | undefined): ModelSource;
28
27
  protected _getModelSource(owner?: string): Omit<ModelSource, 'type' | 'data'>;
29
28
  protected _getSpatialFiltersResolution(source: Omit<ModelSource, 'type' | 'data'>, spatialFilter?: SpatialFilter, referenceViewState?: ViewState): number | undefined;
30
- /****************************************************************************
31
- * CATEGORIES
32
- */
33
29
  /**
34
30
  * Returns a list of labeled datapoints for categorical data. Suitable for
35
31
  * charts including grouped bar charts, pie charts, and tree charts.
36
32
  */
37
- getCategories(options: CategoryRequestOptions): Promise<CategoryResponse>;
38
- /****************************************************************************
39
- * FEATURES
40
- */
33
+ abstract getCategories(options: CategoryRequestOptions): Promise<CategoryResponse>;
41
34
  /**
42
35
  * Given a list of feature IDs (as found in `_carto_feature_id`) returns all
43
36
  * matching features. In datasets containing features with duplicate geometries,
@@ -46,54 +39,36 @@ export declare abstract class WidgetBaseSource<Props extends WidgetBaseSourcePro
46
39
  * @internal
47
40
  * @experimental
48
41
  */
49
- getFeatures(options: FeaturesRequestOptions): Promise<FeaturesResponse>;
50
- /****************************************************************************
51
- * FORMULA
52
- */
42
+ abstract getFeatures(options: FeaturesRequestOptions): Promise<FeaturesResponse>;
53
43
  /**
54
44
  * Returns a scalar numerical statistic over all matching data. Suitable
55
45
  * for 'headline' or 'scorecard' figures such as counts and sums.
56
46
  */
57
- getFormula(options: FormulaRequestOptions): Promise<FormulaResponse>;
58
- /****************************************************************************
59
- * HISTOGRAM
60
- */
47
+ abstract getFormula(options: FormulaRequestOptions): Promise<FormulaResponse>;
61
48
  /**
62
49
  * Returns a list of labeled datapoints for 'bins' of data defined as ticks
63
50
  * over a numerical range. Suitable for histogram charts.
64
51
  */
65
- getHistogram(options: HistogramRequestOptions): Promise<HistogramResponse>;
66
- /****************************************************************************
67
- * RANGE
68
- */
52
+ abstract getHistogram(options: HistogramRequestOptions): Promise<HistogramResponse>;
69
53
  /**
70
54
  * Returns a range (min and max) for a numerical column of matching rows.
71
55
  * Suitable for displaying certain 'headline' or 'scorecard' statistics,
72
56
  * or rendering a range slider UI for filtering.
73
57
  */
74
- getRange(options: RangeRequestOptions): Promise<RangeResponse>;
75
- /****************************************************************************
76
- * SCATTER
77
- */
58
+ abstract getRange(options: RangeRequestOptions): Promise<RangeResponse>;
78
59
  /**
79
60
  * Returns a list of bivariate datapoints defined as numerical 'x' and 'y'
80
61
  * values. Suitable for rendering scatter plots.
81
62
  */
82
- getScatter(options: ScatterRequestOptions): Promise<ScatterResponse>;
83
- /****************************************************************************
84
- * TABLE
85
- */
63
+ abstract getScatter(options: ScatterRequestOptions): Promise<ScatterResponse>;
86
64
  /**
87
65
  * Returns a list of arbitrary data rows, with support for pagination and
88
66
  * sorting. Suitable for displaying tables and lists.
89
67
  */
90
- getTable(options: TableRequestOptions): Promise<TableResponse>;
91
- /****************************************************************************
92
- * TIME SERIES
93
- */
68
+ abstract getTable(options: TableRequestOptions): Promise<TableResponse>;
94
69
  /**
95
70
  * Returns a series of labeled numerical values, grouped into equally-sized
96
71
  * time intervals. Suitable for rendering time series charts.
97
72
  */
98
- getTimeSeries(options: TimeSeriesRequestOptions): Promise<TimeSeriesResponse>;
73
+ abstract getTimeSeries(options: TimeSeriesRequestOptions): Promise<TimeSeriesResponse>;
99
74
  }
@@ -1,5 +1,5 @@
1
1
  import { H3TableSourceOptions, QuadbinTableSourceOptions, VectorTableSourceOptions } from '../sources/index.js';
2
- import { WidgetBaseSource, WidgetBaseSourceProps } from './widget-base-source.js';
2
+ import { WidgetRemoteSource, WidgetRemoteSourceProps } from './widget-remote-source.js';
3
3
  import { ModelSource } from '../models/model.js';
4
4
  type LayerTableSourceOptions = Omit<VectorTableSourceOptions, 'filters'> | Omit<H3TableSourceOptions, 'filters'> | Omit<QuadbinTableSourceOptions, 'filters'>;
5
5
  export type WidgetTableSourceResult = {
@@ -27,7 +27,7 @@ export type WidgetTableSourceResult = {
27
27
  * const { widgetSource } = await data;
28
28
  * ```
29
29
  */
30
- export declare class WidgetTableSource extends WidgetBaseSource<LayerTableSourceOptions & WidgetBaseSourceProps> {
30
+ export declare class WidgetTableSource extends WidgetRemoteSource<LayerTableSourceOptions & WidgetRemoteSourceProps> {
31
31
  protected getModelSource(owner: string): ModelSource;
32
32
  }
33
33
  export {};
@@ -0,0 +1,76 @@
1
+ import { TilesetSourceOptions } from '../sources/index.js';
2
+ import type { ModelSource } from '../models/index.js';
3
+ import { CategoryRequestOptions, CategoryResponse, FeaturesRequestOptions, FeaturesResponse, FormulaRequestOptions, FormulaResponse, HistogramRequestOptions, HistogramResponse, RangeRequestOptions, RangeResponse, ScatterRequestOptions, ScatterResponse, TableRequestOptions, TableResponse, TimeSeriesRequestOptions, TimeSeriesResponse } from './types.js';
4
+ import { TileFormat } from '../constants.js';
5
+ import { SpatialFilter } from '../types.js';
6
+ import { TileFeatureExtractOptions } from '../filters/index.js';
7
+ import { FeatureCollection } from 'geojson';
8
+ import { SpatialDataType } from '../sources/types.js';
9
+ import { WidgetSource, WidgetSourceProps } from './widget-source.js';
10
+ export type WidgetTilesetSourceProps = WidgetSourceProps & Omit<TilesetSourceOptions, 'filters'> & {
11
+ tileFormat: TileFormat;
12
+ spatialDataType: SpatialDataType;
13
+ };
14
+ export type WidgetTilesetSourceResult = {
15
+ widgetSource: WidgetTilesetSource;
16
+ };
17
+ /**
18
+ * Source for Widget API requests on a data source defined by a tileset.
19
+ *
20
+ * Generally not intended to be constructed directly. Instead, call
21
+ * {@link vectorTilesetSource}, {@link h3TilesetSource}, or {@link quadbinTilesetSource},
22
+ * which can be shared with map layers. Sources contain a `widgetSource`
23
+ * property, for use by widget implementations.
24
+ *
25
+ * Example:
26
+ *
27
+ * ```javascript
28
+ * import { vectorTilesetSource } from '@carto/api-client';
29
+ *
30
+ * const data = vectorTilesetSource({
31
+ * accessToken: '••••',
32
+ * connectionName: 'carto_dw',
33
+ * tableName: 'carto-demo-data.demo_rasters.my_tileset_source'
34
+ * });
35
+ *
36
+ * const { widgetSource } = await data;
37
+ * ```
38
+ */
39
+ export declare class WidgetTilesetSource extends WidgetSource<WidgetTilesetSourceProps> {
40
+ private _tiles;
41
+ private _features;
42
+ protected getModelSource(owner: string): ModelSource;
43
+ /**
44
+ * Loads features as a list of tiles (typically provided by deck.gl).
45
+ * After tiles are loaded, {@link extractTileFeatures} must be called
46
+ * before computing statistics on the tiles.
47
+ */
48
+ loadTiles(tiles: unknown[]): void;
49
+ /**
50
+ * Extracts feature data from tiles previously loaded with {@link loadTiles}.
51
+ * Must be called before computing statistics on tiles.
52
+ */
53
+ extractTileFeatures({ spatialFilter, uniqueIdProperty, options, }: {
54
+ spatialFilter: SpatialFilter;
55
+ uniqueIdProperty?: string;
56
+ options?: TileFeatureExtractOptions;
57
+ }): void;
58
+ /** Loads features as GeoJSON (used for testing). */
59
+ loadGeoJSON({ geojson, spatialFilter, uniqueIdProperty, }: {
60
+ geojson: FeatureCollection;
61
+ spatialFilter: SpatialFilter;
62
+ uniqueIdProperty?: string;
63
+ }): void;
64
+ getFeatures(options: FeaturesRequestOptions): Promise<FeaturesResponse>;
65
+ getFormula({ column, operation, joinOperation, filterOwner, }: FormulaRequestOptions): Promise<FormulaResponse>;
66
+ getHistogram({ operation, ticks, column, joinOperation, filterOwner, }: HistogramRequestOptions): Promise<HistogramResponse>;
67
+ getCategories({ column, operation, operationColumn, joinOperation, filterOwner, }: CategoryRequestOptions): Promise<CategoryResponse>;
68
+ getScatter({ xAxisColumn, yAxisColumn, xAxisJoinOperation, yAxisJoinOperation, filterOwner, }: ScatterRequestOptions): Promise<ScatterResponse>;
69
+ getTable(options: TableRequestOptions): Promise<TableResponse>;
70
+ getTimeSeries({ column, stepSize, operation, operationColumn, joinOperation, filterOwner, }: TimeSeriesRequestOptions): Promise<TimeSeriesResponse>;
71
+ getRange({ column, filterOwner, }: RangeRequestOptions): Promise<RangeResponse>;
72
+ /****************************************************************************
73
+ * INTERNAL
74
+ */
75
+ private _getFilteredFeatures;
76
+ }
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "repository": "github:CartoDB/carto-api-client",
5
5
  "author": "Don McCurdy <donmccurdy@carto.com>",
6
6
  "packageManager": "yarn@4.3.1",
7
- "version": "0.4.5-alpha.0",
7
+ "version": "0.4.6-0",
8
8
  "license": "MIT",
9
9
  "publishConfig": {
10
10
  "access": "public",
@@ -52,12 +52,17 @@
52
52
  "LICENSE.md"
53
53
  ],
54
54
  "dependencies": {
55
+ "@loaders.gl/schema": "^4.3.3",
55
56
  "@turf/bbox-clip": "^7.1.0",
56
57
  "@turf/bbox-polygon": "^7.1.0",
58
+ "@turf/boolean-intersects": "^7.1.0",
59
+ "@turf/boolean-within": "^7.1.0",
57
60
  "@turf/helpers": "^7.1.0",
61
+ "@turf/intersect": "^7.1.0",
58
62
  "@turf/invariant": "^7.1.0",
59
63
  "@turf/union": "^7.1.0",
60
- "@types/geojson": "^7946.0.15"
64
+ "@types/geojson": "^7946.0.15",
65
+ "h3-js": "4.1.0"
61
66
  },
62
67
  "devDependencies": {
63
68
  "@deck.gl/aggregation-layers": "^9.0.38",
@@ -86,6 +91,7 @@
86
91
  "prettier": "^3.4.2",
87
92
  "rimraf": "^6.0.1",
88
93
  "semver": "^7.6.3",
94
+ "thenby": "^1.3.4",
89
95
  "typescript": "~5.7.3",
90
96
  "vite": "^6.0.7",
91
97
  "vitest": "2.1.8"
@@ -93,5 +99,5 @@
93
99
  "resolutions": {
94
100
  "rollup": "^4.20.0"
95
101
  },
96
- "stableVersion": "0.4.4"
102
+ "stableVersion": "0.4.5"
97
103
  }
package/src/constants.ts CHANGED
@@ -31,3 +31,28 @@ export enum ApiVersion {
31
31
 
32
32
  /** @internalRemarks Source: @carto/constants, @deck.gl/carto */
33
33
  export const DEFAULT_API_BASE_URL = 'https://gcp-us-east1.api.carto.com';
34
+
35
+ /** @internalRemarks Source: @carto/react-core */
36
+ export enum TileFormat {
37
+ MVT = 'mvt',
38
+ JSON = 'json',
39
+ GEOJSON = 'geojson',
40
+ BINARY = 'binary',
41
+ }
42
+
43
+ /** @internalRemarks Source: @carto/react-core */
44
+ export enum SpatialIndex {
45
+ H3 = 'h3',
46
+ S2 = 's2',
47
+ QUADBIN = 'quadbin',
48
+ }
49
+
50
+ /** @internalRemarks Source: @carto/react-core */
51
+ export enum Provider {
52
+ BIGQUERY = 'bigquery',
53
+ REDSHIFT = 'redshift',
54
+ POSTGRES = 'postgres',
55
+ SNOWFLAKE = 'snowflake',
56
+ DATABRICKS = 'databricks',
57
+ DATABRICKS_REST = 'databricksRest',
58
+ }
@@ -0,0 +1,169 @@
1
+ import {filterFunctions} from './FilterTypes';
2
+ import {Filter, FilterLogicalOperator, Filters} from '../types';
3
+ import {Feature} from 'geojson';
4
+ import {FilterType} from '../constants';
5
+ import {FeatureData} from '../types-internal';
6
+ import {BinaryFeature} from '@loaders.gl/schema';
7
+
8
+ const LOGICAL_OPERATOR_METHODS: Record<
9
+ FilterLogicalOperator,
10
+ 'every' | 'some'
11
+ > = {
12
+ and: 'every',
13
+ or: 'some',
14
+ };
15
+
16
+ function passesFilter(
17
+ columns: string[],
18
+ filters: Filters,
19
+ feature: FeatureData,
20
+ filtersLogicalOperator: FilterLogicalOperator
21
+ ): boolean {
22
+ const method = LOGICAL_OPERATOR_METHODS[filtersLogicalOperator];
23
+ return columns[method]((column) => {
24
+ const columnFilters = filters[column];
25
+ const columnFilterTypes = Object.keys(columnFilters) as FilterType[];
26
+
27
+ if (!feature || feature[column] === null || feature[column] === undefined) {
28
+ return false;
29
+ }
30
+
31
+ return columnFilterTypes.every((filter) => {
32
+ const filterFunction = filterFunctions[filter];
33
+
34
+ if (!filterFunction) {
35
+ throw new Error(`"${filter}" filter is not implemented.`);
36
+ }
37
+
38
+ return filterFunction(
39
+ columnFilters[filter]!.values,
40
+ feature[column],
41
+ (columnFilters[filter] as Filter[FilterType.STRING_SEARCH])!.params
42
+ );
43
+ });
44
+ });
45
+ }
46
+
47
+ export function buildFeatureFilter({
48
+ filters = {},
49
+ type = 'boolean',
50
+ filtersLogicalOperator = 'and',
51
+ }: {
52
+ filters?: Filters;
53
+ type?: 'number' | 'boolean';
54
+ filtersLogicalOperator?: FilterLogicalOperator;
55
+ }) {
56
+ const columns = Object.keys(filters);
57
+
58
+ if (!columns.length) {
59
+ return () => (type === 'number' ? 1 : true);
60
+ }
61
+
62
+ return (feature: Feature | FeatureData) => {
63
+ const f = feature.properties || feature;
64
+ const featurePassesFilter = passesFilter(
65
+ columns,
66
+ filters,
67
+ f as FeatureData,
68
+ filtersLogicalOperator
69
+ );
70
+
71
+ return type === 'number'
72
+ ? Number(featurePassesFilter)
73
+ : featurePassesFilter;
74
+ };
75
+ }
76
+
77
+ // Apply certain filters to a collection of features
78
+ export function applyFilters(
79
+ features: FeatureData[],
80
+ filters: Filters,
81
+ filtersLogicalOperator: FilterLogicalOperator
82
+ ) {
83
+ return Object.keys(filters).length
84
+ ? features.filter(buildFeatureFilter({filters, filtersLogicalOperator}))
85
+ : features;
86
+ }
87
+
88
+ // Binary
89
+ export function buildBinaryFeatureFilter({filters = {}}: {filters: Filters}) {
90
+ const columns = Object.keys(filters);
91
+
92
+ if (!columns.length) {
93
+ return () => 1;
94
+ }
95
+
96
+ return (featureIdIdx: number, binaryData: BinaryFeature) =>
97
+ passesFilterUsingBinary(columns, filters, featureIdIdx, binaryData);
98
+ }
99
+
100
+ function getValueFromNumericProps(
101
+ featureIdIdx: number,
102
+ binaryData: BinaryFeature,
103
+ {column}: {column: string}
104
+ ) {
105
+ return binaryData.numericProps?.[column]?.value[featureIdIdx];
106
+ }
107
+
108
+ function getValueFromProperties(
109
+ featureIdIdx: number,
110
+ binaryData: BinaryFeature,
111
+ {column}: {column: string}
112
+ ) {
113
+ const propertyIdx = binaryData.featureIds.value[featureIdIdx];
114
+ return (binaryData.properties[propertyIdx] as Record<string, unknown>)?.[
115
+ column
116
+ ];
117
+ }
118
+
119
+ const GET_VALUE_BY_BINARY_PROP = {
120
+ properties: getValueFromProperties,
121
+ numericProps: getValueFromNumericProps,
122
+ };
123
+
124
+ function getBinaryPropertyByFilterValues(filterValues: unknown[]) {
125
+ return typeof filterValues.flat()[0] === 'string'
126
+ ? 'properties'
127
+ : 'numericProps';
128
+ }
129
+
130
+ function getFeatureValue(
131
+ featureIdIdx: number,
132
+ binaryData: any,
133
+ filter: {type: FilterType; column: string; values: unknown[]}
134
+ ) {
135
+ const {column, values} = filter;
136
+ const binaryProp = getBinaryPropertyByFilterValues(values);
137
+ const getFeatureValueFn = GET_VALUE_BY_BINARY_PROP[binaryProp];
138
+ return getFeatureValueFn(featureIdIdx, binaryData, {column});
139
+ }
140
+
141
+ function passesFilterUsingBinary(
142
+ columns: string[],
143
+ filters: Filters,
144
+ featureIdIdx: number,
145
+ binaryData: BinaryFeature
146
+ ) {
147
+ return columns.every((column) => {
148
+ const columnFilters = filters[column];
149
+
150
+ return Object.entries(columnFilters).every(([type, {values}]) => {
151
+ const filterFn = filterFunctions[type as FilterType];
152
+ if (!filterFn) {
153
+ throw new Error(`"${type}" filter is not implemented.`);
154
+ }
155
+
156
+ if (!values) return 0;
157
+
158
+ const featureValue = getFeatureValue(featureIdIdx, binaryData, {
159
+ type: type as FilterType,
160
+ column,
161
+ values,
162
+ });
163
+
164
+ if (featureValue === undefined || featureValue === null) return 0;
165
+
166
+ return filterFn(values, featureValue);
167
+ });
168
+ });
169
+ }
@@ -0,0 +1,109 @@
1
+ import {FilterType} from '../constants';
2
+ import {FilterInterval, StringSearchOptions} from '../types';
3
+ import {makeIntervalComplete} from '../utils/makeIntervalComplete.js';
4
+
5
+ export type FilterFunction = (
6
+ filterValues: unknown[],
7
+ featureValue: unknown,
8
+ params?: Record<string, unknown>
9
+ ) => boolean;
10
+
11
+ export const filterFunctions: Record<FilterType, FilterFunction> = {
12
+ [FilterType.IN]: filterIn,
13
+ [FilterType.BETWEEN]: filterBetween,
14
+ [FilterType.TIME]: filterTime,
15
+ [FilterType.CLOSED_OPEN]: filterClosedOpen,
16
+ [FilterType.STRING_SEARCH]: filterStringSearch,
17
+ };
18
+
19
+ function filterIn(filterValues: unknown[], featureValue: unknown): boolean {
20
+ return filterValues.includes(featureValue);
21
+ }
22
+
23
+ // FilterTypes.BETWEEN
24
+ function filterBetween(
25
+ filterValues: unknown[],
26
+ featureValue: unknown
27
+ ): boolean {
28
+ const checkRange = (range: [number, number]) => {
29
+ const [lowerBound, upperBound] = range;
30
+ return (
31
+ (featureValue as number) >= lowerBound &&
32
+ (featureValue as number) <= upperBound
33
+ );
34
+ };
35
+
36
+ return makeIntervalComplete(filterValues as FilterInterval[]).some(
37
+ checkRange
38
+ );
39
+ }
40
+
41
+ function filterTime(filterValues: unknown[], featureValue: unknown) {
42
+ const featureValueAsTimestamp = new Date(featureValue as number).getTime();
43
+ if (isFinite(featureValueAsTimestamp)) {
44
+ return filterBetween(filterValues, featureValueAsTimestamp);
45
+ } else {
46
+ throw new Error(`Column used to filter by time isn't well formatted.`);
47
+ }
48
+ }
49
+
50
+ // FilterTypes.CLOSED_OPEN
51
+ function filterClosedOpen(
52
+ filterValues: unknown[],
53
+ featureValue: unknown
54
+ ): boolean {
55
+ const checkRange = (range: [number, number]) => {
56
+ const [lowerBound, upperBound] = range;
57
+ return (
58
+ (featureValue as number) >= lowerBound &&
59
+ (featureValue as number) < upperBound
60
+ );
61
+ };
62
+
63
+ return makeIntervalComplete(filterValues as [number, number][]).some(
64
+ checkRange
65
+ );
66
+ }
67
+
68
+ // FilterTypes.STRING_SEARCH
69
+ function filterStringSearch(
70
+ filterValues: unknown[],
71
+ featureValue: unknown,
72
+ params: StringSearchOptions = {}
73
+ ): boolean {
74
+ const normalizedFeatureValue = normalize(featureValue, params);
75
+ const stringRegExp = params.useRegExp
76
+ ? filterValues
77
+ : filterValues.map((filterValue) => {
78
+ let stringRegExp = escapeRegExp(normalize(filterValue, params));
79
+
80
+ if (params.mustStart) stringRegExp = `^${stringRegExp}`;
81
+ if (params.mustEnd) stringRegExp = `${stringRegExp}$`;
82
+
83
+ return stringRegExp;
84
+ });
85
+
86
+ const regex = new RegExp(
87
+ stringRegExp.join('|'),
88
+ params.caseSensitive ? 'g' : 'gi'
89
+ );
90
+ return !!normalizedFeatureValue.match(regex);
91
+ }
92
+
93
+ // Aux
94
+ const specialCharRegExp = /[.*+?^${}()|[\]\\]/g;
95
+ const normalizeRegExp = /\p{Diacritic}/gu;
96
+
97
+ function escapeRegExp(value: string) {
98
+ return value.replace(specialCharRegExp, '\\$&');
99
+ }
100
+
101
+ function normalize(data: unknown, params: StringSearchOptions) {
102
+ let normalizedData = String(data);
103
+ if (!params.keepSpecialCharacters)
104
+ normalizedData = normalizedData
105
+ .normalize('NFD')
106
+ .replace(normalizeRegExp, '');
107
+
108
+ return normalizedData;
109
+ }
@@ -0,0 +1,32 @@
1
+ import intersects from '@turf/boolean-intersects';
2
+ import {FeatureCollection} from 'geojson';
3
+ import {SpatialFilter} from '..';
4
+ import {FeatureData} from '../types-internal';
5
+
6
+ export function geojsonFeatures({
7
+ geojson,
8
+ spatialFilter,
9
+ uniqueIdProperty,
10
+ }: {
11
+ geojson: FeatureCollection;
12
+ spatialFilter: SpatialFilter;
13
+ uniqueIdProperty?: string;
14
+ }): FeatureData[] {
15
+ let uniqueIdx = 0;
16
+ const map = new Map();
17
+
18
+ if (!spatialFilter) {
19
+ return [];
20
+ }
21
+
22
+ for (const feature of geojson.features) {
23
+ const uniqueId = uniqueIdProperty
24
+ ? feature.properties![uniqueIdProperty]
25
+ : ++uniqueIdx;
26
+ if (!map.has(uniqueId) && intersects(spatialFilter, feature)) {
27
+ map.set(uniqueId, feature.properties);
28
+ }
29
+ }
30
+
31
+ return Array.from(map.values());
32
+ }
@@ -0,0 +1,6 @@
1
+ export * from './Filter.js';
2
+ export * from './FilterTypes.js';
3
+ export * from './geosjonFeatures.js';
4
+ export * from './tileFeatures.js';
5
+ export * from './tileFeaturesGeometries.js';
6
+ export * from './tileFeaturesSpatialIndex.js';
@@ -0,0 +1,56 @@
1
+ import {SpatialFilter, SpatialIndexTile, Tile} from '../types';
2
+ import {tileFeaturesGeometries} from './tileFeaturesGeometries';
3
+ import {tileFeaturesSpatialIndex} from './tileFeaturesSpatialIndex';
4
+ import {SpatialIndex, TileFormat} from '../constants';
5
+ import {DEFAULT_GEO_COLUMN} from '../constants-internal';
6
+ import {FeatureData} from '../types-internal';
7
+ import {SpatialDataType} from '../sources/types';
8
+
9
+ /** @internalRemarks Source: @carto/react-core */
10
+ export type TileFeatures = {
11
+ tiles: Tile[];
12
+ tileFormat: TileFormat;
13
+ spatialDataType: SpatialDataType;
14
+ spatialDataColumn?: string;
15
+ spatialFilter?: SpatialFilter;
16
+ uniqueIdProperty?: string;
17
+ options?: TileFeatureExtractOptions;
18
+ };
19
+
20
+ /** @internalRemarks Source: @carto/react-core */
21
+ export type TileFeatureExtractOptions = {
22
+ storeGeometry?: boolean;
23
+ };
24
+
25
+ /** @internalRemarks Source: @carto/react-core */
26
+ export function tileFeatures({
27
+ tiles,
28
+ spatialFilter,
29
+ uniqueIdProperty,
30
+ tileFormat,
31
+ spatialDataColumn = DEFAULT_GEO_COLUMN,
32
+ spatialDataType,
33
+ options = {},
34
+ }: TileFeatures): FeatureData[] {
35
+ // TODO(cleanup): Is an empty response the expected result when spatialFilter
36
+ // is omitted? Why not make the parameter required, or return the full input?
37
+ if (!spatialFilter) {
38
+ return [];
39
+ }
40
+
41
+ if (spatialDataType !== 'geo') {
42
+ return tileFeaturesSpatialIndex({
43
+ tiles: tiles as SpatialIndexTile[],
44
+ spatialFilter,
45
+ spatialDataColumn,
46
+ spatialDataType,
47
+ });
48
+ }
49
+ return tileFeaturesGeometries({
50
+ tiles,
51
+ tileFormat,
52
+ spatialFilter,
53
+ uniqueIdProperty,
54
+ options,
55
+ });
56
+ }