Skip to content

Commit

Permalink
avoid flickering when longitude is wrapped while panning
Browse files Browse the repository at this point in the history
When panning cross the antimeridian, the longitude value gets wrapped.
This results in tileIDs getting assigned a different `wrap` value for
tiles that cover roughly the same area on the screen.

This pr calculates what this change in wrap values is and updates the
state of both `SourceCache` and `CrossTileSymbolIndex` so that areas use
the same tile and symbol state for the same screen areas even if they
have a different `wrap` value.

I think this is the long term fix for the CrossTileSymbolIndex. For
SourceCache, it may be better to rework how tiles are retained so that
you can actually use versions of tiles with a different wrap as tiles in
the next frame.
  • Loading branch information
ansis committed Apr 2, 2018
1 parent 9b88d16 commit 75adcce
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 3 deletions.
37 changes: 36 additions & 1 deletion src/source/source_cache.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ class SourceCache extends Evented {
_sourceLoaded: boolean;
_sourceErrored: boolean;
_tiles: {[any]: Tile};
_prevLng: number;
_cache: Cache<Tile>;
_timers: {[any]: TimeoutID};
_cacheTimers: {[any]: TimeoutID};
Expand Down Expand Up @@ -392,6 +393,27 @@ class SourceCache extends Evented {
this._cache.setMaxSize(maxSize);
}

handleWrapJump(lng: number) {
// Wrapping longitude values can cause a jump in `wrap` values. Tiles that
// cover a similar area of the screen can have different wrap values. This
// calculates this difference in wrap values so that we can match tiles
// across frames where the longitude gets wrapped.
const prevLng = this._prevLng === undefined ? lng : this._prevLng;
const wrapDelta = Math.round((lng - prevLng) / 360);
this._prevLng = lng;

if (wrapDelta) {
const tiles = {};
for (const key in this._tiles) {
const tile = this._tiles[key];
tile.tileID = tile.tileID.unwrapTo(tile.tileID.wrap + wrapDelta);
tiles[tile.tileID.key] = tile;
}
this._tiles = tiles;
this._resetTileReloadTimers();
}
}

/**
* Removes tiles that are outside the viewport and adds new tiles that
* are inside the viewport.
Expand All @@ -401,6 +423,8 @@ class SourceCache extends Evented {
if (!this._sourceLoaded || this._paused) { return; }

this.updateCacheSize(transform);
this.handleWrapJump(this.transform.center.lng);

// Covered is a list of retained tiles who's areas are fully covered by other,
// better, retained tiles. They are not drawn separately.
this._coveredTiles = {};
Expand Down Expand Up @@ -573,12 +597,12 @@ class SourceCache extends Evented {
tile = this._cache.getAndRemove((tileID.wrapped().key: any));
if (tile) {
// set the tileID because the cached tile could have had a different wrap value
tile.tileID = tileID;
if (this._cacheTimers[tileID.key]) {
clearTimeout(this._cacheTimers[tileID.key]);
delete this._cacheTimers[tileID.key];
this._setTileReloadTimer(tileID.key, tile);
}
tile.tileID = tileID;
}

const cached = Boolean(tile);
Expand Down Expand Up @@ -612,6 +636,17 @@ class SourceCache extends Evented {
}
}

_resetTileReloadTimers() {
for (const id in this._timers) {
clearTimeout(this._timers[id]);
delete this._timers[id];
}
for (const id in this._tiles) {
const tile = this._tiles[id];
this._setTileReloadTimer(id, tile);
}
}

_setCacheInvalidationTimer(id: string | number, tile: Tile) {
if (id in this._cacheTimers) {
clearTimeout(this._cacheTimers[id]);
Expand Down
8 changes: 8 additions & 0 deletions src/source/tile_id.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ export class OverscaledTileID {
this.key = calculateKey(wrap, overscaledZ, x, y);
}

equals(id: OverscaledTileID) {
return this.overscaledZ === id.overscaledZ && this.wrap === id.wrap && this.canonical.equals(id.canonical);
}

scaledTo(targetZ: number) {
assert(targetZ <= this.overscaledZ);
const zDifference = this.canonical.z - targetZ;
Expand Down Expand Up @@ -122,6 +126,10 @@ export class OverscaledTileID {
return new OverscaledTileID(this.overscaledZ, 0, this.canonical.z, this.canonical.x, this.canonical.y);
}

unwrapTo(wrap: number) {
return new OverscaledTileID(this.overscaledZ, wrap, this.canonical.z, this.canonical.x, this.canonical.y);
}

overscaleFactor() {
return Math.pow(2, this.overscaledZ - this.canonical.z);
}
Expand Down
2 changes: 1 addition & 1 deletion src/style/style.js
Original file line number Diff line number Diff line change
Expand Up @@ -950,7 +950,7 @@ class Style extends Evented {
.sort((a, b) => (b.tileID.overscaledZ - a.tileID.overscaledZ) || (a.tileID.isLessThan(b.tileID) ? -1 : 1));
}

const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source]);
const layerBucketsChanged = this.crossTileSymbolIndex.addLayer(styleLayer, layerTiles[styleLayer.source], transform.center.lng);
symbolBucketsChanged = symbolBucketsChanged || layerBucketsChanged;
}
this.crossTileSymbolIndex.pruneUnusedLayers(this._order);
Expand Down
29 changes: 28 additions & 1 deletion src/symbol/cross_tile_symbol_index.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,10 +118,35 @@ class CrossTileIDs {
class CrossTileSymbolLayerIndex {
indexes: {[zoom: string | number]: {[tileId: string | number]: TileLayerIndex}};
usedCrossTileIDs: {[zoom: string | number]: {[crossTileID: number]: boolean}};
lng: number;

constructor() {
this.indexes = {};
this.usedCrossTileIDs = {};
this.lng = 0;
}

/*
* Sometimes when a user pans across the antimeridian the longitude value gets wrapped.
* To prevent labels from flashing out and in we adjust the tileID values in the indexes
* so that they match the new wrapped version of the map.
*/
handleWrapJump(lng: number) {
const wrapDelta = Math.round((lng - this.lng) / 360);
if (wrapDelta !== 0) {
for (const zoom in this.indexes) {
const zoomIndexes = this.indexes[zoom];
const newZoomIndex = {};
for (const key in zoomIndexes) {
// change the tileID's wrap and add it to a new index
const index = zoomIndexes[key];
index.tileID = index.tileID.unwrapTo(index.tileID.wrap - wrapDelta);
newZoomIndex[index.tileID.key] = index;
}
this.indexes[zoom] = newZoomIndex;
}
}
this.lng = lng;
}

addBucket(tileID: OverscaledTileID, bucket: SymbolBucket, crossTileIDs: CrossTileIDs) {
Expand Down Expand Up @@ -219,7 +244,7 @@ class CrossTileSymbolIndex {
this.maxBucketInstanceId = 0;
}

addLayer(styleLayer: StyleLayer, tiles: Array<Tile>) {
addLayer(styleLayer: StyleLayer, tiles: Array<Tile>, lng: number) {
let layerIndex = this.layerIndexes[styleLayer.id];
if (layerIndex === undefined) {
layerIndex = this.layerIndexes[styleLayer.id] = new CrossTileSymbolLayerIndex();
Expand All @@ -228,6 +253,8 @@ class CrossTileSymbolIndex {
let symbolBucketsChanged = false;
const currentBucketIDs = {};

layerIndex.handleWrapJump(lng);

for (const tile of tiles) {
const symbolBucket = ((tile.getBucket(styleLayer): any): SymbolBucket);
if (!symbolBucket) continue;
Expand Down

0 comments on commit 75adcce

Please sign in to comment.