Merge pull request #7327 from tschaub/tile-management

Prune the tile cache after updating a source's URL
This commit is contained in:
Tim Schaub
2017-10-09 17:01:12 -06:00
committed by GitHub
18 changed files with 308 additions and 79 deletions

View File

@@ -239,7 +239,7 @@ ol.renderer.Layer.prototype.manageTilePyramid = function(
var tileQueue = frameState.tileQueue;
var minZoom = tileGrid.getMinZoom();
var tile, tileRange, tileResolution, x, y, z;
for (z = currentZ; z >= minZoom; --z) {
for (z = minZoom; z <= currentZ; ++z) {
tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z, tileRange);
tileResolution = tileGrid.getResolution(z);
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {

View File

@@ -120,7 +120,7 @@ ol.source.Tile.prototype.forEachLoadedTile = function(projection, z, tileRange,
var tile, tileCoordKey, loaded;
for (var x = tileRange.minX; x <= tileRange.maxX; ++x) {
for (var y = tileRange.minY; y <= tileRange.maxY; ++y) {
tileCoordKey = this.getKeyZXY(z, x, y);
tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
loaded = false;
if (tileCache.containsKey(tileCoordKey)) {
tile = /** @type {!ol.Tile} */ (tileCache.get(tileCoordKey));
@@ -170,16 +170,6 @@ ol.source.Tile.prototype.setKey = function(key) {
};
/**
* @param {number} z Z.
* @param {number} x X.
* @param {number} y Y.
* @return {string} Key.
* @protected
*/
ol.source.Tile.prototype.getKeyZXY = ol.tilecoord.getKeyZXY;
/**
* @param {ol.proj.Projection} projection Projection.
* @return {boolean} Opaque.

View File

@@ -6,6 +6,7 @@ goog.require('ol.TileState');
goog.require('ol.dom');
goog.require('ol.size');
goog.require('ol.source.Tile');
goog.require('ol.tilecoord');
/**
@@ -38,7 +39,7 @@ ol.inherits(ol.source.TileDebug, ol.source.Tile);
* @inheritDoc
*/
ol.source.TileDebug.prototype.getTile = function(z, x, y) {
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(tileCoordKey)) {
return /** @type {!ol.source.TileDebug.Tile_} */ (this.tileCache.get(tileCoordKey));
} else {

View File

@@ -9,6 +9,7 @@ goog.require('ol.events.EventType');
goog.require('ol.proj');
goog.require('ol.reproj.Tile');
goog.require('ol.source.UrlTile');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');
@@ -245,7 +246,7 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
var cache = this.getTileCacheForProjection(projection);
var tileCoord = [z, x, y];
var tile;
var tileCoordKey = this.getKeyZXY.apply(this, tileCoord);
var tileCoordKey = ol.tilecoord.getKey(tileCoord);
if (cache.containsKey(tileCoordKey)) {
tile = /** @type {!ol.Tile} */ (cache.get(tileCoordKey));
}
@@ -293,7 +294,7 @@ ol.source.TileImage.prototype.getTile = function(z, x, y, pixelRatio, projection
*/
ol.source.TileImage.prototype.getTileInternal = function(z, x, y, pixelRatio, projection) {
var tile = null;
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
var key = this.getKey();
if (!this.tileCache.containsKey(tileCoordKey)) {
tile = this.createTile_(z, x, y, pixelRatio, projection, key);

View File

@@ -12,6 +12,7 @@ goog.require('ol.net');
goog.require('ol.proj');
goog.require('ol.source.State');
goog.require('ol.source.Tile');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');
@@ -215,7 +216,7 @@ ol.source.TileUTFGrid.prototype.handleTileJSONResponse = function(tileJSON) {
* @inheritDoc
*/
ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projection) {
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(tileCoordKey)) {
return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
} else {
@@ -240,7 +241,7 @@ ol.source.TileUTFGrid.prototype.getTile = function(z, x, y, pixelRatio, projecti
* @inheritDoc
*/
ol.source.TileUTFGrid.prototype.useTile = function(z, x, y) {
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(tileCoordKey)) {
this.tileCache.get(tileCoordKey);
}

View File

@@ -81,13 +81,6 @@ ol.source.TileWMS = function(opt_options) {
*/
this.hidpi_ = options.hidpi !== undefined ? options.hidpi : true;
/**
* @private
* @type {string}
*/
this.coordKeyPrefix_ = '';
this.resetCoordKeyPrefix_();
/**
* @private
* @type {ol.Extent}
@@ -171,14 +164,6 @@ ol.source.TileWMS.prototype.getGutterInternal = function() {
};
/**
* @inheritDoc
*/
ol.source.TileWMS.prototype.getKeyZXY = function(z, x, y) {
return this.coordKeyPrefix_ + ol.source.TileImage.prototype.getKeyZXY.call(this, z, x, y);
};
/**
* Get the user-provided params, i.e. those passed to the constructor through
* the "params" option, and possibly updated using the updateParams method.
@@ -273,24 +258,6 @@ ol.source.TileWMS.prototype.getTilePixelRatio = function(pixelRatio) {
};
/**
* @private
*/
ol.source.TileWMS.prototype.resetCoordKeyPrefix_ = function() {
var i = 0;
var res = [];
if (this.urls) {
var j, jj;
for (j = 0, jj = this.urls.length; j < jj; ++j) {
res[i++] = this.urls[j];
}
}
this.coordKeyPrefix_ = res.join('#');
};
/**
* @private
* @return {string} The key for the current params.
@@ -352,15 +319,6 @@ ol.source.TileWMS.prototype.fixedTileUrlFunction = function(tileCoord, pixelRati
pixelRatio, projection, baseParams);
};
/**
* @inheritDoc
*/
ol.source.TileWMS.prototype.setUrls = function(urls) {
ol.source.TileImage.prototype.setUrls.call(this, urls);
this.resetCoordKeyPrefix_();
};
/**
* Update the user-provided params.
* @param {Object} params Params.
@@ -368,7 +326,6 @@ ol.source.TileWMS.prototype.setUrls = function(urls) {
*/
ol.source.TileWMS.prototype.updateParams = function(params) {
ol.obj.assign(this.params_, params);
this.resetCoordKeyPrefix_();
this.updateV13_();
this.setKey(this.getKeyForParams_());
};

View File

@@ -5,6 +5,7 @@ goog.require('ol.TileState');
goog.require('ol.TileUrlFunction');
goog.require('ol.source.Tile');
goog.require('ol.source.TileEventType');
goog.require('ol.tilecoord');
/**
@@ -155,6 +156,7 @@ ol.source.UrlTile.prototype.setTileLoadFunction = function(tileLoadFunction) {
*/
ol.source.UrlTile.prototype.setTileUrlFunction = function(tileUrlFunction, opt_key) {
this.tileUrlFunction = tileUrlFunction;
this.tileCache.pruneExceptNewestZ();
if (typeof opt_key !== 'undefined') {
this.setKey(opt_key);
} else {
@@ -194,7 +196,7 @@ ol.source.UrlTile.prototype.setUrls = function(urls) {
* @inheritDoc
*/
ol.source.UrlTile.prototype.useTile = function(z, x, y) {
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(tileCoordKey)) {
this.tileCache.get(tileCoordKey);
}

View File

@@ -5,8 +5,9 @@ goog.require('ol.TileState');
goog.require('ol.VectorImageTile');
goog.require('ol.VectorTile');
goog.require('ol.size');
goog.require('ol.tilegrid');
goog.require('ol.source.UrlTile');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');
/**
@@ -110,7 +111,7 @@ ol.source.VectorTile.prototype.clear = function() {
* @inheritDoc
*/
ol.source.VectorTile.prototype.getTile = function(z, x, y, pixelRatio, projection) {
var tileCoordKey = this.getKeyZXY(z, x, y);
var tileCoordKey = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(tileCoordKey)) {
return /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
} else {

View File

@@ -116,6 +116,34 @@ ol.structs.LRUCache.prototype.get = function(key) {
};
/**
* Remove an entry from the cache.
* @param {string} key The entry key.
* @return {T} The removed entry.
*/
ol.structs.LRUCache.prototype.remove = function(key) {
var entry = this.entries_[key];
ol.asserts.assert(entry !== undefined, 15); // Tried to get a value for a key that does not exist in the cache
if (entry === this.newest_) {
this.newest_ = /** @type {ol.LRUCacheEntry} */ (entry.older);
if (this.newest_) {
this.newest_.newer = null;
}
} else if (entry === this.oldest_) {
this.oldest_ = /** @type {ol.LRUCacheEntry} */ (entry.newer);
if (this.oldest_) {
this.oldest_.older = null;
}
} else {
entry.newer.older = entry.older;
entry.older.newer = entry.newer;
}
delete this.entries_[key];
--this.count_;
return entry.value_;
};
/**
* @return {number} Count.
*/
@@ -168,6 +196,15 @@ ol.structs.LRUCache.prototype.peekLastKey = function() {
};
/**
* Get the key of the newest item in the cache. Throws if the cache is empty.
* @return {string} The newest key.
*/
ol.structs.LRUCache.prototype.peekFirstKey = function() {
return this.newest_.key_;
};
/**
* @return {T} value Value.
*/

View File

@@ -2,6 +2,7 @@ goog.provide('ol.TileCache');
goog.require('ol');
goog.require('ol.structs.LRUCache');
goog.require('ol.tilecoord');
/**
@@ -33,3 +34,22 @@ ol.TileCache.prototype.expireCache = function(usedTiles) {
}
}
};
/**
* Prune all tiles from the cache that don't have the same z as the newest tile.
*/
ol.TileCache.prototype.pruneExceptNewestZ = function() {
if (this.getCount() === 0) {
return;
}
var key = this.peekFirstKey();
var tileCoord = ol.tilecoord.fromKey(key);
var z = tileCoord[0];
this.forEach(function(tile) {
if (tile.tileCoord[0] !== z) {
this.remove(ol.tilecoord.getKey(tile.tileCoord));
tile.dispose();
}
}, this);
};

View File

@@ -31,6 +31,26 @@ ol.tilecoord.getKeyZXY = function(z, x, y) {
};
/**
* Get the key for a tile coord.
* @param {ol.TileCoord} tileCoord The tile coord.
* @return {string} Key.
*/
ol.tilecoord.getKey = function(tileCoord) {
return ol.tilecoord.getKeyZXY(tileCoord[0], tileCoord[1], tileCoord[2]);
};
/**
* Get a tile coord given a key.
* @param {string} key The tile coord key.
* @return {ol.TileCoord} The tile coord.
*/
ol.tilecoord.fromKey = function(key) {
return key.split('/').map(Number);
};
/**
* @param {ol.TileCoord} tileCoord Tile coord.
* @return {number} Hash.

View File

@@ -1,8 +1,11 @@
goog.require('ol.Image');
goog.require('ol.Map');
goog.require('ol.View');
goog.require('ol.layer.Layer');
goog.require('ol.layer.Tile');
goog.require('ol.renderer.Layer');
goog.require('ol.source.XYZ');
goog.require('ol.tilecoord');
describe('ol.renderer.Layer', function() {
@@ -80,4 +83,63 @@ describe('ol.renderer.Layer', function() {
});
});
describe('manageTilePyramid behavior', function() {
var target, map, view, source;
beforeEach(function(done) {
target = document.createElement('div');
Object.assign(target.style, {
position: 'absolute',
left: '-1000px',
top: '-1000px',
width: '360px',
height: '180px'
});
document.body.appendChild(target);
view = new ol.View({
center: [0, 0],
zoom: 0
});
source = new ol.source.XYZ({
url: '#{x}/{y}/{z}'
});
map = new ol.Map({
target: target,
view: view,
layers: [
new ol.layer.Tile({
source: source
})
]
});
map.once('postrender', function() {
done();
});
});
afterEach(function() {
map.dispose();
document.body.removeChild(target);
});
it('accesses tiles from current zoom level last', function(done) {
// expect most recent tile in the cache to be from zoom level 0
var key = source.tileCache.peekFirstKey();
var tileCoord = ol.tilecoord.fromKey(key);
expect(tileCoord[0]).to.be(0);
map.once('moveend', function() {
// expect most recent tile in the cache to be from zoom level 4
var key = source.tileCache.peekFirstKey();
var tileCoord = ol.tilecoord.fromKey(key);
expect(tileCoord[0]).to.be(4);
done();
});
view.setZoom(4);
});
});
});

View File

@@ -5,8 +5,10 @@ goog.require('ol.proj');
goog.require('ol.proj.Projection');
goog.require('ol.source.Source');
goog.require('ol.source.Tile');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid.TileGrid');
/**
* Tile source for tests that uses a EPSG:4326 based grid with 4 resolutions and
* 256x256 tiles.
@@ -40,7 +42,7 @@ ol.inherits(MockTile, ol.source.Tile);
* @inheritDoc
*/
MockTile.prototype.getTile = function(z, x, y) {
var key = this.getKeyZXY(z, x, y);
var key = ol.tilecoord.getKeyZXY(z, x, y);
if (this.tileCache.containsKey(key)) {
return /** @type {!ol.Tile} */ (this.tileCache.get(key));
} else {

View File

@@ -1,5 +1,3 @@
goog.require('ol.ImageTile');
goog.require('ol.TileState');
goog.require('ol.TileUrlFunction');
@@ -9,6 +7,7 @@ goog.require('ol.proj.EPSG3857');
goog.require('ol.proj.Projection');
goog.require('ol.reproj.Tile');
goog.require('ol.source.TileImage');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid');
@@ -52,7 +51,7 @@ describe('ol.source.TileImage', function() {
expect(source.getKey()).to.be('');
source.getTileInternal(0, 0, -1, 1, ol.proj.get('EPSG:3857'));
expect(source.tileCache.getCount()).to.be(1);
tile = source.tileCache.get(source.getKeyZXY(0, 0, -1));
tile = source.tileCache.get(ol.tilecoord.getKeyZXY(0, 0, -1));
});
it('gets the tile from the cache', function() {

View File

@@ -275,15 +275,13 @@ describe('ol.source.TileWMS', function() {
});
describe('#setUrls()', function() {
it ('resets coordKeyPrefix_', function() {
var urls = ['u1', 'u2'];
var source1 = new ol.source.TileWMS({
urls: urls
it ('updates the source key', function() {
var source = new ol.source.TileWMS({
urls: ['u1', 'u2']
});
var source2 = new ol.source.TileWMS({});
expect(source2.coordKeyPrefix_).to.be.empty();
source2.setUrls(urls);
expect(source2.coordKeyPrefix_).to.equal(source1.coordKeyPrefix_);
var originalKey = source.getKey();
source.setUrls(['u3', 'u4']);
expect(source.getKey() !== originalKey).to.be(true);
});
});
});

View File

@@ -164,6 +164,30 @@ describe('ol.structs.LRUCache', function() {
});
});
describe('#peekFirstKey()', function() {
it('returns the newest key in the cache', function() {
var cache = new ol.structs.LRUCache();
cache.set('oldest', 'oldest');
cache.set('oldish', 'oldish');
cache.set('newish', 'newish');
cache.set('newest', 'newest');
expect(cache.peekFirstKey()).to.eql('newest');
});
it('works if the cache has one item', function() {
var cache = new ol.structs.LRUCache();
cache.set('key', 'value');
expect(cache.peekFirstKey()).to.eql('key');
});
it('throws if the cache is empty', function() {
var cache = new ol.structs.LRUCache();
expect(function() {
cache.peekFirstKey();
}).to.throwException();
});
});
describe('peeking at the last value', function() {
it('returns the last key', function() {
fillLRUCache(lruCache);
@@ -188,6 +212,66 @@ describe('ol.structs.LRUCache', function() {
});
});
describe('#remove()', function() {
it('removes an item from the cache', function() {
var cache = new ol.structs.LRUCache();
cache.set('oldest', 'oldest');
cache.set('oldish', 'oldish');
cache.set('newish', 'newish');
cache.set('newest', 'newest');
cache.remove('oldish');
expect(cache.getCount()).to.eql(3);
expect(cache.getValues()).to.eql(['newest', 'newish', 'oldest']);
});
it('works when removing the oldest item', function() {
var cache = new ol.structs.LRUCache();
cache.set('oldest', 'oldest');
cache.set('oldish', 'oldish');
cache.set('newish', 'newish');
cache.set('newest', 'newest');
cache.remove('oldest');
expect(cache.getCount()).to.eql(3);
expect(cache.peekLastKey()).to.eql('oldish');
expect(cache.getValues()).to.eql(['newest', 'newish', 'oldish']);
});
it('works when removing the newest item', function() {
var cache = new ol.structs.LRUCache();
cache.set('oldest', 'oldest');
cache.set('oldish', 'oldish');
cache.set('newish', 'newish');
cache.set('newest', 'newest');
cache.remove('newest');
expect(cache.getCount()).to.eql(3);
expect(cache.peekFirstKey()).to.eql('newish');
expect(cache.getValues()).to.eql(['newish', 'oldish', 'oldest']);
});
it('returns the removed item', function() {
var cache = new ol.structs.LRUCache();
var item = {};
cache.set('key', item);
var returned = cache.remove('key');
expect(returned).to.be(item);
});
it('throws if the key does not exist', function() {
var cache = new ol.structs.LRUCache();
cache.set('foo', 'foo');
cache.set('bar', 'bar');
var call = function() {
cache.remove('bam');
};
expect(call).to.throwException();
});
});
describe('clearing the cache', function() {
it('clears the cache', function() {
fillLRUCache(lruCache);

View File

@@ -0,0 +1,39 @@
goog.require('ol.Tile');
goog.require('ol.TileCache');
goog.require('ol.tilecoord');
describe('ol.TileCache', function() {
describe('#pruneExceptNewestZ()', function() {
it('gets rid of all entries that are not at the newest z', function() {
var tiles = [
new ol.Tile([0, 0, 0]),
new ol.Tile([1, 0, 0]),
new ol.Tile([1, 1, 0]),
new ol.Tile([2, 0, 0]),
new ol.Tile([2, 1, 0]),
new ol.Tile([2, 2, 0]),
new ol.Tile([2, 3, 0]) // newest tile at z: 2
];
var cache = new ol.TileCache();
sinon.spy(tiles[0], 'dispose');
tiles.forEach(function(tile) {
cache.set(ol.tilecoord.getKey(tile.tileCoord), tile);
});
cache.pruneExceptNewestZ();
expect(cache.getKeys()).to.eql([
'2/3/0',
'2/2/0',
'2/1/0',
'2/0/0'
]);
expect(tiles[0].dispose.calledOnce).to.be(true);
});
});
});

View File

@@ -1,5 +1,3 @@
goog.require('ol.tilecoord');
goog.require('ol.tilegrid.TileGrid');
@@ -23,6 +21,23 @@ describe('ol.TileCoord', function() {
});
});
describe('getKey()', function() {
it('returns a key for a tile coord', function() {
var key = ol.tilecoord.getKey([1, 2, 3]);
expect(key).to.eql('1/2/3');
});
});
describe('fromKey()', function() {
it('returns a tile coord given a key', function() {
var tileCoord = [1, 2, 3];
var key = ol.tilecoord.getKey(tileCoord);
var returned = ol.tilecoord.fromKey(key);
expect(returned).to.eql(tileCoord);
});
});
describe('hash', function() {
it('produces different hashes for different tile coords', function() {
var tileCoord1 = [3, 2, 1];