@deck.gl/carto 9.2.0-beta.1 → 9.2.0-beta.3

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
@@ -3,7 +3,7 @@
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
5
  "type": "module",
6
- "version": "9.2.0-beta.1",
6
+ "version": "9.2.0-beta.3",
7
7
  "publishConfig": {
8
8
  "access": "public"
9
9
  },
@@ -42,15 +42,15 @@
42
42
  "prepublishOnly": "npm run build-bundle && npm run build-bundle -- --env=dev"
43
43
  },
44
44
  "dependencies": {
45
- "@carto/api-client": "^0.5.6",
45
+ "@carto/api-client": "^0.5.19",
46
46
  "@loaders.gl/compression": "^4.2.0",
47
47
  "@loaders.gl/gis": "^4.2.0",
48
48
  "@loaders.gl/loader-utils": "^4.2.0",
49
49
  "@loaders.gl/mvt": "^4.2.0",
50
50
  "@loaders.gl/schema": "^4.2.0",
51
51
  "@loaders.gl/tiles": "^4.2.0",
52
- "@luma.gl/core": "^9.2.0-alpha.5",
53
- "@luma.gl/shadertools": "^9.2.0-alpha.5",
52
+ "@luma.gl/core": "^9.2.0",
53
+ "@luma.gl/shadertools": "^9.2.0",
54
54
  "@math.gl/web-mercator": "^4.1.0",
55
55
  "@types/d3-array": "^3.0.2",
56
56
  "@types/d3-color": "^1.4.2",
@@ -73,7 +73,7 @@
73
73
  "@deck.gl/geo-layers": "^9.1.0",
74
74
  "@deck.gl/layers": "^9.1.0",
75
75
  "@loaders.gl/core": "^4.2.0",
76
- "@luma.gl/core": "^9.2.0-alpha.5"
76
+ "@luma.gl/core": "^9.2.0"
77
77
  },
78
- "gitHead": "84407defece157d45954f66cf191cca3c596ee99"
78
+ "gitHead": "15f2330ac9a9ec3ae449a1e5fad9b923eac60bf2"
79
79
  }
@@ -24,7 +24,8 @@ import {
24
24
  GetPickingInfoParams,
25
25
  Layer,
26
26
  LayersList,
27
- PickingInfo
27
+ PickingInfo,
28
+ WebMercatorViewport
28
29
  } from '@deck.gl/core';
29
30
 
30
31
  import {
@@ -34,23 +35,41 @@ import {
34
35
  computeAggregationStats,
35
36
  extractAggregationProperties,
36
37
  ParsedQuadbinCell,
37
- ParsedQuadbinTile
38
+ ParsedQuadbinTile,
39
+ ParsedH3Cell,
40
+ ParsedH3Tile
38
41
  } from './cluster-utils';
39
42
  import {DEFAULT_TILE_SIZE} from '../constants';
40
43
  import QuadbinTileset2D from './quadbin-tileset-2d';
44
+ import H3Tileset2D, {getHexagonResolution} from './h3-tileset-2d';
41
45
  import {getQuadbinPolygon} from './quadbin-utils';
46
+ import {getResolution, cellToLatLng} from 'h3-js';
42
47
  import CartoSpatialTileLoader from './schema/carto-spatial-tile-loader';
43
48
  import {TilejsonPropType, mergeLoadOptions} from './utils';
44
49
  import type {TilejsonResult} from '@carto/api-client';
45
50
 
46
51
  registerLoaders([CartoSpatialTileLoader]);
47
52
 
53
+ function getScheme(tilesetClass: typeof H3Tileset2D | typeof QuadbinTileset2D): 'h3' | 'quadbin' {
54
+ if (tilesetClass === H3Tileset2D) return 'h3';
55
+ if (tilesetClass === QuadbinTileset2D) return 'quadbin';
56
+ throw new Error('Invalid tileset class');
57
+ }
58
+
48
59
  const defaultProps: DefaultProps<ClusterTileLayerProps> = {
49
60
  data: TilejsonPropType,
50
61
  clusterLevel: {type: 'number', value: 5, min: 1},
51
62
  getPosition: {
52
63
  type: 'accessor',
53
- value: ({id}) => getQuadbinPolygon(id, 0.5).slice(2, 4) as [number, number]
64
+ value: ({id}) => {
65
+ // Determine scheme based on ID type: H3 uses string IDs, Quadbin uses bigint IDs
66
+ if (typeof id === 'string') {
67
+ const [lat, lng] = cellToLatLng(id);
68
+ return [lng, lat];
69
+ }
70
+ // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion
71
+ return getQuadbinPolygon(id as bigint, 0.5).slice(2, 4) as [number, number];
72
+ }
54
73
  },
55
74
  getWeight: {type: 'accessor', value: 1},
56
75
  refinementStrategy: 'no-overlap',
@@ -58,14 +77,17 @@ const defaultProps: DefaultProps<ClusterTileLayerProps> = {
58
77
  };
59
78
 
60
79
  export type ClusterTileLayerPickingInfo<FeaturePropertiesT = {}> = TileLayerPickingInfo<
61
- ParsedQuadbinTile<FeaturePropertiesT>,
80
+ ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>,
62
81
  PickingInfo<Feature<Geometry, FeaturePropertiesT>>
63
82
  >;
64
83
 
65
84
  /** All properties supported by ClusterTileLayer. */
66
85
  export type ClusterTileLayerProps<FeaturePropertiesT = unknown> =
67
86
  _ClusterTileLayerProps<FeaturePropertiesT> &
68
- Omit<TileLayerProps<ParsedQuadbinTile<FeaturePropertiesT>>, 'data'>;
87
+ Omit<
88
+ TileLayerProps<ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>>,
89
+ 'data'
90
+ >;
69
91
 
70
92
  /** Properties added by ClusterTileLayer. */
71
93
  type _ClusterTileLayerProps<FeaturePropertiesT> = Omit<
@@ -84,63 +106,84 @@ type _ClusterTileLayerProps<FeaturePropertiesT> = Omit<
84
106
 
85
107
  /**
86
108
  * The (average) position of points in a cell used for clustering.
87
- * If not supplied the center of the quadbin cell is used.
109
+ * If not supplied the center of the quadbin cell or H3 cell is used.
88
110
  *
89
111
  * @default cell center
90
112
  */
91
- getPosition?: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>;
113
+ getPosition?: Accessor<
114
+ ParsedQuadbinCell<FeaturePropertiesT> | ParsedH3Cell<FeaturePropertiesT>,
115
+ [number, number]
116
+ >;
92
117
 
93
118
  /**
94
119
  * The weight of each cell used for clustering.
95
120
  *
96
121
  * @default 1
97
122
  */
98
- getWeight?: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, number>;
123
+ getWeight?: Accessor<
124
+ ParsedQuadbinCell<FeaturePropertiesT> | ParsedH3Cell<FeaturePropertiesT>,
125
+ number
126
+ >;
99
127
  };
100
128
 
101
129
  class ClusterGeoJsonLayer<
102
130
  FeaturePropertiesT extends {} = {},
103
131
  ExtraProps extends {} = {}
104
132
  > extends TileLayer<
105
- ParsedQuadbinTile<FeaturePropertiesT>,
133
+ ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>,
106
134
  ExtraProps & Required<_ClusterTileLayerProps<FeaturePropertiesT>>
107
135
  > {
108
136
  static layerName = 'ClusterGeoJsonLayer';
109
137
  static defaultProps = defaultProps;
110
138
  state!: TileLayer<FeaturePropertiesT>['state'] & {
111
139
  data: BinaryFeatureCollection;
112
- clusterIds: bigint[];
113
- hoveredFeatureId: bigint | number | null;
140
+ clusterIds: (bigint | string)[];
141
+ hoveredFeatureId: bigint | string | number | null;
114
142
  highlightColor: number[];
115
143
  aggregationCache: WeakMap<any, Map<number, ClusteredFeaturePropertiesT<FeaturePropertiesT>[]>>;
144
+ scheme: string | null;
116
145
  };
117
146
 
118
147
  initializeState() {
119
148
  super.initializeState();
120
149
  this.state.aggregationCache = new WeakMap();
150
+ this.state.scheme = getScheme(this.props.TilesetClass as any);
151
+ }
152
+
153
+ updateState(opts) {
154
+ const {props} = opts;
155
+ const scheme = getScheme(props.TilesetClass);
156
+ if (this.state.scheme !== scheme) {
157
+ // Clear caches when scheme changes
158
+ this.setState({scheme, tileset: null});
159
+ this.state.aggregationCache = new WeakMap();
160
+ }
161
+
162
+ super.updateState(opts);
121
163
  }
122
164
 
123
165
  // eslint-disable-next-line max-statements
124
166
  renderLayers(): Layer | null | LayersList {
125
167
  const visibleTiles = this.state.tileset?.tiles.filter((tile: Tile2DHeader) => {
126
168
  return tile.isLoaded && tile.content && this.state.tileset!.isTileVisible(tile);
127
- }) as Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>[];
128
- if (!visibleTiles?.length) {
169
+ }) as Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>>[];
170
+ if (!visibleTiles?.length || !this.state.tileset) {
129
171
  return null;
130
172
  }
131
173
  visibleTiles.sort((a, b) => b.zoom - a.zoom);
174
+ const {getPosition, getWeight} = this.props;
175
+ const {aggregationCache, scheme} = this.state;
132
176
 
133
- const {zoom} = this.context.viewport;
134
- const {clusterLevel, getPosition, getWeight} = this.props;
135
- const {aggregationCache} = this.state;
177
+ const isH3 = scheme === 'h3';
136
178
 
137
179
  const properties = extractAggregationProperties(visibleTiles[0]);
138
180
  const data = [] as ClusteredFeaturePropertiesT<FeaturePropertiesT>[];
139
181
  let needsUpdate = false;
182
+
183
+ const aggregationLevels = this._getAggregationLevels(visibleTiles);
184
+
140
185
  for (const tile of visibleTiles) {
141
186
  // Calculate aggregation based on viewport zoom
142
- const overZoom = Math.round(zoom - tile.zoom);
143
- const aggregationLevels = Math.round(clusterLevel) - overZoom;
144
187
  let tileAggregationCache = aggregationCache.get(tile.content);
145
188
  if (!tileAggregationCache) {
146
189
  tileAggregationCache = new Map();
@@ -152,7 +195,8 @@ class ClusterGeoJsonLayer<
152
195
  aggregationLevels,
153
196
  properties,
154
197
  getPosition,
155
- getWeight
198
+ getWeight,
199
+ isH3 ? 'h3' : 'quadbin'
156
200
  );
157
201
  needsUpdate ||= didAggregate;
158
202
  data.push(...tileAggregationCache.get(aggregationLevels)!);
@@ -186,7 +230,9 @@ class ClusterGeoJsonLayer<
186
230
  }
187
231
 
188
232
  getPickingInfo(params: GetPickingInfoParams): ClusterTileLayerPickingInfo<FeaturePropertiesT> {
189
- const info = params.info as TileLayerPickingInfo<ParsedQuadbinTile<FeaturePropertiesT>>;
233
+ const info = params.info as TileLayerPickingInfo<
234
+ ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>
235
+ >;
190
236
 
191
237
  if (info.index !== -1) {
192
238
  const {data} = params.sourceLayer!.props;
@@ -207,6 +253,31 @@ class ClusterGeoJsonLayer<
207
253
  filterSubLayer() {
208
254
  return true;
209
255
  }
256
+
257
+ private _getAggregationLevels(visibleTiles: Tile2DHeader[]): number {
258
+ const isH3 = this.state.scheme === 'h3';
259
+ const firstTile = visibleTiles[0];
260
+
261
+ // Resolution of data present in tiles
262
+ let tileResolution;
263
+
264
+ // Resolution of tiles that should be (eventually) visible in the viewport
265
+ let viewportResolution;
266
+ if (isH3) {
267
+ tileResolution = getResolution(firstTile.id);
268
+ viewportResolution = getHexagonResolution(
269
+ this.context.viewport as WebMercatorViewport,
270
+ (this.state.tileset as any).opts.tileSize
271
+ );
272
+ } else {
273
+ tileResolution = firstTile.zoom;
274
+ viewportResolution = this.context.viewport.zoom;
275
+ }
276
+
277
+ const resolutionDiff = Math.round(viewportResolution - tileResolution);
278
+ const aggregationLevels = Math.round(this.props.clusterLevel) - resolutionDiff;
279
+ return aggregationLevels;
280
+ }
210
281
  }
211
282
 
212
283
  // Adapter layer around ClusterLayer that converts tileJSON into TileLayer API
@@ -219,9 +290,10 @@ export default class ClusterTileLayer<
219
290
 
220
291
  getLoadOptions(): any {
221
292
  const tileJSON = this.props.data as TilejsonResult;
293
+ const scheme = tileJSON && 'scheme' in tileJSON ? tileJSON.scheme : 'quadbin';
222
294
  return mergeLoadOptions(super.getLoadOptions(), {
223
295
  fetch: {headers: {Authorization: `Bearer ${tileJSON.accessToken}`}},
224
- cartoSpatialTile: {scheme: 'quadbin'}
296
+ cartoSpatialTile: {scheme}
225
297
  });
226
298
  }
227
299
 
@@ -230,13 +302,16 @@ export default class ClusterTileLayer<
230
302
  if (!tileJSON) return null;
231
303
 
232
304
  const {tiles: data, maxresolution: maxZoom} = tileJSON;
305
+ const isH3 = tileJSON && 'scheme' in tileJSON && tileJSON.scheme === 'h3';
306
+ const TilesetClass = isH3 ? H3Tileset2D : QuadbinTileset2D;
307
+
233
308
  return [
234
309
  // @ts-ignore
235
310
  new ClusterGeoJsonLayer(this.props, {
236
311
  id: `cluster-geojson-layer-${this.props.id}`,
237
312
  data,
238
313
  // TODO: Tileset2D should be generic over TileIndex type
239
- TilesetClass: QuadbinTileset2D as any,
314
+ TilesetClass: TilesetClass as any,
240
315
  maxZoom,
241
316
  loadOptions: this.getLoadOptions()
242
317
  })
@@ -3,6 +3,7 @@
3
3
  // Copyright (c) vis.gl contributors
4
4
 
5
5
  import {cellToParent} from 'quadbin';
6
+ import {cellToParent as h3CellToParent, getResolution as getH3Resolution} from 'h3-js';
6
7
  import {_Tile2DHeader as Tile2DHeader} from '@deck.gl/geo-layers';
7
8
  import {Accessor, log} from '@deck.gl/core';
8
9
  import {BinaryFeatureCollection} from '@loaders.gl/schema';
@@ -14,12 +15,14 @@ export type AggregationProperties<FeaturePropertiesT> = {
14
15
  name: keyof FeaturePropertiesT;
15
16
  }[];
16
17
  export type ClusteredFeaturePropertiesT<FeaturePropertiesT> = FeaturePropertiesT & {
17
- id: bigint;
18
+ id: bigint | string;
18
19
  count: number;
19
20
  position: [number, number];
20
21
  };
21
22
  export type ParsedQuadbinCell<FeaturePropertiesT> = {id: bigint; properties: FeaturePropertiesT};
22
23
  export type ParsedQuadbinTile<FeaturePropertiesT> = ParsedQuadbinCell<FeaturePropertiesT>[];
24
+ export type ParsedH3Cell<FeaturePropertiesT> = {id: string; properties: FeaturePropertiesT};
25
+ export type ParsedH3Tile<FeaturePropertiesT> = ParsedH3Cell<FeaturePropertiesT>[];
23
26
 
24
27
  /**
25
28
  * Aggregates tile by specified properties, caching result in tile.userData
@@ -27,12 +30,19 @@ export type ParsedQuadbinTile<FeaturePropertiesT> = ParsedQuadbinCell<FeaturePro
27
30
  * @returns true if data was aggregated, false if cache used
28
31
  */
29
32
  export function aggregateTile<FeaturePropertiesT>(
30
- tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>,
33
+ tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>>,
31
34
  tileAggregationCache: Map<number, ClusteredFeaturePropertiesT<FeaturePropertiesT>[]>,
32
35
  aggregationLevels: number,
33
36
  properties: AggregationProperties<FeaturePropertiesT> = [],
34
- getPosition: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, [number, number]>,
35
- getWeight: Accessor<ParsedQuadbinCell<FeaturePropertiesT>, number>
37
+ getPosition: Accessor<
38
+ ParsedQuadbinCell<FeaturePropertiesT> | ParsedH3Cell<FeaturePropertiesT>,
39
+ [number, number]
40
+ >,
41
+ getWeight: Accessor<
42
+ ParsedQuadbinCell<FeaturePropertiesT> | ParsedH3Cell<FeaturePropertiesT>,
43
+ number
44
+ >,
45
+ scheme: 'quadbin' | 'h3' = 'quadbin'
36
46
  ): boolean {
37
47
  if (!tile.content) return false;
38
48
 
@@ -50,19 +60,24 @@ export function aggregateTile<FeaturePropertiesT>(
50
60
  tileAggregationCache.clear();
51
61
  }
52
62
 
53
- const out: Record<number, any> = {};
63
+ const out: Record<string, any> = {};
54
64
  for (const cell of tile.content) {
55
65
  let id = cell.id;
56
66
  const position = typeof getPosition === 'function' ? getPosition(cell, {} as any) : getPosition;
57
67
 
58
68
  // Aggregate by parent rid
59
69
  for (let i = 0; i < aggregationLevels - 1; i++) {
60
- id = cellToParent(id);
70
+ if (scheme === 'h3') {
71
+ const currentResolution = getH3Resolution(id as string);
72
+ id = h3CellToParent(id as string, Math.max(0, currentResolution - 1));
73
+ } else {
74
+ id = cellToParent(id as bigint);
75
+ }
61
76
  }
62
77
 
63
- // Unfortunately TS doesn't support Record<bigint, any>
78
+ // Use string key for both H3 and Quadbin to avoid TypeScript Record<bigint, any> issues
64
79
  // https://github.com/microsoft/TypeScript/issues/46395
65
- const parentId = Number(id);
80
+ const parentId = String(id);
66
81
  if (!(parentId in out)) {
67
82
  out[parentId] = {id, count: 0, position: [0, 0]};
68
83
  for (const {name, aggregation} of properties) {
@@ -104,7 +119,7 @@ export function aggregateTile<FeaturePropertiesT>(
104
119
  }
105
120
 
106
121
  export function extractAggregationProperties<FeaturePropertiesT extends {}>(
107
- tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT>>
122
+ tile: Tile2DHeader<ParsedQuadbinTile<FeaturePropertiesT> | ParsedH3Tile<FeaturePropertiesT>>
108
123
  ): AggregationProperties<FeaturePropertiesT> {
109
124
  const properties: AggregationProperties<FeaturePropertiesT> = [];
110
125
  const validAggregations: Aggregation[] = ['any', 'average', 'count', 'min', 'max', 'sum'];