Merge pull request #423 from twpayne/preemptive-webgl-texture-uploads
White flash occurs when zooming out from initially zoomed in view with WebGL renderer
This commit is contained in:
@@ -23,6 +23,7 @@ goog.require('ol.layer.LayerState');
|
||||
* backgroundColor: ol.Color,
|
||||
* coordinateToPixelMatrix: goog.vec.Mat4.Number,
|
||||
* extent: (null|ol.Extent),
|
||||
* focus: ol.Coordinate,
|
||||
* layersArray: Array.<ol.layer.Layer>,
|
||||
* layerStates: Object.<number, ol.layer.LayerState>,
|
||||
* pixelToCoordinateMatrix: goog.vec.Mat4.Number,
|
||||
|
||||
@@ -1 +1 @@
|
||||
@exportClass ol.layer.TileLayer ol.layer.LayerOptions
|
||||
@exportClass ol.layer.TileLayer ol.layer.TileLayerOptions
|
||||
|
||||
@@ -4,21 +4,58 @@ goog.require('ol.layer.Layer');
|
||||
goog.require('ol.source.TileSource');
|
||||
|
||||
|
||||
/**
|
||||
* @enum {string}
|
||||
*/
|
||||
ol.layer.TileLayerProperty = {
|
||||
PRELOAD: 'preload'
|
||||
};
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.layer.Layer}
|
||||
* @param {ol.layer.LayerOptions} layerOptions Layer options.
|
||||
* @param {ol.layer.TileLayerOptions} options Options.
|
||||
*/
|
||||
ol.layer.TileLayer = function(layerOptions) {
|
||||
goog.base(this, layerOptions);
|
||||
ol.layer.TileLayer = function(options) {
|
||||
|
||||
goog.base(this, options);
|
||||
|
||||
this.setPreload(
|
||||
goog.isDef(options.preload) ? options.preload : 0);
|
||||
|
||||
};
|
||||
goog.inherits(ol.layer.TileLayer, ol.layer.Layer);
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} Preload.
|
||||
*/
|
||||
ol.layer.TileLayer.prototype.getPreload = function() {
|
||||
return /** @type {number} */ (this.get(ol.layer.TileLayerProperty.PRELOAD));
|
||||
};
|
||||
goog.exportProperty(
|
||||
ol.layer.TileLayer.prototype,
|
||||
'getPreload',
|
||||
ol.layer.TileLayer.prototype.getPreload);
|
||||
|
||||
|
||||
/**
|
||||
* @return {ol.source.TileSource} Source.
|
||||
*/
|
||||
ol.layer.TileLayer.prototype.getTileSource = function() {
|
||||
return /** @type {ol.source.TileSource} */ (this.getSource());
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} preload Preload.
|
||||
*/
|
||||
ol.layer.TileLayer.prototype.setPreload = function(preload) {
|
||||
this.set(ol.layer.TileLayerProperty.PRELOAD, preload);
|
||||
};
|
||||
goog.exportProperty(
|
||||
ol.layer.TileLayer.prototype,
|
||||
'setPreload',
|
||||
ol.layer.TileLayer.prototype.setPreload);
|
||||
|
||||
@@ -55,6 +55,7 @@ goog.require('ol.renderer.dom.Map');
|
||||
goog.require('ol.renderer.dom.SUPPORTED');
|
||||
goog.require('ol.renderer.webgl.Map');
|
||||
goog.require('ol.renderer.webgl.SUPPORTED');
|
||||
goog.require('ol.structs.PriorityQueue');
|
||||
|
||||
|
||||
/**
|
||||
@@ -493,20 +494,22 @@ ol.Map.prototype.getTilePriority =
|
||||
// are outside the visible extent.
|
||||
var frameState = this.frameState_;
|
||||
if (goog.isNull(frameState) || !(tileSourceKey in frameState.wantedTiles)) {
|
||||
return ol.TileQueue.DROP;
|
||||
return ol.structs.PriorityQueue.DROP;
|
||||
}
|
||||
var coordKey = tile.tileCoord.toString();
|
||||
if (!frameState.wantedTiles[tileSourceKey][coordKey]) {
|
||||
return ol.TileQueue.DROP;
|
||||
return ol.structs.PriorityQueue.DROP;
|
||||
}
|
||||
// Prioritize tiles closest to the focus or center. The + 1 helps to
|
||||
// prioritize tiles at higher zoom levels over tiles at lower zoom levels,
|
||||
// even if the tile's center is close to the focus.
|
||||
var focus = goog.isNull(this.focus_) ?
|
||||
frameState.view2DState.center : this.focus_;
|
||||
var deltaX = tileCenter.x - focus.x;
|
||||
var deltaY = tileCenter.y - focus.y;
|
||||
return tileResolution * (deltaX * deltaX + deltaY * deltaY + 1);
|
||||
// Prioritize the highest zoom level tiles closest to the focus.
|
||||
// Tiles at higher zoom levels are prioritized using Math.log(tileResolution).
|
||||
// Within a zoom level, tiles are prioritized by the distance in pixels
|
||||
// between the center of the tile and the focus. The factor of 65536 means
|
||||
// that the prioritization should behave as desired for tiles up to
|
||||
// 65536 * Math.log(2) = 45426 pixels from the focus.
|
||||
var deltaX = tileCenter.x - frameState.focus.x;
|
||||
var deltaY = tileCenter.y - frameState.focus.y;
|
||||
return 65536 * Math.log(tileResolution) +
|
||||
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
|
||||
};
|
||||
|
||||
|
||||
@@ -713,6 +716,7 @@ ol.Map.prototype.renderFrame_ = function(time) {
|
||||
backgroundColor : new ol.Color(255, 255, 255, 1),
|
||||
coordinateToPixelMatrix: this.coordinateToPixelMatrix_,
|
||||
extent: null,
|
||||
focus: goog.isNull(this.focus_) ? view2DState.center : this.focus_,
|
||||
layersArray: layersArray,
|
||||
layerStates: layerStates,
|
||||
pixelToCoordinateMatrix: this.pixelToCoordinateMatrix_,
|
||||
|
||||
@@ -285,8 +285,8 @@ ol.renderer.canvas.TileLayer.prototype.renderFrame =
|
||||
}
|
||||
|
||||
this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
|
||||
this.manageTilePyramid(
|
||||
frameState, tileSource, tileGrid, projection, extent, z);
|
||||
this.manageTilePyramid(frameState, tileSource, tileGrid, projection, extent,
|
||||
z, tileLayer.getPreload());
|
||||
this.scheduleExpireCache(frameState, tileSource);
|
||||
|
||||
var transform = this.transform_;
|
||||
|
||||
@@ -218,8 +218,8 @@ ol.renderer.dom.TileLayer.prototype.renderFrame =
|
||||
}
|
||||
|
||||
this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
|
||||
this.manageTilePyramid(
|
||||
frameState, tileSource, tileGrid, projection, extent, z);
|
||||
this.manageTilePyramid(frameState, tileSource, tileGrid, projection, extent,
|
||||
z, tileLayer.getPreload());
|
||||
this.scheduleExpireCache(frameState, tileSource);
|
||||
|
||||
};
|
||||
|
||||
@@ -17,12 +17,6 @@ goog.require('ol.layer.LayerState');
|
||||
goog.require('ol.source.TileSource');
|
||||
|
||||
|
||||
/**
|
||||
* @define {boolean} Preemptively load low resolution tiles.
|
||||
*/
|
||||
ol.PREEMPTIVELY_LOAD_LOW_RESOLUTION_TILES = true;
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
@@ -297,29 +291,39 @@ ol.renderer.Layer.prototype.snapCenterToPixel =
|
||||
* @param {ol.Projection} projection Projection.
|
||||
* @param {ol.Extent} extent Extent.
|
||||
* @param {number} currentZ Current Z.
|
||||
* @param {number} preload Load low resolution tiles up to 'preload' levels.
|
||||
* @param {function(this: T, ol.Tile)=} opt_tileCallback Tile callback.
|
||||
* @param {T=} opt_obj Object.
|
||||
* @protected
|
||||
* @template T
|
||||
*/
|
||||
ol.renderer.Layer.prototype.manageTilePyramid =
|
||||
function(frameState, tileSource, tileGrid, projection, extent, currentZ) {
|
||||
ol.renderer.Layer.prototype.manageTilePyramid = function(
|
||||
frameState, tileSource, tileGrid, projection, extent, currentZ, preload,
|
||||
opt_tileCallback, opt_obj) {
|
||||
var tileSourceKey = goog.getUid(tileSource).toString();
|
||||
if (!(tileSourceKey in frameState.wantedTiles)) {
|
||||
frameState.wantedTiles[tileSourceKey] = {};
|
||||
}
|
||||
var wantedTiles = frameState.wantedTiles[tileSourceKey];
|
||||
var tileQueue = frameState.tileQueue;
|
||||
var tile, tileCenter, tileRange, tileResolution, x, y, z;
|
||||
var tile, tileRange, tileResolution, x, y, z;
|
||||
// FIXME this should loop up to tileGrid's minZ when implemented
|
||||
for (z = currentZ; z >= 0; --z) {
|
||||
tileRange = tileGrid.getTileRangeForExtentAndZ(extent, z);
|
||||
tileResolution = tileGrid.getResolution(z);
|
||||
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
if (ol.PREEMPTIVELY_LOAD_LOW_RESOLUTION_TILES || z == currentZ) {
|
||||
if (currentZ - z <= preload) {
|
||||
tile = tileSource.getTile(z, x, y, tileGrid, projection);
|
||||
if (tile.getState() == ol.TileState.IDLE) {
|
||||
tileCenter = tileGrid.getTileCoordCenter(tile.tileCoord);
|
||||
wantedTiles[tile.tileCoord.toString()] = true;
|
||||
tileQueue.enqueue(tile, tileSourceKey, tileCenter, tileResolution);
|
||||
if (!tileQueue.isKeyQueued(tile.getKey())) {
|
||||
tileQueue.enqueue([tile, tileSourceKey,
|
||||
tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]);
|
||||
}
|
||||
}
|
||||
if (goog.isDef(opt_tileCallback)) {
|
||||
opt_tileCallback.call(opt_obj, tile);
|
||||
}
|
||||
} else {
|
||||
tileSource.useTile(z, x, y);
|
||||
|
||||
@@ -23,6 +23,7 @@ goog.require('ol.renderer.webgl.map.shader');
|
||||
goog.require('ol.structs.Buffer');
|
||||
goog.require('ol.structs.IntegerSet');
|
||||
goog.require('ol.structs.LRUCache');
|
||||
goog.require('ol.structs.PriorityQueue');
|
||||
goog.require('ol.webgl');
|
||||
goog.require('ol.webgl.WebGLContextEventType');
|
||||
goog.require('ol.webgl.shader');
|
||||
@@ -154,6 +155,52 @@ ol.renderer.webgl.Map = function(container, map) {
|
||||
*/
|
||||
this.textureCache_ = new ol.structs.LRUCache();
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.Coordinate}
|
||||
*/
|
||||
this.focus_ = null;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.structs.PriorityQueue}
|
||||
*/
|
||||
this.tileTextureQueue_ = new ol.structs.PriorityQueue(
|
||||
/**
|
||||
* @param {Array} element Element.
|
||||
* @return {number} Priority.
|
||||
*/
|
||||
goog.bind(function(element) {
|
||||
var tile = /** @type {ol.Tile} */ (element[0]);
|
||||
var tileCenter = /** @type {ol.Coordinate} */ (element[1]);
|
||||
var tileResolution = /** @type {number} */ (element[2]);
|
||||
var deltaX = tileCenter.x - this.focus_.x;
|
||||
var deltaY = tileCenter.y - this.focus_.y;
|
||||
return 65536 * Math.log(tileResolution) +
|
||||
Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution;
|
||||
}, this),
|
||||
/**
|
||||
* @param {Array} element Element.
|
||||
* @return {string} Key.
|
||||
*/
|
||||
function(element) {
|
||||
return /** @type {ol.Tile} */ (element[0]).getKey();
|
||||
});
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.PostRenderFunction}
|
||||
*/
|
||||
this.loadNextTileTexture_ = goog.bind(
|
||||
function(map, frameState) {
|
||||
if (!this.tileTextureQueue_.isEmpty()) {
|
||||
this.tileTextureQueue_.reprioritize();
|
||||
var tile =
|
||||
/** @type {ol.Tile} */ (this.tileTextureQueue_.dequeue()[0]);
|
||||
this.bindTileTexture(tile, goog.webgl.LINEAR, goog.webgl.LINEAR);
|
||||
}
|
||||
}, this);
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {number}
|
||||
@@ -424,6 +471,14 @@ ol.renderer.webgl.Map.prototype.getShader = function(shaderObject) {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {ol.structs.PriorityQueue} Tile texture queue.
|
||||
*/
|
||||
ol.renderer.webgl.Map.prototype.getTileTextureQueue = function() {
|
||||
return this.tileTextureQueue_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {goog.events.Event} event Event.
|
||||
* @protected
|
||||
@@ -494,6 +549,8 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
|
||||
return false;
|
||||
}
|
||||
|
||||
this.focus_ = frameState.focus;
|
||||
|
||||
this.textureCache_.set(frameState.time.toString(), null);
|
||||
++this.textureCacheFrameMarkerCount_;
|
||||
|
||||
@@ -584,4 +641,9 @@ ol.renderer.webgl.Map.prototype.renderFrame = function(frameState) {
|
||||
frameState.postRenderFunctions.push(goog.bind(this.expireCache_, this));
|
||||
}
|
||||
|
||||
if (!this.tileTextureQueue_.isEmpty()) {
|
||||
frameState.postRenderFunctions.push(this.loadNextTileTexture_);
|
||||
frameState.animate = true;
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
@@ -5,7 +5,6 @@ goog.provide('ol.renderer.webgl.TileLayer');
|
||||
|
||||
goog.require('goog.array');
|
||||
goog.require('goog.object');
|
||||
goog.require('goog.structs.PriorityQueue');
|
||||
goog.require('goog.vec.Mat4');
|
||||
goog.require('goog.vec.Vec4');
|
||||
goog.require('goog.webgl');
|
||||
@@ -210,10 +209,8 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
|
||||
var findLoadedTiles = goog.bind(tileSource.findLoadedTiles, tileSource,
|
||||
tilesToDrawByZ, getTileIfLoaded);
|
||||
|
||||
var tilesToLoad = new goog.structs.PriorityQueue();
|
||||
|
||||
var allTilesLoaded = true;
|
||||
var deltaX, deltaY, priority, tile, tileCenter, tileState, x, y;
|
||||
var tile, tileState, x, y;
|
||||
for (x = tileRange.minX; x <= tileRange.maxX; ++x) {
|
||||
for (y = tileRange.minY; y <= tileRange.maxY; ++y) {
|
||||
|
||||
@@ -223,12 +220,6 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
|
||||
if (mapRenderer.isTileTextureLoaded(tile)) {
|
||||
tilesToDrawByZ[z][tile.tileCoord.toString()] = tile;
|
||||
continue;
|
||||
} else {
|
||||
tileCenter = tileGrid.getTileCoordCenter(tile.tileCoord);
|
||||
deltaX = tileCenter.x - center.x;
|
||||
deltaY = tileCenter.y - center.y;
|
||||
priority = Math.sqrt(deltaX * deltaX + deltaY * deltaY);
|
||||
tilesToLoad.enqueue(priority, tile);
|
||||
}
|
||||
} else if (tileState == ol.TileState.ERROR ||
|
||||
tileState == ol.TileState.EMPTY) {
|
||||
@@ -263,19 +254,6 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
|
||||
}, this);
|
||||
}, this);
|
||||
|
||||
if (!tilesToLoad.isEmpty()) {
|
||||
frameState.postRenderFunctions.push(
|
||||
goog.partial(function(mapRenderer, tilesToLoad) {
|
||||
var i, tile;
|
||||
// FIXME determine a suitable number of textures to upload per frame
|
||||
for (i = 0; !tilesToLoad.isEmpty() && i < 4; ++i) {
|
||||
tile = /** @type {ol.Tile} */ (tilesToLoad.remove());
|
||||
mapRenderer.bindTileTexture(
|
||||
tile, goog.webgl.LINEAR, goog.webgl.LINEAR);
|
||||
}
|
||||
}, mapRenderer, tilesToLoad));
|
||||
}
|
||||
|
||||
if (allTilesLoaded) {
|
||||
this.renderedTileRange_ = tileRange;
|
||||
this.renderedFramebufferExtent_ = framebufferExtent;
|
||||
@@ -288,8 +266,21 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame =
|
||||
}
|
||||
|
||||
this.updateUsedTiles(frameState.usedTiles, tileSource, z, tileRange);
|
||||
var tileTextureQueue = mapRenderer.getTileTextureQueue();
|
||||
this.manageTilePyramid(
|
||||
frameState, tileSource, tileGrid, projection, extent, z);
|
||||
frameState, tileSource, tileGrid, projection, extent, z,
|
||||
tileLayer.getPreload(),
|
||||
function(tile) {
|
||||
if (tile.getState() == ol.TileState.LOADED &&
|
||||
!mapRenderer.isTileTextureLoaded(tile) &&
|
||||
!tileTextureQueue.isKeyQueued(tile.getKey())) {
|
||||
tileTextureQueue.enqueue([
|
||||
tile,
|
||||
tileGrid.getTileCoordCenter(tile.tileCoord),
|
||||
tileGrid.getResolution(tile.tileCoord.z)
|
||||
]);
|
||||
}
|
||||
}, this);
|
||||
this.scheduleExpireCache(frameState, tileSource);
|
||||
|
||||
var texCoordMatrix = this.texCoordMatrix;
|
||||
|
||||
288
src/ol/structs/priorityqueue.js
Normal file
288
src/ol/structs/priorityqueue.js
Normal file
@@ -0,0 +1,288 @@
|
||||
goog.provide('ol.structs.PriorityQueue');
|
||||
|
||||
goog.require('goog.asserts');
|
||||
goog.require('goog.object');
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* Priority queue.
|
||||
*
|
||||
* The implementation is inspired from the Closure Library's Heap class and
|
||||
* Python's heapq module.
|
||||
*
|
||||
* @see http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html
|
||||
* @see http://hg.python.org/cpython/file/2.7/Lib/heapq.py
|
||||
*
|
||||
* @constructor
|
||||
* @param {function(?): number} priorityFunction Priority function.
|
||||
* @param {function(?): string} keyFunction Key function.
|
||||
*/
|
||||
ol.structs.PriorityQueue = function(priorityFunction, keyFunction) {
|
||||
|
||||
/**
|
||||
* @type {function(?): number}
|
||||
* @private
|
||||
*/
|
||||
this.priorityFunction_ = priorityFunction;
|
||||
|
||||
/**
|
||||
* @type {function(?): string}
|
||||
* @private
|
||||
*/
|
||||
this.keyFunction_ = keyFunction;
|
||||
|
||||
/**
|
||||
* @type {Array}
|
||||
* @private
|
||||
*/
|
||||
this.elements_ = [];
|
||||
|
||||
/**
|
||||
* @type {Array.<number>}
|
||||
* @private
|
||||
*/
|
||||
this.priorities_ = [];
|
||||
|
||||
/**
|
||||
* @type {Object.<string, boolean>}
|
||||
* @private
|
||||
*/
|
||||
this.queuedElements_ = {};
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const {number}
|
||||
*/
|
||||
ol.structs.PriorityQueue.DROP = Infinity;
|
||||
|
||||
|
||||
/**
|
||||
* FIXME empty desciption for jsdoc
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.assertValid = function() {
|
||||
var elements = this.elements_;
|
||||
var priorities = this.priorities_;
|
||||
var n = elements.length;
|
||||
goog.asserts.assert(priorities.length == n);
|
||||
var i, priority;
|
||||
for (i = 0; i < (n >> 1) - 1; ++i) {
|
||||
priority = priorities[i];
|
||||
goog.asserts.assert(priority <= priorities[this.getLeftChildIndex_(i)]);
|
||||
goog.asserts.assert(priority <= priorities[this.getRightChildIndex_(i)]);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FIXME empty description for jsdoc
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.clear = function() {
|
||||
this.elements_.length = 0;
|
||||
this.priorities_.length = 0;
|
||||
goog.object.clear(this.queuedElements_);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Remove and return the highest-priority element. O(log N).
|
||||
* @return {*} Element.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.dequeue = function() {
|
||||
var elements = this.elements_;
|
||||
goog.asserts.assert(elements.length > 0);
|
||||
var priorities = this.priorities_;
|
||||
var element = elements[0];
|
||||
if (elements.length == 1) {
|
||||
elements.length = 0;
|
||||
priorities.length = 0;
|
||||
} else {
|
||||
elements[0] = elements.pop();
|
||||
priorities[0] = priorities.pop();
|
||||
this.siftUp_(0);
|
||||
}
|
||||
var elementKey = this.keyFunction_(element);
|
||||
goog.asserts.assert(elementKey in this.queuedElements_);
|
||||
delete this.queuedElements_[elementKey];
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enqueue an element. O(log N).
|
||||
* @param {*} element Element.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.enqueue = function(element) {
|
||||
goog.asserts.assert(!(this.keyFunction_(element) in this.queuedElements_));
|
||||
var priority = this.priorityFunction_(element);
|
||||
if (priority != ol.structs.PriorityQueue.DROP) {
|
||||
this.elements_.push(element);
|
||||
this.priorities_.push(priority);
|
||||
this.queuedElements_[this.keyFunction_(element)] = true;
|
||||
this.siftDown_(0, this.elements_.length - 1);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {number} Count.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.getCount = function() {
|
||||
return this.elements_.length;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the left child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the left child for.
|
||||
* @return {number} The index of the left child.
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.getLeftChildIndex_ = function(index) {
|
||||
return index * 2 + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the right child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the right child for.
|
||||
* @return {number} The index of the right child.
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.getRightChildIndex_ = function(index) {
|
||||
return index * 2 + 2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the parent of the node at the given index.
|
||||
* @param {number} index The index of the node to get the parent for.
|
||||
* @return {number} The index of the parent.
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.getParentIndex_ = function(index) {
|
||||
return (index - 1) >> 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make this a heap. O(N).
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.heapify_ = function() {
|
||||
var i;
|
||||
for (i = (this.elements_.length >> 1) - 1; i >= 0; i--) {
|
||||
this.siftUp_(i);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @return {boolean} Is empty.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.isEmpty = function() {
|
||||
return this.elements_.length === 0;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {string} key Key.
|
||||
* @return {boolean} Is key queued.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.isKeyQueued = function(key) {
|
||||
return key in this.queuedElements_;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {*} element Element.
|
||||
* @return {boolean} Is queued.
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.isQueued = function(element) {
|
||||
return this.isKeyQueued(this.keyFunction_(element));
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} index The index of the node to move down.
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.siftUp_ = function(index) {
|
||||
var elements = this.elements_;
|
||||
var priorities = this.priorities_;
|
||||
var count = elements.length;
|
||||
var element = elements[index];
|
||||
var priority = priorities[index];
|
||||
var startIndex = index;
|
||||
|
||||
while (index < (count >> 1)) {
|
||||
var lIndex = this.getLeftChildIndex_(index);
|
||||
var rIndex = this.getRightChildIndex_(index);
|
||||
|
||||
var smallerChildIndex = rIndex < count &&
|
||||
priorities[rIndex] < priorities[lIndex] ?
|
||||
rIndex : lIndex;
|
||||
|
||||
elements[index] = elements[smallerChildIndex];
|
||||
priorities[index] = priorities[smallerChildIndex];
|
||||
index = smallerChildIndex;
|
||||
}
|
||||
|
||||
elements[index] = element;
|
||||
priorities[index] = priority;
|
||||
this.siftDown_(startIndex, index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} startIndex The index of the root.
|
||||
* @param {number} index The index of the node to move up.
|
||||
* @private
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.siftDown_ = function(startIndex, index) {
|
||||
var elements = this.elements_;
|
||||
var priorities = this.priorities_;
|
||||
var element = elements[index];
|
||||
var priority = priorities[index];
|
||||
|
||||
while (index > startIndex) {
|
||||
var parentIndex = this.getParentIndex_(index);
|
||||
if (priorities[parentIndex] > priority) {
|
||||
elements[index] = elements[parentIndex];
|
||||
priorities[index] = priorities[parentIndex];
|
||||
index = parentIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
elements[index] = element;
|
||||
priorities[index] = priority;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FIXME empty description for jsdoc
|
||||
*/
|
||||
ol.structs.PriorityQueue.prototype.reprioritize = function() {
|
||||
var priorityFunction = this.priorityFunction_;
|
||||
var elements = this.elements_;
|
||||
var priorities = this.priorities_;
|
||||
var index = 0;
|
||||
var n = elements.length;
|
||||
var element, i, priority;
|
||||
for (i = 0; i < n; ++i) {
|
||||
element = elements[i];
|
||||
priority = priorityFunction(element);
|
||||
if (priority == ol.structs.PriorityQueue.DROP) {
|
||||
delete this.queuedElements_[this.keyFunction_(element)];
|
||||
} else {
|
||||
priorities[index] = priority;
|
||||
elements[index++] = element;
|
||||
}
|
||||
}
|
||||
elements.length = index;
|
||||
priorities.length = index;
|
||||
this.heapify_();
|
||||
};
|
||||
@@ -5,18 +5,7 @@ goog.require('goog.events');
|
||||
goog.require('goog.events.EventType');
|
||||
goog.require('ol.Coordinate');
|
||||
goog.require('ol.Tile');
|
||||
goog.require('ol.TileState');
|
||||
|
||||
|
||||
/**
|
||||
* Tile Queue.
|
||||
*
|
||||
* The implementation is inspired from the Closure Library's Heap
|
||||
* class and Python's heapq module.
|
||||
*
|
||||
* http://closure-library.googlecode.com/svn/docs/closure_goog_structs_heap.js.source.html
|
||||
* http://hg.python.org/cpython/file/2.7/Lib/heapq.py
|
||||
*/
|
||||
goog.require('ol.structs.PriorityQueue');
|
||||
|
||||
|
||||
/**
|
||||
@@ -28,6 +17,7 @@ ol.TilePriorityFunction;
|
||||
|
||||
/**
|
||||
* @constructor
|
||||
* @extends {ol.structs.PriorityQueue}
|
||||
* @param {ol.TilePriorityFunction} tilePriorityFunction
|
||||
* Tile priority function.
|
||||
* @param {Function} tileChangeCallback
|
||||
@@ -35,11 +25,22 @@ ol.TilePriorityFunction;
|
||||
*/
|
||||
ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {ol.TilePriorityFunction}
|
||||
*/
|
||||
this.tilePriorityFunction_ = tilePriorityFunction;
|
||||
goog.base(
|
||||
this,
|
||||
/**
|
||||
* @param {Array} element Element.
|
||||
* @return {number} Priority.
|
||||
*/
|
||||
function(element) {
|
||||
return tilePriorityFunction.apply(null, element);
|
||||
},
|
||||
/**
|
||||
* @param {Array} element Element.
|
||||
* @return {string} Key.
|
||||
*/
|
||||
function(element) {
|
||||
return /** @type {ol.Tile} */ (element[0]).getKey();
|
||||
});
|
||||
|
||||
/**
|
||||
* @private
|
||||
@@ -59,72 +60,8 @@ ol.TileQueue = function(tilePriorityFunction, tileChangeCallback) {
|
||||
*/
|
||||
this.tilesLoading_ = 0;
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Array.<Array.<*>>}
|
||||
*/
|
||||
this.heap_ = [];
|
||||
|
||||
/**
|
||||
* @private
|
||||
* @type {Object.<string, boolean>}
|
||||
*/
|
||||
this.queuedTileKeys_ = {};
|
||||
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @const {number}
|
||||
*/
|
||||
ol.TileQueue.DROP = Infinity;
|
||||
|
||||
|
||||
/**
|
||||
* Remove and return the highest-priority tile. O(logn).
|
||||
* @private
|
||||
* @return {ol.Tile} Tile.
|
||||
*/
|
||||
ol.TileQueue.prototype.dequeue_ = function() {
|
||||
var heap = this.heap_;
|
||||
goog.asserts.assert(heap.length > 0);
|
||||
var tile = /** @type {ol.Tile} */ (heap[0][1]);
|
||||
if (heap.length == 1) {
|
||||
heap.length = 0;
|
||||
} else {
|
||||
heap[0] = heap.pop();
|
||||
this.siftUp_(0);
|
||||
}
|
||||
var tileKey = tile.getKey();
|
||||
delete this.queuedTileKeys_[tileKey];
|
||||
return tile;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Enqueue a tile. O(logn).
|
||||
* @param {ol.Tile} tile Tile.
|
||||
* @param {string} tileSourceKey Tile source key.
|
||||
* @param {ol.Coordinate} tileCenter Tile center.
|
||||
* @param {number} tileResolution Tile resolution.
|
||||
*/
|
||||
ol.TileQueue.prototype.enqueue = function(
|
||||
tile, tileSourceKey, tileCenter, tileResolution) {
|
||||
if (tile.getState() != ol.TileState.IDLE) {
|
||||
return;
|
||||
}
|
||||
var tileKey = tile.getKey();
|
||||
if (!(tileKey in this.queuedTileKeys_)) {
|
||||
var priority = this.tilePriorityFunction_(
|
||||
tile, tileSourceKey, tileCenter, tileResolution);
|
||||
if (priority != ol.TileQueue.DROP) {
|
||||
this.heap_.push(
|
||||
[priority, tile, tileSourceKey, tileCenter, tileResolution]);
|
||||
this.queuedTileKeys_[tileKey] = true;
|
||||
this.siftDown_(0, this.heap_.length - 1);
|
||||
}
|
||||
}
|
||||
};
|
||||
goog.inherits(ol.TileQueue, ol.structs.PriorityQueue);
|
||||
|
||||
|
||||
/**
|
||||
@@ -136,137 +73,16 @@ ol.TileQueue.prototype.handleTileChange = function() {
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the left child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the left child for.
|
||||
* @return {number} The index of the left child.
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.getLeftChildIndex_ = function(index) {
|
||||
return index * 2 + 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the right child of the node at the given index.
|
||||
* @param {number} index The index of the node to get the right child for.
|
||||
* @return {number} The index of the right child.
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.getRightChildIndex_ = function(index) {
|
||||
return index * 2 + 2;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Gets the index of the parent of the node at the given index.
|
||||
* @param {number} index The index of the node to get the parent for.
|
||||
* @return {number} The index of the parent.
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.getParentIndex_ = function(index) {
|
||||
return (index - 1) >> 1;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* Make _heap a heap. O(n).
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.heapify_ = function() {
|
||||
for (var i = (this.heap_.length >> 1) - 1; i >= 0; i--) {
|
||||
this.siftUp_(i);
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FIXME empty description for jsdoc
|
||||
*/
|
||||
ol.TileQueue.prototype.loadMoreTiles = function() {
|
||||
var tile;
|
||||
while (this.heap_.length > 0 && this.tilesLoading_ < this.maxTilesLoading_) {
|
||||
tile = /** @type {ol.Tile} */ (this.dequeue_());
|
||||
while (!this.isEmpty() && this.tilesLoading_ < this.maxTilesLoading_) {
|
||||
tile = /** @type {ol.Tile} */ (this.dequeue()[0]);
|
||||
goog.events.listenOnce(tile, goog.events.EventType.CHANGE,
|
||||
this.handleTileChange, false, this);
|
||||
tile.load();
|
||||
++this.tilesLoading_;
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} index The index of the node to move down.
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.siftUp_ = function(index) {
|
||||
var heap = this.heap_;
|
||||
var count = heap.length;
|
||||
var node = heap[index];
|
||||
var startIndex = index;
|
||||
|
||||
while (index < (count >> 1)) {
|
||||
var lIndex = this.getLeftChildIndex_(index);
|
||||
var rIndex = this.getRightChildIndex_(index);
|
||||
|
||||
var smallerChildIndex = rIndex < count &&
|
||||
heap[rIndex][0] < heap[lIndex][0] ?
|
||||
rIndex : lIndex;
|
||||
|
||||
heap[index] = heap[smallerChildIndex];
|
||||
index = smallerChildIndex;
|
||||
}
|
||||
|
||||
heap[index] = node;
|
||||
this.siftDown_(startIndex, index);
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* @param {number} startIndex The index of the root.
|
||||
* @param {number} index The index of the node to move up.
|
||||
* @private
|
||||
*/
|
||||
ol.TileQueue.prototype.siftDown_ = function(startIndex, index) {
|
||||
var heap = this.heap_;
|
||||
var node = heap[index];
|
||||
|
||||
while (index > startIndex) {
|
||||
var parentIndex = this.getParentIndex_(index);
|
||||
if (heap[parentIndex][0] > node[0]) {
|
||||
heap[index] = heap[parentIndex];
|
||||
index = parentIndex;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
heap[index] = node;
|
||||
};
|
||||
|
||||
|
||||
/**
|
||||
* FIXME empty description for jsdoc
|
||||
*/
|
||||
ol.TileQueue.prototype.reprioritize = function() {
|
||||
var heap = this.heap_;
|
||||
var i, n = 0, node, priority;
|
||||
var tile, tileCenter, tileKey, tileResolution, tileSourceKey;
|
||||
for (i = 0; i < heap.length; ++i) {
|
||||
node = heap[i];
|
||||
tile = /** @type {ol.Tile} */ (node[1]);
|
||||
tileSourceKey = /** @type {string} */ (node[2]);
|
||||
tileCenter = /** @type {ol.Coordinate} */ (node[3]);
|
||||
tileResolution = /** @type {number} */ (node[4]);
|
||||
priority = this.tilePriorityFunction_(
|
||||
tile, tileSourceKey, tileCenter, tileResolution);
|
||||
if (priority == ol.TileQueue.DROP) {
|
||||
tileKey = tile.getKey();
|
||||
delete this.queuedTileKeys_[tileKey];
|
||||
} else {
|
||||
node[0] = priority;
|
||||
heap[n++] = node;
|
||||
}
|
||||
}
|
||||
heap.length = n;
|
||||
this.heapify_();
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user