From f12f5ccc6716192fe16ee0b981859b72b66aedd3 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Mar 2013 20:42:20 +0100 Subject: [PATCH 01/13] Factor out core elements of ol.TileQueue into ol.structs.PriorityQueue --- src/ol/structs/priorityqueue.js | 288 +++++++++++++++++++++ test/spec/ol/structs/priorityqueue.test.js | 186 +++++++++++++ 2 files changed, 474 insertions(+) create mode 100644 src/ol/structs/priorityqueue.js create mode 100644 test/spec/ol/structs/priorityqueue.test.js diff --git a/src/ol/structs/priorityqueue.js b/src/ol/structs/priorityqueue.js new file mode 100644 index 0000000000..74bfb08e42 --- /dev/null +++ b/src/ol/structs/priorityqueue.js @@ -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.} + * @private + */ + this.priorities_ = []; + + /** + * @type {Object.} + * @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_(); +}; diff --git a/test/spec/ol/structs/priorityqueue.test.js b/test/spec/ol/structs/priorityqueue.test.js new file mode 100644 index 0000000000..c9d7b27cd1 --- /dev/null +++ b/test/spec/ol/structs/priorityqueue.test.js @@ -0,0 +1,186 @@ +goog.provide('ol.test.structs.PriorityQueue'); + + +describe('ol.structs.PriorityQueue', function() { + + describe('when empty', function() { + + var pq; + beforeEach(function() { + pq = new ol.structs.PriorityQueue( + goog.identityFunction, goog.identityFunction); + }); + + it('is valid', function() { + expect(function() { + pq.assertValid(); + }).not.to.throwException(); + }); + + it('is empty', function() { + expect(pq.isEmpty()).to.be(true); + }); + + it('dequeue raises an exception', function() { + expect(function() { + pq.dequeue(); + }).to.throwException(); + }); + + it('enqueue adds an element', function() { + pq.enqueue(0); + expect(function() { + pq.assertValid(); + }).not.to.throwException(); + expect(pq.elements_).to.equalArray([0]); + expect(pq.priorities_).to.equalArray([0]); + }); + + it('maintains the pq property while elements are enqueued', function() { + var i; + for (i = 0; i < 32; ++i) { + pq.enqueue(Math.random()); + expect(function() { + pq.assertValid(); + }).not.to.throwException(); + } + }); + + }); + + describe('when populated', function() { + + var elements, pq; + beforeEach(function() { + elements = []; + pq = new ol.structs.PriorityQueue( + goog.identityFunction, goog.identityFunction); + var element, i; + for (i = 0; i < 32; ++i) { + element = Math.random(); + pq.enqueue(element); + elements.push(element); + } + }); + + it('dequeues elements in the correct order', function() { + elements.sort(); + var i; + for (i = 0; i < elements.length; ++i) { + expect(pq.dequeue()).to.be(elements[i]); + } + expect(pq.isEmpty()).to.be(true); + }); + + }); + + describe('with an impure priority function', function() { + + var pq, target; + beforeEach(function() { + target = 0.5; + pq = new ol.structs.PriorityQueue(function(element) { + return Math.abs(element - target); + }, goog.identityFunction); + var element, i; + for (i = 0; i < 32; ++i) { + pq.enqueue(Math.random()); + } + }); + + it('dequeue elements in the correct order', function() { + var lastDelta = 0; + var delta; + while (!pq.isEmpty()) { + delta = Math.abs(pq.dequeue() - target); + expect(lastDelta <= delta).to.be(true); + lastDelta = delta; + } + }); + + it('allows reprioritization', function() { + var target = 0.5; + pq.reprioritize(); + var lastDelta = 0; + var delta; + while (!pq.isEmpty()) { + delta = Math.abs(pq.dequeue() - target); + expect(lastDelta <= delta).to.be(true); + lastDelta = delta; + } + }); + + it('allows dropping during reprioritization', function() { + var target = 0.5; + var i = 0; + pq.priorityFunction_ = function(element) { + if (i++ % 2 === 0) { + return Math.abs(element - target); + } else { + return ol.structs.PriorityQueue.DROP; + } + }; + pq.reprioritize(); + expect(pq.getCount()).to.be(16); + var lastDelta = 0; + var delta; + while (!pq.isEmpty()) { + delta = Math.abs(pq.dequeue() - target); + expect(lastDelta <= delta).to.be(true); + lastDelta = delta; + } + }); + + }); + + describe('tracks elements in the queue', function() { + + var pq; + beforeEach(function() { + pq = new ol.structs.PriorityQueue( + goog.identityFunction, goog.identityFunction); + pq.enqueue('a'); + pq.enqueue('b'); + pq.enqueue('c'); + }); + + it('tracks which elements have been queued', function() { + expect(pq.isQueued('a')).to.be(true); + expect(pq.isQueued('b')).to.be(true); + expect(pq.isQueued('c')).to.be(true); + }); + + it('tracks which elements have not been queued', function() { + expect(pq.isQueued('d')).to.be(false); + }); + + it('raises an error when an queued element is re-queued', function() { + expect(function() { + pq.enqueue('a'); + }).to.throwException(); + }); + + it('tracks which elements have be dequeued', function() { + expect(pq.isQueued('a')).to.be(true); + expect(pq.isQueued('b')).to.be(true); + expect(pq.isQueued('c')).to.be(true); + expect(pq.dequeue()).to.be('a'); + expect(pq.isQueued('a')).to.be(false); + expect(pq.isQueued('b')).to.be(true); + expect(pq.isQueued('c')).to.be(true); + expect(pq.dequeue()).to.be('b'); + expect(pq.isQueued('a')).to.be(false); + expect(pq.isQueued('b')).to.be(false); + expect(pq.isQueued('c')).to.be(true); + expect(pq.dequeue()).to.be('c'); + expect(pq.isQueued('a')).to.be(false); + expect(pq.isQueued('b')).to.be(false); + expect(pq.isQueued('c')).to.be(false); + }); + + }); + +}); + + +goog.require('ol.structs.PriorityQueue'); From 27f9e837803a197c03002908a7fea03e32212c89 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Mar 2013 21:25:20 +0100 Subject: [PATCH 02/13] Refactor ol.TileQueue to extend ol.structs.PriorityQueue --- src/ol/map.js | 5 +- src/ol/renderer/layerrenderer.js | 8 +- src/ol/tilequeue.js | 226 +++---------------------------- test/spec/ol/tilequeue.test.js | 23 ++-- 4 files changed, 42 insertions(+), 220 deletions(-) diff --git a/src/ol/map.js b/src/ol/map.js index 45a6294b63..34b92731bc 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -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,11 +494,11 @@ 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, diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 6484c01a3e..005e8a03a6 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -307,7 +307,7 @@ ol.renderer.Layer.prototype.manageTilePyramid = } 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); @@ -317,9 +317,11 @@ ol.renderer.Layer.prototype.manageTilePyramid = if (ol.PREEMPTIVELY_LOAD_LOW_RESOLUTION_TILES || z == currentZ) { 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]); + } } } else { tileSource.useTile(z, x, y); diff --git a/src/ol/tilequeue.js b/src/ol/tilequeue.js index 43aaf920d3..4d21f2ef44 100644 --- a/src/ol/tilequeue.js +++ b/src/ol/tilequeue.js @@ -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.>} - */ - this.heap_ = []; - - /** - * @private - * @type {Object.} - */ - 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_(); -}; diff --git a/test/spec/ol/tilequeue.test.js b/test/spec/ol/tilequeue.test.js index ab4b261e87..1e30024b92 100644 --- a/test/spec/ol/tilequeue.test.js +++ b/test/spec/ol/tilequeue.test.js @@ -4,15 +4,15 @@ describe('ol.TileQueue', function() { // is the tile queue's array a heap? function isHeap(tq) { - var heap = tq.heap_; + var priorities = tq.priorities_; var i; var key; var leftKey; var rightKey; - for (i = 0; i < (heap.length >> 1) - 1; i++) { - key = heap[i][0]; - leftKey = heap[tq.getLeftChildIndex_(i)][0]; - rightKey = heap[tq.getRightChildIndex_(i)][0]; + for (i = 0; i < (priorities.length >> 1) - 1; i++) { + key = priorities[i]; + leftKey = priorities[tq.getLeftChildIndex_(i)]; + rightKey = priorities[tq.getRightChildIndex_(i)]; if (leftKey < key || rightKey < key) { return false; } @@ -25,8 +25,9 @@ describe('ol.TileQueue', function() { for (i = 0; i < num; i++) { tile = new ol.Tile(); priority = Math.floor(Math.random() * 100); - tq.heap_.push([priority, tile, '', new ol.Coordinate(0, 0)]); - tq.queuedTileKeys_[tile.getKey()] = true; + tq.elements_.push([tile, '', new ol.Coordinate(0, 0)]); + tq.priorities_.push(priority); + tq.queuedElements_[tile.getKey()] = true; } } @@ -53,15 +54,16 @@ describe('ol.TileQueue', function() { // rest var i = 0; - tq.tilePriorityFunction_ = function() { + tq.priorityFunction_ = function() { if ((i++) % 2 === 0) { - return ol.TileQueue.DROP; + return ol.structs.PriorityQueue.DROP; } return Math.floor(Math.random() * 100); }; tq.reprioritize(); - expect(tq.heap_.length).to.eql(50); + expect(tq.elements_.length).to.eql(50); + expect(tq.priorities_.length).to.eql(50); expect(isHeap(tq)).to.be.ok(); }); @@ -71,3 +73,4 @@ describe('ol.TileQueue', function() { goog.require('ol.Coordinate'); goog.require('ol.Tile'); goog.require('ol.TileQueue'); +goog.require('ol.structs.PriorityQueue'); From 3e4f1773bc343b093ef255d416df379965d31dfa Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Mon, 25 Mar 2013 22:46:27 +0100 Subject: [PATCH 03/13] Replace isHeap with assertValid --- test/spec/ol/tilequeue.test.js | 26 ++++++-------------------- 1 file changed, 6 insertions(+), 20 deletions(-) diff --git a/test/spec/ol/tilequeue.test.js b/test/spec/ol/tilequeue.test.js index 1e30024b92..f46dbb8ac5 100644 --- a/test/spec/ol/tilequeue.test.js +++ b/test/spec/ol/tilequeue.test.js @@ -2,24 +2,6 @@ goog.provide('ol.test.TileQueue'); describe('ol.TileQueue', function() { - // is the tile queue's array a heap? - function isHeap(tq) { - var priorities = tq.priorities_; - var i; - var key; - var leftKey; - var rightKey; - for (i = 0; i < (priorities.length >> 1) - 1; i++) { - key = priorities[i]; - leftKey = priorities[tq.getLeftChildIndex_(i)]; - rightKey = priorities[tq.getRightChildIndex_(i)]; - if (leftKey < key || rightKey < key) { - return false; - } - } - return true; - } - function addRandomPriorityTiles(tq, num) { var i, tile, priority; for (i = 0; i < num; i++) { @@ -38,7 +20,9 @@ describe('ol.TileQueue', function() { addRandomPriorityTiles(tq, 100); tq.heapify_(); - expect(isHeap(tq)).to.be.ok(); + expect(function() { + tq.assertValid(); + }).not.to.throwException(); }); }); @@ -64,7 +48,9 @@ describe('ol.TileQueue', function() { tq.reprioritize(); expect(tq.elements_.length).to.eql(50); expect(tq.priorities_.length).to.eql(50); - expect(isHeap(tq)).to.be.ok(); + expect(function() { + tq.assertValid(); + }).not.to.throwException(); }); }); From 96912c271d7c06b591758c3e957716b953cd8334 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Mar 2013 17:30:31 +0100 Subject: [PATCH 04/13] Add optional per-tile callback to manageTilePyramid --- src/ol/renderer/layerrenderer.js | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 005e8a03a6..3e1b460385 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -297,10 +297,13 @@ ol.renderer.Layer.prototype.snapCenterToPixel = * @param {ol.Projection} projection Projection. * @param {ol.Extent} extent Extent. * @param {number} currentZ Current Z. + * @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, opt_tileCallback, opt_obj) { var tileSourceKey = goog.getUid(tileSource).toString(); if (!(tileSourceKey in frameState.wantedTiles)) { frameState.wantedTiles[tileSourceKey] = {}; @@ -323,6 +326,9 @@ ol.renderer.Layer.prototype.manageTilePyramid = tileGrid.getTileCoordCenter(tile.tileCoord), tileResolution]); } } + if (goog.isDef(opt_tileCallback)) { + opt_tileCallback.call(opt_obj, tile); + } } else { tileSource.useTile(z, x, y); } From 00b862de10e114c4c668ade5a6def7707e857e5a Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Mar 2013 17:49:11 +0100 Subject: [PATCH 05/13] Store focus in frame state --- src/ol/framestate.js | 1 + src/ol/map.js | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/src/ol/framestate.js b/src/ol/framestate.js index a4c97039a0..cffecf36b4 100644 --- a/src/ol/framestate.js +++ b/src/ol/framestate.js @@ -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., * layerStates: Object., * pixelToCoordinateMatrix: goog.vec.Mat4.Number, diff --git a/src/ol/map.js b/src/ol/map.js index 34b92731bc..b5dbc4304a 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -500,13 +500,11 @@ ol.Map.prototype.getTilePriority = if (!frameState.wantedTiles[tileSourceKey][coordKey]) { 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; + // Prioritize tiles closest to the focus. 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 deltaX = tileCenter.x - frameState.focus.x; + var deltaY = tileCenter.y - frameState.focus.y; return tileResolution * (deltaX * deltaX + deltaY * deltaY + 1); }; @@ -714,6 +712,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_, From 6221680b0bc9b7e0703800cc43bebdfb38cabe03 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Mar 2013 18:16:06 +0100 Subject: [PATCH 06/13] Add tile texture queue --- src/ol/renderer/webgl/webglmaprenderer.js | 61 +++++++++++++++++++++++ 1 file changed, 61 insertions(+) diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 655350d6f9..b9fa87ad7b 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -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,51 @@ 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 tileResolution * (deltaX * deltaX + deltaY * deltaY + 1); + }, 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 +470,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 +548,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 +640,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; + } + }; From 5e8b0de66d373ae6f9db8d576a7652ae6c7faca6 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Mar 2013 18:16:21 +0100 Subject: [PATCH 07/13] Use tile texture queue --- .../renderer/webgl/webgltilelayerrenderer.js | 38 +++++++------------ 1 file changed, 14 insertions(+), 24 deletions(-) diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index a53ee77fce..f591ffebab 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -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,20 @@ 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, + 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; From 1d22d2fddec7625cee00ad9d356f9659fb478e62 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Tue, 26 Mar 2013 20:38:34 +0100 Subject: [PATCH 08/13] Tune tile priority functions --- src/ol/map.js | 12 ++++++++---- src/ol/renderer/webgl/webglmaprenderer.js | 3 ++- 2 files changed, 10 insertions(+), 5 deletions(-) diff --git a/src/ol/map.js b/src/ol/map.js index b5dbc4304a..c594839c76 100644 --- a/src/ol/map.js +++ b/src/ol/map.js @@ -500,12 +500,16 @@ ol.Map.prototype.getTilePriority = if (!frameState.wantedTiles[tileSourceKey][coordKey]) { return ol.structs.PriorityQueue.DROP; } - // Prioritize tiles closest to the focus. 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. + // 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 tileResolution * (deltaX * deltaX + deltaY * deltaY + 1); + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; }; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index b9fa87ad7b..93b5073e61 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -176,7 +176,8 @@ ol.renderer.webgl.Map = function(container, map) { var tileResolution = /** @type {number} */ (element[2]); var deltaX = tileCenter.x - this.focus_.x; var deltaY = tileCenter.y - this.focus_.y; - return tileResolution * (deltaX * deltaX + deltaY * deltaY + 1); + return 65536 * Math.log(tileResolution) + + Math.sqrt(deltaX * deltaX + deltaY * deltaY) / tileResolution; }, this), /** * @param {Array} element Element. From 494d61250e94d26e6489e526a97e8a55f690e2bf Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 27 Mar 2013 13:55:14 +0100 Subject: [PATCH 09/13] Make tile pre-loading a per-layer option --- src/objectliterals.exports | 10 +++++ src/ol/layer/tilelayer.exports | 2 +- src/ol/layer/tilelayer.js | 43 +++++++++++++++++-- .../canvas/canvastilelayerrenderer.js | 4 +- src/ol/renderer/dom/domtilelayerrenderer.js | 4 +- src/ol/renderer/layerrenderer.js | 14 +++--- .../renderer/webgl/webgltilelayerrenderer.js | 1 + 7 files changed, 61 insertions(+), 17 deletions(-) diff --git a/src/objectliterals.exports b/src/objectliterals.exports index c60cc92e9a..aa07df96ef 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -100,6 +100,16 @@ @exportObjectLiteralProperty ol.layer.LayerOptions.source ol.source.Source @exportObjectLiteralProperty ol.layer.LayerOptions.visible boolean|undefined +@exportObjectLiteral ol.layer.TileLayerOptions +@exportObjectLiteralProperty ol.layer.TileLayerOptions.brightness number|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.contrast number|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.hue number|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.opacity number|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.preload boolean|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.saturation number|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.source ol.source.Source +@exportObjectLiteralProperty ol.layer.TileLayerOptions.visible boolean|undefined + @exportObjectLiteral ol.layer.VectorLayerOptions @exportObjectLiteralProperty ol.layer.VectorLayerOptions.opacity number|undefined @exportObjectLiteralProperty ol.layer.VectorLayerOptions.source ol.source.Source diff --git a/src/ol/layer/tilelayer.exports b/src/ol/layer/tilelayer.exports index a567e216ff..9e6ffaf01f 100644 --- a/src/ol/layer/tilelayer.exports +++ b/src/ol/layer/tilelayer.exports @@ -1 +1 @@ -@exportClass ol.layer.TileLayer ol.layer.LayerOptions +@exportClass ol.layer.TileLayer ol.layer.TileLayerOptions diff --git a/src/ol/layer/tilelayer.js b/src/ol/layer/tilelayer.js index 878185e77f..a843713101 100644 --- a/src/ol/layer/tilelayer.js +++ b/src/ol/layer/tilelayer.js @@ -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 : false); + }; goog.inherits(ol.layer.TileLayer, ol.layer.Layer); +/** + * @return {boolean} Preload. + */ +ol.layer.TileLayer.prototype.getPreload = function() { + return /** @type {boolean} */ (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 {boolean} 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); diff --git a/src/ol/renderer/canvas/canvastilelayerrenderer.js b/src/ol/renderer/canvas/canvastilelayerrenderer.js index 70e528542c..64a1398344 100644 --- a/src/ol/renderer/canvas/canvastilelayerrenderer.js +++ b/src/ol/renderer/canvas/canvastilelayerrenderer.js @@ -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_; diff --git a/src/ol/renderer/dom/domtilelayerrenderer.js b/src/ol/renderer/dom/domtilelayerrenderer.js index 8c056742bd..61cb6a3908 100644 --- a/src/ol/renderer/dom/domtilelayerrenderer.js +++ b/src/ol/renderer/dom/domtilelayerrenderer.js @@ -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); }; diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 3e1b460385..8a7100ef60 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -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,13 +291,15 @@ ol.renderer.Layer.prototype.snapCenterToPixel = * @param {ol.Projection} projection Projection. * @param {ol.Extent} extent Extent. * @param {number} currentZ Current Z. + * @param {boolean} preload Preload low resolution tiles. * @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, opt_tileCallback, opt_obj) { +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] = {}; @@ -317,7 +313,7 @@ ol.renderer.Layer.prototype.manageTilePyramid = function(frameState, tileSource, 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 (preload || z == currentZ) { tile = tileSource.getTile(z, x, y, tileGrid, projection); if (tile.getState() == ol.TileState.IDLE) { wantedTiles[tile.tileCoord.toString()] = true; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index f591ffebab..690dd6ffaa 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -269,6 +269,7 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = var tileTextureQueue = mapRenderer.getTileTextureQueue(); this.manageTilePyramid( frameState, tileSource, tileGrid, projection, extent, z, + tileLayer.getPreload(), function(tile) { if (tile.getState() == ol.TileState.LOADED && !mapRenderer.isTileTextureLoaded(tile) && From 5077413c349f74b2a127d801e5a8ed956e57aa2b Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 27 Mar 2013 14:17:38 +0100 Subject: [PATCH 10/13] Add preload example --- examples/preload.html | 58 +++++++++++++++++++++++++++++++++++++++++++ examples/preload.js | 40 +++++++++++++++++++++++++++++ 2 files changed, 98 insertions(+) create mode 100644 examples/preload.html create mode 100644 examples/preload.js diff --git a/examples/preload.html b/examples/preload.html new file mode 100644 index 0000000000..58b421320c --- /dev/null +++ b/examples/preload.html @@ -0,0 +1,58 @@ + + + + + + + + + + Preload example + + + + + +
+ +
+
+
+
+
+
+
+
+ +
+ +
+

Preload example

+

Example of tile preloading. Low resolution tiles for the map are preloaded, the map on the right does not use any preloading. Try zooming out and panning to see the difference.

+
+

See the preload.js source to see how this is done.

+
+
preload, openstreetmap
+
+ +
+ +
+ + + + + + diff --git a/examples/preload.js b/examples/preload.js new file mode 100644 index 0000000000..945c670b16 --- /dev/null +++ b/examples/preload.js @@ -0,0 +1,40 @@ +goog.require('ol.Coordinate'); +goog.require('ol.Map'); +goog.require('ol.RendererHints'); +goog.require('ol.View2D'); +goog.require('ol.layer.TileLayer'); +goog.require('ol.source.BingMaps'); + + +var map1 = new ol.Map({ + layers: [ + new ol.layer.TileLayer({ + preload: true, + source: new ol.source.BingMaps({ + key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', + style: 'Aerial' + }) + }) + ], + renderers: ol.RendererHints.createFromQueryData(), + target: 'map1', + view: new ol.View2D({ + center: new ol.Coordinate(-4808600, -2620936), + zoom: 8 + }) +}); + +var map2 = new ol.Map({ + layers: [ + new ol.layer.TileLayer({ + preload: false, + source: new ol.source.BingMaps({ + key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', + style: 'AerialWithLabels' + }) + }) + ], + renderers: ol.RendererHints.createFromQueryData(), + target: 'map2' +}); +map2.bindTo('view', map1); From 421135b3e7c31febc1f56b35cd0085bd6f09f8a2 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 27 Mar 2013 14:24:06 +0100 Subject: [PATCH 11/13] Set preload appropriately in examples --- examples/animation.js | 1 + examples/bing-maps.js | 1 + examples/rotation.js | 1 + examples/stamen.js | 1 + 4 files changed, 4 insertions(+) diff --git a/examples/animation.js b/examples/animation.js index f287201039..3bb3ae7cda 100644 --- a/examples/animation.js +++ b/examples/animation.js @@ -23,6 +23,7 @@ var bern = ol.projection.transform( var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ + preload: true, source: new ol.source.OpenStreetMap() }) ], diff --git a/examples/bing-maps.js b/examples/bing-maps.js index e4be7be46a..376dd2c869 100644 --- a/examples/bing-maps.js +++ b/examples/bing-maps.js @@ -12,6 +12,7 @@ var layers = []; for (var i = 0; i < styles.length; ++i) { layers.push(new ol.layer.TileLayer({ visible: false, + preload: true, source: new ol.source.BingMaps({ key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', style: styles[i] diff --git a/examples/rotation.js b/examples/rotation.js index 9322443256..263829a229 100644 --- a/examples/rotation.js +++ b/examples/rotation.js @@ -9,6 +9,7 @@ goog.require('ol.source.OpenStreetMap'); var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ + preload: true, source: new ol.source.OpenStreetMap() }) ], diff --git a/examples/stamen.js b/examples/stamen.js index 993cb6381f..1507f5f035 100644 --- a/examples/stamen.js +++ b/examples/stamen.js @@ -10,6 +10,7 @@ goog.require('ol.source.Stamen'); var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ + preload: true, source: new ol.source.Stamen({ layer: 'watercolor' }) From 66c6a8a3e0de39100f414f860349f5772c53f12e Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 27 Mar 2013 14:34:20 +0100 Subject: [PATCH 12/13] Make preload a number of levels instead of a boolean --- examples/animation.js | 2 +- examples/bing-maps.js | 2 +- examples/preload.js | 4 ++-- examples/rotation.js | 2 +- examples/stamen.js | 2 +- src/objectliterals.exports | 2 +- src/ol/layer/tilelayer.js | 8 ++++---- src/ol/renderer/layerrenderer.js | 4 ++-- 8 files changed, 13 insertions(+), 13 deletions(-) diff --git a/examples/animation.js b/examples/animation.js index 3bb3ae7cda..136fd54b68 100644 --- a/examples/animation.js +++ b/examples/animation.js @@ -23,7 +23,7 @@ var bern = ol.projection.transform( var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ - preload: true, + preload: 4, source: new ol.source.OpenStreetMap() }) ], diff --git a/examples/bing-maps.js b/examples/bing-maps.js index 376dd2c869..4b75f11264 100644 --- a/examples/bing-maps.js +++ b/examples/bing-maps.js @@ -12,7 +12,7 @@ var layers = []; for (var i = 0; i < styles.length; ++i) { layers.push(new ol.layer.TileLayer({ visible: false, - preload: true, + preload: Infinity, source: new ol.source.BingMaps({ key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', style: styles[i] diff --git a/examples/preload.js b/examples/preload.js index 945c670b16..278e1226b4 100644 --- a/examples/preload.js +++ b/examples/preload.js @@ -9,7 +9,7 @@ goog.require('ol.source.BingMaps'); var map1 = new ol.Map({ layers: [ new ol.layer.TileLayer({ - preload: true, + preload: Infinity, source: new ol.source.BingMaps({ key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', style: 'Aerial' @@ -27,7 +27,7 @@ var map1 = new ol.Map({ var map2 = new ol.Map({ layers: [ new ol.layer.TileLayer({ - preload: false, + preload: 0, // default value source: new ol.source.BingMaps({ key: 'AgtFlPYDnymLEe9zJ5PCkghbNiFZE9aAtTy3mPaEnEBXqLHtFuTcKoZ-miMC3w7R', style: 'AerialWithLabels' diff --git a/examples/rotation.js b/examples/rotation.js index 263829a229..75a519f4f9 100644 --- a/examples/rotation.js +++ b/examples/rotation.js @@ -9,7 +9,7 @@ goog.require('ol.source.OpenStreetMap'); var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ - preload: true, + preload: 4, source: new ol.source.OpenStreetMap() }) ], diff --git a/examples/stamen.js b/examples/stamen.js index 1507f5f035..d5dd0306cd 100644 --- a/examples/stamen.js +++ b/examples/stamen.js @@ -10,7 +10,7 @@ goog.require('ol.source.Stamen'); var map = new ol.Map({ layers: [ new ol.layer.TileLayer({ - preload: true, + preload: 4, source: new ol.source.Stamen({ layer: 'watercolor' }) diff --git a/src/objectliterals.exports b/src/objectliterals.exports index aa07df96ef..593cae744f 100644 --- a/src/objectliterals.exports +++ b/src/objectliterals.exports @@ -105,7 +105,7 @@ @exportObjectLiteralProperty ol.layer.TileLayerOptions.contrast number|undefined @exportObjectLiteralProperty ol.layer.TileLayerOptions.hue number|undefined @exportObjectLiteralProperty ol.layer.TileLayerOptions.opacity number|undefined -@exportObjectLiteralProperty ol.layer.TileLayerOptions.preload boolean|undefined +@exportObjectLiteralProperty ol.layer.TileLayerOptions.preload number|undefined @exportObjectLiteralProperty ol.layer.TileLayerOptions.saturation number|undefined @exportObjectLiteralProperty ol.layer.TileLayerOptions.source ol.source.Source @exportObjectLiteralProperty ol.layer.TileLayerOptions.visible boolean|undefined diff --git a/src/ol/layer/tilelayer.js b/src/ol/layer/tilelayer.js index a843713101..bc27f66982 100644 --- a/src/ol/layer/tilelayer.js +++ b/src/ol/layer/tilelayer.js @@ -23,17 +23,17 @@ ol.layer.TileLayer = function(options) { goog.base(this, options); this.setPreload( - goog.isDef(options.preload) ? options.preload : false); + goog.isDef(options.preload) ? options.preload : 0); }; goog.inherits(ol.layer.TileLayer, ol.layer.Layer); /** - * @return {boolean} Preload. + * @return {number} Preload. */ ol.layer.TileLayer.prototype.getPreload = function() { - return /** @type {boolean} */ (this.get(ol.layer.TileLayerProperty.PRELOAD)); + return /** @type {number} */ (this.get(ol.layer.TileLayerProperty.PRELOAD)); }; goog.exportProperty( ol.layer.TileLayer.prototype, @@ -50,7 +50,7 @@ ol.layer.TileLayer.prototype.getTileSource = function() { /** - * @param {boolean} preload Preload. + * @param {number} preload Preload. */ ol.layer.TileLayer.prototype.setPreload = function(preload) { this.set(ol.layer.TileLayerProperty.PRELOAD, preload); diff --git a/src/ol/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 8a7100ef60..ec05d79c8f 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -291,7 +291,7 @@ ol.renderer.Layer.prototype.snapCenterToPixel = * @param {ol.Projection} projection Projection. * @param {ol.Extent} extent Extent. * @param {number} currentZ Current Z. - * @param {boolean} preload Preload low resolution tiles. + * @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 @@ -313,7 +313,7 @@ ol.renderer.Layer.prototype.manageTilePyramid = function( tileResolution = tileGrid.getResolution(z); for (x = tileRange.minX; x <= tileRange.maxX; ++x) { for (y = tileRange.minY; y <= tileRange.maxY; ++y) { - if (preload || z == currentZ) { + if (currentZ - z <= preload) { tile = tileSource.getTile(z, x, y, tileGrid, projection); if (tile.getState() == ol.TileState.IDLE) { wantedTiles[tile.tileCoord.toString()] = true; From 6b01ff932791f61325d710bd38d10768d6d972be Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Wed, 27 Mar 2013 14:43:22 +0100 Subject: [PATCH 13/13] Correct preload example tags, thanks @fredj --- examples/preload.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/preload.html b/examples/preload.html index 58b421320c..179642c3b2 100644 --- a/examples/preload.html +++ b/examples/preload.html @@ -44,7 +44,7 @@

See the preload.js source to see how this is done.

-
preload, openstreetmap
+
preload, bing