From 97d83666a6075ddfcae1e5506081efa0a4f96888 Mon Sep 17 00:00:00 2001 From: Tom Payne Date: Sat, 5 Jan 2013 16:26:58 +0100 Subject: [PATCH] Factor out ImageTile and ImageTileSource --- src/ol/imagetile.js | 137 ++++++++++++++++++ src/ol/renderer/webgl/webglmaprenderer.js | 23 +-- .../renderer/webgl/webgltilelayerrenderer.js | 26 ++-- src/ol/source/bingmapssource.js | 6 +- src/ol/source/imagetilesource.js | 95 ++++++++++++ src/ol/source/tiledwmssource.js | 6 +- src/ol/source/tilejsonsource.js | 6 +- src/ol/source/tilesource.js | 53 +------ src/ol/source/xyzsource.js | 6 +- src/ol/tile.js | 116 ++------------- 10 files changed, 284 insertions(+), 190 deletions(-) create mode 100644 src/ol/imagetile.js create mode 100644 src/ol/source/imagetilesource.js diff --git a/src/ol/imagetile.js b/src/ol/imagetile.js new file mode 100644 index 0000000000..c75c11ea23 --- /dev/null +++ b/src/ol/imagetile.js @@ -0,0 +1,137 @@ +goog.provide('ol.ImageTile'); + +goog.require('goog.array'); +goog.require('goog.events'); +goog.require('goog.events.EventTarget'); +goog.require('goog.events.EventType'); +goog.require('ol.Tile'); +goog.require('ol.TileCoord'); +goog.require('ol.TileState'); + + + +/** + * @constructor + * @extends {ol.Tile} + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @param {string} src Image source URI. + * @param {?string} crossOrigin Cross origin. + */ +ol.ImageTile = function(tileCoord, src, crossOrigin) { + + goog.base(this, tileCoord); + + /** + * Image URI + * + * @private + * @type {string} + */ + this.src_ = src; + + /** + * @private + * @type {Image} + */ + this.image_ = new Image(); + if (!goog.isNull(crossOrigin)) { + this.image_.crossOrigin = crossOrigin; + } + + /** + * @private + * @type {Object.} + */ + this.imageByContext_ = {}; + + /** + * @private + * @type {Array.} + */ + this.imageListenerKeys_ = null; + +}; +goog.inherits(ol.ImageTile, ol.Tile); + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getImage = function(opt_context) { + if (goog.isDef(opt_context)) { + var image; + var key = goog.getUid(opt_context); + if (key in this.imageByContext_) { + return this.imageByContext_[key]; + } else if (goog.object.isEmpty(this.imageByContext_)) { + image = this.image_; + } else { + image = /** @type {Image} */ (this.image_.cloneNode(false)); + } + this.imageByContext_[key] = image; + return image; + } else { + return this.image_; + } +}; + + +/** + * @inheritDoc + */ +ol.ImageTile.prototype.getKey = function() { + return this.src_; +}; + + +/** + * Tracks loading or read errors. + * + * @private + */ +ol.ImageTile.prototype.handleImageError_ = function() { + this.state = ol.TileState.ERROR; + this.unlistenImage_(); +}; + + +/** + * Tracks successful image load. + * + * @private + */ +ol.ImageTile.prototype.handleImageLoad_ = function() { + this.state = ol.TileState.LOADED; + this.unlistenImage_(); + this.dispatchChangeEvent(); +}; + + +/** + * Load not yet loaded URI. + */ +ol.ImageTile.prototype.load = function() { + if (this.state == ol.TileState.IDLE) { + this.state = ol.TileState.LOADING; + goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); + this.imageListenerKeys_ = [ + goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, + this.handleImageError_, false, this), + goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, + this.handleImageLoad_, false, this) + ]; + this.image_.src = this.src_; + } +}; + + +/** + * Discards event handlers which listen for load completion or errors. + * + * @private + */ +ol.ImageTile.prototype.unlistenImage_ = function() { + goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); + goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); + this.imageListenerKeys_ = null; +}; diff --git a/src/ol/renderer/webgl/webglmaprenderer.js b/src/ol/renderer/webgl/webglmaprenderer.js index 72c98aa8e3..4bfea15f74 100644 --- a/src/ol/renderer/webgl/webglmaprenderer.js +++ b/src/ol/renderer/webgl/webglmaprenderer.js @@ -17,6 +17,7 @@ goog.require('goog.events.EventType'); goog.require('goog.functions'); goog.require('goog.style'); goog.require('goog.webgl'); +goog.require('ol.Tile'); goog.require('ol.layer.Layer'); goog.require('ol.layer.TileLayer'); goog.require('ol.renderer.webgl.FragmentShader'); @@ -219,15 +220,15 @@ ol.renderer.webgl.Map.prototype.addLayer = function(layer) { /** - * @param {Image} image Image. + * @param {ol.Tile} tile Tile. * @param {number} magFilter Mag filter. * @param {number} minFilter Min filter. */ -ol.renderer.webgl.Map.prototype.bindImageTexture = - function(image, magFilter, minFilter) { +ol.renderer.webgl.Map.prototype.bindTileTexture = + function(tile, magFilter, minFilter) { var gl = this.getGL(); - var imageKey = image.src; - var textureCacheEntry = this.textureCache_[imageKey]; + var tileKey = tile.getKey(); + var textureCacheEntry = this.textureCache_[tileKey]; if (goog.isDef(textureCacheEntry)) { gl.bindTexture(goog.webgl.TEXTURE_2D, textureCacheEntry.texture); if (textureCacheEntry.magFilter != magFilter) { @@ -244,7 +245,7 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = var texture = gl.createTexture(); gl.bindTexture(goog.webgl.TEXTURE_2D, texture); gl.texImage2D(goog.webgl.TEXTURE_2D, 0, goog.webgl.RGBA, goog.webgl.RGBA, - goog.webgl.UNSIGNED_BYTE, image); + goog.webgl.UNSIGNED_BYTE, tile.getImage()); gl.texParameteri( goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_MAG_FILTER, magFilter); gl.texParameteri( @@ -253,7 +254,7 @@ ol.renderer.webgl.Map.prototype.bindImageTexture = goog.webgl.CLAMP_TO_EDGE); gl.texParameteri(goog.webgl.TEXTURE_2D, goog.webgl.TEXTURE_WRAP_T, goog.webgl.CLAMP_TO_EDGE); - this.textureCache_[imageKey] = { + this.textureCache_[tileKey] = { texture: texture, magFilter: magFilter, minFilter: minFilter @@ -485,11 +486,11 @@ ol.renderer.webgl.Map.prototype.initializeGL_ = function() { /** - * @param {Image} image Image. - * @return {boolean} Is image texture loaded. + * @param {ol.Tile} tile Tile. + * @return {boolean} Is tile texture loaded. */ -ol.renderer.webgl.Map.prototype.isImageTextureLoaded = function(image) { - return image.src in this.textureCache_; +ol.renderer.webgl.Map.prototype.isTileTextureLoaded = function(tile) { + return tile.getKey() in this.textureCache_; }; diff --git a/src/ol/renderer/webgl/webgltilelayerrenderer.js b/src/ol/renderer/webgl/webgltilelayerrenderer.js index b43cf8710f..2152b6a824 100644 --- a/src/ol/renderer/webgl/webgltilelayerrenderer.js +++ b/src/ol/renderer/webgl/webgltilelayerrenderer.js @@ -386,9 +386,9 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { var tilesToDrawByZ = {}; /** - * @type {Array.} + * @type {Array.} */ - var imagesToLoad = []; + var tilesToLoad = []; var allTilesLoaded = true; @@ -405,12 +405,11 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { if (tileState == ol.TileState.IDLE) { tile.load(); } else if (tileState == ol.TileState.LOADED) { - var image = tile.getImage(); - if (mapRenderer.isImageTextureLoaded(image)) { + if (mapRenderer.isTileTextureLoaded(tile)) { tilesToDrawByZ[z][tileCoord.toString()] = tile; return; } else { - imagesToLoad.push(image); + tilesToLoad.push(tile); } } else if (tileState == ol.TileState.ERROR) { return; @@ -459,29 +458,28 @@ ol.renderer.webgl.TileLayer.prototype.renderFrame = function(time) { framebufferExtentSize.height - 1; goog.vec.Vec4.setFromValues(uTileOffset, sx, sy, tx, ty); gl.uniform4fv(this.locations_.uTileOffset, uTileOffset); - mapRenderer.bindImageTexture( - tile.getImage(), goog.webgl.LINEAR, goog.webgl.LINEAR); + mapRenderer.bindTileTexture(tile, goog.webgl.LINEAR, goog.webgl.LINEAR); gl.drawArrays(goog.webgl.TRIANGLE_STRIP, 0, 4); }, this); }, this); - if (!goog.array.isEmpty(imagesToLoad)) { + if (!goog.array.isEmpty(tilesToLoad)) { goog.events.listenOnce( map, ol.MapEventType.POSTRENDER, - goog.partial(function(mapRenderer, imagesToLoad) { + goog.partial(function(mapRenderer, tilesToLoad) { if (goog.DEBUG) { this.logger.info( - 'uploading ' + imagesToLoad.length + ' textures'); + 'uploading ' + tilesToLoad.length + ' textures'); } - goog.array.forEach(imagesToLoad, function(image) { - mapRenderer.bindImageTexture( - image, goog.webgl.LINEAR, goog.webgl.LINEAR); + goog.array.forEach(tilesToLoad, function(tile) { + mapRenderer.bindTileTexture( + tile, goog.webgl.LINEAR, goog.webgl.LINEAR); }); if (goog.DEBUG) { this.logger.info('uploaded textures'); } - }, mapRenderer, imagesToLoad)); + }, mapRenderer, tilesToLoad)); } if (allTilesLoaded) { diff --git a/src/ol/source/bingmapssource.js b/src/ol/source/bingmapssource.js index 1b805faf93..7693cb81d9 100644 --- a/src/ol/source/bingmapssource.js +++ b/src/ol/source/bingmapssource.js @@ -6,7 +6,7 @@ goog.require('goog.events'); goog.require('goog.events.EventType'); goog.require('goog.net.Jsonp'); goog.require('ol.TileCoverageArea'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -25,7 +25,7 @@ ol.BingMapsStyle = { /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.BingMapsOptions} bingMapsOptions Bing Maps options. */ ol.source.BingMaps = function(bingMapsOptions) { @@ -57,7 +57,7 @@ ol.source.BingMaps = function(bingMapsOptions) { }, goog.bind(this.handleImageryMetadataResponse, this)); }; -goog.inherits(ol.source.BingMaps, ol.source.TileSource); +goog.inherits(ol.source.BingMaps, ol.source.ImageTileSource); /** diff --git a/src/ol/source/imagetilesource.js b/src/ol/source/imagetilesource.js new file mode 100644 index 0000000000..5baea4539f --- /dev/null +++ b/src/ol/source/imagetilesource.js @@ -0,0 +1,95 @@ +goog.provide('ol.source.ImageTileSource'); +goog.provide('ol.source.ImageTileSourceOptions'); + +goog.require('ol.Attribution'); +goog.require('ol.Extent'); +goog.require('ol.ImageTile'); +goog.require('ol.Projection'); +goog.require('ol.TileCoord'); +goog.require('ol.TileUrlFunction'); +goog.require('ol.TileUrlFunctionType'); +goog.require('ol.source.TileSource'); +goog.require('ol.tilegrid.TileGrid'); + + +/** + * @typedef {{attributions: (Array.|undefined), + * crossOrigin: (null|string|undefined), + * extent: (ol.Extent|undefined), + * projection: (ol.Projection|undefined), + * tileGrid: (ol.tilegrid.TileGrid|undefined), + * tileUrlFunction: (ol.TileUrlFunctionType|undefined)}} + */ +ol.source.ImageTileSourceOptions; + + + +/** + * @constructor + * @extends {ol.source.TileSource} + * @param {ol.source.ImageTileSourceOptions} options Options. + */ +ol.source.ImageTileSource = function(options) { + + goog.base(this, { + attributions: options.attributions, + extent: options.extent, + projection: options.projection, + tileGrid: options.tileGrid + }); + + /** + * @protected + * @type {ol.TileUrlFunctionType} + */ + this.tileUrlFunction = goog.isDef(options.tileUrlFunction) ? + options.tileUrlFunction : + ol.TileUrlFunction.nullTileUrlFunction; + + /** + * @private + * @type {?string} + */ + this.crossOrigin_ = + goog.isDef(options.crossOrigin) ? options.crossOrigin : 'anonymous'; + + /** + * @private + * @type {Object.} + * FIXME will need to expire elements from this cache + * FIXME see elemoine's work with goog.structs.LinkedMap + */ + this.tileCache_ = {}; + +}; +goog.inherits(ol.source.ImageTileSource, ol.source.TileSource); + + +/** + * @inheritDoc + */ +ol.source.ImageTileSource.prototype.getTile = function(tileCoord) { + var key = tileCoord.toString(); + if (goog.object.containsKey(this.tileCache_, key)) { + return this.tileCache_[key]; + } else { + var tileUrl = this.getTileCoordUrl(tileCoord); + var tile; + if (goog.isDef(tileUrl)) { + tile = new ol.ImageTile(tileCoord, tileUrl, this.crossOrigin_); + } else { + tile = null; + } + this.tileCache_[key] = tile; + return tile; + } +}; + + +/** + * @param {ol.TileCoord} tileCoord Tile coordinate. + * @return {string|undefined} Tile URL. + */ +ol.source.ImageTileSource.prototype.getTileCoordUrl = function(tileCoord) { + return this.tileUrlFunction(tileCoord); +}; diff --git a/src/ol/source/tiledwmssource.js b/src/ol/source/tiledwmssource.js index b3bddea36a..e6d61e520d 100644 --- a/src/ol/source/tiledwmssource.js +++ b/src/ol/source/tiledwmssource.js @@ -10,14 +10,14 @@ goog.require('ol.Attribution'); goog.require('ol.Projection'); goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.TileGrid'); /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.TiledWMSOptions} tiledWMSOptions options. */ ol.source.TiledWMS = function(tiledWMSOptions) { @@ -116,4 +116,4 @@ ol.source.TiledWMS = function(tiledWMSOptions) { }); }; -goog.inherits(ol.source.TiledWMS, ol.source.TileSource); +goog.inherits(ol.source.TiledWMS, ol.source.ImageTileSource); diff --git a/src/ol/source/tilejsonsource.js b/src/ol/source/tilejsonsource.js index 35c9124468..81eacaf7a4 100644 --- a/src/ol/source/tilejsonsource.js +++ b/src/ol/source/tilejsonsource.js @@ -16,7 +16,7 @@ goog.require('goog.string'); goog.require('ol.Projection'); goog.require('ol.TileCoverageArea'); goog.require('ol.TileUrlFunction'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -45,7 +45,7 @@ goog.exportSymbol('grid', grid); /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.TileJSONOptions} tileJsonOptions TileJSON optios. */ ol.source.TileJSON = function(tileJsonOptions) { @@ -69,7 +69,7 @@ ol.source.TileJSON = function(tileJsonOptions) { this.deferred_.addCallback(this.handleTileJSONResponse, this); }; -goog.inherits(ol.source.TileJSON, ol.source.TileSource); +goog.inherits(ol.source.TileJSON, ol.source.ImageTileSource); /** diff --git a/src/ol/source/tilesource.js b/src/ol/source/tilesource.js index 9b915efe16..6b82b8f3b8 100644 --- a/src/ol/source/tilesource.js +++ b/src/ol/source/tilesource.js @@ -14,11 +14,9 @@ goog.require('ol.tilegrid.TileGrid'); /** * @typedef {{attributions: (Array.|undefined), - * crossOrigin: (null|string|undefined), * extent: (ol.Extent|undefined), * projection: (ol.Projection|undefined), - * tileGrid: (ol.tilegrid.TileGrid|undefined), - * tileUrlFunction: (ol.TileUrlFunctionType|undefined)}} + * tileGrid: (ol.tilegrid.TileGrid|undefined)}} */ ol.source.TileSourceOptions; @@ -44,29 +42,6 @@ ol.source.TileSource = function(tileSourceOptions) { this.tileGrid = goog.isDef(tileSourceOptions.tileGrid) ? tileSourceOptions.tileGrid : null; - /** - * @protected - * @type {ol.TileUrlFunctionType} - */ - this.tileUrlFunction = goog.isDef(tileSourceOptions.tileUrlFunction) ? - tileSourceOptions.tileUrlFunction : - ol.TileUrlFunction.nullTileUrlFunction; - - /** - * @private - * @type {?string} - */ - this.crossOrigin_ = goog.isDef(tileSourceOptions.crossOrigin) ? - tileSourceOptions.crossOrigin : 'anonymous'; - - /** - * @private - * @type {Object.} - * FIXME will need to expire elements from this cache - * FIXME see elemoine's work with goog.structs.LinkedMap - */ - this.tileCache_ = {}; - }; goog.inherits(ol.source.TileSource, ol.source.Source); @@ -83,31 +58,7 @@ ol.source.TileSource.prototype.getResolutions = function() { * @param {ol.TileCoord} tileCoord Tile coordinate. * @return {ol.Tile} Tile. */ -ol.source.TileSource.prototype.getTile = function(tileCoord) { - var key = tileCoord.toString(); - if (goog.object.containsKey(this.tileCache_, key)) { - return this.tileCache_[key]; - } else { - var tileUrl = this.getTileCoordUrl(tileCoord); - var tile; - if (goog.isDef(tileUrl)) { - tile = new ol.Tile(tileCoord, tileUrl, this.crossOrigin_); - } else { - tile = null; - } - this.tileCache_[key] = tile; - return tile; - } -}; - - -/** - * @param {ol.TileCoord} tileCoord Tile coordinate. - * @return {string|undefined} Tile URL. - */ -ol.source.TileSource.prototype.getTileCoordUrl = function(tileCoord) { - return this.tileUrlFunction(tileCoord); -}; +ol.source.TileSource.prototype.getTile = goog.abstractMethod; /** diff --git a/src/ol/source/xyzsource.js b/src/ol/source/xyzsource.js index f1ed03cc3b..0758d99f89 100644 --- a/src/ol/source/xyzsource.js +++ b/src/ol/source/xyzsource.js @@ -11,7 +11,7 @@ goog.require('ol.Size'); goog.require('ol.TileCoord'); goog.require('ol.TileUrlFunction'); goog.require('ol.TileUrlFunctionType'); -goog.require('ol.source.TileSource'); +goog.require('ol.source.ImageTileSource'); goog.require('ol.tilegrid.XYZ'); @@ -31,7 +31,7 @@ ol.source.XYZOptions; /** * @constructor - * @extends {ol.source.TileSource} + * @extends {ol.source.ImageTileSource} * @param {ol.source.XYZOptions} xyzOptions XYZ options. */ ol.source.XYZ = function(xyzOptions) { @@ -110,4 +110,4 @@ ol.source.XYZ = function(xyzOptions) { }); }; -goog.inherits(ol.source.XYZ, ol.source.TileSource); +goog.inherits(ol.source.XYZ, ol.source.ImageTileSource); diff --git a/src/ol/tile.js b/src/ol/tile.js index c390db3bd5..364374c9e6 100644 --- a/src/ol/tile.js +++ b/src/ol/tile.js @@ -24,10 +24,8 @@ ol.TileState = { * @constructor * @extends {goog.events.EventTarget} * @param {ol.TileCoord} tileCoord Tile coordinate. - * @param {string} src Image source URI. - * @param {?string} crossOrigin Cross origin. */ -ol.Tile = function(tileCoord, src, crossOrigin) { +ol.Tile = function(tileCoord) { goog.base(this); @@ -37,39 +35,10 @@ ol.Tile = function(tileCoord, src, crossOrigin) { this.tileCoord = tileCoord; /** - * Image URI - * - * @private - * @type {string} - */ - this.src_ = src; - - /** - * @private + * @protected * @type {ol.TileState} */ - this.state_ = ol.TileState.IDLE; - - /** - * @private - * @type {Image} - */ - this.image_ = new Image(); - if (!goog.isNull(crossOrigin)) { - this.image_.crossOrigin = crossOrigin; - } - - /** - * @private - * @type {Object.} - */ - this.imageByContext_ = {}; - - /** - * @private - * @type {Array.} - */ - this.imageListenerKeys_ = null; + this.state = ol.TileState.IDLE; }; goog.inherits(ol.Tile, goog.events.EventTarget); @@ -85,24 +54,16 @@ ol.Tile.prototype.dispatchChangeEvent = function() { /** * @param {Object=} opt_context Object. - * @return {Image} Image. + * @return {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement} Image. */ -ol.Tile.prototype.getImage = function(opt_context) { - if (goog.isDef(opt_context)) { - var image; - var key = goog.getUid(opt_context); - if (key in this.imageByContext_) { - return this.imageByContext_[key]; - } else if (goog.object.isEmpty(this.imageByContext_)) { - image = this.image_; - } else { - image = /** @type {Image} */ (this.image_.cloneNode(false)); - } - this.imageByContext_[key] = image; - return image; - } else { - return this.image_; - } +ol.Tile.prototype.getImage = goog.abstractMethod; + + +/** + * @return {string} Key. + */ +ol.Tile.prototype.getKey = function() { + return goog.getUid(this).toString(); }; @@ -110,59 +71,10 @@ ol.Tile.prototype.getImage = function(opt_context) { * @return {ol.TileState} State. */ ol.Tile.prototype.getState = function() { - return this.state_; + return this.state; }; /** - * Tracks loading or read errors. - * - * @private */ -ol.Tile.prototype.handleImageError_ = function() { - this.state_ = ol.TileState.ERROR; - this.unlistenImage_(); -}; - - -/** - * Tracks successful image load. - * - * @private - */ -ol.Tile.prototype.handleImageLoad_ = function() { - this.state_ = ol.TileState.LOADED; - this.unlistenImage_(); - this.dispatchChangeEvent(); -}; - - -/** - * Load not yet loaded URI. - */ -ol.Tile.prototype.load = function() { - if (this.state_ == ol.TileState.IDLE) { - this.state_ = ol.TileState.LOADING; - goog.asserts.assert(goog.isNull(this.imageListenerKeys_)); - this.imageListenerKeys_ = [ - goog.events.listenOnce(this.image_, goog.events.EventType.ERROR, - this.handleImageError_, false, this), - goog.events.listenOnce(this.image_, goog.events.EventType.LOAD, - this.handleImageLoad_, false, this) - ]; - this.image_.src = this.src_; - } -}; - - -/** - * Discards event handlers which listen for load completion or errors. - * - * @private - */ -ol.Tile.prototype.unlistenImage_ = function() { - goog.asserts.assert(!goog.isNull(this.imageListenerKeys_)); - goog.array.forEach(this.imageListenerKeys_, goog.events.unlistenByKey); - this.imageListenerKeys_ = null; -}; - +ol.Tile.prototype.load = goog.abstractMethod;