@carto/api-client 0.5.7-alpha.6 → 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/CHANGELOG.md +6 -1
- package/build/api-client.cjs +390 -344
- package/build/api-client.cjs.map +1 -1
- package/build/api-client.d.cts +4 -4
- package/build/api-client.d.ts +4 -4
- package/build/api-client.js +378 -334
- package/build/api-client.js.map +1 -1
- package/build/worker-compat.js +729 -688
- package/build/worker-compat.js.map +1 -1
- package/build/worker.js +378 -334
- package/build/worker.js.map +1 -1
- package/package.json +2 -3
- package/src/filters/tileFeatures.ts +1 -1
- package/src/filters/tileFeaturesGeometries.ts +28 -55
- package/src/filters/tileFeaturesRaster.ts +10 -21
- package/src/filters/tileFeaturesSpatialIndex.ts +37 -50
- package/src/filters/tileIntersection.ts +155 -0
- package/src/widget-sources/widget-tileset-source-impl.ts +8 -4
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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
74
|
-
const
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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 (
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
74
|
+
if (intersection === false) continue;
|
|
89
75
|
|
|
90
|
-
|
|
91
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
211
|
+
spatialFilter,
|
|
237
212
|
});
|
|
238
213
|
}
|
|
239
214
|
}
|
|
@@ -350,8 +325,7 @@ function getRingCoordinatesFor(
|
|
|
350
325
|
|
|
351
326
|
function calculateFeatures({
|
|
352
327
|
map,
|
|
353
|
-
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
363
|
+
spatialFilter,
|
|
391
364
|
type,
|
|
392
365
|
bbox,
|
|
393
366
|
tileFormat,
|
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import {
|
|
2
2
|
cellToChildren as _cellToChildren,
|
|
3
|
-
cellToBoundary,
|
|
4
3
|
cellToTile,
|
|
5
|
-
geometryToCells,
|
|
6
4
|
getResolution,
|
|
7
5
|
} from 'quadbin';
|
|
8
6
|
import type {RasterTile, SpatialFilter, Tile} from '../types.js';
|
|
@@ -12,13 +10,11 @@ import type {
|
|
|
12
10
|
RasterMetadataBand,
|
|
13
11
|
SpatialDataType,
|
|
14
12
|
} from '../sources/types.js';
|
|
15
|
-
import
|
|
16
|
-
import intersect from '@turf/intersect';
|
|
17
|
-
import {feature, featureCollection} from '@turf/helpers';
|
|
13
|
+
import {intersectTileRaster} from './tileIntersection.js';
|
|
18
14
|
|
|
19
15
|
export type TileFeaturesRasterOptions = {
|
|
20
16
|
tiles: RasterTile[];
|
|
21
|
-
spatialFilter
|
|
17
|
+
spatialFilter?: SpatialFilter;
|
|
22
18
|
spatialDataColumn: string;
|
|
23
19
|
spatialDataType: SpatialDataType;
|
|
24
20
|
rasterMetadata: RasterMetadata;
|
|
@@ -52,27 +48,20 @@ export function tileFeaturesRaster({
|
|
|
52
48
|
for (const tile of tiles as Required<RasterTile>[]) {
|
|
53
49
|
const parent = tile.index.q;
|
|
54
50
|
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
// resolution=14, we have ~18,000,000 cells in the viewport.)
|
|
60
|
-
const tilePolygon = cellToBoundary(parent);
|
|
61
|
-
const tileFilter = intersect(
|
|
62
|
-
featureCollection([feature(tilePolygon), feature(options.spatialFilter)])
|
|
51
|
+
const intersection = intersectTileRaster(
|
|
52
|
+
parent,
|
|
53
|
+
cellResolution,
|
|
54
|
+
options.spatialFilter
|
|
63
55
|
);
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const tileFilterCells = needsFilter
|
|
68
|
-
? new Set(geometryToCells(tileFilter!.geometry, cellResolution))
|
|
69
|
-
: null;
|
|
56
|
+
|
|
57
|
+
if (intersection === false) continue;
|
|
58
|
+
|
|
70
59
|
const tileSortedCells = cellToChildrenSorted(parent, cellResolution);
|
|
71
60
|
|
|
72
61
|
// For each pixel/cell within the spatial filter, create a FeatureData.
|
|
73
62
|
// Order is row-major, starting from NW and ending at SE.
|
|
74
63
|
for (let i = 0; i < tileSortedCells.length; i++) {
|
|
75
|
-
if (
|
|
64
|
+
if (intersection !== true && !intersection.has(tileSortedCells[i])) {
|
|
76
65
|
continue;
|
|
77
66
|
}
|
|
78
67
|
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import {SpatialIndex} from '../constants.js';
|
|
2
|
-
import {getResolution as quadbinGetResolution
|
|
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 {
|
|
6
|
-
import {getResolution as h3GetResolution
|
|
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
|
|
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
|
|
25
|
+
const cellResolution = getResolution(tiles, spatialIndex);
|
|
26
26
|
const spatialIndexIDName = spatialDataColumn
|
|
27
27
|
? spatialDataColumn
|
|
28
28
|
: spatialIndex;
|
|
29
29
|
|
|
30
|
-
if (!
|
|
30
|
+
if (!cellResolution) {
|
|
31
31
|
return [];
|
|
32
32
|
}
|
|
33
|
-
const cells = getCellsCoverGeometry(spatialFilter, spatialIndex, resolution);
|
|
34
33
|
|
|
35
|
-
|
|
36
|
-
return [];
|
|
37
|
-
}
|
|
34
|
+
let intersection: undefined | boolean | Set<bigint | string>;
|
|
38
35
|
|
|
39
|
-
//
|
|
40
|
-
|
|
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
|
-
|
|
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
|
|
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
|
+
}
|