diff --git a/examples/image-load-events.html b/examples/image-load-events.html new file mode 100644 index 0000000000..a979d70284 --- /dev/null +++ b/examples/image-load-events.html @@ -0,0 +1,79 @@ + + + + + + + + + + + Image load events example + + + + + + +
+ +
+
+
+
+
+
+ +
+ +
+

Image load events example

+

Example using image load events.

+
+

+ Image sources fire events related to image loading. You can + listen for imageloadstart, imageloadend, + and imageloaderror type events to monitor image loading + progress. This example registers listeners for these events and + renders an image loading progress bar at the bottom of the map. +

+

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

+
+
image, events, loading
+
+ +
+ +
+ + + + + + + diff --git a/examples/image-load-events.js b/examples/image-load-events.js new file mode 100644 index 0000000000..c82a5b9988 --- /dev/null +++ b/examples/image-load-events.js @@ -0,0 +1,105 @@ +goog.require('ol.Map'); +goog.require('ol.View'); +goog.require('ol.layer.Image'); +goog.require('ol.source.ImageWMS'); + + + +/** + * 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.ImageWMS({ + url: 'http://demo.boundlessgeo.com/geoserver/wms', + params: {'LAYERS': 'topp:states'}, + serverType: 'geoserver' +}); + +source.on('imageloadstart', function(event) { + progress.addLoading(); +}); + +source.on('imageloadend', function(event) { + progress.addLoaded(); +}); +source.on('imageloaderror', function(event) { + progress.addLoaded(); +}); + +var map = new ol.Map({ + logo: false, + layers: [ + new ol.layer.Image({source: source}) + ], + renderer: exampleNS.getRendererFromQueryString(), + target: 'map', + view: new ol.View({ + center: [-10997148, 4569099], + zoom: 4 + }) +}); diff --git a/externs/oli.js b/externs/oli.js index c339fb3914..ed861b409d 100644 --- a/externs/oli.js +++ b/externs/oli.js @@ -229,6 +229,18 @@ oli.render.Event.prototype.vectorContext; oli.source; +/** + * @interface + */ +oli.source.ImageEvent = function() {}; + + +/** + * @type {ol.Image} + */ +oli.source.ImageEvent.prototype.image; + + /** * @interface */ diff --git a/src/ol/image.js b/src/ol/image.js index f2492ceaa8..463a210e51 100644 --- a/src/ol/image.js +++ b/src/ol/image.js @@ -128,6 +128,7 @@ ol.Image.prototype.handleImageLoad_ = function() { ol.Image.prototype.load = function() { if (this.state == ol.ImageState.IDLE) { this.state = ol.ImageState.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/renderer/layerrenderer.js b/src/ol/renderer/layerrenderer.js index 53adca1c18..e6c3598146 100644 --- a/src/ol/renderer/layerrenderer.js +++ b/src/ol/renderer/layerrenderer.js @@ -151,7 +151,7 @@ ol.renderer.Layer.prototype.loadImage = function(image) { // listener (a noop if the listener was already registered) goog.asserts.assert(imageState == ol.ImageState.IDLE || imageState == ol.ImageState.LOADING); - goog.events.listenOnce(image, goog.events.EventType.CHANGE, + goog.events.listen(image, goog.events.EventType.CHANGE, this.handleImageChange_, false, this); } if (imageState == ol.ImageState.IDLE) { diff --git a/src/ol/source/imagemapguidesource.js b/src/ol/source/imagemapguidesource.js index de728275eb..6c5a4da926 100644 --- a/src/ol/source/imagemapguidesource.js +++ b/src/ol/source/imagemapguidesource.js @@ -1,5 +1,7 @@ goog.provide('ol.source.ImageMapGuide'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('goog.uri.utils'); goog.require('ol.Image'); @@ -15,6 +17,7 @@ goog.require('ol.source.Image'); * Source for images from Mapguide servers * * @constructor + * @fires ol.source.ImageEvent * @extends {ol.source.Image} * @param {olx.source.ImageMapGuideOptions} options Options. * @api stable @@ -150,6 +153,8 @@ ol.source.ImageMapGuide.prototype.getImage = image = new ol.Image(extent, resolution, pixelRatio, this.getAttributions(), imageUrl, this.crossOrigin_, this.imageLoadFunction_); + goog.events.listen(image, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); } else { image = null; } diff --git a/src/ol/source/imagesource.js b/src/ol/source/imagesource.js index 6cab2fe42d..d5e650e255 100644 --- a/src/ol/source/imagesource.js +++ b/src/ol/source/imagesource.js @@ -2,8 +2,10 @@ goog.provide('ol.source.Image'); goog.require('goog.array'); goog.require('goog.asserts'); +goog.require('goog.events.Event'); goog.require('ol.Attribution'); goog.require('ol.Extent'); +goog.require('ol.ImageState'); goog.require('ol.array'); goog.require('ol.source.Source'); @@ -90,6 +92,33 @@ ol.source.Image.prototype.findNearestResolution = ol.source.Image.prototype.getImage = goog.abstractMethod; +/** + * Handle image change events. + * @param {goog.events.Event} event Event. + * @protected + */ +ol.source.Image.prototype.handleImageChange = function(event) { + var image = /** @type {ol.Image} */ (event.target); + switch (image.getState()) { + case ol.ImageState.LOADING: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADSTART, + image)); + break; + case ol.ImageState.LOADED: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADEND, + image)); + break; + case ol.ImageState.ERROR: + this.dispatchEvent( + new ol.source.ImageEvent(ol.source.ImageEventType.IMAGELOADERROR, + image)); + break; + } +}; + + /** * Default image load function for image sources that use ol.Image image * instances. @@ -99,3 +128,59 @@ ol.source.Image.prototype.getImage = goog.abstractMethod; ol.source.Image.defaultImageLoadFunction = function(image, src) { image.getImage().src = src; }; + + + +/** + * @classdesc + * Events emitted by {@link ol.source.Image} instances are instances of this + * type. + * + * @constructor + * @extends {goog.events.Event} + * @implements {oli.source.ImageEvent} + * @param {string} type Type. + * @param {ol.Image} image The image. + */ +ol.source.ImageEvent = function(type, image) { + + goog.base(this, type); + + /** + * The image related to the event. + * @type {ol.Image} + * @api + */ + this.image = image; + +}; +goog.inherits(ol.source.ImageEvent, goog.events.Event); + + +/** + * @enum {string} + */ +ol.source.ImageEventType = { + + /** + * Triggered when an image starts loading. + * @event ol.source.ImageEvent#imageloadstart + * @api + */ + IMAGELOADSTART: 'imageloadstart', + + /** + * Triggered when an image finishes loading. + * @event ol.source.ImageEvent#imageloadend + * @api + */ + IMAGELOADEND: 'imageloadend', + + /** + * Triggered if image loading results in an error. + * @event ol.source.ImageEvent#imageloaderror + * @api + */ + IMAGELOADERROR: 'imageloaderror' + +}; diff --git a/src/ol/source/imagewmssource.js b/src/ol/source/imagewmssource.js index b058b82557..9f1f544e2a 100644 --- a/src/ol/source/imagewmssource.js +++ b/src/ol/source/imagewmssource.js @@ -3,6 +3,8 @@ goog.provide('ol.source.ImageWMS'); goog.require('goog.asserts'); +goog.require('goog.events'); +goog.require('goog.events.EventType'); goog.require('goog.object'); goog.require('goog.string'); goog.require('goog.uri.utils'); @@ -22,6 +24,7 @@ goog.require('ol.source.wms.ServerType'); * Source for WMS servers providing single, untiled images. * * @constructor + * @fires ol.source.ImageEvent * @extends {ol.source.Image} * @param {olx.source.ImageWMSOptions=} opt_options Options. * @api stable @@ -247,6 +250,9 @@ ol.source.ImageWMS.prototype.getImage = this.renderedRevision_ = this.getRevision(); + goog.events.listen(this.image_, goog.events.EventType.CHANGE, + this.handleImageChange, false, this); + return this.image_; };