@carto/api-client 0.5.7-alpha.5 → 0.5.7

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/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.7-alpha.5",
11
+ "version": "0.5.7",
12
12
  "license": "MIT",
13
13
  "publishConfig": {
14
14
  "access": "public"
@@ -131,6 +131,5 @@
131
131
  "resolutions": {
132
132
  "@carto/api-client": "portal:./",
133
133
  "rollup": "^4.20.0"
134
- },
135
- "stableVersion": "0.5.4"
134
+ }
136
135
  }
@@ -19,7 +19,7 @@ export type TileFeatures = {
19
19
  tileFormat: TileFormat;
20
20
  spatialDataType: SpatialDataType;
21
21
  spatialDataColumn?: string;
22
- spatialFilter: SpatialFilter;
22
+ spatialFilter?: SpatialFilter;
23
23
  uniqueIdProperty?: string;
24
24
  rasterMetadata?: RasterMetadata;
25
25
  storeGeometry?: boolean;
@@ -1,22 +1,15 @@
1
- import bboxPolygon from '@turf/bbox-polygon';
2
1
  import intersects from '@turf/boolean-intersects';
3
- import booleanWithin from '@turf/boolean-within';
4
- import intersect from '@turf/intersect';
5
- import {transformToTileCoords} from '../utils/transformToTileCoords.js';
6
2
  import {transformTileCoordsToWGS84} from '../utils/transformTileCoordsToWGS84.js';
7
3
  import {TileFormat} from '../constants.js';
8
4
  import type {
9
5
  BBox,
10
- Feature,
11
6
  Geometry,
12
7
  LineString,
13
- MultiPolygon,
14
8
  Point,
15
9
  Polygon,
16
10
  Position,
17
11
  } from 'geojson';
18
12
  import type {SpatialFilter, Tile} from '../types.js';
19
- import {featureCollection} from '@turf/helpers';
20
13
  import type {FeatureData} from '../types-internal.js';
21
14
  import type {
22
15
  BinaryAttribute,
@@ -27,6 +20,7 @@ import type {
27
20
  BinaryPolygonFeature,
28
21
  TypedArrayConstructor,
29
22
  } from '@loaders.gl/schema';
23
+ import {intersectTileGeometry} from './tileIntersection.js';
30
24
 
31
25
  export const FEATURE_GEOM_PROPERTY = '__geomValue';
32
26
 
@@ -51,7 +45,7 @@ export function tileFeaturesGeometries({
51
45
  }: {
52
46
  tiles: Tile[];
53
47
  tileFormat?: TileFormat;
54
- spatialFilter: SpatialFilter;
48
+ spatialFilter?: SpatialFilter;
55
49
  uniqueIdProperty?: string;
56
50
  options?: GeometryExtractOptions;
57
51
  }): FeatureData[] {
@@ -64,65 +58,50 @@ export function tileFeaturesGeometries({
64
58
  continue;
65
59
  }
66
60
 
67
- const bbox = [
61
+ const tileBbox = [
68
62
  tile.bbox.west,
69
63
  tile.bbox.south,
70
64
  tile.bbox.east,
71
65
  tile.bbox.north,
72
66
  ] as BBox;
73
- const bboxToGeom = bboxPolygon(bbox);
74
- const tileIsFullyVisible = booleanWithin(bboxToGeom, spatialFilter);
75
-
76
- // Clip the geometry to intersect with the tile
77
- const spatialFilterFeature: Feature<Polygon | MultiPolygon> = {
78
- type: 'Feature',
79
- geometry: spatialFilter,
80
- properties: {},
81
- };
82
- const clippedGeometryToIntersect = intersect(
83
- featureCollection([bboxToGeom, spatialFilterFeature])
67
+
68
+ const intersection = intersectTileGeometry(
69
+ tileBbox,
70
+ tileFormat,
71
+ spatialFilter
84
72
  );
85
73
 
86
- if (!clippedGeometryToIntersect) {
87
- continue;
88
- }
74
+ if (intersection === false) continue;
89
75
 
90
- // We assume that MVT tileFormat uses local coordinates so we transform the geometry to intersect to tile coordinates [0..1],
91
- // while in the case of 'geojson' or binary, the geometries are already in WGS84
92
- const transformedGeometryToIntersect =
93
- tileFormat === TileFormat.MVT
94
- ? transformToTileCoords(clippedGeometryToIntersect.geometry, bbox)
95
- : clippedGeometryToIntersect.geometry;
76
+ const transformedSpatialFilter =
77
+ intersection === true ? undefined : intersection;
96
78
 
97
79
  calculateFeatures({
98
80
  map,
99
- tileIsFullyVisible,
100
- geometryIntersection: transformedGeometryToIntersect,
81
+ spatialFilter: transformedSpatialFilter,
101
82
  data: tile.data.points!,
102
83
  type: 'Point',
103
- bbox,
84
+ bbox: tileBbox,
104
85
  tileFormat,
105
86
  uniqueIdProperty,
106
87
  options,
107
88
  });
108
89
  calculateFeatures({
109
90
  map,
110
- tileIsFullyVisible,
111
- geometryIntersection: transformedGeometryToIntersect,
91
+ spatialFilter: transformedSpatialFilter,
112
92
  data: tile.data.lines!,
113
93
  type: 'LineString',
114
- bbox,
94
+ bbox: tileBbox,
115
95
  tileFormat,
116
96
  uniqueIdProperty,
117
97
  options,
118
98
  });
119
99
  calculateFeatures({
120
100
  map,
121
- tileIsFullyVisible,
122
- geometryIntersection: transformedGeometryToIntersect,
101
+ spatialFilter: transformedSpatialFilter,
123
102
  data: tile.data.polygons!,
124
103
  type: 'Polygon',
125
- bbox,
104
+ bbox: tileBbox,
126
105
  tileFormat,
127
106
  uniqueIdProperty,
128
107
  options,
@@ -141,7 +120,7 @@ function processTileFeatureProperties({
141
120
  tileFormat,
142
121
  uniqueIdProperty,
143
122
  storeGeometry,
144
- geometryIntersection,
123
+ spatialFilter,
145
124
  }: {
146
125
  map: TileMap;
147
126
  data: BinaryFeature;
@@ -152,7 +131,7 @@ function processTileFeatureProperties({
152
131
  tileFormat?: TileFormat;
153
132
  uniqueIdProperty?: string;
154
133
  storeGeometry: boolean;
155
- geometryIntersection?: Geometry;
134
+ spatialFilter?: Geometry;
156
135
  }) {
157
136
  const tileProps = getPropertiesFromTile(data, startIndex);
158
137
  const uniquePropertyValue = getUniquePropertyValue(
@@ -167,7 +146,7 @@ function processTileFeatureProperties({
167
146
  let geometry: Geometry | null = null;
168
147
 
169
148
  // Only calculate geometry if necessary
170
- if (storeGeometry || geometryIntersection) {
149
+ if (storeGeometry || spatialFilter) {
171
150
  const {positions} = data;
172
151
  const ringCoordinates = getRingCoordinatesFor(
173
152
  startIndex,
@@ -178,11 +157,7 @@ function processTileFeatureProperties({
178
157
  }
179
158
 
180
159
  // If intersection is required, check before proceeding
181
- if (
182
- geometry &&
183
- geometryIntersection &&
184
- !intersects(geometry, geometryIntersection)
185
- ) {
160
+ if (geometry && spatialFilter && !intersects(geometry, spatialFilter)) {
186
161
  return;
187
162
  }
188
163
 
@@ -201,7 +176,7 @@ function processTileFeatureProperties({
201
176
  function addIntersectedFeaturesInTile({
202
177
  map,
203
178
  data,
204
- geometryIntersection,
179
+ spatialFilter,
205
180
  type,
206
181
  bbox,
207
182
  tileFormat,
@@ -210,7 +185,7 @@ function addIntersectedFeaturesInTile({
210
185
  }: {
211
186
  map: TileMap;
212
187
  data: BinaryFeature;
213
- geometryIntersection: Geometry;
188
+ spatialFilter: Geometry;
214
189
  type: BinaryGeometryType;
215
190
  bbox: BBox;
216
191
  tileFormat?: TileFormat;
@@ -233,7 +208,7 @@ function addIntersectedFeaturesInTile({
233
208
  tileFormat,
234
209
  uniqueIdProperty,
235
210
  storeGeometry,
236
- geometryIntersection,
211
+ spatialFilter,
237
212
  });
238
213
  }
239
214
  }
@@ -350,8 +325,7 @@ function getRingCoordinatesFor(
350
325
 
351
326
  function calculateFeatures({
352
327
  map,
353
- tileIsFullyVisible,
354
- geometryIntersection,
328
+ spatialFilter,
355
329
  data,
356
330
  type,
357
331
  bbox,
@@ -360,8 +334,7 @@ function calculateFeatures({
360
334
  options,
361
335
  }: {
362
336
  map: TileMap;
363
- tileIsFullyVisible: boolean;
364
- geometryIntersection: SpatialFilter;
337
+ spatialFilter?: SpatialFilter;
365
338
  data: BinaryFeature;
366
339
  type: BinaryGeometryType;
367
340
  bbox: BBox;
@@ -373,7 +346,7 @@ function calculateFeatures({
373
346
  return;
374
347
  }
375
348
 
376
- if (tileIsFullyVisible) {
349
+ if (!spatialFilter) {
377
350
  addAllFeaturesInTile({
378
351
  map,
379
352
  data,
@@ -387,7 +360,7 @@ function calculateFeatures({
387
360
  addIntersectedFeaturesInTile({
388
361
  map,
389
362
  data,
390
- geometryIntersection,
363
+ spatialFilter,
391
364
  type,
392
365
  bbox,
393
366
  tileFormat,
@@ -1,7 +1,6 @@
1
1
  import {
2
2
  cellToChildren as _cellToChildren,
3
3
  cellToTile,
4
- geometryToCells,
5
4
  getResolution,
6
5
  } from 'quadbin';
7
6
  import type {RasterTile, SpatialFilter, Tile} from '../types.js';
@@ -11,11 +10,11 @@ import type {
11
10
  RasterMetadataBand,
12
11
  SpatialDataType,
13
12
  } from '../sources/types.js';
14
- import {CellSet} from '../utils/CellSet.js';
13
+ import {intersectTileRaster} from './tileIntersection.js';
15
14
 
16
15
  export type TileFeaturesRasterOptions = {
17
16
  tiles: RasterTile[];
18
- spatialFilter: SpatialFilter;
17
+ spatialFilter?: SpatialFilter;
19
18
  spatialDataColumn: string;
20
19
  spatialDataType: SpatialDataType;
21
20
  rasterMetadata: RasterMetadata;
@@ -44,23 +43,27 @@ export function tileFeaturesRaster({
44
43
  const tileBlockSize = tiles[0].data!.blockSize;
45
44
  const cellResolution = tileResolution + BigInt(Math.log2(tileBlockSize));
46
45
 
47
- // Compute covering cells for the spatial filter, at same resolution as the
48
- // raster pixels, to be used as a mask.
49
- const spatialFilterCells = new CellSet(
50
- geometryToCells(options.spatialFilter, cellResolution)
51
- );
52
-
53
46
  const data = new Map<bigint, FeatureData>();
54
47
 
55
48
  for (const tile of tiles as Required<RasterTile>[]) {
56
49
  const parent = tile.index.q;
57
50
 
58
- const children = cellToChildrenSorted(parent, cellResolution);
51
+ const intersection = intersectTileRaster(
52
+ parent,
53
+ cellResolution,
54
+ options.spatialFilter
55
+ );
56
+
57
+ if (intersection === false) continue;
58
+
59
+ const tileSortedCells = cellToChildrenSorted(parent, cellResolution);
59
60
 
60
61
  // For each pixel/cell within the spatial filter, create a FeatureData.
61
62
  // Order is row-major, starting from NW and ending at SE.
62
- for (let i = 0; i < children.length; i++) {
63
- if (!spatialFilterCells.has(children[i])) continue;
63
+ for (let i = 0; i < tileSortedCells.length; i++) {
64
+ if (intersection !== true && !intersection.has(tileSortedCells[i])) {
65
+ continue;
66
+ }
64
67
 
65
68
  const cellData: FeatureData = {};
66
69
  let cellDataExists = false;
@@ -76,7 +79,7 @@ export function tileFeaturesRaster({
76
79
  }
77
80
 
78
81
  if (cellDataExists) {
79
- data.set(children[i], cellData);
82
+ data.set(tileSortedCells[i], cellData);
80
83
  }
81
84
  }
82
85
  }
@@ -1,15 +1,15 @@
1
1
  import {SpatialIndex} from '../constants.js';
2
- import {getResolution as quadbinGetResolution, geometryToCells} from 'quadbin';
3
- import bboxClip from '@turf/bbox-clip';
2
+ import {getResolution as quadbinGetResolution} from 'quadbin';
4
3
  import type {SpatialFilter, SpatialIndexTile} from '../types.js';
5
- import type {BBox, Feature} from 'geojson';
6
- import {getResolution as h3GetResolution, polygonToCells} from 'h3-js';
4
+ import type {Feature} from 'geojson';
5
+ import {getResolution as h3GetResolution} from 'h3-js';
7
6
  import type {FeatureData} from '../types-internal.js';
8
7
  import type {SpatialDataType} from '../sources/types.js';
8
+ import {intersectTileH3, intersectTileQuadbin} from './tileIntersection.js';
9
9
 
10
10
  export type TileFeaturesSpatialIndexOptions = {
11
11
  tiles: SpatialIndexTile[];
12
- spatialFilter: SpatialFilter;
12
+ spatialFilter?: SpatialFilter;
13
13
  spatialDataColumn: string;
14
14
  spatialDataType: SpatialDataType;
15
15
  };
@@ -22,30 +22,41 @@ export function tileFeaturesSpatialIndex({
22
22
  }: TileFeaturesSpatialIndexOptions): FeatureData[] {
23
23
  const map = new Map();
24
24
  const spatialIndex = getSpatialIndex(spatialDataType);
25
- const resolution = getResolution(tiles, spatialIndex);
25
+ const cellResolution = getResolution(tiles, spatialIndex);
26
26
  const spatialIndexIDName = spatialDataColumn
27
27
  ? spatialDataColumn
28
28
  : spatialIndex;
29
29
 
30
- if (!resolution) {
30
+ if (!cellResolution) {
31
31
  return [];
32
32
  }
33
- const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
34
33
 
35
- if (!cells?.length) {
36
- return [];
37
- }
34
+ let intersection: undefined | boolean | Set<bigint | string>;
38
35
 
39
- // We transform cells to Set to improve the performace
40
- const cellsSet = new Set<bigint | string>(cells);
36
+ // Compute H3 intersection globally, Quadbin intersection per-tile. See tileIntersection.ts.
37
+ if (spatialIndex === SpatialIndex.H3) {
38
+ intersection = intersectTileH3(cellResolution as number, spatialFilter);
39
+ }
41
40
 
42
41
  for (const tile of tiles) {
43
42
  if (tile.isVisible === false || !tile.data) {
44
43
  continue;
45
44
  }
46
45
 
46
+ if (spatialIndex === SpatialIndex.QUADBIN) {
47
+ const parent = getTileIndex(tile, spatialIndex);
48
+ intersection = intersectTileQuadbin(
49
+ parent as bigint,
50
+ cellResolution as bigint,
51
+ spatialFilter
52
+ );
53
+ }
54
+
55
+ if (!intersection) continue;
56
+
47
57
  tile.data.forEach((d: Feature) => {
48
- if (cellsSet.has(d.id as bigint | string)) {
58
+ // @ts-expect-error Mixed types for cell indices.
59
+ if (intersection === true || intersection.has(d.id as bigint | string)) {
49
60
  map.set(d.id, {...d.properties, [spatialIndexIDName]: d.id});
50
61
  }
51
62
  });
@@ -53,10 +64,21 @@ export function tileFeaturesSpatialIndex({
53
64
  return Array.from(map.values());
54
65
  }
55
66
 
67
+ function getTileIndex(
68
+ tile: SpatialIndexTile,
69
+ spatialIndex: SpatialIndex
70
+ ): bigint | string {
71
+ if (spatialIndex === SpatialIndex.QUADBIN) {
72
+ // @ts-expect-error Missing types for quadbin tile indices.
73
+ return tile.index.q;
74
+ }
75
+ return tile.id;
76
+ }
77
+
56
78
  function getResolution(
57
79
  tiles: SpatialIndexTile[],
58
80
  spatialIndex: SpatialIndex
59
- ): number | undefined {
81
+ ): bigint | number | undefined {
60
82
  const data = tiles.find((tile) => tile.data?.length)?.data;
61
83
 
62
84
  if (!data) {
@@ -72,41 +94,6 @@ function getResolution(
72
94
  }
73
95
  }
74
96
 
75
- const bboxWest: BBox = [-180, -90, 0, 90];
76
- const bboxEast: BBox = [0, -90, 180, 90];
77
-
78
- function getCellsCoverGeometry(
79
- geometry: SpatialFilter,
80
- spatialIndex: SpatialIndex,
81
- resolution: number
82
- ) {
83
- if (spatialIndex === SpatialIndex.QUADBIN) {
84
- // @ts-expect-error TODO: Probably ought to be stricter about number vs. bigint types in this file.
85
- return geometryToCells(geometry, resolution);
86
- }
87
-
88
- if (spatialIndex === SpatialIndex.H3) {
89
- // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
90
- // so we clip the geometry to be sure that none of them is greater than 180 degrees
91
- // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
92
- return polygonToCells(
93
- bboxClip(geometry, bboxWest).geometry.coordinates as
94
- | number[][]
95
- | number[][][],
96
- resolution,
97
- true
98
- ).concat(
99
- polygonToCells(
100
- bboxClip(geometry, bboxEast).geometry.coordinates as
101
- | number[][]
102
- | number[][][],
103
- resolution,
104
- true
105
- )
106
- );
107
- }
108
- }
109
-
110
97
  function getSpatialIndex(spatialDataType: SpatialDataType): SpatialIndex {
111
98
  switch (spatialDataType) {
112
99
  case 'h3':
@@ -0,0 +1,155 @@
1
+ import type {BBox} from 'geojson';
2
+ import type {SpatialFilter} from '../types.js';
3
+ import bboxPolygon from '@turf/bbox-polygon';
4
+ import booleanWithin from '@turf/boolean-within';
5
+ import intersect from '@turf/intersect';
6
+ import {feature, featureCollection} from '@turf/helpers';
7
+ import {TileFormat} from '../constants.js';
8
+ import {transformToTileCoords} from '../utils/transformToTileCoords.js';
9
+ import {
10
+ cellToBoundary as quadbinCellToBoundary,
11
+ geometryToCells as quadbinGeometryToCells,
12
+ } from 'quadbin';
13
+ import {polygonToCells as h3PolygonToCells} from 'h3-js';
14
+ import bboxClip from '@turf/bbox-clip';
15
+
16
+ // Computes intersections between spatial filters and tiles in various formats,
17
+ // for pre-filtering each tile's features before performing widget calculations.
18
+ //
19
+ // Return types:
20
+ // - true: All features in tile are within spatial filter.
21
+ // - false: No features in tile are within spatial filter; skip tile.
22
+ // - SpatialFilter/Set: Requires a more detailed per-feature check for each
23
+ // feature in the tile. Provides a clipped spatial filter local to the tile
24
+ // or, for spatial indexes, a set of indices ("covering set").
25
+ //
26
+ // Computing a covering set for spatial indexes may be very expensive for large
27
+ // spatial filters and small cell resolutions. For example, a viewport at z=3
28
+ // would contain ~18,000,000 raster cells at resolution=14. To avoid ever
29
+ // creating a covering set of this size, compute per-tile (not global) coverings
30
+ // when possible. For tiles fully inside or outside the spatial filter, creating
31
+ // a covering set can be skipped.
32
+ //
33
+ // H3 is currently a special case where coverage must be computed for the entire
34
+ // spatial filter; per-tile coverage would return a different result, because
35
+ // H3 child cells are not fully contained within their parents.
36
+
37
+ ///////////////////////////////////////////////////////////////////////////////
38
+ // GEOMETRY
39
+
40
+ /** @internal */
41
+ export function intersectTileGeometry(
42
+ tileBbox: BBox,
43
+ tileFormat?: TileFormat,
44
+ spatialFilter?: SpatialFilter
45
+ ): boolean | SpatialFilter {
46
+ const tilePolygon = bboxPolygon(tileBbox);
47
+
48
+ if (!spatialFilter || booleanWithin(tilePolygon, spatialFilter)) {
49
+ return true;
50
+ }
51
+
52
+ const clippedSpatialFilter = intersect(
53
+ featureCollection([tilePolygon, feature(spatialFilter)])
54
+ );
55
+
56
+ if (!clippedSpatialFilter) {
57
+ return false;
58
+ }
59
+
60
+ // Transform into local coordinates [0..1]. We assume MVT tiles use local
61
+ // coordinates but geojson or binary features are already WGS84.
62
+ return tileFormat === TileFormat.MVT
63
+ ? transformToTileCoords(clippedSpatialFilter.geometry, tileBbox)
64
+ : clippedSpatialFilter.geometry;
65
+ }
66
+
67
+ ///////////////////////////////////////////////////////////////////////////////
68
+ // RASTER
69
+
70
+ /** @internal */
71
+ export function intersectTileRaster(
72
+ parent: bigint,
73
+ cellResolution: bigint,
74
+ spatialFilter?: SpatialFilter
75
+ ) {
76
+ return intersectTileQuadbin(parent, cellResolution, spatialFilter);
77
+ }
78
+
79
+ ///////////////////////////////////////////////////////////////////////////////
80
+ // QUADBIN
81
+
82
+ /** @internal */
83
+ export function intersectTileQuadbin(
84
+ parent: bigint,
85
+ cellResolution: bigint,
86
+ spatialFilter?: SpatialFilter
87
+ ): boolean | Set<bigint> {
88
+ const tilePolygon = quadbinCellToBoundary(parent);
89
+
90
+ if (!spatialFilter || booleanWithin(tilePolygon, spatialFilter)) {
91
+ return true;
92
+ }
93
+
94
+ const clippedSpatialFilter = intersect(
95
+ featureCollection([feature(tilePolygon), feature(spatialFilter)])
96
+ );
97
+
98
+ if (!clippedSpatialFilter) {
99
+ return false;
100
+ }
101
+
102
+ const cells = quadbinGeometryToCells(
103
+ clippedSpatialFilter.geometry,
104
+ cellResolution
105
+ );
106
+
107
+ return new Set(cells);
108
+ }
109
+
110
+ ///////////////////////////////////////////////////////////////////////////////
111
+ // H3
112
+
113
+ const BBOX_WEST: BBox = [-180, -90, 0, 90];
114
+ const BBOX_EAST: BBox = [0, -90, 180, 90];
115
+
116
+ /** @internal */
117
+ export function intersectTileH3(
118
+ cellResolution: number,
119
+ spatialFilter?: SpatialFilter
120
+ ): true | Set<string> {
121
+ if (!spatialFilter) {
122
+ return true;
123
+ }
124
+
125
+ // Unlike quadbin and raster tiles, H3 children do not align to the parent's
126
+ // boundaries. Per-tile coverage with `h3PolygonToCells` would return only
127
+ // cells with _centers_ inside the clipped polygon — fewer cells. So for H3
128
+ // we compute the coverage set for the entire spatial filter (more expensive).
129
+ // In the future this could be replaced with `polygonToCellsExperimental`,
130
+ // which can compute child cells in different modes.
131
+
132
+ const spatialFilterFeature = feature(spatialFilter);
133
+
134
+ // The current H3 polyfill algorithm can't deal with polygon segments of greater than 180 degrees longitude
135
+ // so we clip the geometry to be sure that none of them is greater than 180 degrees
136
+ // https://github.com/uber/h3-js/issues/24#issuecomment-431893796
137
+
138
+ const cellsWest = h3PolygonToCells(
139
+ bboxClip(spatialFilterFeature, BBOX_WEST).geometry.coordinates as
140
+ | number[][]
141
+ | number[][][],
142
+ cellResolution,
143
+ true
144
+ );
145
+
146
+ const cellsEast = h3PolygonToCells(
147
+ bboxClip(spatialFilterFeature, BBOX_EAST).geometry.coordinates as
148
+ | number[][]
149
+ | number[][][],
150
+ cellResolution,
151
+ true
152
+ );
153
+
154
+ return new Set(cellsWest.concat(cellsEast));
155
+ }
@@ -74,14 +74,13 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
74
74
  this._features.length = 0;
75
75
  }
76
76
 
77
- protected _extractTileFeatures(spatialFilter: SpatialFilter) {
77
+ protected _extractTileFeatures(spatialFilter?: SpatialFilter) {
78
78
  // When spatial filter has not changed, don't redo extraction. If tiles or
79
79
  // tile extract options change, features will have been cleared already.
80
80
  const prevInputs = this._tileFeatureExtractPreviousInputs;
81
81
  if (
82
82
  this._features.length &&
83
- prevInputs.spatialFilter &&
84
- booleanEqual(prevInputs.spatialFilter, spatialFilter)
83
+ spatialFilterEquals(prevInputs.spatialFilter, spatialFilter)
85
84
  ) {
86
85
  return;
87
86
  }
@@ -383,7 +382,6 @@ export class WidgetTilesetSourceImpl extends WidgetSource<WidgetTilesetSourcePro
383
382
  filters?: Record<string, Filter>,
384
383
  filterOwner?: string
385
384
  ): FeatureData[] {
386
- assert(spatialFilter, 'spatialFilter required for tilesets');
387
385
  this._extractTileFeatures(spatialFilter);
388
386
  return applyFilters(
389
387
  this._features,
@@ -422,3 +420,9 @@ function normalizeColumns(columns: string | string[]): string[] {
422
420
  ? [columns]
423
421
  : [];
424
422
  }
423
+
424
+ function spatialFilterEquals(a?: SpatialFilter, b?: SpatialFilter) {
425
+ if (a === b) return true;
426
+ if (!a || !b) return false;
427
+ return booleanEqual(a, b);
428
+ }