From 23c909220159134d270064bc4c89440c59c22d20 Mon Sep 17 00:00:00 2001 From: ahocevar Date: Sat, 13 Aug 2011 00:59:01 +0000 Subject: [PATCH] Tile.Image improvements and partial rewrite. Thanks erilem for the excellent collaboration during the review phase. p=me,erilem r=erilem (closes #3419) git-svn-id: http://svn.openlayers.org/trunk/openlayers@12241 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf --- examples/wms-long-url.js | 4 +- lib/OpenLayers.js | 1 + lib/OpenLayers/Layer/Bing.js | 2 +- lib/OpenLayers/Tile.js | 80 +--- lib/OpenLayers/Tile/BackBufferable.js | 201 ++++++++ lib/OpenLayers/Tile/Image.js | 644 ++++++++------------------ lib/OpenLayers/Tile/Image/IFrame.js | 264 +++++------ tests/Layer/WMS/Post.html | 5 +- tests/Tile.html | 26 -- tests/Tile/BackBufferable.html | 114 +++++ tests/Tile/Image.html | 65 ++- tests/Tile/Image/IFrame.html | 90 ++-- tests/list-tests.html | 1 + tests/run-tests.html | 2 +- 14 files changed, 722 insertions(+), 777 deletions(-) create mode 100644 lib/OpenLayers/Tile/BackBufferable.js create mode 100644 tests/Tile/BackBufferable.html diff --git a/examples/wms-long-url.js b/examples/wms-long-url.js index ecbda39312..ef9517775e 100644 --- a/examples/wms-long-url.js +++ b/examples/wms-long-url.js @@ -5,12 +5,12 @@ var map = new OpenLayers.Map( 'map' ); var base = new OpenLayers.Layer.WMS( "OpenLayers WMS", "http://vmap0.tiles.osgeo.org/wms/vmap0", {layers: 'basic', makeTheUrlLong: longText}, - {tileOptions: {maxGetUrlLength: 2048}} + {tileOptions: {maxGetUrlLength: 2048}, transitionEffect: 'resize'} ); var overlay = new OpenLayers.Layer.WMS("Overlay", "http://suite.opengeo.org/geoserver/wms", {layers: "usa:states", transparent: true, makeTheUrlLong: longText}, - {ratio: 1, singleTile: true, tileOptions: {maxGetUrlLength: 2048}} + {ratio: 1, singleTile: true, tileOptions: {maxGetUrlLength: 2048}, transitionEffect: 'resize'} ); map.addLayers([base, overlay]); map.zoomToMaxExtent(); diff --git a/lib/OpenLayers.js b/lib/OpenLayers.js index 0d7bb3aca7..0b4e37db20 100644 --- a/lib/OpenLayers.js +++ b/lib/OpenLayers.js @@ -121,6 +121,7 @@ "OpenLayers/Marker/Box.js", "OpenLayers/Popup.js", "OpenLayers/Tile.js", + "OpenLayers/Tile/BackBufferable.js", "OpenLayers/Tile/Google.js", "OpenLayers/Tile/Image.js", "OpenLayers/Tile/Image/IFrame.js", diff --git a/lib/OpenLayers/Layer/Bing.js b/lib/OpenLayers/Layer/Bing.js index fa330bba4c..625eddeae6 100644 --- a/lib/OpenLayers/Layer/Bing.js +++ b/lib/OpenLayers/Layer/Bing.js @@ -159,7 +159,7 @@ OpenLayers.Layer.Bing = OpenLayers.Class(OpenLayers.Layer.XYZ, { */ getURL: function(bounds) { if (!this.url) { - return OpenLayers.Util.getImagesLocation() + "blank.gif"; + return; } var xyz = this.getXYZ(bounds), x = xyz.x, y = xyz.y, z = xyz.z; var quadDigits = []; diff --git a/lib/OpenLayers/Tile.js b/lib/OpenLayers/Tile.js index 5d114e99ef..888d4f753e 100644 --- a/lib/OpenLayers/Tile.js +++ b/lib/OpenLayers/Tile.js @@ -77,7 +77,7 @@ OpenLayers.Tile = OpenLayers.Class({ * {} Top Left pixel of the tile */ position: null, - + /** * Property: isLoading * {Boolean} Is the tile loading? @@ -144,53 +144,37 @@ OpenLayers.Tile = OpenLayers.Class({ }, /** - * Method: clone - * - * Parameters: - * obj - {} The tile to be cloned - * + * Method: draw + * Clear whatever is currently in the tile, then return whether or not + * it should actually be re-drawn. This is an example implementation + * that can be overridden by subclasses. The minimum thing to do here + * is to call and return the result from . + * * Returns: - * {} An exact clone of this + * {Boolean} Whether or not the tile should actually be drawn. */ - clone: function (obj) { - if (obj == null) { - obj = new OpenLayers.Tile(this.layer, - this.position, - this.bounds, - this.url, - this.size); - } - - // catch any randomly tagged-on properties - OpenLayers.Util.applyDefaults(obj, this); + draw: function() { + //clear tile's contents and mark as not drawn + this.clear(); - return obj; + return this.shouldDraw(); }, - + /** - * Method: draw - * Clear whatever is currently in the tile, then return whether or not - * it should actually be re-drawn. + * Method: shouldDraw + * Return whether or not the tile should actually be (re-)drawn. The only + * case where we *wouldn't* want to draw the tile is if the tile is outside + * its layer's maxExtent * * Returns: - * {Boolean} Whether or not the tile should actually be drawn. Note that - * this is not really the best way of doing things, but such is - * the way the code has been developed. Subclasses call this and - * depend on the return to know if they should draw or not. + * {Boolean} Whether or not the tile should actually be drawn. */ - draw: function() { + shouldDraw: function() { var maxExtent = this.layer.maxExtent; var withinMaxExtent = (maxExtent && this.bounds.intersectsBounds(maxExtent, false)); - - // The only case where we *wouldn't* want to draw the tile is if the - // tile is outside its layer's maxExtent. - this.shouldDraw = (withinMaxExtent || this.layer.displayOutsideMaxExtent); - - //clear tile's contents and mark as not drawn - this.clear(); - return this.shouldDraw; + return withinMaxExtent || this.layer.displayOutsideMaxExtent; }, /** @@ -220,7 +204,7 @@ OpenLayers.Tile = OpenLayers.Class({ * Clear the tile of any bounds/position-related data so that it can * be reused in a new location. To be implemented by subclasses. */ - clear: function() { + clear: function(draw) { // to be implemented by subclasses }, @@ -260,29 +244,7 @@ OpenLayers.Tile = OpenLayers.Class({ bottomRight.lon, topLeft.lat); return bounds; - }, - - /** - * Method: showTile - * Show the tile only if it should be drawn. - */ - showTile: function() { - if (this.shouldDraw) { - this.show(); - } }, - /** - * Method: show - * Show the tile. To be implemented by subclasses. - */ - show: function() { }, - - /** - * Method: hide - * Hide the tile. To be implemented by subclasses. - */ - hide: function() { }, - CLASS_NAME: "OpenLayers.Tile" }); diff --git a/lib/OpenLayers/Tile/BackBufferable.js b/lib/OpenLayers/Tile/BackBufferable.js new file mode 100644 index 0000000000..aa75459ab0 --- /dev/null +++ b/lib/OpenLayers/Tile/BackBufferable.js @@ -0,0 +1,201 @@ +/* Copyright (c) 2006-2011 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the Clear BSD license. + * See http://svn.openlayers.org/trunk/openlayers/license.txt for the + * full text of the license. */ + + +/* + * @requires OpenLayers/Tile.js + * @requires OpenLayers/Util.js + */ + +/* + * Class: OpenLayers.Tile.BackBufferable + * Base class for tiles that can have backbuffers during transitions. Do not + * create instances of this class. + */ +OpenLayers.Tile.BackBufferable = OpenLayers.Class(OpenLayers.Tile, { + + /** + * Property: backBufferMode + * {Integer} Bitmap: 0 for no backbuffering at all, 1 for singleTile + * layers, 2 for transition effect set, 3 for both. + */ + backBufferMode: null, + + /** + * Property: backBufferData + * {Object} Object including the necessary data for the back + * buffer. + * + * The object includes three properties: + * tile - {DOMElement} The DOM element for the back buffer. + * bounds - {} The bounds of the tile to back. + * resolution - {Number} The resolution of the tile to back. + */ + backBufferData: null, + + /** + * Method: initialize + * Determines the backBuffer mode and registers events + */ + initialize: function() { + OpenLayers.Tile.prototype.initialize.apply(this, arguments); + + var transitionSupported = OpenLayers.Util.indexOf( + this.layer.SUPPORTED_TRANSITIONS, + this.layer.transitionEffect) != -1; + this.backBufferMode = (this.layer.singleTile && 1) | + (transitionSupported && 2); + + this.backBufferData = {}; + if (!this.size) { + this.size = new OpenLayers.Size(256, 256); + } + }, + + /** + * Method: draw + * Check that a tile should be drawn, and draw it. + * + * Returns: + * {Boolean} Was a tile drawn? + */ + draw: function() { + var draw = OpenLayers.Tile.prototype.shouldDraw.apply(this, arguments), + backBufferMode = this.backBufferMode; + if (draw) { + this.updateBackBuffer(); + } + this.clear(); + if (!draw) { + this.resetBackBuffer(); + }; + return draw; + }, + + /** + * Method: getTile + * Get the tile's markup. To be implemented by subclasses. + * + * Returns: + * {DOMElement} The tile's markup + */ + + /** + * Method: createBackBuffer + * Create a copy of this tile's markup for the back buffer. To be + * implemented by subclasses. + * + * Returns: + * {DOMElement} A copy of the tile's markup. + */ + + /** + * Method: setBackBufferData + * Stores the current bounds and resolution, for offset and ratio + * calculations + */ + setBackBufferData: function() { + this.backBufferData = OpenLayers.Util.extend(this.backBufferData, { + bounds: this.bounds, + resolution: this.layer.map.getResolution() + }); + }, + + /** + * Method: updateBackBuffer + * Update the , and return a new or reposition the + * backBuffer. When a backbuffer is returned, the tile's markup is not + * available any more. + * + * Returns: + * {HTMLDivElement} the tile's markup in a cloned element, or undefined if + * no backbuffer is currently available or needed + */ + updateBackBuffer: function() { + var layer = this.layer, map = layer.map, + backBufferMode = this.backBufferMode, + data = this.backBufferData, + tile = this.getTile(), + backBuffer = data.tile, + resolution = data.resolution, + ratio = resolution ? resolution / map.getResolution() : 1, + + // Cases where we don't position and return a back buffer, but only + // update backBufferData and return undefined: + // (1) current ratio and backBufferMode dont't require a backbuffer + notNeeded = !(ratio == 1 ? backBufferMode & 1 : backBufferMode & 2), + // (2) the tile is not appended to the layer's div + noParent = tile && tile.parentNode !== layer.div, + // (3) we don't have a tile available that we could use as buffer + noTile = !(tile && tile.childNodes.length > 0), + // (4) no backbuffer is displayed for a tile that's still loading + noBackBuffer = data.tile && !this.isLoading; + if (notNeeded || noParent || noTile || noBackBuffer) { + this.setBackBufferData(); + return; + } + + // Create a back buffer tile and add it to the DOM + if (!backBuffer) { + backBuffer = this.createBackBuffer(); + // some browsers fire the onload event before the image is + // displayed, so we keep the buffer until the whole layer finished + // loading to avoid visual glitches + layer.events.register("loadend", this, this.resetBackBuffer); + data.tile = backBuffer; + layer.div.insertBefore(backBuffer, tile); + } + + // Position the back buffer now that we have one + var lonLat = {lon: data.bounds.left, lat: data.bounds.top}, + position = map.getPixelFromLonLat(lonLat), + containerStyle = map.layerContainerDiv.style, + leftOffset = parseInt(containerStyle.left, 10), + topOffset = parseInt(containerStyle.top, 10), + style = backBuffer.style; + style.left = (position.x - leftOffset) + "px"; + style.top = (position.y - topOffset) + "px"; + style.width = (this.size.w * ratio) + "px"; + style.height = (this.size.h * ratio) + "px"; + + return backBuffer; + }, + + /** + * Method: resetBackBuffer + * Handler for the layer's loadend event. + */ + resetBackBuffer: function() { + this.layer.events.unregister("loadend", this, this.resetBackBuffer); + this.removeBackBuffer(); + this.setBackBufferData(); + }, + + /** + * Method: removeBackBuffer + * Removes the backBuffer for this tile. + */ + removeBackBuffer: function() { + var backBufferData = this.backBufferData; + var backBuffer = backBufferData.tile; + delete backBufferData.tile; + var parent = backBuffer && backBuffer.parentNode; + if (backBuffer) { + parent.removeChild(backBuffer); + } + }, + + /** + * APIMethod: destroy + * nullify references to prevent circular references and memory leaks + */ + destroy: function() { + this.removeBackBuffer(); + this.layer.events.unregister("loadend", this, this.resetBackBuffer); + this.backBufferData = null; + OpenLayers.Tile.prototype.destroy.apply(this, arguments); + } + +}); \ No newline at end of file diff --git a/lib/OpenLayers/Tile/Image.js b/lib/OpenLayers/Tile/Image.js index b195ac0ab3..2f04344d0d 100644 --- a/lib/OpenLayers/Tile/Image.js +++ b/lib/OpenLayers/Tile/Image.js @@ -5,7 +5,7 @@ /** - * @requires OpenLayers/Tile.js + * @requires OpenLayers/Tile/BackBufferable.js */ /** @@ -15,9 +15,9 @@ * constructor. * * Inherits from: - * - + * - */ -OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { +OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile.BackBufferable, { /** * Property: url @@ -28,16 +28,22 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { /** * Property: imgDiv - * {DOMElement} The div element which wraps the image. + * {HTMLImageElement} The image for this tile. */ imgDiv: null, - + /** * Property: frame * {DOMElement} The image element is appended to the frame. Any gutter on * the image will be hidden behind the frame. */ frame: null, + + /** + * Property: imageReloadAttempts + * {Integer} Attempts to load the image. + */ + imageReloadAttempts: null, /** * Property: layerAlphaHack @@ -46,28 +52,11 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { layerAlphaHack: null, /** - * Property: isBackBuffer - * {Boolean} Is this tile a back buffer tile? - */ - isBackBuffer: false, - - /** - * Property: isFirstDraw - * {Boolean} Is this the first time the tile is being drawn? - * This is used to force resetBackBuffer to synchronize - * the backBufferTile with the foreground tile the first time - * the foreground tile loads so that if the user zooms - * before the layer has fully loaded, the backBufferTile for - * tiles that have been loaded can be used. - */ - isFirstDraw: true, - - /** - * Property: backBufferTile - * {} A clone of the tile used to create transition - * effects when the tile is moved or changes resolution. + * Property: asyncRequestId + * {Integer} ID of an request to see if request is still valid. This is a + * number which increments by 1 for each asynchronous request. */ - backBufferTile: null, + asyncRequestId: null, /** * APIProperty: maxGetUrlLength @@ -77,12 +66,9 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * characters. * * Caution: - * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and - * Opera < 10.0 do not fully support this option. - * - * Note: - * Do not use this option for layers that have a transitionEffect - * configured - IFrame tiles from POST requests can not be resized. + * Older versions of Gecko based browsers (e.g. Firefox < 3.5) and most + * Opera versions do not fully support this option. On all browsers, + * transition effects are not supported if POST requests are used. */ maxGetUrlLength: null, @@ -102,72 +88,34 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * options - {Object} */ initialize: function(layer, position, bounds, url, size, options) { - OpenLayers.Tile.prototype.initialize.apply(this, arguments); - - if (this.maxGetUrlLength != null) { - OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame); - } + OpenLayers.Tile.BackBufferable.prototype.initialize.apply(this, arguments); this.url = url; //deprecated remove me + + this.frame = document.createElement("div"); + this.frame.style.position = "absolute"; + this.frame.style.overflow = "hidden"; - this.frame = document.createElement('div'); - this.frame.style.overflow = 'hidden'; - this.frame.style.position = 'absolute'; + this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack(); - this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack(); + if (this.maxGetUrlLength != null) { + OpenLayers.Util.extend(this, OpenLayers.Tile.Image.IFrame); + } }, - + /** * APIMethod: destroy * nullify references to prevent circular references and memory leaks */ destroy: function() { - if (this.imgDiv != null) { - this.removeImgDiv(); + if (this.frame != null) { + this.clear(); + this.imgDiv = null; + this.frame = null; } - this.imgDiv = null; - if ((this.frame != null) && (this.frame.parentNode == this.layer.div)) { - this.layer.div.removeChild(this.frame); - } - this.frame = null; - - /* clean up the backBufferTile if it exists */ - if (this.backBufferTile) { - this.backBufferTile.destroy(); - this.backBufferTile = null; - } - - this.layer.events.unregister("loadend", this, this.resetBackBuffer); - - OpenLayers.Tile.prototype.destroy.apply(this, arguments); - }, - - /** - * Method: clone - * - * Parameters: - * obj - {} The tile to be cloned - * - * Returns: - * {} An exact clone of this - */ - clone: function (obj) { - if (obj == null) { - obj = new OpenLayers.Tile.Image(this.layer, - this.position, - this.bounds, - this.url, - this.size); - } - - //pick up properties from superclass - obj = OpenLayers.Tile.prototype.clone.apply(this, [obj]); - - //dont want to directly copy the image div - obj.imgDiv = null; - - - return obj; + // don't handle async requests any more + this.asyncRequestId = null; + OpenLayers.Tile.BackBufferable.prototype.destroy.apply(this, arguments); }, /** @@ -175,103 +123,27 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * Check that a tile should be drawn, and draw it. * * Returns: - * {Boolean} Always returns true. + * {Boolean} Was a tile drawn? */ draw: function() { - if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { - this.bounds = this.getBoundsFromBaseLayer(this.position); - } - var drawTile = OpenLayers.Tile.prototype.draw.apply(this, arguments); - - if ((OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) || - this.layer.singleTile) { - if (drawTile) { - //we use a clone of this tile to create a double buffer for visual - //continuity. The backBufferTile is used to create transition - //effects while the tile in the grid is repositioned and redrawn - if (!this.backBufferTile) { - this.backBufferTile = this.clone(); - this.backBufferTile.hide(); - // this is important. It allows the backBuffer to place itself - // appropriately in the DOM. The Image subclass needs to put - // the backBufferTile behind the main tile so the tiles can - // load over top and display as soon as they are loaded. - this.backBufferTile.isBackBuffer = true; - - // potentially end any transition effects when the tile loads - this.events.register('loadend', this, this.resetBackBuffer); - - // clear transition back buffer tile only after all tiles in - // this layer have loaded to avoid visual glitches - this.layer.events.register("loadend", this, this.resetBackBuffer); - } - // run any transition effects - this.startTransition(); + var drawn = OpenLayers.Tile.BackBufferable.prototype.draw.apply(this, arguments); + if (drawn) { + if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { + this.bounds = this.getBoundsFromBaseLayer(this.position); + } + if (this.isLoading) { + //if we're already loading, send 'reload' instead of 'loadstart'. + this.events.triggerEvent("reload"); } else { - // if we aren't going to draw the tile, then the backBuffer should - // be hidden too! - if (this.backBufferTile) { - this.backBufferTile.clear(); - } + this.isLoading = true; + this.events.triggerEvent("loadstart"); } + this.positionTile(); + this.renderTile(); } else { - if (drawTile && this.isFirstDraw) { - this.events.register('loadend', this, this.showTile); - this.isFirstDraw = false; - } - } - - if (!drawTile) { - return false; - } - - if (this.isLoading) { - //if we're already loading, send 'reload' instead of 'loadstart'. - this.events.triggerEvent("reload"); - } else { - this.isLoading = true; - this.events.triggerEvent("loadstart"); - } - - return this.renderTile(); - }, - - /** - * Method: resetBackBuffer - * Triggered by two different events, layer loadend, and tile loadend. - * In any of these cases, we check to see if we can hide the - * backBufferTile yet and update its parameters to match the - * foreground tile. - * - * Basic logic: - * - If the backBufferTile hasn't been drawn yet, reset it - * - If layer is still loading, show foreground tile but don't hide - * the backBufferTile yet - * - If layer is done loading, reset backBuffer tile and show - * foreground tile - */ - resetBackBuffer: function() { - this.showTile(); - if (this.backBufferTile && - (this.isFirstDraw || !this.layer.numLoadingTiles)) { - this.isFirstDraw = false; - // check to see if the backBufferTile is within the max extents - // before rendering it - var maxExtent = this.layer.maxExtent; - var withinMaxExtent = (maxExtent && - this.bounds.intersectsBounds(maxExtent, false)); - if (withinMaxExtent) { - this.backBufferTile.position = this.position; - this.backBufferTile.bounds = this.bounds; - this.backBufferTile.size = this.size; - this.backBufferTile.imageSize = this.layer.getImageSize(this.bounds) || this.size; - this.backBufferTile.imageOffset = this.layer.imageOffset; - this.backBufferTile.resolution = this.layer.getResolution(); - this.backBufferTile.renderTile(); - } - - this.backBufferTile.hide(); + this.unload(); } + return drawn; }, /** @@ -280,324 +152,194 @@ OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { * position it correctly, and set its url. */ renderTile: function() { + this.layer.div.appendChild(this.frame); if (this.layer.async) { - this.initImgDiv(); - // Asyncronous image requests call the asynchronous getURL method + // Asynchronous image requests call the asynchronous getURL method // on the layer to fetch an image that covers 'this.bounds', in the scope of // 'this', setting the 'url' property of the layer itself, and running - // the callback 'positionFrame' when the image request returns. - this.layer.getURLasync(this.bounds, this, "url", this.positionImage); + // the callback 'initImage' when the image request returns. + var myId = this.asyncRequestId = (this.asyncRequestId || 0) + 1; + this.layer.getURLasync(this.bounds, this, "url", function() { + if (myId == this.asyncRequestId) { + this.initImage(); + } + }); } else { - // syncronous image requests get the url and position the frame immediately, - // and don't wait for an image request to come back. - + // synchronous image requests get the url immediately. this.url = this.layer.getURL(this.bounds); - - this.initImgDiv(); - - // position the frame immediately - this.positionImage(); + this.initImage(); } - return true; }, /** - * Method: positionImage + * Method: positionTile * Using the properties currenty set on the layer, position the tile correctly. * This method is used both by the async and non-async versions of the Tile.Image * code. */ - positionImage: function() { - // if the this layer doesn't exist at the point the image is - // returned, do not attempt to use it for size computation - if (this.layer === null) { - return; - } - // position the frame - OpenLayers.Util.modifyDOMElement(this.frame, - null, this.position, this.size); - - var imageSize = this.layer.getImageSize(this.bounds); - if (this.layerAlphaHack) { - OpenLayers.Util.modifyAlphaImageDiv(this.imgDiv, - null, null, imageSize, this.url); - } else { - OpenLayers.Util.modifyDOMElement(this.imgDiv, - null, null, imageSize) ; - this.imgDiv.src = this.url; - } + positionTile: function() { + var style = this.frame.style; + style.left = this.position.x + "px"; + style.top = this.position.y + "px"; + style.width = this.size.w + "px"; + style.height = this.size.h + "px"; }, /** * Method: clear - * Clear the tile of any bounds/position-related data so that it can - * be reused in a new location. + * Remove the tile from the DOM, clear it of any image related data so that + * it can be reused in a new location. */ clear: function() { - if(this.imgDiv) { - this.hide(); - if (OpenLayers.Tile.Image.useBlankTile) { - this.imgDiv.src = OpenLayers.Util.getImagesLocation() + "blank.gif"; - } + var img = this.imgDiv; + if (img) { + OpenLayers.Event.stopObservingElement(img); + if (this.frame.parentNode === this.layer.div) { + this.layer.div.removeChild(this.frame); + } + this.setImgSrc(); + if (this.layerAlphaHack === true) { + img.style.filter = ""; + } + OpenLayers.Element.removeClass(img, "olImageLoadError"); } }, - + /** - * Method: initImgDiv - * Creates the imgDiv property on the tile. + * Method: createImage + * Creates the content for the frame on the tile. */ - initImgDiv: function() { - if (this.imgDiv == null) { - var offset = this.layer.imageOffset; - var size = this.layer.getImageSize(this.bounds); - - if (this.layerAlphaHack) { - this.imgDiv = OpenLayers.Util.createAlphaImageDiv(null, - offset, - size, - null, - "relative", - null, - null, - null, - true); - } else { - this.imgDiv = OpenLayers.Util.createImage(null, - offset, - size, - null, - "relative", - null, - null, - true); - } - - // needed for changing to a different server for onload error - if (OpenLayers.Util.isArray(this.layer.url)) { - this.imgDiv.urls = this.layer.url.slice(); - } - - this.imgDiv.className = 'olTileImage'; - - /* checkImgURL used to be used to called as a work around, but it - ended up hiding problems instead of solving them and broke things - like relative URLs. See discussion on the dev list: - http://openlayers.org/pipermail/dev/2007-January/000205.html - - OpenLayers.Event.observe( this.imgDiv, "load", - OpenLayers.Function.bind(this.checkImgURL, this) ); - */ - this.frame.style.zIndex = this.isBackBuffer ? 0 : 1; - this.frame.appendChild(this.imgDiv); - this.layer.div.appendChild(this.frame); - - if(this.layer.opacity < 1) { - - OpenLayers.Util.modifyDOMElement(this.imgDiv, null, null, null, - null, null, null, - this.layer.opacity); - } - - // we need this reference to check back the viewRequestID - this.imgDiv.map = this.layer.map; - - //bind a listener to the onload of the image div so that we - // can register when a tile has finished loading. - var onload = function() { - - //normally isLoading should always be true here but there are some - // right funky conditions where loading and then reloading a tile - // with the same url *really*fast*. this check prevents sending - // a 'loadend' if the msg has already been sent - // - if (this.isLoading) { - this.isLoading = false; - this.events.triggerEvent("loadend"); - } - }; - - if (this.layerAlphaHack) { - OpenLayers.Event.observe(this.imgDiv.childNodes[0], 'load', - OpenLayers.Function.bind(onload, this)); - } else { - OpenLayers.Event.observe(this.imgDiv, 'load', - OpenLayers.Function.bind(onload, this)); - } - - - // Bind a listener to the onerror of the image div so that we - // can registere when a tile has finished loading with errors. - var onerror = function() { - - // If we have gone through all image reload attempts, it is time - // to realize that we are done with this image. Since - // OpenLayers.Util.onImageLoadError already has taken care about - // the error, we can continue as if the image was loaded - // successfully. - if (this.imgDiv._attempts > OpenLayers.IMAGE_RELOAD_ATTEMPTS) { - onload.call(this); - } - }; - OpenLayers.Event.observe(this.imgDiv, "error", - OpenLayers.Function.bind(onerror, this)); + createImage: function() { + var img = document.createElement("img"); + this.imgDiv = img; + + img.className = "olTileImage"; + // avoid image gallery menu in IE6 + img.galleryImg = "no"; + + var style = img.style, + gutter = this.layer.gutter; + if (gutter) { + var tileSize = this.layer.tileSize, + left = (gutter / tileSize.w * 100), + top = (gutter / tileSize.h * 100); + style.left = -left + "%"; + style.top = -top + "%"; + style.width = (2 * left + 100) + "%"; + style.height = (2 * top + 100) + "%"; + style.position = "absolute"; + } else { + style.width = "100%"; + style.height = "100%"; } - - this.imgDiv.viewRequestID = this.layer.map.viewRequestID; + if (this.layer.opacity < 1) { + OpenLayers.Util.modifyDOMElement(img, null, null, null, null, null, + null, this.layer.opacity); + } + if (this.layerAlphaHack) { + // move the image out of sight + style.paddingTop = style.height; + style.height = "0"; + } + + this.frame.appendChild(img); + return img; }, /** - * Method: removeImgDiv - * Removes the imgDiv from the DOM and stops listening to events on it. + * Method: initImage + * Creates the content for the frame on the tile. */ - removeImgDiv: function() { - // unregister the "load" and "error" handlers. Only the "error" handler if - // this.layerAlphaHack is true. - OpenLayers.Event.stopObservingElement(this.imgDiv); - - if (this.imgDiv.parentNode == this.frame) { - this.frame.removeChild(this.imgDiv); - this.imgDiv.map = null; - } - this.imgDiv.urls = null; - - var child = this.imgDiv.firstChild; - //check for children (alphaHack img or IFrame) - if (child) { - OpenLayers.Event.stopObservingElement(child); - this.imgDiv.removeChild(child); - delete child; + initImage: function() { + var img = this.imgDiv || this.createImage(); + if (this.url && img.getAttribute("src") == this.url) { + this.onImageLoad(); } else { - // abort any currently loading image - this.imgDiv.src = OpenLayers.Util.getImagesLocation() + "blank.gif"; + OpenLayers.Event.observe( + img, "load", OpenLayers.Function.bind(this.onImageLoad, this) + ); + OpenLayers.Event.observe( + img, "error", OpenLayers.Function.bind(this.onImageError, this) + ); + this.imageReloadAttempts = 0; + this.setImgSrc(this.url); } }, - + /** - * Method: checkImgURL - * Make sure that the image that just loaded is the one this tile is meant - * to display, since panning/zooming might have changed the tile's URL in - * the meantime. If the tile URL did change before the image loaded, set - * the imgDiv display to 'none', as either (a) it will be reset to visible - * when the new URL loads in the image, or (b) we don't want to display - * this tile after all because its new bounds are outside our maxExtent. - * - * This function should no longer be neccesary with the improvements to - * Grid.js in OpenLayers 2.3. The lack of a good isEquivilantURL function - * caused problems in 2.2, but it's possible that with the improved - * isEquivilant URL function, this might be neccesary at some point. - * - * See discussion in the thread at - * http://openlayers.org/pipermail/dev/2007-January/000205.html + * Method: setImgSrc + * Sets the source for the tile image + * + * Parameters: + * url - {String} or undefined to hide the image */ - checkImgURL: function () { - // Sometimes our image will load after it has already been removed - // from the map, in which case this check is not needed. - if (this.layer) { - var loaded = this.layerAlphaHack ? this.imgDiv.firstChild.src : this.imgDiv.src; - if (!OpenLayers.Util.isEquivalentUrl(loaded, this.url)) { - this.hide(); - } + setImgSrc: function(url) { + this.imgDiv.style.display = "none"; + if (url) { + this.imgDiv.src = url; } }, /** - * Method: startTransition - * This method is invoked on tiles that are backBuffers for tiles in the - * grid. The grid tile is about to be cleared and a new tile source - * loaded. This is where the transition effect needs to be started - * to provide visual continuity. + * Method: getTile + * Get the tile's markup. + * + * Returns: + * {DOMElement} The tile's markup */ - startTransition: function() { - // backBufferTile has to be valid and ready to use - if (!this.backBufferTile || !this.backBufferTile.imgDiv) { - return; - } - - // calculate the ratio of change between the current resolution of the - // backBufferTile and the layer. If several animations happen in a - // row, then the backBufferTile will scale itself appropriately for - // each request. - var ratio = 1; - if (this.backBufferTile.resolution) { - ratio = this.backBufferTile.resolution / this.layer.getResolution(); - } - - // if the ratio is not the same as it was last time (i.e. we are - // zooming), then we need to adjust the backBuffer tile - if (ratio != 1) { - if (this.layer.transitionEffect == 'resize') { - // In this case, we can just immediately resize the - // backBufferTile. - var upperLeft = new OpenLayers.LonLat( - this.backBufferTile.bounds.left, - this.backBufferTile.bounds.top - ); - var size = new OpenLayers.Size( - this.backBufferTile.size.w * ratio, - this.backBufferTile.size.h * ratio - ); - - var px = this.layer.map.getLayerPxFromLonLat(upperLeft); - OpenLayers.Util.modifyDOMElement(this.backBufferTile.frame, - null, px, size); - var imageSize = this.backBufferTile.imageSize; - imageSize = new OpenLayers.Size(imageSize.w * ratio, - imageSize.h * ratio); - var imageOffset = this.backBufferTile.imageOffset; - if(imageOffset) { - imageOffset = new OpenLayers.Pixel( - imageOffset.x * ratio, imageOffset.y * ratio - ); - } - - OpenLayers.Util.modifyDOMElement( - this.backBufferTile.imgDiv, null, imageOffset, imageSize - ) ; - - this.backBufferTile.show(); - } - } else { - // default effect is just to leave the existing tile - // until the new one loads if this is a singleTile and - // there was no change in resolution. Otherwise we - // don't bother to show the backBufferTile at all - if (this.layer.singleTile) { - this.backBufferTile.show(); - } else { - this.backBufferTile.hide(); - } - } + getTile: function() { + return this.frame; + }, + /** + * Method: createBackBuffer + * Create a copy of this tile's markup for the 's backBufferDiv + * + * Returns: + * {DOMElement} a clone of the tile content + */ + createBackBuffer: function() { + var frame = this.frame.cloneNode(false); + OpenLayers.Event.stopObservingElement(this.imgDiv); + frame.appendChild(this.imgDiv); + this.imgDiv = null; + return frame; }, - - /** - * Method: show - * Show the tile by showing its frame. + + /** + * Method: onImageLoad + * Handler for the image onload event */ - show: function() { - this.frame.style.display = ''; - // Force a reflow on gecko based browsers to actually show the element - // before continuing execution. - if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, - this.layer.transitionEffect) != -1) { - if (OpenLayers.IS_GECKO === true) { - this.frame.scrollLeft = this.frame.scrollLeft; - } + onImageLoad: function() { + var img = this.imgDiv; + img.style.display = ""; + this.isLoading = false; + this.events.triggerEvent("loadend"); + + if (this.layerAlphaHack === true) { + img.style.filter = + "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + img.src + "', sizingMethod='scale')"; } }, - /** - * Method: hide - * Hide the tile by hiding its frame. + /** + * Method: onImageError + * Handler for the image onerror event */ - hide: function() { - this.frame.style.display = 'none'; + onImageError: function() { + var img = this.imgDiv; + if (img.src != null) { + this.imageReloadAttempts++; + if (this.imageReloadAttempts <= OpenLayers.IMAGE_RELOAD_ATTEMPTS) { + this.setImgSrc(this.layer.getURL(this.bounds)); + } else { + OpenLayers.Element.addClass(img, "olImageLoadError"); + this.onImageLoad(); + } + } }, - + CLASS_NAME: "OpenLayers.Tile.Image" - } -); -OpenLayers.Tile.Image.useBlankTile = ( - OpenLayers.BROWSER_NAME == "safari" || - OpenLayers.BROWSER_NAME == "opera"); +}); \ No newline at end of file diff --git a/lib/OpenLayers/Tile/Image/IFrame.js b/lib/OpenLayers/Tile/Image/IFrame.js index 7735637019..6bc7655d1f 100644 --- a/lib/OpenLayers/Tile/Image/IFrame.js +++ b/lib/OpenLayers/Tile/Image/IFrame.js @@ -14,14 +14,13 @@ * remote services. Images will be loaded using HTTP-POST into an IFrame. * * This mixin will be applied to instances - * configured with or - * set to true. + * configured with set. * * Inherits from: * - */ OpenLayers.Tile.Image.IFrame = { - + /** * Property: useIFrame * {Boolean} true if we are currently using an IFrame to render POST @@ -30,158 +29,110 @@ OpenLayers.Tile.Image.IFrame = { useIFrame: null, /** - * Method: clear - * Removes the iframe from DOM (avoids back-button problems). + * Property: blankImageUrl + * {String} This is only used as background image for the eventPane, so we + * don't care that this doesn't actually result in a blank image on all + * browsers */ - clear: function() { - if (this.useIFrame) { - if (this.imgDiv) { - var iFrame = this.imgDiv.firstChild; - OpenLayers.Event.stopObservingElement(iFrame); - this.imgDiv.removeChild(iFrame); - delete iFrame; - } - } else { - OpenLayers.Tile.Image.prototype.clear.apply(this, arguments); - } - }, + blankImageUrl: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7", /** - * Method: renderTile + * Method: updateBackBuffer + * Update the , and return a new or reposition the + * backBuffer. When a backbuffer is returned, the tile's markup is not + * available any more. + * + * Returns: + * {HTMLDivElement} the tile's markup in a cloned element, or undefined if + * no backbuffer is currently available or needed */ - renderTile: function() { - if (OpenLayers.Tile.Image.prototype.renderTile.apply(this, arguments) && - this.useIFrame) { - // create a html form and add it temporary to the layer div - var form = this.createRequestForm(); - this.imgDiv.appendChild(form); - - // submit the form (means fetching the image) - form.submit(); - this.imgDiv.removeChild(form); - delete form; + updateBackBuffer: function() { + this.url = this.layer.getURL(this.bounds); + var usedIFrame = this.useIFrame; + this.useIFrame = this.maxGetUrlLength !== null && !this.layer.async && + this.url.length > this.maxGetUrlLength; + var fromIFrame = usedIFrame && !this.useIFrame; + var toIFrame = !usedIFrame && this.useIFrame; + if (fromIFrame || toIFrame) { + // switch between get (image) and post (iframe) + this.clear(); + if (this.imgDiv && this.imgDiv.parentNode === this.frame) { + this.frame.removeChild(this.imgDiv); + } + this.imgDiv = null; + if (fromIFrame) { + // remove eventPane + this.frame.removeChild(this.frame.firstChild); + this.resetBackBuffer(); + } + } + if (!this.useIFrame) { + OpenLayers.Tile.Image.prototype.updateBackBuffer.apply(this, arguments); } - return true; }, - + /** - * Method: initImgDiv - * Creates the imgDiv property on the tile. + * Method: createImage + * Creates the content for the frame on the tile. */ - initImgDiv: function() { - this.useIFrame = this.maxGetUrlLength !== null && !this.layer.async && - this.url.length > this.maxGetUrlLength; - if (this.imgDiv != null) { - var nodeName = this.imgDiv.nodeName.toLowerCase(); - if ((this.useIFrame && nodeName == "img") || - (!this.useIFrame && nodeName == "div")) { - // switch between get and post - this.removeImgDiv(); - this.imgDiv = null; + createImage: function() { + if (this.useIFrame === true) { + if (!this.frame.childNodes.length) { + var eventPane = document.createElement("div"), + style = eventPane.style; + style.position = "absolute"; + style.width = "100%"; + style.height = "100%"; + style.zIndex = 1; + style.backgroundImage = "url(" + this.blankImageUrl + ")"; + this.frame.appendChild(eventPane); } - } - if (this.useIFrame) { - if (this.imgDiv == null) { - var eventPane = document.createElement("div"); - if(OpenLayers.BROWSER_NAME == "msie") { - // IE cannot handle events on elements without backgroundcolor. - // So we use this little hack to make elements transparent - eventPane.style.backgroundColor = '#FFFFFF'; - eventPane.style.filter = 'chroma(color=#FFFFFF)'; - } - - OpenLayers.Util.modifyDOMElement(eventPane, null, - new OpenLayers.Pixel(0,0), this.layer.getImageSize(), "absolute"); - - this.imgDiv = document.createElement("div"); - this.imgDiv.appendChild(eventPane); - - OpenLayers.Util.modifyDOMElement(this.imgDiv, this.id, null, - this.layer.getImageSize(), "relative"); - this.imgDiv.className = 'olTileImage'; + var id = this.id + '_iFrame', iframe; + if (parseFloat(navigator.appVersion.split("MSIE")[1]) < 9) { + // Older IE versions do not set the name attribute of an iFrame + // properly via DOM manipulation, so we need to do it on our own with + // this hack. + iframe = document.createElement('