diff --git a/examples/tile-load-events.html b/examples/tile-load-events.html new file mode 100644 index 0000000000..883e42ec6d --- /dev/null +++ b/examples/tile-load-events.html @@ -0,0 +1,79 @@ + + + + + + + + + + + Tile load events example + + + + + + +
+ +
+
+
+
+
+
+ +
+ +
+

Tile load events example

+

Example using tile load events.

+
+

+ Image tile sources fire events related to tile loading. You can + listen for tileloadstart, tileloadend, + and tileloaderror type events to monitor tile loading + progress. This example registers listeners for these events and + renders a tile loading progress bar at the bottom of the map. +

+

+ See the tile-load-events.js source + for more detail on how this is done. +

+
+
tile, events, loading
+
+ +
+ +
+ + + + + + + diff --git a/examples/tile-load-events.js b/examples/tile-load-events.js new file mode 100644 index 0000000000..48a6e61f0f --- /dev/null +++ b/examples/tile-load-events.js @@ -0,0 +1,104 @@ +goog.require('ol.Map'); +goog.require('ol.View'); +goog.require('ol.layer.Tile'); +goog.require('ol.source.TileJSON'); + + + +/** + * Renders a progress bar. + * @param {Element} el The target element. + * @constructor + */ +function Progress(el) { + this.el = el; + this.loading = 0; + this.loaded = 0; +} + + +/** + * Increment the count of loading tiles. + */ +Progress.prototype.addLoading = function() { + if (this.loading === 0) { + this.show(); + } + ++this.loading; + this.update(); +}; + + +/** + * Increment the count of loaded tiles. + */ +Progress.prototype.addLoaded = function() { + setTimeout(function() { + ++this.loaded; + this.update(); + }.bind(this), 100); +}; + + +/** + * Update the progress bar. + */ +Progress.prototype.update = function() { + var width = (this.loaded / this.loading * 100).toFixed(1) + '%'; + this.el.style.width = width; + if (this.loading === this.loaded) { + this.loading = 0; + this.loaded = 0; + setTimeout(this.hide.bind(this), 500); + } +}; + + +/** + * Show the progress bar. + */ +Progress.prototype.show = function() { + this.el.style.visibility = 'visible'; +}; + + +/** + * Hide the progress bar. + */ +Progress.prototype.hide = function() { + if (this.loading === this.loaded) { + this.el.style.visibility = 'hidden'; + this.el.style.width = 0; + } +}; + +var progress = new Progress(document.getElementById('progress')); + +var source = new ol.source.TileJSON({ + url: 'http://api.tiles.mapbox.com/v3/mapbox.world-bright.jsonp', + crossOrigin: 'anonymous' +}); + +source.on('tileloadstart', function(event) { + progress.addLoading(); +}); + +source.on('tileloadend', function(event) { + progress.addLoaded(); +}); +source.on('tileloaderror', function(event) { + progress.addLoaded(); +}); + +var map = new ol.Map({ + logo: false, + layers: [ + new ol.layer.Tile({source: source}) + ], + renderer: exampleNS.getRendererFromQueryString(), + target: 'map', + view: new ol.View({ + center: [0, 0], + zoom: 2 + }) +}); diff --git a/externs/oli.js b/externs/oli.js index 9ffb8877f3..a97117e207 100644 --- a/externs/oli.js +++ b/externs/oli.js @@ -210,6 +210,17 @@ oli.render.Event.prototype.vectorContext; oli.source; +/** + * @interface + */ +oli.source.TileEvent = function() {}; + + +/** + * @type {ol.Tile} + */ +oli.source.TileEvent.prototype.tile; + /** * @interface diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js index b0fc00dfb5..4deb7bd362 100644 --- a/src/ol/imagetile.js +++ b/src/ol/imagetile.js @@ -64,6 +64,15 @@ ol.ImageTile = function(tileCoord, state, src, crossOrigin, tileLoadFunction) { goog.inherits(ol.ImageTile, ol.Tile); +/** + * @inheritDoc + */ +ol.ImageTile.prototype.disposeInternal = function() { + this.unlistenImage_(); + goog.base(this, 'disposeInternal'); +}; + + /** * @inheritDoc * @api @@ -136,6 +145,7 @@ ol.ImageTile.prototype.handleImageLoad_ = function() { ol.ImageTile.prototype.load = function() { if (this.state == ol.TileState.IDLE) { this.state = ol.TileState.LOADING; + this.changed(); goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); this.imageListenerKeys_ = [ goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, diff --git a/src/ol/source/tileimagesource.js b/src/ol/source/tileimagesource.js index 600eb69313..1e53f22d10 100644 --- a/src/ol/source/tileimagesource.js +++ b/src/ol/source/tileimagesource.js @@ -1,6 +1,8 @@ goog.provide('ol.source.TileImage'); goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('ol.ImageTile'); goog.require('ol.TileCache'); goog.require('ol.TileCoord'); @@ -17,6 +19,7 @@ goog.require('ol.source.Tile'); * Base class for sources providing images divided into a tile grid. * * @constructor + * @fires ol.source.TileEvent * @extends {ol.source.Tile} * @param {olx.source.TileImageOptions} options Image tile options. * @api @@ -118,6 +121,9 @@ ol.source.TileImage.prototype.getTile = goog.isDef(tileUrl) ? tileUrl : '', this.crossOrigin, this.tileLoadFunction); + goog.events.listen(tile, goog.events.EventType.CHANGE, + this.handleTileChange_, false, this); + this.tileCache.set(tileCoordKey, tile); return tile; } @@ -142,6 +148,30 @@ ol.source.TileImage.prototype.getTileUrlFunction = function() { }; +/** + * Handle tile change events. + * @param {goog.events.Event} event Event. + * @private + */ +ol.source.TileImage.prototype.handleTileChange_ = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + switch (tile.getState()) { + case ol.TileState.LOADING: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADSTART, tile)); + break; + case ol.TileState.LOADED: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADEND, tile)); + break; + case ol.TileState.ERROR: + this.dispatchEvent( + new ol.source.TileEvent(ol.source.TileEventType.TILELOADERROR, tile)); + break; + } +}; + + /** * @param {ol.TileLoadFunctionType} tileLoadFunction Tile load function. * @api diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index 3457b4cb13..504910ae84 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -1,6 +1,7 @@ goog.provide('ol.source.Tile'); goog.provide('ol.source.TileOptions'); +goog.require('goog.events.Event'); goog.require('goog.functions'); goog.require('ol.Attribution'); goog.require('ol.Extent'); @@ -206,3 +207,59 @@ ol.source.Tile.prototype.getTilePixelSize = * @param {number} y Tile coordinate y. */ ol.source.Tile.prototype.useTile = goog.nullFunction; + + + +/** + * @classdesc + * Events emitted by {@link ol.source.Tile} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.TileEvent} + * @param {string} type Type. + * @param {ol.Tile} tile The tile. + */ +ol.source.TileEvent = function(type, tile) { + + goog.base(this, type); + + /** + * The tile related to the event. + * @type {ol.Tile} + * @api + */ + this.tile = tile; + +}; +goog.inherits(ol.source.TileEvent, goog.events.Event); + + +/** + * @enum {string} + */ +ol.source.TileEventType = { + + /** + * Triggered when a tile starts loading. + * @event ol.source.TileEvent#tileloadstart + * @api + */ + TILELOADSTART: 'tileloadstart', + + /** + * Triggered when a tile finishes loading. + * @event ol.source.TileEvent#tileloadend + * @api + */ + TILELOADEND: 'tileloadend', + + /** + * Triggered if tile loading results in an error. + * @event ol.source.TileEvent#tileloaderror + * @api + */ + TILELOADERROR: 'tileloaderror' + +}; diff --git a/src/ol/tilequeue.js b/src/ol/tilequeue.js index 668d39683c..68da9ed5fa 100644 --- a/src/ol/tilequeue.js +++ b/src/ol/tilequeue.js @@ -4,6 +4,7 @@ goog.provide('ol.TileQueue'); goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('ol.Coordinate'); +goog.require('ol.TileState'); goog.require('ol.structs.PriorityQueue'); @@ -67,11 +68,17 @@ ol.TileQueue.prototype.getTilesLoading = function() { /** + * @param {goog.events.Event} event Event. * @protected */ -ol.TileQueue.prototype.handleTileChange = function() { - --this.tilesLoading_; - this.tileChangeCallback_(); +ol.TileQueue.prototype.handleTileChange = function(event) { + var tile = /** @type {ol.Tile} */ (event.target); + var state = tile.getState(); + if (state === ol.TileState.LOADED || state === ol.TileState.ERROR || + state === ol.TileState.EMPTY) { + --this.tilesLoading_; + this.tileChangeCallback_(); + } }; @@ -85,7 +92,7 @@ ol.TileQueue.prototype.loadMoreTiles = function(maxTotalLoading, maxNewLoads) { var i, tile; for (i = 0; i < newLoads; ++i) { tile = /** @type {ol.Tile} */ (this.dequeue()[0]); - goog.events.listenOnce(tile, goog.events.EventType.CHANGE, + goog.events.listen(tile, goog.events.EventType.CHANGE, this.handleTileChange, false, this); tile.load(); }