@carto/api-client 0.5.0-alpha.7 → 0.5.0-alpha.8
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 +66 -52
- package/build/api-client.d.cts +61 -54
- package/build/api-client.d.ts +61 -54
- package/build/api-client.js +2101 -113
- package/build/worker.js +2119 -4
- package/package.json +5 -1
- package/src/sources/h3-tileset-source.ts +1 -8
- package/src/sources/quadbin-tileset-source.ts +1 -8
- package/src/sources/vector-tileset-source.ts +1 -8
- package/src/widget-sources/index.ts +0 -1
- package/src/widget-sources/widget-source.ts +11 -0
- package/src/widget-sources/widget-tileset-source-impl.ts +417 -0
- package/src/widget-sources/widget-tileset-source.ts +192 -331
- package/src/workers/widget-tileset-worker.ts +6 -3
- package/build/chunk-LEI5PI5X.js +0 -2063
- package/src/widget-sources/widget-tileset-worker-source.ts +0 -240
- package/src/workers/utils.ts +0 -33
package/package.json
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
"homepage": "https://github.com/CartoDB/carto-api-client#readme",
|
|
9
9
|
"author": "Don McCurdy <donmccurdy@carto.com>",
|
|
10
10
|
"packageManager": "yarn@4.3.1",
|
|
11
|
-
"version": "0.5.0-alpha.
|
|
11
|
+
"version": "0.5.0-alpha.8",
|
|
12
12
|
"license": "MIT",
|
|
13
13
|
"type": "module",
|
|
14
14
|
"sideEffects": false,
|
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
"test": "vitest run --typecheck",
|
|
41
41
|
"test:watch": "vitest watch --typecheck",
|
|
42
42
|
"coverage": "vitest run --coverage.enabled --coverage.all false",
|
|
43
|
+
"benchmark": "node scripts/benchmark.js",
|
|
43
44
|
"lint": "eslint .",
|
|
44
45
|
"format": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --write",
|
|
45
46
|
"format:check": "prettier \"**/*.{cjs,html,js,json,md,ts}\" --check",
|
|
@@ -86,6 +87,8 @@
|
|
|
86
87
|
"@luma.gl/core": "~9.1.0",
|
|
87
88
|
"@luma.gl/engine": "~9.1.0",
|
|
88
89
|
"@luma.gl/shadertools": "~9.1.0",
|
|
90
|
+
"@turf/buffer": "^7.2.0",
|
|
91
|
+
"@turf/random": "^7.2.0",
|
|
89
92
|
"@types/json-schema": "^7.0.15",
|
|
90
93
|
"@types/react": "^18.3.18",
|
|
91
94
|
"@types/semver": "^7.5.8",
|
|
@@ -101,6 +104,7 @@
|
|
|
101
104
|
"rimraf": "^6.0.1",
|
|
102
105
|
"semver": "^7.7.1",
|
|
103
106
|
"thenby": "^1.3.4",
|
|
107
|
+
"tinybench": "^3.1.1",
|
|
104
108
|
"tsup": "^8.3.6",
|
|
105
109
|
"typescript": "~5.7.3",
|
|
106
110
|
"typescript-eslint": "^8.24.0",
|
|
@@ -6,9 +6,7 @@ import {getTileFormat} from '../utils/getTileFormat.js';
|
|
|
6
6
|
import {
|
|
7
7
|
WidgetTilesetSource,
|
|
8
8
|
WidgetTilesetSourceResult,
|
|
9
|
-
WidgetTilesetWorkerSource,
|
|
10
9
|
} from '../widget-sources/index.js';
|
|
11
|
-
import {isModuleWorkerSupported} from '../workers/utils.js';
|
|
12
10
|
import {baseSource} from './base-source.js';
|
|
13
11
|
import type {
|
|
14
12
|
SourceOptions,
|
|
@@ -28,15 +26,10 @@ export const h3TilesetSource = async function (
|
|
|
28
26
|
const {tableName, spatialDataColumn = 'h3'} = options;
|
|
29
27
|
const urlParameters: UrlParameters = {name: tableName};
|
|
30
28
|
|
|
31
|
-
const WidgetSourceClass =
|
|
32
|
-
options.widgetSourceWorker !== false && isModuleWorkerSupported()
|
|
33
|
-
? WidgetTilesetWorkerSource
|
|
34
|
-
: WidgetTilesetSource;
|
|
35
|
-
|
|
36
29
|
return baseSource<UrlParameters>('tileset', options, urlParameters).then(
|
|
37
30
|
(result) => ({
|
|
38
31
|
...(result as TilejsonResult),
|
|
39
|
-
widgetSource: new
|
|
32
|
+
widgetSource: new WidgetTilesetSource({
|
|
40
33
|
...options,
|
|
41
34
|
tileFormat: getTileFormat(result as TilejsonResult),
|
|
42
35
|
spatialDataColumn,
|
|
@@ -6,9 +6,7 @@ import {getTileFormat} from '../utils/getTileFormat.js';
|
|
|
6
6
|
import {
|
|
7
7
|
WidgetTilesetSource,
|
|
8
8
|
WidgetTilesetSourceResult,
|
|
9
|
-
WidgetTilesetWorkerSource,
|
|
10
9
|
} from '../widget-sources/index.js';
|
|
11
|
-
import {isModuleWorkerSupported} from '../workers/utils.js';
|
|
12
10
|
import {baseSource} from './base-source.js';
|
|
13
11
|
import type {
|
|
14
12
|
SourceOptions,
|
|
@@ -28,15 +26,10 @@ export const quadbinTilesetSource = async function (
|
|
|
28
26
|
const {tableName, spatialDataColumn = 'quadbin'} = options;
|
|
29
27
|
const urlParameters: UrlParameters = {name: tableName};
|
|
30
28
|
|
|
31
|
-
const WidgetSourceClass =
|
|
32
|
-
options.widgetSourceWorker !== false && isModuleWorkerSupported()
|
|
33
|
-
? WidgetTilesetWorkerSource
|
|
34
|
-
: WidgetTilesetSource;
|
|
35
|
-
|
|
36
29
|
return baseSource<UrlParameters>('tileset', options, urlParameters).then(
|
|
37
30
|
(result) => ({
|
|
38
31
|
...(result as TilejsonResult),
|
|
39
|
-
widgetSource: new
|
|
32
|
+
widgetSource: new WidgetTilesetSource({
|
|
40
33
|
...options,
|
|
41
34
|
tileFormat: getTileFormat(result as TilejsonResult),
|
|
42
35
|
spatialDataColumn,
|
|
@@ -7,9 +7,7 @@ import {getTileFormat} from '../utils/getTileFormat.js';
|
|
|
7
7
|
import {
|
|
8
8
|
WidgetTilesetSource,
|
|
9
9
|
WidgetTilesetSourceResult,
|
|
10
|
-
WidgetTilesetWorkerSource,
|
|
11
10
|
} from '../widget-sources/index.js';
|
|
12
|
-
import {isModuleWorkerSupported} from '../workers/utils.js';
|
|
13
11
|
import {baseSource} from './base-source.js';
|
|
14
12
|
import type {
|
|
15
13
|
SourceOptions,
|
|
@@ -29,15 +27,10 @@ export const vectorTilesetSource = async function (
|
|
|
29
27
|
const {tableName, spatialDataColumn = DEFAULT_GEO_COLUMN} = options;
|
|
30
28
|
const urlParameters: UrlParameters = {name: tableName};
|
|
31
29
|
|
|
32
|
-
const WidgetSourceClass =
|
|
33
|
-
options.widgetSourceWorker !== false && isModuleWorkerSupported()
|
|
34
|
-
? WidgetTilesetWorkerSource
|
|
35
|
-
: WidgetTilesetSource;
|
|
36
|
-
|
|
37
30
|
return baseSource<UrlParameters>('tileset', options, urlParameters).then(
|
|
38
31
|
(result) => ({
|
|
39
32
|
...(result as TilejsonResult),
|
|
40
|
-
widgetSource: new
|
|
33
|
+
widgetSource: new WidgetTilesetSource({
|
|
41
34
|
...options,
|
|
42
35
|
tileFormat: getTileFormat(result as TilejsonResult),
|
|
43
36
|
spatialDataColumn,
|
|
@@ -50,6 +50,17 @@ export abstract class WidgetSource<Props extends WidgetSourceProps> {
|
|
|
50
50
|
this.props = {...WidgetSource.defaultProps, ...props};
|
|
51
51
|
}
|
|
52
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Destroys the widget source and releases allocated resources.
|
|
55
|
+
*
|
|
56
|
+
* For remote sources (tables, queries) this has no effect, but for local
|
|
57
|
+
* sources (tilesets, rasters) these resources will affect performance
|
|
58
|
+
* and stability if many (10+) sources are created and not released.
|
|
59
|
+
*/
|
|
60
|
+
destroy() {
|
|
61
|
+
// no-op in most cases, but required for worker sources.
|
|
62
|
+
}
|
|
63
|
+
|
|
53
64
|
protected _getSpatialFiltersResolution(
|
|
54
65
|
source: Omit<ModelSource, 'type' | 'data'>,
|
|
55
66
|
spatialFilter?: SpatialFilter,
|
|
@@ -0,0 +1,417 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/require-await */
|
|
2
|
+
import {
|
|
3
|
+
CategoryRequestOptions,
|
|
4
|
+
CategoryResponse,
|
|
5
|
+
FeaturesResponse,
|
|
6
|
+
FormulaRequestOptions,
|
|
7
|
+
FormulaResponse,
|
|
8
|
+
HistogramRequestOptions,
|
|
9
|
+
HistogramResponse,
|
|
10
|
+
RangeRequestOptions,
|
|
11
|
+
RangeResponse,
|
|
12
|
+
ScatterRequestOptions,
|
|
13
|
+
ScatterResponse,
|
|
14
|
+
TableRequestOptions,
|
|
15
|
+
TableResponse,
|
|
16
|
+
TimeSeriesRequestOptions,
|
|
17
|
+
TimeSeriesResponse,
|
|
18
|
+
} from './types.js';
|
|
19
|
+
import {InvalidColumnError, assert, getApplicableFilters} from '../utils.js';
|
|
20
|
+
import {Filter, SpatialFilter, Tile} from '../types.js';
|
|
21
|
+
import {
|
|
22
|
+
TileFeatureExtractOptions,
|
|
23
|
+
applyFilters,
|
|
24
|
+
geojsonFeatures,
|
|
25
|
+
tileFeatures,
|
|
26
|
+
} from '../filters/index.js';
|
|
27
|
+
import {
|
|
28
|
+
aggregationFunctions,
|
|
29
|
+
applySorting,
|
|
30
|
+
groupValuesByColumn,
|
|
31
|
+
groupValuesByDateColumn,
|
|
32
|
+
histogram,
|
|
33
|
+
scatterPlot,
|
|
34
|
+
} from '../operations/index.js';
|
|
35
|
+
import {FeatureData} from '../types-internal.js';
|
|
36
|
+
import {FeatureCollection} from 'geojson';
|
|
37
|
+
import {WidgetSource} from './widget-source.js';
|
|
38
|
+
import {booleanEqual} from '@turf/boolean-equal';
|
|
39
|
+
import type {WidgetTilesetSourceProps} from './widget-tileset-source.js';
|
|
40
|
+
|
|
41
|
+
// TODO(cleanup): Parameter defaults in source functions and widget API calls are
|
|
42
|
+
// currently duplicated and possibly inconsistent. Consider consolidating and
|
|
43
|
+
// operating on Required<T> objects. See:
|
|
44
|
+
// https://github.com/CartoDB/carto-api-client/issues/39
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Local (in-memory) implementation of tileset widget calculations. This class
|
|
48
|
+
* may be instantiated by {@link WidgetTilesetSource} in a Web Worker when
|
|
49
|
+
* supported, or on the main thread.
|
|
50
|
+
*/
|
|
51
|
+
export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourceProps> {
|
|
52
|
+
private _tiles: Tile[] = [];
|
|
53
|
+
private _features: FeatureData[] = [];
|
|
54
|
+
private _tileFeatureExtractOptions: TileFeatureExtractOptions = {};
|
|
55
|
+
private _tileFeatureExtractPreviousInputs: {spatialFilter?: SpatialFilter} =
|
|
56
|
+
{};
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Loads features as a list of tiles (typically provided by deck.gl).
|
|
60
|
+
* After tiles are loaded, {@link extractTileFeatures} must be called
|
|
61
|
+
* before computing statistics on the tiles.
|
|
62
|
+
*/
|
|
63
|
+
loadTiles(tiles: unknown[]) {
|
|
64
|
+
this._tiles = tiles as Tile[];
|
|
65
|
+
this._features.length = 0;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
/** Configures options used to extract features from tiles. */
|
|
69
|
+
setTileFeatureExtractOptions(options: TileFeatureExtractOptions) {
|
|
70
|
+
this._tileFeatureExtractOptions = options;
|
|
71
|
+
this._features.length = 0;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
protected _extractTileFeatures(spatialFilter: SpatialFilter) {
|
|
75
|
+
// When spatial filter has not changed, don't redo extraction. If tiles or
|
|
76
|
+
// tile extract options change, features will have been cleared already.
|
|
77
|
+
const prevInputs = this._tileFeatureExtractPreviousInputs;
|
|
78
|
+
if (
|
|
79
|
+
this._features.length &&
|
|
80
|
+
prevInputs.spatialFilter &&
|
|
81
|
+
booleanEqual(prevInputs.spatialFilter, spatialFilter)
|
|
82
|
+
) {
|
|
83
|
+
return;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
this._features = tileFeatures({
|
|
87
|
+
tiles: this._tiles,
|
|
88
|
+
tileFormat: this.props.tileFormat,
|
|
89
|
+
...this._tileFeatureExtractOptions,
|
|
90
|
+
|
|
91
|
+
spatialFilter,
|
|
92
|
+
spatialDataColumn: this.props.spatialDataColumn,
|
|
93
|
+
spatialDataType: this.props.spatialDataType,
|
|
94
|
+
});
|
|
95
|
+
|
|
96
|
+
prevInputs.spatialFilter = spatialFilter;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
/**
|
|
100
|
+
* Loads features as GeoJSON (used for testing).
|
|
101
|
+
* @experimental
|
|
102
|
+
* @internal Not for public use. Spatial filters in other method calls will be ignored.
|
|
103
|
+
*/
|
|
104
|
+
loadGeoJSON({
|
|
105
|
+
geojson,
|
|
106
|
+
spatialFilter,
|
|
107
|
+
}: {
|
|
108
|
+
geojson: FeatureCollection;
|
|
109
|
+
spatialFilter: SpatialFilter;
|
|
110
|
+
}) {
|
|
111
|
+
this._features = geojsonFeatures({
|
|
112
|
+
geojson,
|
|
113
|
+
spatialFilter,
|
|
114
|
+
...this._tileFeatureExtractOptions,
|
|
115
|
+
});
|
|
116
|
+
this._tileFeatureExtractPreviousInputs.spatialFilter = spatialFilter;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
override async getFeatures(): Promise<FeaturesResponse> {
|
|
120
|
+
throw new Error('getFeatures not supported for tilesets');
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async getFormula({
|
|
124
|
+
column = '*',
|
|
125
|
+
operation = 'count',
|
|
126
|
+
joinOperation,
|
|
127
|
+
filters,
|
|
128
|
+
filterOwner,
|
|
129
|
+
spatialFilter,
|
|
130
|
+
}: FormulaRequestOptions): Promise<FormulaResponse> {
|
|
131
|
+
if (operation === 'custom') {
|
|
132
|
+
throw new Error('Custom aggregation not supported for tilesets');
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// Column is required except when operation is 'count'.
|
|
136
|
+
if ((column && column !== '*') || operation !== 'count') {
|
|
137
|
+
assertColumn(this._features, column);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
141
|
+
spatialFilter,
|
|
142
|
+
filters,
|
|
143
|
+
filterOwner
|
|
144
|
+
);
|
|
145
|
+
|
|
146
|
+
if (filteredFeatures.length === 0 && operation !== 'count') {
|
|
147
|
+
return {value: null};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
const targetOperation = aggregationFunctions[operation];
|
|
151
|
+
return {
|
|
152
|
+
value: targetOperation(filteredFeatures, column, joinOperation),
|
|
153
|
+
};
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
override async getHistogram({
|
|
157
|
+
operation = 'count',
|
|
158
|
+
ticks,
|
|
159
|
+
column,
|
|
160
|
+
joinOperation,
|
|
161
|
+
filters,
|
|
162
|
+
filterOwner,
|
|
163
|
+
spatialFilter,
|
|
164
|
+
}: HistogramRequestOptions): Promise<HistogramResponse> {
|
|
165
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
166
|
+
spatialFilter,
|
|
167
|
+
filters,
|
|
168
|
+
filterOwner
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
if (!this._features.length) {
|
|
172
|
+
return [];
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
assertColumn(this._features, column);
|
|
176
|
+
|
|
177
|
+
return histogram({
|
|
178
|
+
data: filteredFeatures,
|
|
179
|
+
valuesColumns: normalizeColumns(column),
|
|
180
|
+
joinOperation,
|
|
181
|
+
ticks,
|
|
182
|
+
operation,
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
override async getCategories({
|
|
187
|
+
column,
|
|
188
|
+
operation = 'count',
|
|
189
|
+
operationColumn,
|
|
190
|
+
joinOperation,
|
|
191
|
+
filters,
|
|
192
|
+
filterOwner,
|
|
193
|
+
spatialFilter,
|
|
194
|
+
}: CategoryRequestOptions): Promise<CategoryResponse> {
|
|
195
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
196
|
+
spatialFilter,
|
|
197
|
+
filters,
|
|
198
|
+
filterOwner
|
|
199
|
+
);
|
|
200
|
+
|
|
201
|
+
if (!filteredFeatures.length) {
|
|
202
|
+
return [];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
assertColumn(this._features, column, operationColumn as string);
|
|
206
|
+
|
|
207
|
+
const groups = groupValuesByColumn({
|
|
208
|
+
data: filteredFeatures,
|
|
209
|
+
valuesColumns: normalizeColumns(operationColumn || column),
|
|
210
|
+
joinOperation,
|
|
211
|
+
keysColumn: column,
|
|
212
|
+
operation,
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
return groups || [];
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
override async getScatter({
|
|
219
|
+
xAxisColumn,
|
|
220
|
+
yAxisColumn,
|
|
221
|
+
xAxisJoinOperation,
|
|
222
|
+
yAxisJoinOperation,
|
|
223
|
+
filters,
|
|
224
|
+
filterOwner,
|
|
225
|
+
spatialFilter,
|
|
226
|
+
}: ScatterRequestOptions): Promise<ScatterResponse> {
|
|
227
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
228
|
+
spatialFilter,
|
|
229
|
+
filters,
|
|
230
|
+
filterOwner
|
|
231
|
+
);
|
|
232
|
+
|
|
233
|
+
if (!filteredFeatures.length) {
|
|
234
|
+
return [];
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
assertColumn(this._features, xAxisColumn, yAxisColumn);
|
|
238
|
+
|
|
239
|
+
return scatterPlot({
|
|
240
|
+
data: filteredFeatures,
|
|
241
|
+
xAxisColumns: normalizeColumns(xAxisColumn),
|
|
242
|
+
xAxisJoinOperation,
|
|
243
|
+
yAxisColumns: normalizeColumns(yAxisColumn),
|
|
244
|
+
yAxisJoinOperation,
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
override async getTable({
|
|
249
|
+
columns,
|
|
250
|
+
searchFilterColumn,
|
|
251
|
+
searchFilterText,
|
|
252
|
+
sortBy,
|
|
253
|
+
sortDirection,
|
|
254
|
+
sortByColumnType,
|
|
255
|
+
offset = 0,
|
|
256
|
+
limit = 10,
|
|
257
|
+
filters,
|
|
258
|
+
filterOwner,
|
|
259
|
+
spatialFilter,
|
|
260
|
+
}: TableRequestOptions): Promise<TableResponse> {
|
|
261
|
+
// Filter.
|
|
262
|
+
let filteredFeatures = this._getFilteredFeatures(
|
|
263
|
+
spatialFilter,
|
|
264
|
+
filters,
|
|
265
|
+
filterOwner
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
if (!filteredFeatures.length) {
|
|
269
|
+
return {rows: [], totalCount: 0};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
// Search.
|
|
273
|
+
if (searchFilterColumn && searchFilterText) {
|
|
274
|
+
filteredFeatures = filteredFeatures.filter(
|
|
275
|
+
(row) =>
|
|
276
|
+
row[searchFilterColumn] &&
|
|
277
|
+
String(row[searchFilterColumn] as unknown)
|
|
278
|
+
.toLowerCase()
|
|
279
|
+
.includes(String(searchFilterText).toLowerCase())
|
|
280
|
+
);
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// Sort.
|
|
284
|
+
let rows = applySorting(filteredFeatures, {
|
|
285
|
+
sortBy,
|
|
286
|
+
sortByDirection: sortDirection,
|
|
287
|
+
sortByColumnType,
|
|
288
|
+
});
|
|
289
|
+
const totalCount = rows.length;
|
|
290
|
+
|
|
291
|
+
// Offset and limit.
|
|
292
|
+
rows = rows.slice(
|
|
293
|
+
Math.min(offset, totalCount),
|
|
294
|
+
Math.min(offset + limit, totalCount)
|
|
295
|
+
);
|
|
296
|
+
|
|
297
|
+
// Select columns.
|
|
298
|
+
rows = rows.map((srcRow: FeatureData) => {
|
|
299
|
+
const dstRow: FeatureData = {};
|
|
300
|
+
for (const column of columns) {
|
|
301
|
+
dstRow[column] = srcRow[column];
|
|
302
|
+
}
|
|
303
|
+
return dstRow;
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
return {rows, totalCount} as TableResponse;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
override async getTimeSeries({
|
|
310
|
+
column,
|
|
311
|
+
stepSize,
|
|
312
|
+
operation,
|
|
313
|
+
operationColumn,
|
|
314
|
+
joinOperation,
|
|
315
|
+
filters,
|
|
316
|
+
filterOwner,
|
|
317
|
+
spatialFilter,
|
|
318
|
+
}: TimeSeriesRequestOptions): Promise<TimeSeriesResponse> {
|
|
319
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
320
|
+
spatialFilter,
|
|
321
|
+
filters,
|
|
322
|
+
filterOwner
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
if (!filteredFeatures.length) {
|
|
326
|
+
return {rows: []};
|
|
327
|
+
}
|
|
328
|
+
|
|
329
|
+
assertColumn(this._features, column, operationColumn as string);
|
|
330
|
+
|
|
331
|
+
const rows =
|
|
332
|
+
groupValuesByDateColumn({
|
|
333
|
+
data: filteredFeatures,
|
|
334
|
+
valuesColumns: normalizeColumns(operationColumn || column),
|
|
335
|
+
keysColumn: column,
|
|
336
|
+
groupType: stepSize,
|
|
337
|
+
operation,
|
|
338
|
+
joinOperation,
|
|
339
|
+
}) || [];
|
|
340
|
+
|
|
341
|
+
return {rows};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
override async getRange({
|
|
345
|
+
column,
|
|
346
|
+
filters,
|
|
347
|
+
filterOwner,
|
|
348
|
+
spatialFilter,
|
|
349
|
+
}: RangeRequestOptions): Promise<RangeResponse> {
|
|
350
|
+
assertColumn(this._features, column);
|
|
351
|
+
|
|
352
|
+
const filteredFeatures = this._getFilteredFeatures(
|
|
353
|
+
spatialFilter,
|
|
354
|
+
filters,
|
|
355
|
+
filterOwner
|
|
356
|
+
);
|
|
357
|
+
|
|
358
|
+
if (!this._features.length) {
|
|
359
|
+
// TODO: Is this the only nullable response in the Widgets API? If so,
|
|
360
|
+
// can we do something more consistent?
|
|
361
|
+
return null;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
return {
|
|
365
|
+
min: aggregationFunctions.min(filteredFeatures, column),
|
|
366
|
+
max: aggregationFunctions.max(filteredFeatures, column),
|
|
367
|
+
};
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
/****************************************************************************
|
|
371
|
+
* INTERNAL
|
|
372
|
+
*/
|
|
373
|
+
|
|
374
|
+
private _getFilteredFeatures(
|
|
375
|
+
spatialFilter?: SpatialFilter,
|
|
376
|
+
filters?: Record<string, Filter>,
|
|
377
|
+
filterOwner?: string
|
|
378
|
+
): FeatureData[] {
|
|
379
|
+
assert(spatialFilter, 'spatialFilter required for tilesets');
|
|
380
|
+
this._extractTileFeatures(spatialFilter);
|
|
381
|
+
return applyFilters(
|
|
382
|
+
this._features,
|
|
383
|
+
getApplicableFilters(filterOwner, filters || this.props.filters),
|
|
384
|
+
this.props.filtersLogicalOperator || 'and'
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function assertColumn(
|
|
390
|
+
features: FeatureData[],
|
|
391
|
+
...columnArgs: string[] | string[][]
|
|
392
|
+
) {
|
|
393
|
+
// TODO(cleanup): Can drop support for multiple column shapes here?
|
|
394
|
+
|
|
395
|
+
// Due to the multiple column shape, we normalise it as an array with normalizeColumns
|
|
396
|
+
const columns = Array.from(new Set(columnArgs.map(normalizeColumns).flat()));
|
|
397
|
+
|
|
398
|
+
const featureKeys = Object.keys(features[0]);
|
|
399
|
+
|
|
400
|
+
const invalidColumns = columns.filter(
|
|
401
|
+
(column) => !featureKeys.includes(column)
|
|
402
|
+
);
|
|
403
|
+
|
|
404
|
+
if (invalidColumns.length) {
|
|
405
|
+
throw new InvalidColumnError(
|
|
406
|
+
`Missing column(s): ${invalidColumns.join(', ')}`
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function normalizeColumns(columns: string | string[]): string[] {
|
|
412
|
+
return Array.isArray(columns)
|
|
413
|
+
? columns
|
|
414
|
+
: typeof columns === 'string'
|
|
415
|
+
? [columns]
|
|
416
|
+
: [];
|
|
417
|
+
}
|