diff options
Diffstat (limited to 'misc/openlayers/lib/OpenLayers/Tile')
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Tile/Image.js | 510 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js | 233 | ||||
-rw-r--r-- | misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js | 252 |
3 files changed, 995 insertions, 0 deletions
diff --git a/misc/openlayers/lib/OpenLayers/Tile/Image.js b/misc/openlayers/lib/OpenLayers/Tile/Image.js new file mode 100644 index 0000000..2fdffb3 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Tile/Image.js @@ -0,0 +1,510 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Tile.js + * @requires OpenLayers/Animation.js + * @requires OpenLayers/Util.js + */ + +/** + * Class: OpenLayers.Tile.Image + * Instances of OpenLayers.Tile.Image are used to manage the image tiles + * used by various layers. Create a new image tile with the + * <OpenLayers.Tile.Image> constructor. + * + * Inherits from: + * - <OpenLayers.Tile> + */ +OpenLayers.Tile.Image = OpenLayers.Class(OpenLayers.Tile, { + + /** + * APIProperty: events + * {<OpenLayers.Events>} An events object that handles all + * events on the tile. + * + * Register a listener for a particular event with the following syntax: + * (code) + * tile.events.register(type, obj, listener); + * (end) + * + * Supported event types (in addition to the <OpenLayers.Tile> events): + * beforeload - Triggered before an image is prepared for loading, when the + * url for the image is known already. Listeners may call <setImage> on + * the tile instance. If they do so, that image will be used and no new + * one will be created. + */ + + /** + * APIProperty: url + * {String} The URL of the image being requested. No default. Filled in by + * layer.getURL() function. May be modified by loadstart listeners. + */ + url: null, + + /** + * Property: imgDiv + * {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. If no gutter is set, + * this will be null. + */ + frame: null, + + /** + * Property: imageReloadAttempts + * {Integer} Attempts to load the image. + */ + imageReloadAttempts: null, + + /** + * Property: layerAlphaHack + * {Boolean} True if the png alpha hack needs to be applied on the layer's div. + */ + layerAlphaHack: null, + + /** + * 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. + */ + asyncRequestId: null, + + /** + * APIProperty: maxGetUrlLength + * {Number} If set, requests that would result in GET urls with more + * characters than the number provided will be made using form-encoded + * HTTP POST. It is good practice to avoid urls that are longer than 2048 + * characters. + * + * Caution: + * 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, + + /** + * Property: canvasContext + * {CanvasRenderingContext2D} A canvas context associated with + * the tile image. + */ + canvasContext: null, + + /** + * APIProperty: crossOriginKeyword + * The value of the crossorigin keyword to use when loading images. This is + * only relevant when using <getCanvasContext> for tiles from remote + * origins and should be set to either 'anonymous' or 'use-credentials' + * for servers that send Access-Control-Allow-Origin headers with their + * tiles. + */ + crossOriginKeyword: null, + + /** TBD 3.0 - reorder the parameters to the init function to remove + * URL. the getUrl() function on the layer gets called on + * each draw(), so no need to specify it here. + */ + + /** + * Constructor: OpenLayers.Tile.Image + * Constructor for a new <OpenLayers.Tile.Image> instance. + * + * Parameters: + * layer - {<OpenLayers.Layer>} layer that the tile will go in. + * position - {<OpenLayers.Pixel>} + * bounds - {<OpenLayers.Bounds>} + * url - {<String>} Deprecated. Remove me in 3.0. + * size - {<OpenLayers.Size>} + * options - {Object} + */ + initialize: function(layer, position, bounds, url, size, options) { + OpenLayers.Tile.prototype.initialize.apply(this, arguments); + + this.url = url; //deprecated remove me + + this.layerAlphaHack = this.layer.alpha && OpenLayers.Util.alphaHack(); + + if (this.maxGetUrlLength != null || this.layer.gutter || this.layerAlphaHack) { + // only create frame if it's needed + this.frame = document.createElement("div"); + this.frame.style.position = "absolute"; + this.frame.style.overflow = "hidden"; + } + 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) { + this.clear(); + this.imgDiv = null; + this.frame = null; + } + // don't handle async requests any more + this.asyncRequestId = null; + OpenLayers.Tile.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Check that a tile should be drawn, and draw it. + * + * Returns: + * {Boolean} Was a tile drawn? Or null if a beforedraw listener returned + * false. + */ + draw: function() { + var shouldDraw = OpenLayers.Tile.prototype.draw.apply(this, arguments); + if (shouldDraw) { + // The layer's reproject option is deprecated. + if (this.layer != this.layer.map.baseLayer && this.layer.reproject) { + // getBoundsFromBaseLayer is defined in deprecated.js. + this.bounds = this.getBoundsFromBaseLayer(this.position); + } + if (this.isLoading) { + //if we're already loading, send 'reload' instead of 'loadstart'. + this._loadEvent = "reload"; + } else { + this.isLoading = true; + this._loadEvent = "loadstart"; + } + this.renderTile(); + this.positionTile(); + } else if (shouldDraw === false) { + this.unload(); + } + return shouldDraw; + }, + + /** + * Method: renderTile + * Internal function to actually initialize the image tile, + * position it correctly, and set its url. + */ + renderTile: function() { + if (this.layer.async) { + // Asynchronous image requests call the asynchronous getURL method + // on the layer to fetch an image that covers 'this.bounds'. + var id = this.asyncRequestId = (this.asyncRequestId || 0) + 1; + this.layer.getURLasync(this.bounds, function(url) { + if (id == this.asyncRequestId) { + this.url = url; + this.initImage(); + } + }, this); + } else { + // synchronous image requests get the url immediately. + this.url = this.layer.getURL(this.bounds); + this.initImage(); + } + }, + + /** + * 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. + */ + positionTile: function() { + var style = this.getTile().style, + size = this.frame ? this.size : + this.layer.getImageSize(this.bounds), + ratio = 1; + if (this.layer instanceof OpenLayers.Layer.Grid) { + ratio = this.layer.getServerResolution() / this.layer.map.getResolution(); + } + style.left = this.position.x + "px"; + style.top = this.position.y + "px"; + style.width = Math.round(ratio * size.w) + "px"; + style.height = Math.round(ratio * size.h) + "px"; + }, + + /** + * Method: clear + * 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() { + OpenLayers.Tile.prototype.clear.apply(this, arguments); + var img = this.imgDiv; + if (img) { + var tile = this.getTile(); + if (tile.parentNode === this.layer.div) { + this.layer.div.removeChild(tile); + } + this.setImgSrc(); + if (this.layerAlphaHack === true) { + img.style.filter = ""; + } + OpenLayers.Element.removeClass(img, "olImageLoadError"); + } + this.canvasContext = null; + }, + + /** + * Method: getImage + * Returns or creates and returns the tile image. + */ + getImage: function() { + if (!this.imgDiv) { + this.imgDiv = OpenLayers.Tile.Image.IMAGE.cloneNode(false); + + var style = this.imgDiv.style; + if (this.frame) { + var left = 0, top = 0; + if (this.layer.gutter) { + left = this.layer.gutter / this.layer.tileSize.w * 100; + top = this.layer.gutter / this.layer.tileSize.h * 100; + } + style.left = -left + "%"; + style.top = -top + "%"; + style.width = (2 * left + 100) + "%"; + style.height = (2 * top + 100) + "%"; + } + style.visibility = "hidden"; + style.opacity = 0; + if (this.layer.opacity < 1) { + style.filter = 'alpha(opacity=' + + (this.layer.opacity * 100) + + ')'; + } + style.position = "absolute"; + if (this.layerAlphaHack) { + // move the image out of sight + style.paddingTop = style.height; + style.height = "0"; + style.width = "100%"; + } + if (this.frame) { + this.frame.appendChild(this.imgDiv); + } + } + + return this.imgDiv; + }, + + /** + * APIMethod: setImage + * Sets the image element for this tile. This method should only be called + * from beforeload listeners. + * + * Parameters + * img - {HTMLImageElement} The image to use for this tile. + */ + setImage: function(img) { + this.imgDiv = img; + }, + + /** + * Method: initImage + * Creates the content for the frame on the tile. + */ + initImage: function() { + if (!this.url && !this.imgDiv) { + // fast path out - if there is no tile url and no previous image + this.isLoading = false; + return; + } + this.events.triggerEvent('beforeload'); + this.layer.div.appendChild(this.getTile()); + this.events.triggerEvent(this._loadEvent); + var img = this.getImage(); + var src = img.getAttribute('src') || ''; + if (this.url && OpenLayers.Util.isEquivalentUrl(src, this.url)) { + this._loadTimeout = window.setTimeout( + OpenLayers.Function.bind(this.onImageLoad, this), 0 + ); + } else { + this.stopLoading(); + if (this.crossOriginKeyword) { + img.removeAttribute("crossorigin"); + } + 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: setImgSrc + * Sets the source for the tile image + * + * Parameters: + * url - {String} or undefined to hide the image + */ + setImgSrc: function(url) { + var img = this.imgDiv; + if (url) { + img.style.visibility = 'hidden'; + img.style.opacity = 0; + // don't set crossOrigin if the url is a data URL + if (this.crossOriginKeyword) { + if (url.substr(0, 5) !== 'data:') { + img.setAttribute("crossorigin", this.crossOriginKeyword); + } else { + img.removeAttribute("crossorigin"); + } + } + img.src = url; + } else { + // Remove reference to the image, and leave it to the browser's + // caching and garbage collection. + this.stopLoading(); + this.imgDiv = null; + if (img.parentNode) { + img.parentNode.removeChild(img); + } + } + }, + + /** + * Method: getTile + * Get the tile's markup. + * + * Returns: + * {DOMElement} The tile's markup + */ + getTile: function() { + return this.frame ? this.frame : this.getImage(); + }, + + /** + * Method: createBackBuffer + * Create a backbuffer for this tile. A backbuffer isn't exactly a clone + * of the tile's markup, because we want to avoid the reloading of the + * image. So we clone the frame, and steal the image from the tile. + * + * Returns: + * {DOMElement} The markup, or undefined if the tile has no image + * or if it's currently loading. + */ + createBackBuffer: function() { + if (!this.imgDiv || this.isLoading) { + return; + } + var backBuffer; + if (this.frame) { + backBuffer = this.frame.cloneNode(false); + backBuffer.appendChild(this.imgDiv); + } else { + backBuffer = this.imgDiv; + } + this.imgDiv = null; + return backBuffer; + }, + + /** + * Method: onImageLoad + * Handler for the image onload event + */ + onImageLoad: function() { + var img = this.imgDiv; + this.stopLoading(); + img.style.visibility = 'inherit'; + img.style.opacity = this.layer.opacity; + this.isLoading = false; + this.canvasContext = null; + this.events.triggerEvent("loadend"); + + if (this.layerAlphaHack === true) { + img.style.filter = + "progid:DXImageTransform.Microsoft.AlphaImageLoader(src='" + + img.src + "', sizingMethod='scale')"; + } + }, + + /** + * Method: onImageError + * Handler for the image onerror event + */ + 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.events.triggerEvent("loaderror"); + this.onImageLoad(); + } + } + }, + + /** + * Method: stopLoading + * Stops a loading sequence so <onImageLoad> won't be executed. + */ + stopLoading: function() { + OpenLayers.Event.stopObservingElement(this.imgDiv); + window.clearTimeout(this._loadTimeout); + delete this._loadTimeout; + }, + + /** + * APIMethod: getCanvasContext + * Returns a canvas context associated with the tile image (with + * the image drawn on it). + * Returns undefined if the browser does not support canvas, if + * the tile has no image or if it's currently loading. + * + * The function returns a canvas context instance but the + * underlying canvas is still available in the 'canvas' property: + * (code) + * var context = tile.getCanvasContext(); + * if (context) { + * var data = context.canvas.toDataURL('image/jpeg'); + * } + * (end) + * + * Returns: + * {Boolean} + */ + getCanvasContext: function() { + if (OpenLayers.CANVAS_SUPPORTED && this.imgDiv && !this.isLoading) { + if (!this.canvasContext) { + var canvas = document.createElement("canvas"); + canvas.width = this.size.w; + canvas.height = this.size.h; + this.canvasContext = canvas.getContext("2d"); + this.canvasContext.drawImage(this.imgDiv, 0, 0); + } + return this.canvasContext; + } + }, + + CLASS_NAME: "OpenLayers.Tile.Image" + +}); + +/** + * Constant: OpenLayers.Tile.Image.IMAGE + * {HTMLImageElement} The image for a tile. + */ +OpenLayers.Tile.Image.IMAGE = (function() { + var img = new Image(); + img.className = "olTileImage"; + // avoid image gallery menu in IE6 + img.galleryImg = "no"; + return img; +}()); + diff --git a/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js b/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js new file mode 100644 index 0000000..9e33acc --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Tile/Image/IFrame.js @@ -0,0 +1,233 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Tile/Image.js + */ + +/** + * Constant: OpenLayers.Tile.Image.IFrame + * Mixin for tiles that use form-encoded POST requests to get images from + * remote services. Images will be loaded using HTTP-POST into an IFrame. + * + * This mixin will be applied to <OpenLayers.Tile.Image> instances + * configured with <OpenLayers.Tile.Image.maxGetUrlLength> set. + */ +OpenLayers.Tile.Image.IFrame = { + + /** + * Property: useIFrame + * {Boolean} true if we are currently using an IFrame to render POST + * responses, false if we are using an img element to render GET responses. + */ + useIFrame: null, + + /** + * Property: blankImageUrl + * {String} Using a data scheme url is not supported by all browsers, but + * we don't care because we either set it as css backgroundImage, or the + * image's display style is set to "none" when we use it. + */ + blankImageUrl: "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAQAIBRAA7", + + /** + * Method: draw + * Set useIFrame in the instance, and operate the image/iframe switch. + * Then call Tile.Image.draw. + * + * Returns: + * {Boolean} + */ + draw: function() { + var draw = OpenLayers.Tile.Image.prototype.shouldDraw.call(this); + if(draw) { + + // this.url isn't set to the currect value yet, so we call getURL + // on the layer and store the result in a local variable + var url = this.layer.getURL(this.bounds); + + var usedIFrame = this.useIFrame; + this.useIFrame = this.maxGetUrlLength !== null && + !this.layer.async && + url.length > this.maxGetUrlLength; + + var fromIFrame = usedIFrame && !this.useIFrame; + var toIFrame = !usedIFrame && this.useIFrame; + + if(fromIFrame || toIFrame) { + + // Switching between GET (image) and POST (iframe). + + // We remove the imgDiv (really either an image or an iframe) + // from the frame and set it to null to make sure initImage + // will call getImage. + + if(this.imgDiv && this.imgDiv.parentNode === this.frame) { + this.frame.removeChild(this.imgDiv); + } + this.imgDiv = null; + + // And if we had an iframe we also remove the event pane. + + if(fromIFrame) { + this.frame.removeChild(this.frame.firstChild); + } + } + } + return OpenLayers.Tile.Image.prototype.draw.apply(this, arguments); + }, + + /** + * Method: getImage + * Creates the content for the frame on the tile. + */ + getImage: 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); + } + + 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('<iframe name="'+id+'">'); + + // IFrames in older IE versions are not transparent, if you set + // the backgroundColor transparent. This is a workaround to get + // transparent iframes. + iframe.style.backgroundColor = '#FFFFFF'; + iframe.style.filter = 'chroma(color=#FFFFFF)'; + } + else { + iframe = document.createElement('iframe'); + iframe.style.backgroundColor = 'transparent'; + + // iframe.name needs to be an unique id, otherwise it + // could happen that other iframes are overwritten. + iframe.name = id; + } + + // some special properties to avoid scaling the images and scrollbars + // in the iframe + iframe.scrolling = 'no'; + iframe.marginWidth = '0px'; + iframe.marginHeight = '0px'; + iframe.frameBorder = '0'; + + iframe.style.position = "absolute"; + iframe.style.width = "100%"; + iframe.style.height = "100%"; + + if (this.layer.opacity < 1) { + OpenLayers.Util.modifyDOMElement(iframe, null, null, null, + null, null, null, this.layer.opacity); + } + this.frame.appendChild(iframe); + this.imgDiv = iframe; + return iframe; + } else { + return OpenLayers.Tile.Image.prototype.getImage.apply(this, arguments); + } + }, + + /** + * Method: createRequestForm + * Create the html <form> element with width, height, bbox and all + * parameters specified in the layer params. + * + * Returns: + * {DOMElement} The form element which sends the HTTP-POST request to the + * WMS. + */ + createRequestForm: function() { + // creation of the form element + var form = document.createElement('form'); + form.method = 'POST'; + var cacheId = this.layer.params["_OLSALT"]; + cacheId = (cacheId ? cacheId + "_" : "") + this.bounds.toBBOX(); + form.action = OpenLayers.Util.urlAppend(this.layer.url, cacheId); + form.target = this.id + '_iFrame'; + + // adding all parameters in layer params as hidden fields to the html + // form element + var imageSize = this.layer.getImageSize(), + params = OpenLayers.Util.getParameters(this.url), + field; + + for(var par in params) { + field = document.createElement('input'); + field.type = 'hidden'; + field.name = par; + field.value = params[par]; + form.appendChild(field); + } + + return form; + }, + + /** + * Method: setImgSrc + * Sets the source for the tile image + * + * Parameters: + * url - {String} + */ + setImgSrc: function(url) { + if (this.useIFrame === true) { + if (url) { + var form = this.createRequestForm(); + this.frame.appendChild(form); + form.submit(); + this.frame.removeChild(form); + } else if (this.imgDiv.parentNode === this.frame) { + // we don't reuse iframes to avoid caching issues + this.frame.removeChild(this.imgDiv); + this.imgDiv = null; + } + } else { + OpenLayers.Tile.Image.prototype.setImgSrc.apply(this, arguments); + } + }, + + /** + * Method: onImageLoad + * Handler for the image onload event + */ + onImageLoad: function() { + //TODO de-uglify opacity handling + OpenLayers.Tile.Image.prototype.onImageLoad.apply(this, arguments); + if (this.useIFrame === true) { + this.imgDiv.style.opacity = 1; + this.frame.style.opacity = this.layer.opacity; + } + }, + + /** + * Method: createBackBuffer + * Override createBackBuffer to do nothing when we use an iframe. Moving an + * iframe from one element to another makes it necessary to reload the iframe + * because its content is lost. So we just give up. + * + * Returns: + * {DOMElement} + */ + createBackBuffer: function() { + var backBuffer; + if(this.useIFrame === false) { + backBuffer = OpenLayers.Tile.Image.prototype.createBackBuffer.call(this); + } + return backBuffer; + } +}; diff --git a/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js b/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js new file mode 100644 index 0000000..2836ee0 --- /dev/null +++ b/misc/openlayers/lib/OpenLayers/Tile/UTFGrid.js @@ -0,0 +1,252 @@ +/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for + * full list of contributors). Published under the 2-clause BSD license. + * See license.txt in the OpenLayers distribution or repository for the + * full text of the license. */ + + +/** + * @requires OpenLayers/Tile.js + * @requires OpenLayers/Format/JSON.js + * @requires OpenLayers/Request.js + */ + +/** + * Class: OpenLayers.Tile.UTFGrid + * Instances of OpenLayers.Tile.UTFGrid are used to manage + * UTFGrids. This is an unusual tile type in that it doesn't have a + * rendered image; only a 'hit grid' that can be used to + * look up feature attributes. + * + * See the <OpenLayers.Tile.UTFGrid> constructor for details on constructing a + * new instance. + * + * Inherits from: + * - <OpenLayers.Tile> + */ +OpenLayers.Tile.UTFGrid = OpenLayers.Class(OpenLayers.Tile, { + + /** + * Property: url + * {String} + * The URL of the UTFGrid file being requested. Provided by the <getURL> + * method. + */ + url: null, + + /** + * Property: utfgridResolution + * {Number} + * Ratio of the pixel width to the width of a UTFGrid data point. If an + * entry in the grid represents a 4x4 block of pixels, the + * utfgridResolution would be 4. Default is 2. + */ + utfgridResolution: 2, + + /** + * Property: json + * {Object} + * Stores the parsed JSON tile data structure. + */ + json: null, + + /** + * Property: format + * {OpenLayers.Format.JSON} + * Parser instance used to parse JSON for cross browser support. The native + * JSON.parse method will be used where available (all except IE<8). + */ + format: null, + + /** + * Constructor: OpenLayers.Tile.UTFGrid + * Constructor for a new <OpenLayers.Tile.UTFGrid> instance. + * + * Parameters: + * layer - {<OpenLayers.Layer>} layer that the tile will go in. + * position - {<OpenLayers.Pixel>} + * bounds - {<OpenLayers.Bounds>} + * url - {<String>} Deprecated. Remove me in 3.0. + * size - {<OpenLayers.Size>} + * options - {Object} + */ + + /** + * APIMethod: destroy + * Clean up. + */ + destroy: function() { + this.clear(); + OpenLayers.Tile.prototype.destroy.apply(this, arguments); + }, + + /** + * Method: draw + * Check that a tile should be drawn, and draw it. + * In the case of UTFGrids, "drawing" it means fetching and + * parsing the json. + * + * Returns: + * {Boolean} Was a tile drawn? + */ + draw: function() { + var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments); + if (drawn) { + if (this.isLoading) { + this.abortLoading(); + //if we're already loading, send 'reload' instead of 'loadstart'. + this.events.triggerEvent("reload"); + } else { + this.isLoading = true; + this.events.triggerEvent("loadstart"); + } + this.url = this.layer.getURL(this.bounds); + + if (this.layer.useJSONP) { + // Use JSONP method to avoid xbrowser policy + var ols = new OpenLayers.Protocol.Script({ + url: this.url, + callback: function(response) { + this.isLoading = false; + this.events.triggerEvent("loadend"); + this.json = response.data; + }, + scope: this + }); + ols.read(); + this.request = ols; + } else { + // Use standard XHR + this.request = OpenLayers.Request.GET({ + url: this.url, + callback: function(response) { + this.isLoading = false; + this.events.triggerEvent("loadend"); + if (response.status === 200) { + this.parseData(response.responseText); + } + }, + scope: this + }); + } + } else { + this.unload(); + } + return drawn; + }, + + /** + * Method: abortLoading + * Cancel a pending request. + */ + abortLoading: function() { + if (this.request) { + this.request.abort(); + delete this.request; + } + this.isLoading = false; + }, + + /** + * Method: getFeatureInfo + * Get feature information associated with a pixel offset. If the pixel + * offset corresponds to a feature, the returned object will have id + * and data properties. Otherwise, null will be returned. + * + * + * Parameters: + * i - {Number} X-axis pixel offset (from top left of tile) + * j - {Number} Y-axis pixel offset (from top left of tile) + * + * Returns: + * {Object} Object with feature id and data properties corresponding to the + * given pixel offset. + */ + getFeatureInfo: function(i, j) { + var info = null; + if (this.json) { + var id = this.getFeatureId(i, j); + if (id !== null) { + info = {id: id, data: this.json.data[id]}; + } + } + return info; + }, + + /** + * Method: getFeatureId + * Get the identifier for the feature associated with a pixel offset. + * + * Parameters: + * i - {Number} X-axis pixel offset (from top left of tile) + * j - {Number} Y-axis pixel offset (from top left of tile) + * + * Returns: + * {Object} The feature identifier corresponding to the given pixel offset. + * Returns null if pixel doesn't correspond to a feature. + */ + getFeatureId: function(i, j) { + var id = null; + if (this.json) { + var resolution = this.utfgridResolution; + var row = Math.floor(j / resolution); + var col = Math.floor(i / resolution); + var charCode = this.json.grid[row].charCodeAt(col); + var index = this.indexFromCharCode(charCode); + var keys = this.json.keys; + if (!isNaN(index) && (index in keys)) { + id = keys[index]; + } + } + return id; + }, + + /** + * Method: indexFromCharCode + * Given a character code for one of the UTFGrid "grid" characters, + * resolve the integer index for the feature id in the UTFGrid "keys" + * array. + * + * Parameters: + * charCode - {Integer} + * + * Returns: + * {Integer} Index for the feature id from the keys array. + */ + indexFromCharCode: function(charCode) { + if (charCode >= 93) { + charCode--; + } + if (charCode >= 35) { + charCode --; + } + return charCode - 32; + }, + + /** + * Method: parseData + * Parse the JSON from a request + * + * Parameters: + * str - {String} UTFGrid as a JSON string. + * + * Returns: + * {Object} parsed javascript data + */ + parseData: function(str) { + if (!this.format) { + this.format = new OpenLayers.Format.JSON(); + } + this.json = this.format.read(str); + }, + + /** + * Method: clear + * Delete data stored with this tile. + */ + clear: function() { + this.json = null; + }, + + CLASS_NAME: "OpenLayers.Tile.UTFGrid" + +}); |