diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js
index dcbadb162c..ccf1db3d2e 100644
--- a/src/ol/renderer/canvas/canvastilelayerrenderer.js
+++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js
@@ -287,11 +287,7 @@ ol.renderer.canvas.TileLayer.prototype.prepareFrame =
/** @type {Array.
} */
var tilesToClear = [];
- var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
- return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
- }, tileSource, pixelRatio, projection);
- var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
- tilesToDrawByZ, getTileIfLoaded);
+ var findLoadedTiles = this.createLoadedTileFinder(tileSource, tilesToDrawByZ);
var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js
index cfcb766990..091de7701d 100644
--- a/src/ol/renderer/dom/domtilelayerrenderer.js
+++ b/src/ol/renderer/dom/domtilelayerrenderer.js
@@ -128,11 +128,7 @@ ol.renderer.dom.TileLayer.prototype.prepareFrame =
var tilesToDrawByZ = {};
tilesToDrawByZ[z] = {};
- var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
- return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED;
- }, tileSource, pixelRatio, projection);
- var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
- tilesToDrawByZ, getTileIfLoaded);
+ var findLoadedTiles = this.createLoadedTileFinder(tileSource, tilesToDrawByZ);
var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js
index 0682ccb159..aab54f9306 100644
--- a/src/ol/renderer/layerrenderer.js
+++ b/src/ol/renderer/layerrenderer.js
@@ -85,6 +85,33 @@ ol.renderer.Layer.prototype.forEachLayerAtPixel =
ol.renderer.Layer.prototype.hasFeatureAtCoordinate = goog.functions.FALSE;
+/**
+ * Create a function that adds loaded tiles to the tile lookup.
+ * @param {ol.source.Tile} source Tile source.
+ * @param {Object.>} tiles Lookup of loaded
+ * tiles by zoom level.
+ * @return {function(number, ol.TileRange):boolean} A function that can be
+ * called with a zoom level and a tile range to add loaded tiles to the
+ * lookup.
+ * @protected
+ */
+ol.renderer.Layer.prototype.createLoadedTileFinder = function(source, tiles) {
+ /**
+ * @param {number} zoom Zoom level.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} The tile range is fully loaded.
+ */
+ return function(zoom, tileRange) {
+ return source.forEachLoadedTile(zoom, tileRange, function(tile) {
+ if (!tiles[zoom]) {
+ tiles[zoom] = {};
+ }
+ tiles[zoom][tile.tileCoord.toString()] = tile;
+ });
+ };
+};
+
+
/**
* @protected
* @return {ol.layer.Layer} Layer.
diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js
index a73ea4f1fd..b652126324 100644
--- a/src/ol/renderer/webgl/webgltilelayerrenderer.js
+++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js
@@ -94,6 +94,40 @@ ol.renderer.webgl.TileLayer.prototype.disposeInternal = function() {
};
+/**
+ * Create a function that adds loaded tiles to the tile lookup.
+ * @param {ol.source.Tile} source Tile source.
+ * @param {Object.>} tiles Lookup of loaded
+ * tiles by zoom level.
+ * @return {function(number, ol.TileRange):boolean} A function that can be
+ * called with a zoom level and a tile range to add loaded tiles to the
+ * lookup.
+ * @protected
+ */
+ol.renderer.webgl.TileLayer.prototype.createLoadedTileFinder =
+ function(source, tiles) {
+ var mapRenderer = this.mapRenderer;
+
+ /**
+ * @param {number} zoom Zoom level.
+ * @param {ol.TileRange} tileRange Tile range.
+ * @return {boolean} The tile range is fully loaded.
+ */
+ return function(zoom, tileRange) {
+ return source.forEachLoadedTile(zoom, tileRange, function(tile) {
+ var loaded = mapRenderer.isTileTextureLoaded(tile);
+ if (loaded) {
+ if (!tiles[zoom]) {
+ tiles[zoom] = {};
+ }
+ tiles[zoom][tile.tileCoord.toString()] = tile;
+ }
+ return loaded;
+ });
+ };
+};
+
+
/**
* @inheritDoc
*/
@@ -190,12 +224,8 @@ ol.renderer.webgl.TileLayer.prototype.prepareFrame =
var tilesToDrawByZ = {};
tilesToDrawByZ[z] = {};
- var getTileIfLoaded = this.createGetTileIfLoadedFunction(function(tile) {
- return !goog.isNull(tile) && tile.getState() == ol.TileState.LOADED &&
- mapRenderer.isTileTextureLoaded(tile);
- }, tileSource, pixelRatio, projection);
- var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
- tilesToDrawByZ, getTileIfLoaded);
+ var findLoadedTiles = this.createLoadedTileFinder(
+ tileSource, tilesToDrawByZ);
var useInterimTilesOnError = tileLayer.getUseInterimTilesOnError();
var allTilesLoaded = true;
diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js
index d789f2a643..092215e272 100644
--- a/src/ol/source/tilesource.js
+++ b/src/ol/source/tilesource.js
@@ -6,6 +6,7 @@ goog.require('ol.Attribution');
goog.require('ol.Extent');
goog.require('ol.TileCache');
goog.require('ol.TileRange');
+goog.require('ol.TileState');
goog.require('ol.source.Source');
goog.require('ol.tilecoord');
goog.require('ol.tilegrid.TileGrid');
@@ -92,41 +93,33 @@ ol.source.Tile.prototype.expireCache = function(usedTiles) {
/**
- * Look for loaded tiles over a given tile range and zoom level. Adds
- * properties to the provided lookup representing key/tile pairs for already
- * loaded tiles.
- *
- * @param {Object.>} loadedTilesByZ A lookup of
- * loaded tiles by zoom level.
- * @param {function(number, number, number): ol.Tile} getTileIfLoaded A function
- * that returns the tile only if it is fully loaded.
* @param {number} z Zoom level.
* @param {ol.TileRange} tileRange Tile range.
+ * @param {function(ol.Tile):(boolean|undefined)} callback Called with each
+ * loaded tile. If the callback returns `false`, the tile will not be
+ * considered loaded.
* @return {boolean} The tile range is fully covered with loaded tiles.
*/
-ol.source.Tile.prototype.findLoadedTiles = function(loadedTilesByZ,
- getTileIfLoaded, z, tileRange) {
- // FIXME this could be more efficient about filling partial holes
- var fullyCovered = true;
- var tile, tileCoordKey, x, y;
- for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
- for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
+ol.source.Tile.prototype.forEachLoadedTile = function(z, tileRange, callback) {
+ var covered = true;
+ 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);
- if (loadedTilesByZ[z] && loadedTilesByZ[z][tileCoordKey]) {
- continue;
- }
- tile = getTileIfLoaded(z, x, y);
- if (!goog.isNull(tile)) {
- if (!loadedTilesByZ[z]) {
- loadedTilesByZ[z] = {};
+ loaded = false;
+ if (this.tileCache.containsKey(tileCoordKey)) {
+ tile = /** @type {!ol.Tile} */ (this.tileCache.get(tileCoordKey));
+ loaded = tile.getState() === ol.TileState.LOADED;
+ if (loaded) {
+ loaded = (callback(tile) !== false);
}
- loadedTilesByZ[z][tileCoordKey] = tile;
- } else {
- fullyCovered = false;
+ }
+ if (!loaded) {
+ covered = false;
}
}
}
- return fullyCovered;
+ return covered;
};
diff --git a/test/spec/ol/source/tilesource.test.js b/test/spec/ol/source/tilesource.test.js
index d2340c0a44..3024a1cfcb 100644
--- a/test/spec/ol/source/tilesource.test.js
+++ b/test/spec/ol/source/tilesource.test.js
@@ -12,176 +12,105 @@ describe('ol.source.Tile', function() {
});
});
- describe('#findLoadedTiles()', function() {
+ describe('#forEachLoadedTile()', function() {
- it('adds no tiles if none are already loaded', function() {
- // a source with no loaded tiles
+ var callback;
+ beforeEach(function() {
+ callback = sinon.spy();
+ });
+
+ it('does not call the callback if no tiles are loaded', function() {
var source = new ol.test.source.TileMock({});
-
- var loadedTilesByZ = {};
var grid = source.getTileGrid();
var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 3);
+ var zoom = 3;
+ var range = grid.getTileRangeForExtentAndZ(extent, zoom);
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- source.findLoadedTiles(loadedTilesByZ, getTileIfLoaded, 3, range);
-
- var keys = goog.object.getKeys(loadedTilesByZ);
- expect(keys.length).to.be(0);
+ source.forEachLoadedTile(zoom, range, callback);
+ expect(callback.callCount).to.be(0);
});
- it('adds loaded tiles to the lookup (z: 0)', function() {
- // a source with no loaded tiles
- var source = new ol.test.source.TileMock({
- '0/0/0': true,
- '1/0/0': true
- });
-
- var loadedTilesByZ = {};
- var grid = source.getTileGrid();
- var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 0);
-
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- source.findLoadedTiles(loadedTilesByZ, getTileIfLoaded, 0, range);
- var keys = goog.object.getKeys(loadedTilesByZ);
- expect(keys.length).to.be(1);
- var tile = loadedTilesByZ['0']['0/0/0'];
- expect(tile).to.be.a(ol.Tile);
- expect(tile.state).to.be(ol.TileState.LOADED);
- });
-
- it('adds loaded tiles to the lookup (z: 1)', function() {
- // a source with no loaded tiles
- var source = new ol.test.source.TileMock({
- '0/0/0': true,
- '1/0/0': true
- });
-
- var loadedTilesByZ = {};
- var grid = source.getTileGrid();
- var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 1);
-
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- source.findLoadedTiles(loadedTilesByZ, getTileIfLoaded, 1, range);
- var keys = goog.object.getKeys(loadedTilesByZ);
- expect(keys.length).to.be(1);
- var tile = loadedTilesByZ['1']['1/0/0'];
- expect(tile).to.be.a(ol.Tile);
- expect(tile.state).to.be(ol.TileState.LOADED);
- });
-
- it('returns true when all tiles are already loaded', function() {
- // a source with no loaded tiles
- var source = new ol.test.source.TileMock({
- '1/0/0': true,
- '1/0/1': true,
- '1/1/0': true,
- '1/1/1': true
- });
-
- var loadedTilesByZ = {};
- var grid = source.getTileGrid();
- var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 1);
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- var loaded = source.findLoadedTiles(
- loadedTilesByZ, getTileIfLoaded, 1, range);
- expect(loaded).to.be(true);
- });
-
- it('returns true when all tiles are already loaded (part 2)', function() {
- // a source with no loaded tiles
+ it('does not call getTile() if no tiles are loaded', function() {
var source = new ol.test.source.TileMock({});
-
- var loadedTilesByZ = {
- '1': {
- '1/0/0': true,
- '1/0/1': true,
- '1/1/0': true,
- '1/1/1': true,
- '1/1/2': true
- }
- };
+ sinon.spy(source, 'getTile');
var grid = source.getTileGrid();
var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 1);
+ var zoom = 3;
+ var range = grid.getTileRangeForExtentAndZ(extent, zoom);
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- var loaded = source.findLoadedTiles(
- loadedTilesByZ, getTileIfLoaded, 1, range);
- expect(loaded).to.be(true);
+ source.forEachLoadedTile(zoom, range, callback);
+ expect(source.getTile.callCount).to.be(0);
+ source.getTile.restore();
});
- it('returns false when all tiles are already loaded', function() {
- // a source with no loaded tiles
+
+ it('calls callback for each loaded tile', function() {
var source = new ol.test.source.TileMock({
- '1/0/0': true,
- '1/0/1': true,
- '1/1/0': true,
- '1/1/1': false
+ '1/0/0': ol.TileState.LOADED,
+ '1/0/1': ol.TileState.LOADED,
+ '1/1/0': ol.TileState.LOADING,
+ '1/1/1': ol.TileState.LOADED
});
- var loadedTilesByZ = {};
- var grid = source.getTileGrid();
- var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 1);
+ var zoom = 1;
+ var range = new ol.TileRange(0, 1, 0, 1);
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- var loaded = source.findLoadedTiles(
- loadedTilesByZ, getTileIfLoaded, 1, range);
- expect(loaded).to.be(false);
+ source.forEachLoadedTile(zoom, range, callback);
+ expect(callback.callCount).to.be(3);
});
- it('returns false when all tiles are already loaded (part 2)', function() {
+ it('returns true if range is fully loaded', function() {
// a source with no loaded tiles
- var source = new ol.test.source.TileMock({});
+ var source = new ol.test.source.TileMock({
+ '1/0/0': ol.TileState.LOADED,
+ '1/0/1': ol.TileState.LOADED,
+ '1/1/0': ol.TileState.LOADED,
+ '1/1/1': ol.TileState.LOADED
+ });
- var loadedTilesByZ = {
- '1': {
- '1/0/0': true,
- '1/0/1': true,
- '1/1/0': true,
- '1/1/1': false
- }
- };
- var grid = source.getTileGrid();
- var extent = [-180, -180, 180, 180];
- var range = grid.getTileRangeForExtentAndZ(extent, 1);
+ var zoom = 1;
+ var range = new ol.TileRange(0, 1, 0, 1);
- function getTileIfLoaded(z, x, y) {
- var tile = source.getTile(z, x, y);
- return (!goog.isNull(tile) && tile.getState() === ol.TileState.LOADED) ?
- tile : null;
- }
- var loaded = source.findLoadedTiles(
- loadedTilesByZ, getTileIfLoaded, 1, range);
- expect(loaded).to.be(false);
+ var covered = source.forEachLoadedTile(zoom, range, function() {
+ return true;
+ });
+ expect(covered).to.be(true);
+ });
+
+ it('returns false if range is not fully loaded', function() {
+ // a source with no loaded tiles
+ var source = new ol.test.source.TileMock({
+ '1/0/0': ol.TileState.LOADED,
+ '1/0/1': ol.TileState.LOADED,
+ '1/1/0': ol.TileState.LOADING,
+ '1/1/1': ol.TileState.LOADED
+ });
+
+ var zoom = 1;
+ var range = new ol.TileRange(0, 1, 0, 1);
+
+ var covered = source.forEachLoadedTile(zoom, range, function() {
+ return true;
+ });
+ expect(covered).to.be(false);
+ });
+
+ it('allows callback to override loaded check', function() {
+ // a source with no loaded tiles
+ var source = new ol.test.source.TileMock({
+ '1/0/0': ol.TileState.LOADED,
+ '1/0/1': ol.TileState.LOADED,
+ '1/1/0': ol.TileState.LOADED,
+ '1/1/1': ol.TileState.LOADED
+ });
+
+ var zoom = 1;
+ var range = new ol.TileRange(0, 1, 0, 1);
+
+ var covered = source.forEachLoadedTile(zoom, range, function() {
+ return false;
+ });
+ expect(covered).to.be(false);
});
});
@@ -196,9 +125,10 @@ describe('ol.source.Tile', function() {
*
* @constructor
* @extends {ol.source.Tile}
- * @param {Object.} loaded Lookup of already loaded tiles.
+ * @param {Object.} tileStates Lookup of tile key to
+ * tile state.
*/
-ol.test.source.TileMock = function(loaded) {
+ol.test.source.TileMock = function(tileStates) {
var tileGrid = new ol.tilegrid.TileGrid({
resolutions: [360 / 256, 180 / 256, 90 / 256, 45 / 256],
origin: [-180, -180],
@@ -210,11 +140,9 @@ ol.test.source.TileMock = function(loaded) {
tileGrid: tileGrid
});
- /**
- * @type {Object.}
- * @private
- */
- this.loaded_ = loaded;
+ for (var key in tileStates) {
+ this.tileCache.set(key, new ol.Tile(key.split('/'), tileStates[key]));
+ }
};
goog.inherits(ol.test.source.TileMock, ol.source.Tile);
@@ -224,9 +152,14 @@ goog.inherits(ol.test.source.TileMock, ol.source.Tile);
* @inheritDoc
*/
ol.test.source.TileMock.prototype.getTile = function(z, x, y) {
- var key = ol.tilecoord.getKeyZXY(z, x, y);
- var tileState = this.loaded_[key] ? ol.TileState.LOADED : ol.TileState.IDLE;
- return new ol.Tile([z, x, y], tileState);
+ var key = this.getKeyZXY(z, x, y);
+ if (this.tileCache.containsKey(key)) {
+ return /** @type {!ol.Tile} */ (this.tileCache.get(key));
+ } else {
+ var tile = new ol.Tile(key, ol.TileState.IDLE);
+ this.tileCache.set(key, tile);
+ return tile;
+ }
};
@@ -243,8 +176,8 @@ describe('ol.test.source.TileMock', function() {
describe('#getTile()', function() {
it('returns a tile with state based on constructor arg', function() {
var source = new ol.test.source.TileMock({
- '0/0/0': true,
- '1/0/0': true
+ '0/0/0': ol.TileState.LOADED,
+ '1/0/0': ol.TileState.LOADED
});
var tile;
@@ -270,9 +203,9 @@ describe('ol.test.source.TileMock', function() {
goog.require('goog.object');
goog.require('ol.Tile');
+goog.require('ol.TileRange');
goog.require('ol.TileState');
goog.require('ol.proj');
goog.require('ol.source.Source');
goog.require('ol.source.Tile');
-goog.require('ol.tilecoord');
goog.require('ol.tilegrid.TileGrid');