diff --git a/src/ol/framestate.js b/src/ol/framestate.js index 032dac1ed7..32a7edb31c 100644 --- a/src/ol/framestate.js +++ b/src/ol/framestate.js @@ -8,6 +8,7 @@ goog.require('ol.Color'); goog.require('ol.Coordinate'); goog.require('ol.Extent'); goog.require('ol.Size'); +goog.require('ol.TileQueue'); goog.require('ol.View2DState'); goog.require('ol.layer.LayerState'); @@ -20,6 +21,7 @@ goog.require('ol.layer.LayerState'); * layerStates: Object., * postRenderFunctions: Array., * size: ol.Size, + * tileQueue: ol.TileQueue, * time: number, * view2DState: ol.View2DState}} */ diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js index c75c11ea23..12255034e3 100644 --- a/src/ol/imagetile.js +++ b/src/ol/imagetile.js @@ -92,6 +92,7 @@ ol.ImageTile.prototype.getKey = function() { ol.ImageTile.prototype.handleImageError_ = function() { this.state = ol.TileState.ERROR; this.unlistenImage_(); + this.dispatchChangeEvent(); }; diff --git a/src/ol/map.js b/src/ol/map.js index 01a180637c..a617727b2e 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -36,6 +36,7 @@ goog.require('ol.Pixel'); goog.require('ol.ResolutionConstraint'); goog.require('ol.RotationConstraint'); goog.require('ol.Size'); +goog.require('ol.TileQueue'); goog.require('ol.TransformFunction'); goog.require('ol.View'); goog.require('ol.View2D'); @@ -249,6 +250,12 @@ ol.Map = function(mapOptions) { */ this.handlePostRender_ = goog.bind(this.handlePostRender, this); + /** + * @private + * @type {ol.TileQueue} + */ + this.tileQueue_ = new ol.TileQueue(goog.bind(this.getTilePriority, this)); + this.setValues(mapOptionsInternal.values); this.handleBrowserWindowResize(); @@ -420,6 +427,24 @@ ol.Map.prototype.getOverlayContainer = function() { }; +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + * @return {number|undefined} Tile priority. + */ +ol.Map.prototype.getTilePriority = function(tile, tileCenter, tileResolution) { + if (goog.isNull(this.frameState_)) { + return undefined; + } else { + var center = this.frameState_.view2DState.center; + var deltaX = tileCenter.x - center.x; + var deltaY = tileCenter.y - center.y; + return Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; + } +}; + + /** * @param {goog.events.BrowserEvent} browserEvent Browser event. * @param {string=} opt_type Type. @@ -474,6 +499,8 @@ ol.Map.prototype.handleMapBrowserEvent = function(mapBrowserEvent) { * @protected */ ol.Map.prototype.handlePostRender = function() { + this.tileQueue_.reprioritize(); // FIXME only call if needed + this.tileQueue_.loadMoreTiles(); goog.array.forEach( this.postRenderFunctions_, function(postRenderFunction) { @@ -572,6 +599,7 @@ ol.Map.prototype.renderFrame_ = function(time) { layerStates: layerStates, postRenderFunctions: [], size: size, + tileQueue: this.tileQueue_, view2DState: view2DState, time: time }; diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index 9f139550f9..e5205853c8 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -84,6 +84,7 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = var view2DState = frameState.view2DState; var z = tileGrid.getZForResolution(view2DState.resolution); + var tileResolution = tileGrid.getResolution(z); /** @type {Object.>} */ var tilesToDrawByZ = {}; @@ -104,7 +105,8 @@ ol.renderer.dom.TileLayer.prototype.renderFrame = var tileState = tile.getState(); if (tileState == ol.TileState.IDLE) { - tile.load(); + var tileCenter = tileGrid.getTileCoordCenter(tileCoord); + frameState.tileQueue.enqueue(tile, tileCenter, tileResolution); } else if (tileState == ol.TileState.LOADED) { tilesToDrawByZ[z][tile.tileCoord.toString()] = tile; return; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index fa4160dfe1..7fef2e09c4 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -391,7 +391,8 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = var tileState = tile.getState(); if (tileState == ol.TileState.IDLE) { - tile.load(); + var tileCenter = tileGrid.getTileCoordCenter(tileCoord); + frameState.tileQueue.enqueue(tile, tileCenter, tileResolution); } else if (tileState == ol.TileState.LOADED) { if (mapRenderer.isTileTextureLoaded(tile)) { tilesToDrawByZ[z][tileCoord.toString()] = tile; diff --git a/src/ol/tilequeue.js b/src/ol/tilequeue.js new file mode 100644 index 0000000000..d6a487a405 --- /dev/null +++ b/src/ol/tilequeue.js @@ -0,0 +1,116 @@ +goog.provide('ol.TilePriorityFunction'); +goog.provide('ol.TileQueue'); + +goog.require('goog.events'); +goog.require('goog.events.EventType'); +goog.require('goog.structs.PriorityQueue'); +goog.require('ol.Coordinate'); +goog.require('ol.Tile'); +goog.require('ol.TileState'); + + +/** + * @typedef {function(ol.Tile, ol.Coordinate, number): (number|undefined)} + */ +ol.TilePriorityFunction; + + + +/** + * @constructor + * @param {ol.TilePriorityFunction} tilePriorityFunction + * Tile priority function. + */ +ol.TileQueue = function(tilePriorityFunction) { + + /** + * @private + * @type {ol.TilePriorityFunction} + */ + this.tilePriorityFunction_ = tilePriorityFunction; + + /** + * @private + * @type {number} + */ + this.maxTilesLoading_ = 8; + + /** + * @private + * @type {number} + */ + this.tilesLoading_ = 0; + + /** + * @private + * @type {goog.structs.PriorityQueue} + */ + this.queue_ = new goog.structs.PriorityQueue(); + + /** + * @private + * @type {Object.} + */ + this.queuedTileKeys_ = {}; + +}; + + +/** + * @param {ol.Tile} tile Tile. + * @param {ol.Coordinate} tileCenter Tile center. + * @param {number} tileResolution Tile resolution. + */ +ol.TileQueue.prototype.enqueue = + function(tile, tileCenter, tileResolution) { + goog.asserts.assert(tile.getState() == ol.TileState.IDLE); + var tileKey = tile.getKey(); + if (!(tileKey in this.queuedTileKeys_)) { + var priority = this.tilePriorityFunction_(tile, tileCenter, tileResolution); + if (goog.isDef(priority)) { + this.queue_.enqueue(priority, arguments); + this.queuedTileKeys_[tileKey] = true; + } else { + // FIXME fire drop event? + } + } +}; + + +/** + * @protected + */ +ol.TileQueue.prototype.handleTileChange = function() { + --this.tilesLoading_; +}; + + +/** + */ +ol.TileQueue.prototype.loadMoreTiles = function() { + var tile, tileKey; + while (!this.queue_.isEmpty() && this.tilesLoading_ < this.maxTilesLoading_) { + tile = (/** @type {Array} */ (this.queue_.remove()))[0]; + tileKey = tile.getKey(); + delete this.queuedTileKeys_[tileKey]; + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange, false, this); + tile.load(); + ++this.tilesLoading_; + } +}; + + +/** + */ +ol.TileQueue.prototype.reprioritize = function() { + if (!this.queue_.isEmpty()) { + var values = /** @type {Array.} */ this.queue_.getValues(); + this.queue_.clear(); + this.queuedTileKeys_ = {}; + var i; + for (i = 0; i < values.length; ++i) { + this.enqueue.apply(this, values[i]); + } + } +};