Skip to content

Commit

Permalink
Upgrade to KDBush v4 & Supercluster v8 for better performance (#12682)
Browse files Browse the repository at this point in the history
* upgrade to KDBush v4 & Supercluster v8

* fix symbol unit test
  • Loading branch information
mourner authored Apr 27, 2023
1 parent 1af64fc commit 29588a7
Show file tree
Hide file tree
Showing 7 changed files with 61 additions and 54 deletions.
7 changes: 4 additions & 3 deletions flow-typed/kdbush.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
// @flow strict
declare module 'kdbush' {
declare export default class KDBush<T> {
points: Array<T>;
constructor(points: Array<T>, getX: (T) => number, getY: (T) => number, nodeSize?: number, arrayType?: Class<$ArrayBufferView>): KDBush<T>;
declare export default class KDBush {
constructor(numPoints: number, nodeSize?: number, arrayType?: Class<$ArrayBufferView>): KDBush;
add(x: number, y: number): number;
finish(): void;
range(minX: number, minY: number, maxX: number, maxY: number): Array<number>;
}
}
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,13 @@
"geojson-vt": "^3.2.1",
"gl-matrix": "^3.4.3",
"grid-index": "^1.1.0",
"kdbush": "^3.0.0",
"kdbush": "^4.0.1",
"murmurhash-js": "^1.0.0",
"pbf": "^3.2.1",
"potpack": "^2.0.0",
"quickselect": "^2.0.0",
"rw": "^1.3.3",
"supercluster": "^7.1.5",
"supercluster": "^8.0.0",
"tinyqueue": "^2.0.3",
"vt-pbf": "^3.1.3"
},
Expand Down
4 changes: 2 additions & 2 deletions src/style/pauseable_placement.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class LayerPlacement {
_sortAcrossTiles: boolean;
_currentTileIndex: number;
_currentPartIndex: number;
_seenCrossTileIDs: { [string | number]: boolean };
_seenCrossTileIDs: Set<number>;
_bucketParts: Array<BucketPart>;

constructor(styleLayer: SymbolStyleLayer) {
Expand All @@ -25,7 +25,7 @@ class LayerPlacement {

this._currentTileIndex = 0;
this._currentPartIndex = 0;
this._seenCrossTileIDs = {};
this._seenCrossTileIDs = new Set();
this._bucketParts = [];
}

Expand Down
64 changes: 35 additions & 29 deletions src/symbol/cross_tile_symbol_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import {SymbolInstanceArray} from '../data/array_types.js';
import KDBush from 'kdbush';

import type Projection from '../geo/projection/projection.js';
import type {SymbolInstance} from '../data/array_types.js';
import type {OverscaledTileID} from '../source/tile_id.js';
import type SymbolBucket from '../data/bucket/symbol_bucket.js';
import type StyleLayer from '../style/style_layer.js';
Expand All @@ -32,56 +31,63 @@ const roundingFactor = 512 / EXTENT / 2;
class TileLayerIndex {
tileID: OverscaledTileID;
bucketInstanceId: number;
index: KDBush<{x: number, y: number, key: number, crossTileID: number}>;
index: KDBush;
keys: Array<number>;
crossTileIDs: Array<number>;

constructor(tileID: OverscaledTileID, symbolInstances: SymbolInstanceArray, bucketInstanceId: number) {
this.tileID = tileID;
this.bucketInstanceId = bucketInstanceId;
const coords = [];
for (let i = 0; i < symbolInstances.length; i++) {
const symbolInstance = symbolInstances.get(i);
const {key, crossTileID} = symbolInstance;
const {x, y} = this.getScaledCoordinates(symbolInstance, tileID);
coords.push({x, y, key, crossTileID});
}

// create a spatial index for deduplicating symbol instances;
// use a low nodeSize because we're optimizing for search performance, not indexing
this.index = new KDBush(coords, p => p.x, p => p.y, 16, Int32Array);
}
this.index = new KDBush(symbolInstances.length, 16, Int32Array);
this.keys = [];
this.crossTileIDs = [];
const tx = tileID.canonical.x * EXTENT;
const ty = tileID.canonical.y * EXTENT;

// Converts the coordinates of the input symbol instance into coordinates that be can compared
// against other symbols in this index. Coordinates are:
// (1) world-based (so after conversion the source tile is irrelevant)
// (2) converted to the z-scale of this TileLayerIndex
// (3) down-sampled by "roundingFactor" from tile coordinate precision in order to be
// more tolerant of small differences between tiles.
getScaledCoordinates(symbolInstance: SymbolInstance, childTileID: OverscaledTileID): {|x: number, y: number|} {
const scale = roundingFactor / Math.pow(2, childTileID.canonical.z - this.tileID.canonical.z);
return {
x: Math.floor((childTileID.canonical.x * EXTENT + symbolInstance.tileAnchorX) * scale),
y: Math.floor((childTileID.canonical.y * EXTENT + symbolInstance.tileAnchorY) * scale)
};
for (let i = 0; i < symbolInstances.length; i++) {
const {key, crossTileID, tileAnchorX, tileAnchorY} = symbolInstances.get(i);

// Converts the coordinates of the input symbol instance into coordinates that be can compared
// against other symbols in this index. Coordinates are:
// (1) world-based (so after conversion the source tile is irrelevant)
// (2) converted to the z-scale of this TileLayerIndex
// (3) down-sampled by "roundingFactor" from tile coordinate precision in order to be
// more tolerant of small differences between tiles.
const x = Math.floor((tx + tileAnchorX) * roundingFactor);
const y = Math.floor((ty + tileAnchorY) * roundingFactor);

this.index.add(x, y);
this.keys.push(key);
this.crossTileIDs.push(crossTileID);
}
this.index.finish();
}

findMatches(symbolInstances: SymbolInstanceArray, newTileID: OverscaledTileID, zoomCrossTileIDs: Set<number>) {
const tolerance = this.tileID.canonical.z < newTileID.canonical.z ? 1 : Math.pow(2, this.tileID.canonical.z - newTileID.canonical.z);
const symbols = this.index.points;
const scale = roundingFactor / Math.pow(2, newTileID.canonical.z - this.tileID.canonical.z);
const tx = newTileID.canonical.x * EXTENT;
const ty = newTileID.canonical.y * EXTENT;

for (let i = 0; i < symbolInstances.length; i++) {
const symbolInstance = symbolInstances.get(i);
if (symbolInstance.crossTileID) {
// already has a match, skip
continue;
}

const {x, y} = this.getScaledCoordinates(symbolInstance, newTileID);
const {key, tileAnchorX, tileAnchorY} = symbolInstance;
const x = Math.floor((tx + tileAnchorX) * scale);
const y = Math.floor((ty + tileAnchorY) * scale);

// Return any symbol with the same keys whose coordinates are within 1
// grid unit. (with a 4px grid, this covers a 12px by 12px area)
const matchedIds = this.index.range(x - tolerance, y - tolerance, x + tolerance, y + tolerance);
for (const id of matchedIds) {
const {key, crossTileID} = symbols[id];
if (key === symbolInstance.key && !zoomCrossTileIDs.has(crossTileID)) {
const crossTileID = this.crossTileIDs[id];
if (this.keys[id] === key && !zoomCrossTileIDs.has(crossTileID)) {
// Once we've marked ourselves duplicate against this parent symbol,
// don't let any other symbols at the same zoom level duplicate against
// the same parent (see issue #5993)
Expand Down Expand Up @@ -201,7 +207,7 @@ class CrossTileSymbolLayerIndex {
}

removeBucketCrossTileIDs(zoom: string | number, removedBucket: TileLayerIndex) {
for (const {crossTileID} of removedBucket.index.points) {
for (const crossTileID of removedBucket.crossTileIDs) {
this.usedCrossTileIDs[zoom].delete(crossTileID);
}
}
Expand Down
16 changes: 8 additions & 8 deletions src/symbol/placement.js
Original file line number Diff line number Diff line change
Expand Up @@ -388,7 +388,7 @@ export class Placement {
}
}

placeLayerBucketPart(bucketPart: Object, seenCrossTileIDs: { [string | number]: boolean }, showCollisionBoxes: boolean, updateCollisionBoxIfNecessary: boolean) {
placeLayerBucketPart(bucketPart: Object, seenCrossTileIDs: Set<number>, showCollisionBoxes: boolean, updateCollisionBoxIfNecessary: boolean) {

const {
bucket,
Expand Down Expand Up @@ -470,12 +470,12 @@ export class Placement {

if (shouldClip) {
this.placements[crossTileID] = new JointPlacement(false, false, false, true);
seenCrossTileIDs[crossTileID] = true;
seenCrossTileIDs.add(crossTileID);
return;
}
}

if (seenCrossTileIDs[crossTileID]) return;
if (seenCrossTileIDs.has(crossTileID)) return;
if (holdingForFade) {
// Mark all symbols from this tile as "not placed", but don't add to seenCrossTileIDs, because we don't
// know yet if we have a duplicate in a parent tile that _should_ be placed.
Expand Down Expand Up @@ -790,7 +790,7 @@ export class Placement {
alwaysShowIcon = alwaysShowIcon && (notGlobe || !iconOccluded);

this.placements[crossTileID] = new JointPlacement(placeText || alwaysShowText, placeIcon || alwaysShowIcon, offscreen || bucket.justReloaded);
seenCrossTileIDs[crossTileID] = true;
seenCrossTileIDs.add(crossTileID);
};

if (zOrderByViewportY) {
Expand Down Expand Up @@ -917,7 +917,7 @@ export class Placement {
}

updateLayerOpacities(styleLayer: StyleLayer, tiles: Array<Tile>) {
const seenCrossTileIDs = {};
const seenCrossTileIDs = new Set();
for (const tile of tiles) {
const symbolBucket = ((tile.getBucket(styleLayer): any): SymbolBucket);
if (symbolBucket && tile.latestFeatureIndex && styleLayer.id === symbolBucket.layerIds[0]) {
Expand All @@ -926,7 +926,7 @@ export class Placement {
}
}

updateBucketOpacities(bucket: SymbolBucket, seenCrossTileIDs: { [string | number]: boolean }, collisionBoxArray: ?CollisionBoxArray) {
updateBucketOpacities(bucket: SymbolBucket, seenCrossTileIDs: Set<number>, collisionBoxArray: ?CollisionBoxArray) {
if (bucket.hasTextData()) bucket.text.opacityVertexArray.clear();
if (bucket.hasIconData()) bucket.icon.opacityVertexArray.clear();
if (bucket.hasIconCollisionBoxData()) bucket.iconCollisionBox.collisionVertexArray.clear();
Expand Down Expand Up @@ -971,7 +971,7 @@ export class Placement {
numIconVertices
} = symbolInstance;

const isDuplicate = seenCrossTileIDs[crossTileID];
const isDuplicate = seenCrossTileIDs.has(crossTileID);

let opacityState = this.opacities[crossTileID];
if (isDuplicate) {
Expand All @@ -982,7 +982,7 @@ export class Placement {
this.opacities[crossTileID] = opacityState;
}

seenCrossTileIDs[crossTileID] = true;
seenCrossTileIDs.add(crossTileID);

const hasText = numHorizontalGlyphVertices > 0 || numVerticalGlyphVertices > 0;
const hasIcon = numIconVertices > 0;
Expand Down
2 changes: 1 addition & 1 deletion test/unit/data/symbol_bucket.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ test('SymbolBucket', (t) => {
const parts = [];
placement.getBucketParts(parts, layer, tile, false);
for (const part of parts) {
placement.placeLayerBucketPart(part, {}, false);
placement.placeLayerBucketPart(part, new Set(), false);
}
};
const a = placement.collisionIndex.grid.keysLength();
Expand Down
18 changes: 9 additions & 9 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -4735,10 +4735,10 @@ just-extend@^4.0.2:
resolved "https://registry.yarnpkg.com/just-extend/-/just-extend-4.2.1.tgz#ef5e589afb61e5d66b24eca749409a8939a8c744"
integrity sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==

kdbush@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-3.0.0.tgz#f8484794d47004cc2d85ed3a79353dbe0abc2bf0"
integrity sha512-hRkd6/XW4HTsA9vjVpY9tuXJYLSlelnkTmVFu4M9/7MIYQtFcHpbugAU7UbOfjOiVSVYl2fqgBuJ32JUmRo5Ew==
kdbush@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/kdbush/-/kdbush-4.0.1.tgz#c411c5571d246c7f012ea5980ea720023d663329"
integrity sha512-RlSWHFOf40nU5Be8dZIzdA8j6Jn9IYskSPSZwkDCooGb16uXeUx/6QZuW+krdEToViiK2OAwfW7QWDHY+kAz0A==

kind-of@^6.0.2, kind-of@^6.0.3:
version "6.0.3"
Expand Down Expand Up @@ -7471,12 +7471,12 @@ subarg@^1.0.0:
dependencies:
minimist "^1.1.0"

supercluster@^7.1.5:
version "7.1.5"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-7.1.5.tgz#65a6ce4a037a972767740614c19051b64b8be5a3"
integrity sha512-EulshI3pGUM66o6ZdH3ReiFcvHpM3vAigyK+vcxdjpJyEbIIrtbmBdY23mGgnI24uXiGFvrGq9Gkum/8U7vJWg==
supercluster@^8.0.0:
version "8.0.0"
resolved "https://registry.yarnpkg.com/supercluster/-/supercluster-8.0.0.tgz#021872ea0ad0a4bfaba4be3cd305fd0a510a9670"
integrity sha512-/cyICCWE1LjHwN5vKjBB1OHn7TwSyrnerRSMvcLkDgSIyPLN7KJEqx3upzB3BxK4Efs5JrF37bmqMuaxfH0EmA==
dependencies:
kdbush "^3.0.0"
kdbush "^4.0.1"

supports-color@^5.3.0:
version "5.5.0"
Expand Down

0 comments on commit 29588a7

Please sign in to comment.