6
6
7
7
import type { ShaderModule } from '@luma.gl/shadertools' ;
8
8
import { getResolution } from 'quadbin' ;
9
+ import { getResolution as getH3Resolution , getNumCells } from 'h3-js' ;
9
10
10
11
import {
11
12
Accessor ,
12
13
Color ,
13
14
CompositeLayer ,
14
15
CompositeLayerProps ,
16
+ _deepEqual as deepEqual ,
15
17
DefaultProps ,
16
18
Layer ,
17
19
UpdateParameters
@@ -21,6 +23,7 @@ import {SolidPolygonLayer} from '@deck.gl/layers';
21
23
import { HeatmapProps , heatmap } from './heatmap' ;
22
24
import { RTTModifier , PostProcessModifier } from './post-process-utils' ;
23
25
import QuadbinTileLayer , { QuadbinTileLayerProps } from './quadbin-tile-layer' ;
26
+ import H3TileLayer , { H3TileLayerProps } from './h3-tile-layer' ;
24
27
import { TilejsonPropType } from './utils' ;
25
28
import { TilejsonResult } from '@carto/api-client' ;
26
29
import { _Tile2DHeader as Tile2DHeader } from '@deck.gl/geo-layers' ;
@@ -48,13 +51,23 @@ const TEXTURE_PROPS: TextureProps = {
48
51
}
49
52
} ;
50
53
/**
51
- * Computes the unit density (inverse of cell area)
54
+ * Computes the unit density for Quadbin cells.
55
+ * The unit density is the number of cells needed to cover the Earth's surface at a given resolution. It is inversely proportional to the cell area.
52
56
*/
53
- function unitDensityForCell ( cell : bigint ) {
57
+ function unitDensityForQuadbinCell ( cell : bigint ) {
54
58
const cellResolution = Number ( getResolution ( cell ) ) ;
55
59
return Math . pow ( 4.0 , cellResolution ) ;
56
60
}
57
61
62
+ /**
63
+ * Computes the unit density for H3 cells.
64
+ * The unit density is the number of cells needed to cover the Earth's surface at a given resolution. It is inversely proportional to the cell area.
65
+ */
66
+ function unitDensityForH3Cell ( cellId : string ) {
67
+ const cellResolution = Number ( getH3Resolution ( cellId ) ) ;
68
+ return getNumCells ( cellResolution ) ;
69
+ }
70
+
58
71
/**
59
72
* Converts a colorRange array to a flat array with 4 components per color
60
73
*/
@@ -120,12 +133,15 @@ class RTTSolidPolygonLayer extends RTTModifier(SolidPolygonLayer) {
120
133
121
134
draw ( this , opts : any ) {
122
135
const cell = this . props ! . data [ 0 ] ;
123
- const maxDensity = this . props . elevationScale ;
124
- const densityProps : DensityProps = {
125
- factor : unitDensityForCell ( cell . id ) / maxDensity
126
- } ;
127
- for ( const model of this . state . models ) {
128
- model . shaderInputs . setProps ( { density : densityProps } ) ;
136
+ if ( cell ) {
137
+ const maxDensity = this . props . elevationScale ;
138
+ const { scheme} = this . parent . parent . parent . parent . parent . state ;
139
+ const unitDensity =
140
+ scheme === 'h3' ? unitDensityForH3Cell ( cell . id ) : unitDensityForQuadbinCell ( cell . id ) ;
141
+ const densityProps : DensityProps = { factor : unitDensity / maxDensity } ;
142
+ for ( const model of this . state . models ) {
143
+ model . shaderInputs . setProps ( { density : densityProps } ) ;
144
+ }
129
145
}
130
146
131
147
super . draw ( opts ) ;
@@ -135,6 +151,9 @@ class RTTSolidPolygonLayer extends RTTModifier(SolidPolygonLayer) {
135
151
// Modify QuadbinTileLayer to apply heatmap post process effect
136
152
const PostProcessQuadbinTileLayer = PostProcessModifier ( QuadbinTileLayer , heatmap ) ;
137
153
154
+ // Modify H3TileLayer to apply heatmap post process effect
155
+ const PostProcessH3TileLayer = PostProcessModifier ( H3TileLayer , heatmap ) ;
156
+
138
157
const defaultProps : DefaultProps < HeatmapTileLayerProps > = {
139
158
data : TilejsonPropType ,
140
159
getWeight : { type : 'accessor' , value : 1 } ,
@@ -152,7 +171,7 @@ export type HeatmapTileLayerProps<DataT = unknown> = _HeatmapTileLayerProps<Data
152
171
} ;
153
172
154
173
/** Properties added by HeatmapTileLayer. */
155
- type _HeatmapTileLayerProps < DataT > = QuadbinTileLayerProps < DataT > &
174
+ type _HeatmapTileLayerProps < DataT > = ( QuadbinTileLayerProps < DataT > | H3TileLayerProps < DataT > ) &
156
175
HeatmapProps & {
157
176
/**
158
177
* Specified as an array of colors [color1, color2, ...].
@@ -181,12 +200,14 @@ class HeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends Composit
181
200
state ! : {
182
201
colorTexture ?: Texture ;
183
202
isLoaded : boolean ;
203
+ scheme : string | null ;
184
204
tiles : Set < Tile2DHeader > ;
185
205
viewportChanged ?: boolean ;
186
206
} ;
187
207
initializeState ( ) {
188
208
this . state = {
189
209
isLoaded : false ,
210
+ scheme : null ,
190
211
tiles : new Set ( ) ,
191
212
viewportChanged : false
192
213
} ;
@@ -201,9 +222,15 @@ class HeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends Composit
201
222
updateState ( opts : UpdateParameters < this> ) {
202
223
const { props, oldProps} = opts ;
203
224
super . updateState ( opts ) ;
204
- if ( props . colorRange !== oldProps . colorRange ) {
225
+ if ( ! deepEqual ( props . colorRange , oldProps . colorRange , 2 ) ) {
205
226
this . _updateColorTexture ( opts ) ;
206
227
}
228
+
229
+ const scheme = props . data && 'scheme' in props . data ? props . data . scheme : null ;
230
+ if ( this . state . scheme !== scheme ) {
231
+ this . setState ( { scheme} ) ;
232
+ this . state . tiles . clear ( ) ;
233
+ }
207
234
}
208
235
209
236
renderLayers ( ) : Layer {
@@ -222,15 +249,18 @@ class HeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends Composit
222
249
...tileLayerProps
223
250
} = this . props ;
224
251
252
+ const isH3 = this . state . scheme === 'h3' ;
253
+
254
+ const cellLayerName = isH3 ? 'hexagon-cell-hifi' : 'cell' ;
225
255
// Inject modified polygon layer as sublayer into TileLayer
226
256
const subLayerProps = {
227
257
..._subLayerProps ,
228
- cell : {
229
- ..._subLayerProps ?. cell ,
258
+ [ cellLayerName ] : {
259
+ ..._subLayerProps ?. [ cellLayerName ] ,
230
260
_subLayerProps : {
231
- ..._subLayerProps ?. cell ?. _subLayerProps ,
261
+ ..._subLayerProps ?. [ cellLayerName ] ?. _subLayerProps ,
232
262
fill : {
233
- ..._subLayerProps ?. cell ?. _subLayerProps ?. fill ,
263
+ ..._subLayerProps ?. [ cellLayerName ] ?. _subLayerProps ?. fill ,
234
264
type : RTTSolidPolygonLayer
235
265
}
236
266
}
@@ -248,21 +278,37 @@ class HeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends Composit
248
278
249
279
for ( const tile of tiles ) {
250
280
const cell = tile . content [ 0 ] ;
251
- const unitDensity = unitDensityForCell ( cell . id ) ;
281
+ const unitDensity = isH3 ? unitDensityForH3Cell ( cell . id ) : unitDensityForQuadbinCell ( cell . id ) ;
252
282
maxDensity = Math . max ( tile . userData ! . maxWeight * unitDensity , maxDensity ) ;
253
283
tileZ = Math . max ( tile . zoom , tileZ ) ;
254
284
}
255
285
256
- // Between zoom levels the max density will change, but it isn't possible to know by what factor. Uniform data distributions lead to a factor of 4, while very localized data gives 1. As a heurstic estimate with a value inbetween (2) to make the transitions less obvious.
257
- const overzoom = this . context . viewport . zoom - tileZ ;
258
- const estimatedMaxDensity = maxDensity * Math . pow ( 2 , overzoom ) ;
286
+ // Between zoom levels the max density will change, but it isn't possible to know by what factor.
287
+ // As a heuristic, an estimatedGrowthFactor makes the transitions less obvious.
288
+ // For quadbin, uniform data distributions lead to an estimatedGrowthFactor of 4, while very localized data gives 1.
289
+ // For H3 the same logic applies but the aperture is 7, rather than 4, so a slightly higher estimatedGrowthFactor is used.
290
+ let overzoom : number ;
291
+ let estimatedGrowthFactor : number ;
292
+ if ( isH3 ) {
293
+ // For H3, we need to account for the viewport zoom to H3 resolution mapping (see getHexagonResolution())
294
+ overzoom = ( 2 / 3 ) * this . context . viewport . zoom - tileZ - 2.25 ;
295
+ estimatedGrowthFactor = 2.2 ;
296
+ } else {
297
+ overzoom = this . context . viewport . zoom - tileZ ;
298
+ estimatedGrowthFactor = 2 ;
299
+ }
259
300
260
- maxDensity = estimatedMaxDensity ;
301
+ maxDensity = maxDensity * Math . pow ( estimatedGrowthFactor , overzoom ) ;
261
302
if ( typeof onMaxDensityChange === 'function' ) {
262
303
onMaxDensityChange ( maxDensity ) ;
263
304
}
264
- return new PostProcessQuadbinTileLayer (
265
- tileLayerProps as Omit < QuadbinTileLayerProps , 'data' > ,
305
+ const PostProcessTileLayer = isH3 ? PostProcessH3TileLayer : PostProcessQuadbinTileLayer ;
306
+ const layerProps = isH3
307
+ ? ( tileLayerProps as Omit < H3TileLayerProps , 'data' > )
308
+ : ( tileLayerProps as Omit < QuadbinTileLayerProps , 'data' > ) ;
309
+
310
+ return new PostProcessTileLayer (
311
+ layerProps ,
266
312
this . getSubLayerProps ( {
267
313
id : 'heatmap' ,
268
314
data,
@@ -330,18 +376,14 @@ class HeatmapTileLayer<DataT = any, ExtraProps extends {} = {}> extends Composit
330
376
let { colorTexture} = this . state ;
331
377
const colors = colorRangeToFlatArray ( colorRange ) ;
332
378
333
- if ( colorTexture && colorTexture ?. width === colorRange . length ) {
334
- // TODO(v9): Unclear whether `setSubImageData` is a public API, or what to use if not.
335
- ( colorTexture as any ) . setTexture2DData ( { data : colors } ) ;
336
- } else {
337
- colorTexture ?. destroy ( ) ;
338
- colorTexture = this . context . device . createTexture ( {
339
- ...TEXTURE_PROPS ,
340
- data : colors ,
341
- width : colorRange . length ,
342
- height : 1
343
- } ) ;
344
- }
379
+ colorTexture ?. destroy ( ) ;
380
+ colorTexture = this . context . device . createTexture ( {
381
+ ...TEXTURE_PROPS ,
382
+ data : colors ,
383
+ width : colorRange . length ,
384
+ height : 1
385
+ } ) ;
386
+
345
387
this . setState ( { colorTexture} ) ;
346
388
}
347
389
}
0 commit comments