@@ -20,7 +20,12 @@ const defaultOptions = {
2020 map : props => props // props => ({sum: props.my_value})
2121} ;
2222
23- const fround = Math . fround || ( tmp => ( ( x ) => { tmp [ 0 ] = + x ; return tmp [ 0 ] ; } ) ) ( new Float32Array ( 1 ) ) ;
23+ // Int32 encoding of source coords in [0, 1]: (coord - 0.5) * SCALE in [-2^30, 2^30].
24+ // Keeps every stored value inside V8's 31-bit SMI fast path.
25+ const SCALE = 0x80000000 ; // 2^31
26+ const INV_SCALE = 1 / SCALE ;
27+ const encode = c => ( c - 0.5 ) * SCALE ;
28+ const decode = v => v * INV_SCALE + 0.5 ;
2429
2530const OFFSET_ZOOM = 2 ;
2631const OFFSET_ID = 3 ;
@@ -45,28 +50,26 @@ export default class Supercluster {
4550 if ( log ) console . time ( timerId ) ;
4651
4752 this . points = points ;
53+ const stride = this . stride ;
54+ const notProcessed = maxZoom + 1 ; // sentinel for "not yet processed at any zoom"
4855
4956 // generate a cluster object for each point and index input points into a KD-tree
50- const data = [ ] ;
51-
57+ const data = new Int32Array ( points . length * stride ) ;
58+ let w = 0 ;
5259 for ( let i = 0 ; i < points . length ; i ++ ) {
5360 const p = points [ i ] ;
5461 if ( ! p . geometry ) continue ;
5562
5663 const [ lng , lat ] = p . geometry . coordinates ;
57- const x = fround ( lngX ( lng ) ) ;
58- const y = fround ( latY ( lat ) ) ;
59- // store internal point/cluster data in flat numeric arrays for performance
60- data . push (
61- x , y , // projected point coordinates
62- Infinity , // the last zoom the point was processed at
63- i , // index of the source feature in the original input array
64- - 1 , // parent cluster id
65- 1 // number of points in a cluster
66- ) ;
67- if ( this . options . reduce ) data . push ( 0 ) ; // noop
64+ // store internal point/cluster data in flat typed arrays for performance
65+ writeSlot ( data , w , encode ( lngX ( lng ) ) , encode ( latY ( lat ) ) , notProcessed , i , 1 ) ;
66+ w += stride ;
6867 }
69- let tree = this . trees [ maxZoom + 1 ] = this . _createTree ( data ) ;
68+ const numInput = w / stride ;
69+ const inputSlab = w === data . length ? data : data . subarray ( 0 , w ) ;
70+ let prev = inputSlab ;
71+ let prevNum = numInput ;
72+ let tree = this . trees [ maxZoom + 1 ] = this . _createTree ( prev , prevNum ) ;
7073
7174 if ( log ) console . timeEnd ( timerId ) ;
7275
@@ -75,8 +78,12 @@ export default class Supercluster {
7578 for ( let z = maxZoom ; z >= minZoom ; z -- ) {
7679 const now = + Date . now ( ) ;
7780
78- // create a new set of clusters for the zoom and index them with a KD-tree
79- tree = this . trees [ z ] = this . _createTree ( this . _cluster ( tree , z ) ) ;
81+ // allocate a tight Int32 slab for this zoom; output is strictly <= input length
82+ const out = new Int32Array ( prev . length ) ;
83+ const written = this . _cluster ( prev , prevNum , z , out ) ;
84+ tree = this . trees [ z ] = this . _createTree ( out , written ) ;
85+ prev = out ;
86+ prevNum = written ;
8087
8188 if ( log ) console . log ( 'z%d: %d clusters in %dms' , z , tree . numItems , + Date . now ( ) - now ) ;
8289 }
@@ -102,7 +109,7 @@ export default class Supercluster {
102109 }
103110
104111 const tree = this . trees [ this . _limitZoom ( zoom ) ] ;
105- const ids = tree . range ( lngX ( minLng ) , latY ( maxLat ) , lngX ( maxLng ) , latY ( minLat ) ) ;
112+ const ids = tree . range ( encode ( lngX ( minLng ) ) , encode ( latY ( maxLat ) ) , encode ( lngX ( maxLng ) ) , encode ( latY ( minLat ) ) ) ;
106113 const data = tree . data ;
107114 const clusters = [ ] ;
108115 for ( const id of ids ) {
@@ -121,12 +128,12 @@ export default class Supercluster {
121128 if ( ! tree ) throw new Error ( errorMsg ) ;
122129
123130 const data = tree . data ;
124- if ( originId * this . stride >= data . length ) throw new Error ( errorMsg ) ;
131+ if ( originId >= tree . numItems ) throw new Error ( errorMsg ) ;
125132
126133 const r = this . options . radius / ( this . options . extent * Math . pow ( 2 , originZoom - 1 ) ) ;
127134 const x = data [ originId * this . stride ] ;
128135 const y = data [ originId * this . stride + 1 ] ;
129- const ids = tree . within ( x , y , r ) ;
136+ const ids = tree . within ( x , y , r * SCALE ) ;
130137 const children = [ ] ;
131138 for ( const id of ids ) {
132139 const k = id * this . stride ;
@@ -155,25 +162,23 @@ export default class Supercluster {
155162 const z2 = Math . pow ( 2 , z ) ;
156163 const { extent, radius} = this . options ;
157164 const p = radius / extent ;
158- const top = ( y - p ) / z2 ;
159- const bottom = ( y + 1 + p ) / z2 ;
165+ const top = encode ( ( y - p ) / z2 ) ;
166+ const bottom = encode ( ( y + 1 + p ) / z2 ) ;
160167
161- const tile = {
162- features : [ ]
163- } ;
168+ const tile = { features : [ ] } ;
164169
165170 this . _addTileFeatures (
166- tree . range ( ( x - p ) / z2 , top , ( x + 1 + p ) / z2 , bottom ) ,
171+ tree . range ( encode ( ( x - p ) / z2 ) , top , encode ( ( x + 1 + p ) / z2 ) , bottom ) ,
167172 tree . data , x , y , z2 , tile ) ;
168173
169174 if ( x === 0 ) {
170175 this . _addTileFeatures (
171- tree . range ( 1 - p / z2 , top , 1 , bottom ) ,
176+ tree . range ( encode ( 1 - p / z2 ) , top , encode ( 1 ) , bottom ) ,
172177 tree . data , z2 , y , z2 , tile ) ;
173178 }
174179 if ( x === z2 - 1 ) {
175180 this . _addTileFeatures (
176- tree . range ( 0 , top , p / z2 , bottom ) ,
181+ tree . range ( encode ( 0 ) , top , encode ( p / z2 ) , bottom ) ,
177182 tree . data , - 1 , y , z2 , tile ) ;
178183 }
179184
@@ -219,9 +224,10 @@ export default class Supercluster {
219224 return skipped ;
220225 }
221226
222- _createTree ( data ) {
223- const tree = new KDBush ( data . length / this . stride | 0 , this . options . nodeSize , Float32Array ) ;
224- for ( let i = 0 ; i < data . length ; i += this . stride ) tree . add ( data [ i ] , data [ i + 1 ] ) ;
227+ _createTree ( data , numItems ) {
228+ const tree = new KDBush ( numItems , this . options . nodeSize , Int32Array ) ;
229+ const stride = this . stride ;
230+ for ( let i = 0 ; i < numItems ; i ++ ) tree . add ( data [ i * stride ] , data [ i * stride + 1 ] ) ;
225231 tree . finish ( ) ;
226232 tree . data = data ;
227233 return tree ;
@@ -235,8 +241,8 @@ export default class Supercluster {
235241 let tags , px , py ;
236242 if ( isCluster ) {
237243 tags = getClusterProperties ( data , k , this . clusterProps ) ;
238- px = data [ k ] ;
239- py = data [ k + 1 ] ;
244+ px = decode ( data [ k ] ) ;
245+ py = decode ( data [ k + 1 ] ) ;
240246 } else {
241247 const p = this . points [ data [ k + OFFSET_ID ] ] ;
242248 tags = p . properties ;
@@ -274,23 +280,25 @@ export default class Supercluster {
274280 return Math . max ( this . options . minZoom , Math . min ( Math . floor ( + z ) , this . options . maxZoom + 1 ) ) ;
275281 }
276282
277- _cluster ( tree , zoom ) {
278- const { radius, extent, reduce, minPoints} = this . options ;
279- const r = radius / ( extent * Math . pow ( 2 , zoom ) ) ;
280- const data = tree . data ;
281- const nextData = [ ] ;
283+ _cluster ( data , numItems , zoom , out ) {
284+ const { radius, extent, reduce, minPoints, maxZoom } = this . options ;
285+ const r = radius / ( extent * Math . pow ( 2 , zoom ) ) * SCALE ;
286+ const notProcessed = maxZoom + 1 ;
287+ const tree = this . trees [ zoom + 1 ] ;
282288 const stride = this . stride ;
289+ const limit = numItems * stride ;
290+ let cursor = 0 ;
283291
284292 // loop through each point
285- for ( let i = 0 ; i < data . length ; i += stride ) {
293+ for ( let i = 0 ; i < limit ; i += stride ) {
286294 // if we've already visited the point at this zoom level, skip it
287295 if ( data [ i + OFFSET_ZOOM ] <= zoom ) continue ;
288296 data [ i + OFFSET_ZOOM ] = zoom ;
289297
290298 // find all nearby points
291299 const x = data [ i ] ;
292300 const y = data [ i + 1 ] ;
293- const neighborIds = tree . within ( data [ i ] , data [ i + 1 ] , r ) ;
301+ const neighborIds = tree . within ( x , y , r ) ;
294302
295303 const numPointsOrigin = data [ i + OFFSET_NUM ] ;
296304 let numPoints = numPointsOrigin ;
@@ -336,24 +344,26 @@ export default class Supercluster {
336344 }
337345
338346 data [ i + OFFSET_PARENT ] = id ;
339- nextData . push ( wx / numPoints , wy / numPoints , Infinity , id , - 1 , numPoints ) ;
340- if ( reduce ) nextData . push ( clusterPropIndex ) ;
347+ writeSlot ( out , cursor , wx / numPoints , wy / numPoints , notProcessed , id , numPoints , reduce ? clusterPropIndex : undefined ) ;
348+ cursor += stride ;
341349
342350 } else { // left points as unclustered
343- for ( let j = 0 ; j < stride ; j ++ ) nextData . push ( data [ i + j ] ) ;
351+ for ( let j = 0 ; j < stride ; j ++ ) out [ cursor + j ] = data [ i + j ] ;
352+ cursor += stride ;
344353
345354 if ( numPoints > 1 ) {
346355 for ( const neighborId of neighborIds ) {
347356 const k = neighborId * stride ;
348357 if ( data [ k + OFFSET_ZOOM ] <= zoom ) continue ;
349358 data [ k + OFFSET_ZOOM ] = zoom ;
350- for ( let j = 0 ; j < stride ; j ++ ) nextData . push ( data [ k + j ] ) ;
359+ for ( let j = 0 ; j < stride ; j ++ ) out [ cursor + j ] = data [ k + j ] ;
360+ cursor += stride ;
351361 }
352362 }
353363 }
354364 }
355365
356- return nextData ;
366+ return cursor / stride ;
357367 }
358368
359369 // get index of the point from which the cluster originated
@@ -377,14 +387,25 @@ export default class Supercluster {
377387 }
378388}
379389
390+ // write one stride-tuple (cluster or input point) into a typed slab
391+ function writeSlot ( data , k , x , y , zoom , id , num , propIndex ) {
392+ data [ k ] = x ;
393+ data [ k + 1 ] = y ;
394+ data [ k + 2 ] = zoom ;
395+ data [ k + 3 ] = id ;
396+ data [ k + 4 ] = - 1 ; // parent cluster id
397+ data [ k + 5 ] = num ;
398+ if ( propIndex !== undefined ) data [ k + 6 ] = propIndex ;
399+ }
400+
380401function getClusterJSON ( data , i , clusterProps ) {
381402 return {
382403 type : 'Feature' ,
383404 id : data [ i + OFFSET_ID ] ,
384405 properties : getClusterProperties ( data , i , clusterProps ) ,
385406 geometry : {
386407 type : 'Point' ,
387- coordinates : [ xLng ( data [ i ] ) , yLat ( data [ i + 1 ] ) ]
408+ coordinates : [ xLng ( decode ( data [ i ] ) ) , yLat ( decode ( data [ i + 1 ] ) ) ]
388409 }
389410 } ;
390411}
0 commit comments