@deck.gl/carto 8.8.0-alpha.2 → 8.8.0-alpha.5

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.
Files changed (64) hide show
  1. package/dist/dist.dev.js +1752 -1262
  2. package/dist/es5/api/layer-map.js +17 -5
  3. package/dist/es5/api/layer-map.js.map +1 -1
  4. package/dist/es5/api/maps-api-common.js +1 -0
  5. package/dist/es5/api/maps-api-common.js.map +1 -1
  6. package/dist/es5/api/parseMap.js +14 -3
  7. package/dist/es5/api/parseMap.js.map +1 -1
  8. package/dist/es5/index.js +16 -0
  9. package/dist/es5/index.js.map +1 -1
  10. package/dist/es5/layers/carto-layer.js +16 -1
  11. package/dist/es5/layers/carto-layer.js.map +1 -1
  12. package/dist/es5/layers/carto-tile-layer.js +17 -12
  13. package/dist/es5/layers/carto-tile-layer.js.map +1 -1
  14. package/dist/es5/layers/h3-tile-layer.js +107 -0
  15. package/dist/es5/layers/h3-tile-layer.js.map +1 -0
  16. package/dist/es5/layers/h3-tileset-2d.js +175 -0
  17. package/dist/es5/layers/h3-tileset-2d.js.map +1 -0
  18. package/dist/es5/layers/quadkey-tile-layer.js +103 -0
  19. package/dist/es5/layers/quadkey-tile-layer.js.map +1 -0
  20. package/dist/es5/layers/quadkey-tileset-2d.js +89 -0
  21. package/dist/es5/layers/quadkey-tileset-2d.js.map +1 -0
  22. package/dist/es5/layers/schema/carto-tile.js +261 -188
  23. package/dist/es5/layers/schema/carto-tile.js.map +1 -1
  24. package/dist/es5/layers/spatial-index-tile-layer.js +92 -0
  25. package/dist/es5/layers/spatial-index-tile-layer.js.map +1 -0
  26. package/dist/esm/api/layer-map.js +12 -4
  27. package/dist/esm/api/layer-map.js.map +1 -1
  28. package/dist/esm/api/maps-api-common.js +1 -0
  29. package/dist/esm/api/maps-api-common.js.map +1 -1
  30. package/dist/esm/api/parseMap.js +15 -4
  31. package/dist/esm/api/parseMap.js.map +1 -1
  32. package/dist/esm/index.js +2 -0
  33. package/dist/esm/index.js.map +1 -1
  34. package/dist/esm/layers/carto-layer.js +15 -2
  35. package/dist/esm/layers/carto-layer.js.map +1 -1
  36. package/dist/esm/layers/carto-tile-layer.js +20 -16
  37. package/dist/esm/layers/carto-tile-layer.js.map +1 -1
  38. package/dist/esm/layers/h3-tile-layer.js +69 -0
  39. package/dist/esm/layers/h3-tile-layer.js.map +1 -0
  40. package/dist/esm/layers/h3-tileset-2d.js +118 -0
  41. package/dist/esm/layers/h3-tileset-2d.js.map +1 -0
  42. package/dist/esm/layers/quadkey-tile-layer.js +63 -0
  43. package/dist/esm/layers/quadkey-tile-layer.js.map +1 -0
  44. package/dist/esm/layers/quadkey-tileset-2d.js +48 -0
  45. package/dist/esm/layers/quadkey-tileset-2d.js.map +1 -0
  46. package/dist/esm/layers/schema/carto-tile.js +174 -189
  47. package/dist/esm/layers/schema/carto-tile.js.map +1 -1
  48. package/dist/esm/layers/spatial-index-tile-layer.js +57 -0
  49. package/dist/esm/layers/spatial-index-tile-layer.js.map +1 -0
  50. package/dist.min.js +14 -14
  51. package/package.json +5 -3
  52. package/src/api/layer-map.ts +14 -4
  53. package/src/api/maps-api-common.ts +1 -0
  54. package/src/api/parseMap.ts +20 -5
  55. package/src/index.ts +2 -0
  56. package/src/layers/carto-layer.ts +16 -2
  57. package/src/layers/{carto-tile-layer.js → carto-tile-layer.ts} +64 -27
  58. package/src/layers/h3-tile-layer.js +52 -0
  59. package/src/layers/h3-tileset-2d.js +108 -0
  60. package/src/layers/quadkey-tile-layer.js +47 -0
  61. package/src/layers/quadkey-tileset-2d.js +36 -0
  62. package/src/layers/schema/carto-tile.ts +221 -0
  63. package/src/layers/spatial-index-tile-layer.js +48 -0
  64. package/src/layers/schema/carto-tile.js +0 -195
package/package.json CHANGED
@@ -2,7 +2,7 @@
2
2
  "name": "@deck.gl/carto",
3
3
  "description": "CARTO official integration with Deck.gl. Build geospatial applications using CARTO and Deck.gl.",
4
4
  "license": "MIT",
5
- "version": "8.8.0-alpha.2",
5
+ "version": "8.8.0-alpha.5",
6
6
  "publishConfig": {
7
7
  "access": "public"
8
8
  },
@@ -23,6 +23,7 @@
23
23
  "files": [
24
24
  "dist",
25
25
  "src",
26
+ "typed",
26
27
  "dist.min.js"
27
28
  ],
28
29
  "sideEffects": false,
@@ -35,7 +36,7 @@
35
36
  "@loaders.gl/loader-utils": "^3.2.0-alpha.4",
36
37
  "@loaders.gl/mvt": "^3.2.0-alpha.4",
37
38
  "@loaders.gl/tiles": "^3.2.0-alpha.4",
38
- "@luma.gl/constants": "^8.5.10",
39
+ "@luma.gl/constants": "^8.5.13",
39
40
  "@math.gl/web-mercator": "3.6.0-alpha.5",
40
41
  "cartocolor": "^4.0.2",
41
42
  "d3-array": "^2.8.0",
@@ -46,6 +47,7 @@
46
47
  "pbf": "^3.2.1"
47
48
  },
48
49
  "devDependencies": {
50
+ "@loaders.gl/schema": "^3.2.0-alpha.4",
49
51
  "@types/d3-array": "^3.0.2",
50
52
  "@types/d3-color": "^1.4.2",
51
53
  "@types/d3-scale": "^3.0.0"
@@ -58,5 +60,5 @@
58
60
  "@deck.gl/layers": "^8.0.0",
59
61
  "@loaders.gl/core": "^3.0.0"
60
62
  },
61
- "gitHead": "a52215449dff06fb683f5afd6399526353d11f35"
63
+ "gitHead": "a5e210f62321ab78b7cacbcd0614e0ffcdf6bca7"
62
64
  }
@@ -42,6 +42,12 @@ export const AGGREGATION = {
42
42
  sum: 'SUM'
43
43
  };
44
44
 
45
+ export const OPACITY_MAP = {
46
+ getFillColor: 'opacity',
47
+ getLineColor: 'strokeOpacity',
48
+ getTextColor: 'opacity'
49
+ };
50
+
45
51
  const AGGREGATION_FUNC = {
46
52
  'count unique': (values, accessor) => groupSort(values, v => v.length, accessor).length,
47
53
  median,
@@ -71,7 +77,6 @@ const sharedPropMap = {
71
77
  enable3d: 'extruded',
72
78
  elevationScale: 'elevationScale',
73
79
  filled: 'filled',
74
- opacity: 'opacity',
75
80
  strokeColor: 'getLineColor',
76
81
  stroked: 'stroked',
77
82
  thickness: 'getLineWidth',
@@ -225,6 +230,10 @@ function normalizeAccessor(accessor, data) {
225
230
  return accessor;
226
231
  }
227
232
 
233
+ export function opacityToAlpha(opacity) {
234
+ return opacity !== undefined ? Math.round(255 * Math.pow(opacity, 1 / 2.2)) : 255;
235
+ }
236
+
228
237
  export function getColorValueAccessor({name}, colorAggregation, data: any) {
229
238
  const aggregator = AGGREGATION_FUNC[colorAggregation];
230
239
  const accessor = values => aggregator(values, p => p[name]);
@@ -259,11 +268,12 @@ export function getColorAccessor(
259
268
  scale.domain(domain);
260
269
  scale.range(scaleColor);
261
270
  scale.unknown(UNKNOWN_COLOR);
262
- const alpha = opacity !== undefined ? Math.round(255 * Math.pow(opacity, 1 / 2.2)) : 255;
271
+ const alpha = opacityToAlpha(opacity);
263
272
 
264
273
  const accessor = properties => {
265
- const {r, g, b} = rgb(scale(properties[name]));
266
- return [r, g, b, alpha];
274
+ const propertyValue = properties[name];
275
+ const {r, g, b} = rgb(scale(propertyValue));
276
+ return [r, g, b, propertyValue === null ? 0 : alpha];
267
277
  };
268
278
  return normalizeAccessor(accessor, data);
269
279
  }
@@ -35,6 +35,7 @@ export type Format = ValuesOf<typeof FORMATS>;
35
35
  // AVAILABLE FORMATS FOR TILES
36
36
  export const TILE_FORMATS = {
37
37
  MVT: 'mvt',
38
+ JSON: 'json',
38
39
  GEOJSON: 'geojson',
39
40
  BINARY: 'binary'
40
41
  } as const;
@@ -6,7 +6,9 @@ import {
6
6
  getColorValueAccessor,
7
7
  getSizeAccessor,
8
8
  getTextAccessor,
9
- getTextPixelOffsetAccessor
9
+ getTextPixelOffsetAccessor,
10
+ OPACITY_MAP,
11
+ opacityToAlpha
10
12
  } from './layer-map';
11
13
  import {_flatten as flatten, log} from '@deck.gl/core';
12
14
  import {assert} from '../utils';
@@ -131,10 +133,21 @@ function createStyleProps(config, mapping) {
131
133
  mapProps(config, result, mapping);
132
134
 
133
135
  // Kepler format sometimes omits strokeColor. TODO: remove once we can rely on
134
- // `strokeColor` always being sert when `stroke: true`.
136
+ // `strokeColor` always being set when `stroke: true`.
135
137
  if (result.stroked && !result.getLineColor) {
136
138
  result.getLineColor = result.getFillColor;
137
139
  }
140
+
141
+ for (const colorAccessor in OPACITY_MAP) {
142
+ if (Array.isArray(result[colorAccessor])) {
143
+ const color = [...result[colorAccessor]];
144
+ const opacityKey = OPACITY_MAP[colorAccessor];
145
+ const opacity = config.visConfig[opacityKey];
146
+ color[3] = opacityToAlpha(opacity);
147
+ result[colorAccessor] = color;
148
+ }
149
+ }
150
+
138
151
  result.highlightColor = config.visConfig.enable3d ? [255, 255, 255, 60] : [252, 242, 26, 255];
139
152
  return result;
140
153
  }
@@ -159,7 +172,7 @@ function createChannelProps(visualChannels, type, config, data) {
159
172
  if (!AGGREGATION[colorAggregation]) {
160
173
  result.getColorValue = getColorValueAccessor(colorField, colorAggregation, data);
161
174
  } else {
162
- result.getColorWeight = d => [colorField.name];
175
+ result.getColorWeight = d => d[colorField.name];
163
176
  }
164
177
  }
165
178
  } else if (colorField) {
@@ -167,13 +180,15 @@ function createChannelProps(visualChannels, type, config, data) {
167
180
  colorField,
168
181
  colorScale,
169
182
  visConfig.colorRange,
170
- 1, // Rely on layer opacity
183
+ visConfig.opacity,
171
184
  data
172
185
  );
173
186
  }
174
187
 
175
188
  if (strokeColorField) {
176
- const opacity = visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : 1;
189
+ const fallbackOpacity = type === 'point' ? visConfig.opacity : 1;
190
+ const opacity =
191
+ visConfig.strokeOpacity !== undefined ? visConfig.strokeOpacity : fallbackOpacity;
177
192
  result.getLineColor = getColorAccessor(
178
193
  strokeColorField,
179
194
  strokeColorScale,
package/src/index.ts CHANGED
@@ -1,6 +1,8 @@
1
1
  export {getDefaultCredentials, setDefaultCredentials} from './config';
2
2
  export {default as CartoLayer} from './layers/carto-layer';
3
3
  export {default as _CartoTileLayer} from './layers/carto-tile-layer';
4
+ export {default as _H3TileLayer} from './layers/h3-tile-layer';
5
+ export {default as _QuadkeyTileLayer} from './layers/quadkey-tile-layer';
4
6
  export {default as BASEMAP} from './basemap';
5
7
  export {default as colorBins} from './style/color-bins-style';
6
8
  export {default as colorCategories} from './style/color-categories-style';
@@ -1,5 +1,7 @@
1
1
  import {CompositeLayer, Layer, log} from '@deck.gl/core';
2
2
  import CartoTileLayer from './carto-tile-layer';
3
+ import H3TileLayer from './h3-tile-layer';
4
+ import QuadkeyTileLayer from './quadkey-tile-layer';
3
5
  import {MVTLayer} from '@deck.gl/geo-layers';
4
6
  import {GeoJsonLayer} from '@deck.gl/layers';
5
7
  import {fetchLayerData, getDataV2, API_VERSIONS} from '../api';
@@ -55,7 +57,13 @@ const defaultProps = {
55
57
  geoColumn: null,
56
58
 
57
59
  // (Array<String>, optional): names of columns to fetch. By default, all columns are fetched.
58
- columns: {type: 'array', value: null}
60
+ columns: {type: 'array', value: null},
61
+
62
+ // (String, optional): aggregration SQL expression. Only used for spatial index datasets
63
+ aggregationExp: null,
64
+
65
+ // (Number, optional): aggregration resolution level. Only used for spatial index datasets, defaults to 6 for quadkeys, 4 for h3
66
+ aggregationResLevel: null
59
67
  };
60
68
 
61
69
  export interface CartoLayerProps<DataT = any> extends CompositeLayerProps<DataT> {
@@ -240,7 +248,7 @@ export default class CartoLayer<DataT = any> extends CompositeLayer<CartoLayerPr
240
248
  source,
241
249
  clientId,
242
250
  credentials: credentials as CloudNativeCredentials,
243
- connection: connection as string,
251
+ connection,
244
252
  ...rest
245
253
  });
246
254
  }
@@ -283,6 +291,12 @@ export default class CartoLayer<DataT = any> extends CompositeLayer<CartoLayerPr
283
291
  (tileUrl.searchParams.get('formatTiles') as TileFormat) ||
284
292
  TILE_FORMATS.MVT;
285
293
 
294
+ if (data.scheme === 'h3') {
295
+ return [H3TileLayer, props];
296
+ }
297
+ if (data.scheme === 'quadkey') {
298
+ return [QuadkeyTileLayer, props];
299
+ }
286
300
  return props.formatTiles === TILE_FORMATS.MVT ? [MVTLayer, props] : [CartoTileLayer, props];
287
301
  }
288
302
 
@@ -1,29 +1,39 @@
1
1
  /* global TextDecoder */
2
2
  import Protobuf from 'pbf';
3
- import {log} from '@deck.gl/core';
3
+ import {Layer, LayersList, log} from '@deck.gl/core';
4
4
  import {ClipExtension} from '@deck.gl/extensions';
5
- import {MVTLayer, _getURLFromTemplate} from '@deck.gl/geo-layers';
5
+ import {
6
+ MVTLayer,
7
+ MVTLayerProps,
8
+ TileLayer,
9
+ _getURLFromTemplate,
10
+ _Tile2DHeader,
11
+ _TileLoadProps as TileLoadProps
12
+ } from '@deck.gl/geo-layers';
6
13
  import {GeoJsonLayer} from '@deck.gl/layers';
7
14
  import {geojsonToBinary} from '@loaders.gl/gis';
8
- import {Tile} from './schema/carto-tile';
9
- import {TILE_FORMATS} from '../api/maps-api-common';
15
+ import {Properties, Tile, TileReader} from './schema/carto-tile';
16
+ import {TileFormat, TILE_FORMATS} from '../api/maps-api-common';
17
+ import {LoaderOptions, LoaderWithParser} from '@loaders.gl/loader-utils';
18
+ import type {BinaryFeatures} from '@loaders.gl/schema';
19
+ import type {Feature} from 'geojson';
10
20
 
11
- function parseJSON(arrayBuffer) {
21
+ function parseJSON(arrayBuffer: ArrayBuffer): any {
12
22
  return JSON.parse(new TextDecoder().decode(arrayBuffer));
13
23
  }
14
24
 
15
- function parsePbf(buffer) {
25
+ function parsePbf(buffer: ArrayBuffer): Tile {
16
26
  const pbf = new Protobuf(buffer);
17
- const tile = Tile.read(pbf);
27
+ const tile = TileReader.read(pbf);
18
28
  return tile;
19
29
  }
20
30
 
21
- function unpackProperties(properties) {
31
+ function unpackProperties(properties: Properties[]) {
22
32
  if (!properties || !properties.length) {
23
33
  return [];
24
34
  }
25
35
  return properties.map(item => {
26
- const currentRecord = {};
36
+ const currentRecord: Record<string, unknown> = {};
27
37
  item.data.forEach(({key, value}) => {
28
38
  currentRecord[key] = value;
29
39
  });
@@ -31,7 +41,7 @@ function unpackProperties(properties) {
31
41
  });
32
42
  }
33
43
 
34
- function parseCartoTile(arrayBuffer, options) {
44
+ function parseCartoTile(arrayBuffer: ArrayBuffer, options?: LoaderOptions): BinaryFeatures | null {
35
45
  if (!arrayBuffer) return null;
36
46
  const formatTiles = options && options.cartoTile && options.cartoTile.formatTiles;
37
47
  if (formatTiles === TILE_FORMATS.GEOJSON) return geojsonToBinary(parseJSON(arrayBuffer).features);
@@ -45,13 +55,15 @@ function parseCartoTile(arrayBuffer, options) {
45
55
  polygons: {...polygons, properties: unpackProperties(polygons.properties)}
46
56
  };
47
57
 
48
- return data;
58
+ // Note: there is slight, difference in `numericProps` type, however geojson/mvtlayer can cope with this
59
+ return data as unknown as BinaryFeatures;
49
60
  }
50
61
 
51
62
  const defaultTileFormat = TILE_FORMATS.BINARY;
52
63
 
53
- const CartoTileLoader = {
64
+ const CartoTileLoader: LoaderWithParser = {
54
65
  name: 'CARTO Tile',
66
+ version: '1',
55
67
  id: 'cartoTile',
56
68
  module: 'carto',
57
69
  extensions: ['pbf'],
@@ -73,8 +85,29 @@ const defaultProps = {
73
85
  loaders: [CartoTileLoader]
74
86
  };
75
87
 
76
- export default class CartoTileLayer extends MVTLayer {
77
- getTileData(tile) {
88
+ /** All properties supported by CartoTileLayer. */
89
+ export type CartoTileLayerProps<DataT extends Feature = Feature> = _CartoTileLayerProps &
90
+ MVTLayerProps<DataT>;
91
+
92
+ /** Properties added by CartoTileLayer. */
93
+ type _CartoTileLayerProps = {
94
+ /** Use to override the default tile data format.
95
+ *
96
+ * Possible values are: `TILE_FORMATS.BINARY`, `TILE_FORMATS.GEOJSON` and `TILE_FORMATS.MVT`.
97
+ *
98
+ * Only supported when `apiVersion` is `API_VERSIONS.V3` and `format` is `FORMATS.TILEJSON`.
99
+ * */
100
+ formatTiles?: TileFormat;
101
+ };
102
+
103
+ export default class CartoTileLayer<
104
+ DataT extends Feature = Feature,
105
+ ExtraProps = {}
106
+ > extends MVTLayer<DataT, Required<_CartoTileLayerProps> & ExtraProps> {
107
+ static layerName = 'CartoTileLayer';
108
+ static defaultProps = defaultProps;
109
+
110
+ getTileData(tile: TileLoadProps) {
78
111
  const url = _getURLFromTemplate(this.state.data, tile);
79
112
  if (!url) {
80
113
  return Promise.reject('Invalid URL');
@@ -100,25 +133,29 @@ export default class CartoTileLayer extends MVTLayer {
100
133
  return fetch(url, {propName: 'data', layer: this, loadOptions, signal});
101
134
  }
102
135
 
103
- renderSubLayers(props) {
136
+ renderSubLayers(
137
+ props: TileLayer['props'] & {
138
+ id: string;
139
+ data: any;
140
+ _offset: number;
141
+ tile: _Tile2DHeader;
142
+ }
143
+ ): GeoJsonLayer | null {
104
144
  if (props.data === null) {
105
145
  return null;
106
146
  }
107
147
 
108
- props.autoHighlight = false;
148
+ const tileBbox = props.tile.bbox as any;
149
+ const {west, south, east, north} = tileBbox;
109
150
 
110
- const {
111
- bbox: {west, south, east, north}
112
- } = props.tile;
113
- props.extensions = [new ClipExtension(), ...(props.extensions || [])];
114
- props.clipBounds = [west, south, east, north];
151
+ const subLayerProps = {
152
+ ...props,
153
+ autoHighlight: false,
154
+ extensions: [new ClipExtension(), ...(props.extensions || [])],
155
+ clipBounds: [west, south, east, north]
156
+ };
115
157
 
116
- const subLayer = new GeoJsonLayer({
117
- ...props
118
- });
158
+ const subLayer = new GeoJsonLayer(subLayerProps);
119
159
  return subLayer;
120
160
  }
121
161
  }
122
-
123
- CartoTileLayer.layerName = 'CartoTileLayer';
124
- CartoTileLayer.defaultProps = defaultProps;
@@ -0,0 +1,52 @@
1
+ import {CompositeLayer} from '@deck.gl/core';
2
+ import {H3HexagonLayer} from '@deck.gl/geo-layers';
3
+ import H3Tileset2D from './h3-tileset-2d';
4
+ import SpatialIndexTileLayer from './spatial-index-tile-layer';
5
+
6
+ const renderSubLayers = props => {
7
+ const {data} = props;
8
+ const {index} = props.tile;
9
+ if (!data || !data.length) return null;
10
+
11
+ return new H3HexagonLayer(props, {
12
+ centerHexagon: index,
13
+ highPrecision: true
14
+ });
15
+ };
16
+
17
+ const defaultProps = {
18
+ aggregationResLevel: 4
19
+ };
20
+
21
+ export default class H3TileLayer extends CompositeLayer {
22
+ initializeState() {
23
+ this.setState({data: null, tileJSON: null});
24
+ }
25
+
26
+ updateState({props, oldProps, context, changeFlags}) {
27
+ if (changeFlags.dataChanged) {
28
+ let {data} = this.props;
29
+ const tileJSON = data;
30
+ data = tileJSON.tiles;
31
+ this.setState({data, tileJSON});
32
+ }
33
+ }
34
+
35
+ renderLayers() {
36
+ const {data, tileJSON} = this.state;
37
+ const maxZoom = parseInt(tileJSON.maxresolution);
38
+ return [
39
+ new SpatialIndexTileLayer(this.props, {
40
+ id: `h3-tile-layer-${this.props.id}`,
41
+ data,
42
+ getHexagon: d => d.id,
43
+ TilesetClass: H3Tileset2D,
44
+ renderSubLayers,
45
+ maxZoom
46
+ })
47
+ ];
48
+ }
49
+ }
50
+
51
+ H3TileLayer.layerName = 'H3TileLayer';
52
+ H3TileLayer.defaultProps = defaultProps;
@@ -0,0 +1,108 @@
1
+ import {_Tileset2D as Tileset2D} from '@deck.gl/geo-layers';
2
+ import {
3
+ polyfill,
4
+ getRes0Indexes,
5
+ geoToH3,
6
+ h3GetResolution,
7
+ h3ToGeoBoundary,
8
+ h3ToParent,
9
+ kRing
10
+ } from 'h3-js';
11
+
12
+ function getHexagonsInBoundingBox({west, north, east, south}, resolution) {
13
+ if (resolution === 0) {
14
+ return getRes0Indexes();
15
+ }
16
+ if (east - west > 180) {
17
+ // This is a known issue in h3-js: polyfill does not work correctly
18
+ // when longitude span is larger than 180 degrees.
19
+ return getHexagonsInBoundingBox({west, north, east: 0, south}, resolution).concat(
20
+ getHexagonsInBoundingBox({west: 0, north, east, south}, resolution)
21
+ );
22
+ }
23
+
24
+ // `polyfill()` fills based on hexagon center, which means tiles vanish
25
+ // prematurely. Get more accurate coverage by oversampling
26
+ const oversample = 2;
27
+ const h3Indices = polyfill(
28
+ [
29
+ [
30
+ [west, north],
31
+ [west, south],
32
+ [east, south],
33
+ [east, north],
34
+ [west, north]
35
+ ]
36
+ ],
37
+ resolution + oversample,
38
+ true
39
+ );
40
+
41
+ return oversample ? [...new Set(h3Indices.map(i => h3ToParent(i, resolution)))] : h3Indices;
42
+ }
43
+
44
+ function tileToBoundingBox(index) {
45
+ const coordinates = h3ToGeoBoundary(index);
46
+ const latitudes = coordinates.map(c => c[0]);
47
+ const longitudes = coordinates.map(c => c[1]);
48
+ const west = Math.min(...longitudes);
49
+ const south = Math.min(...latitudes);
50
+ const east = Math.max(...longitudes);
51
+ const north = Math.max(...latitudes);
52
+ return {west, south, east, north};
53
+ }
54
+
55
+ // Resolution conversion function. Takes a WebMercatorViewport and returns
56
+ // a H3 resolution such that the screen space size of the hexagons is
57
+ // similar
58
+ // Relative scale factor (0 = no biasing, 2 = a few hexagons cover view)
59
+ const BIAS = 2;
60
+ function getHexagonResolution(viewport) {
61
+ const hexagonScaleFactor = (2 / 3) * viewport.zoom;
62
+ const latitudeScaleFactor = Math.log(1 / Math.cos((Math.PI * viewport.latitude) / 180));
63
+
64
+ // Clip and bias
65
+ return Math.max(0, Math.floor(hexagonScaleFactor + latitudeScaleFactor - BIAS));
66
+ }
67
+
68
+ export default class H3Tileset2D extends Tileset2D {
69
+ getTileIndices({viewport, minZoom, maxZoom}) {
70
+ const [east, south, west, north] = viewport.getBounds();
71
+
72
+ let z = getHexagonResolution(viewport);
73
+ let indices = [];
74
+ if (typeof minZoom === 'number' && Number.isFinite(minZoom) && z < minZoom) {
75
+ z = minZoom;
76
+ }
77
+ if (typeof maxZoom === 'number' && Number.isFinite(maxZoom) && z > maxZoom) {
78
+ z = maxZoom;
79
+
80
+ // Once we are at max zoom, getHexagonsInBoundingBox doesn't work, simply
81
+ // get a ring centered on the hexagon in the viewport center
82
+ const center = geoToH3(viewport.latitude, viewport.longitude, maxZoom);
83
+ indices = kRing(center, 1);
84
+ } else {
85
+ indices = getHexagonsInBoundingBox({west, north, east, south}, z);
86
+ }
87
+
88
+ return indices.map(i => ({i}));
89
+ }
90
+
91
+ getTileId({i}) {
92
+ return i;
93
+ }
94
+
95
+ getTileMetadata({i}) {
96
+ return {bbox: tileToBoundingBox(i)};
97
+ }
98
+
99
+ getTileZoom({i}) {
100
+ return h3GetResolution(i);
101
+ }
102
+
103
+ getParentIndex(index) {
104
+ const resolution = h3GetResolution(index.i);
105
+ const i = h3ToParent(index.i, resolution - 1);
106
+ return {i};
107
+ }
108
+ }
@@ -0,0 +1,47 @@
1
+ import {CompositeLayer} from '@deck.gl/core';
2
+ import {QuadkeyLayer} from '@deck.gl/geo-layers';
3
+ import QuadkeyTileset2D from './quadkey-tileset-2d';
4
+ import SpatialIndexTileLayer from './spatial-index-tile-layer';
5
+
6
+ const renderSubLayers = props => {
7
+ const {data} = props;
8
+ if (!data || !data.length) return null;
9
+ return new QuadkeyLayer(props);
10
+ };
11
+
12
+ const defaultProps = {
13
+ aggregationResLevel: 6
14
+ };
15
+
16
+ export default class QuadkeyTileLayer extends CompositeLayer {
17
+ initializeState() {
18
+ this.setState({data: null, tileJSON: null});
19
+ }
20
+
21
+ updateState({props, oldProps, context, changeFlags}) {
22
+ if (changeFlags.dataChanged) {
23
+ let {data} = this.props;
24
+ const tileJSON = data;
25
+ data = tileJSON.tiles;
26
+ this.setState({data, tileJSON});
27
+ }
28
+ }
29
+
30
+ renderLayers() {
31
+ const {data, tileJSON} = this.state;
32
+ const maxZoom = parseInt(tileJSON.maxresolution);
33
+ return [
34
+ new SpatialIndexTileLayer(this.props, {
35
+ id: `quadkey-tile-layer-${this.props.id}`,
36
+ data,
37
+ getQuadkey: d => d.id,
38
+ TilesetClass: QuadkeyTileset2D,
39
+ renderSubLayers,
40
+ maxZoom
41
+ })
42
+ ];
43
+ }
44
+ }
45
+
46
+ QuadkeyTileLayer.layerName = 'QuadkeyTileLayer';
47
+ QuadkeyTileLayer.defaultProps = defaultProps;
@@ -0,0 +1,36 @@
1
+ import {_Tileset2D as Tileset2D} from '@deck.gl/geo-layers';
2
+
3
+ function tileToQuadkey(tile) {
4
+ let index = '';
5
+ for (let z = tile.z; z > 0; z--) {
6
+ let b = 0;
7
+ const mask = 1 << (z - 1);
8
+ if ((tile.x & mask) !== 0) b++;
9
+ if ((tile.y & mask) !== 0) b += 2;
10
+ index += b.toString();
11
+ }
12
+ return {i: index};
13
+ }
14
+
15
+ export default class QuadkeyTileset2D extends Tileset2D {
16
+ getTileIndices(opts) {
17
+ return super.getTileIndices(opts).map(tileToQuadkey);
18
+ }
19
+
20
+ getTileId({i}) {
21
+ return i;
22
+ }
23
+
24
+ getTileMetadata() {
25
+ return {};
26
+ }
27
+
28
+ getTileZoom({i}) {
29
+ return i.length;
30
+ }
31
+
32
+ getParentIndex(index) {
33
+ const i = index.i.slice(0, -1);
34
+ return {i};
35
+ }
36
+ }