@carto/api-client 0.4.5 → 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.
- package/build/api-client.cjs +2179 -216
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.modern.js +2061 -226
- package/build/api-client.modern.js.map +1 -1
- package/build/constants.d.ts +22 -0
- package/build/filters/Filter.d.ts +13 -0
- package/build/filters/FilterTypes.d.ts +3 -0
- package/build/filters/geosjonFeatures.d.ts +8 -0
- package/build/filters/index.d.ts +6 -0
- package/build/filters/tileFeatures.d.ts +20 -0
- package/build/filters/tileFeaturesGeometries.d.ts +13 -0
- package/build/filters/tileFeaturesSpatialIndex.d.ts +10 -0
- package/build/index.d.ts +4 -0
- package/build/models/index.d.ts +1 -1
- package/build/operations/aggregation.d.ts +8 -0
- package/build/operations/applySorting.d.ts +20 -0
- package/build/operations/groupBy.d.ts +15 -0
- package/build/operations/groupByDate.d.ts +11 -0
- package/build/operations/histogram.d.ts +13 -0
- package/build/operations/index.d.ts +6 -0
- package/build/operations/scatterPlot.d.ts +14 -0
- package/build/sources/h3-tileset-source.d.ts +2 -1
- package/build/sources/index.d.ts +1 -1
- package/build/sources/quadbin-tileset-source.d.ts +2 -1
- package/build/sources/vector-tileset-source.d.ts +2 -1
- package/build/types-internal.d.ts +4 -0
- package/build/types.d.ts +61 -1
- package/build/utils/dateUtils.d.ts +10 -0
- package/build/utils/getTileFormat.d.ts +3 -0
- package/build/utils/makeIntervalComplete.d.ts +2 -0
- package/build/utils/transformTileCoordsToWGS84.d.ts +8 -0
- package/build/utils/transformToTileCoords.d.ts +9 -0
- package/build/widget-sources/index.d.ts +3 -1
- package/build/widget-sources/types.d.ts +31 -22
- package/build/widget-sources/widget-query-source.d.ts +2 -2
- package/build/widget-sources/widget-remote-source.d.ts +18 -0
- package/build/widget-sources/{widget-base-source.d.ts → widget-source.d.ts} +13 -38
- package/build/widget-sources/widget-table-source.d.ts +2 -2
- package/build/widget-sources/widget-tileset-source.d.ts +76 -0
- package/package.json +10 -3
- package/src/constants.ts +25 -0
- package/src/filters/Filter.ts +169 -0
- package/src/filters/FilterTypes.ts +109 -0
- package/src/filters/geosjonFeatures.ts +32 -0
- package/src/filters/index.ts +6 -0
- package/src/filters/tileFeatures.ts +56 -0
- package/src/filters/tileFeaturesGeometries.ts +444 -0
- package/src/filters/tileFeaturesSpatialIndex.ts +119 -0
- package/src/index.ts +6 -0
- package/src/models/index.ts +1 -1
- package/src/operations/aggregation.ts +154 -0
- package/src/operations/applySorting.ts +109 -0
- package/src/operations/groupBy.ts +59 -0
- package/src/operations/groupByDate.ts +98 -0
- package/src/operations/histogram.ts +66 -0
- package/src/operations/index.ts +6 -0
- package/src/operations/scatterPlot.ts +50 -0
- package/src/sources/h3-tileset-source.ts +18 -6
- package/src/sources/index.ts +1 -1
- package/src/sources/quadbin-tileset-source.ts +18 -6
- package/src/sources/raster-source.ts +1 -0
- package/src/sources/vector-query-source.ts +5 -2
- package/src/sources/vector-table-source.ts +5 -2
- package/src/sources/vector-tileset-source.ts +19 -6
- package/src/types-internal.ts +6 -0
- package/src/types.ts +60 -2
- package/src/utils/dateUtils.ts +28 -0
- package/src/utils/getTileFormat.ts +9 -0
- package/src/utils/makeIntervalComplete.ts +17 -0
- package/src/utils/transformTileCoordsToWGS84.ts +77 -0
- package/src/utils/transformToTileCoords.ts +85 -0
- package/src/widget-sources/index.ts +3 -1
- package/src/widget-sources/types.ts +32 -22
- package/src/widget-sources/widget-query-source.ts +6 -3
- package/src/widget-sources/{widget-base-source.ts → widget-remote-source.ts} +12 -147
- package/src/widget-sources/widget-source.ts +160 -0
- package/src/widget-sources/widget-table-source.ts +6 -3
- 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
|
|
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
|
|
16
|
+
export declare abstract class WidgetSource<Props extends WidgetSourceProps> {
|
|
18
17
|
readonly props: Props;
|
|
19
|
-
static defaultProps: Partial<
|
|
18
|
+
static defaultProps: Partial<WidgetSourceProps>;
|
|
20
19
|
constructor(props: Props);
|
|
21
20
|
/**
|
|
22
|
-
* Subclasses of {@link
|
|
23
|
-
* {@link
|
|
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 {
|
|
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
|
|
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.
|
|
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,11 +91,13 @@
|
|
|
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"
|
|
92
98
|
},
|
|
93
99
|
"resolutions": {
|
|
94
100
|
"rollup": "^4.20.0"
|
|
95
|
-
}
|
|
101
|
+
},
|
|
102
|
+
"stableVersion": "0.4.5"
|
|
96
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,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
|
+
}
|