diff --git a/lib/OpenLayers/Control/UTFGrid.js b/lib/OpenLayers/Control/UTFGrid.js new file mode 100644 index 0000000000..0b8249b4ef --- /dev/null +++ b/lib/OpenLayers/Control/UTFGrid.js @@ -0,0 +1,285 @@ +/* Copyright (c) 2006-2012 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/Control.js + */ + +/** + * Class: OpenLayers.Control.UTFGrid + * The UTFGrid control displays .... + * pointer, as it is moved about the map. + * + * Inherits from: + * - + */ +OpenLayers.Control.UTFGrid = OpenLayers.Class(OpenLayers.Control, { + + /** + * APIProperty: autoActivate + * {Boolean} Activate the control when it is added to a map. Default is + * true. + */ + autoActivate: true, + + /** + * Property: element + * {DOMElement} + */ + element: null, + + /** + * APIProperty: prefix + * {String} + */ + prefix: '', + + /** + * APIProperty: separator + * {String} + */ + separator: ', ', + + /** + * APIProperty: suffix + * {String} + */ + suffix: '', + + /** + * APIProperty: numDigits + * {Integer} + */ + numDigits: 5, + + /** + * APIProperty: granularity + * {Integer} + */ + granularity: 10, + + /** + * APIProperty: emptyString + * {String} Set this to some value to set when the mouse is outside the + * map. + */ + emptyString: null, + + /** + * Property: lastXy + * {} + */ + lastXy: null, + + /** + * APIProperty: displayProjection + * {} The projection in which the + * mouse position is displayed + */ + displayProjection: null, + + /** + * Constructor: OpenLayers.Control.UTFGrid + * + * Parameters: + * options - {Object} Options for control. + */ + + /** + * Method: destroy + */ + destroy: function() { + this.deactivate(); + OpenLayers.Control.prototype.destroy.apply(this, arguments); + }, + + /** + * APIMethod: activate + */ + activate: function() { + if (OpenLayers.Control.prototype.activate.apply(this, arguments)) { + this.map.events.register('mousemove', this, this.redraw); + this.map.events.register('mouseout', this, this.reset); + //this.map.events.register('click', this, this.redraw); + this.redraw(); + return true; + } else { + return false; + } + }, + + /** + * APIMethod: deactivate + */ + deactivate: function() { + if (OpenLayers.Control.prototype.deactivate.apply(this, arguments)) { + //this.map.events.unregister('click', this, this.redraw); + this.map.events.unregister('mousemove', this, this.redraw); + this.map.events.unregister('mouseout', this, this.reset); + this.element.innerHTML = ""; + return true; + } else { + return false; + } + }, + + /** + * Method: draw + * {DOMElement} + */ + draw: function() { + OpenLayers.Control.prototype.draw.apply(this, arguments); + + if (!this.element) { + this.div.left = ""; + this.div.top = ""; + this.element = this.div; + } + + return this.div; + }, + + /** + * Method: redraw + */ + redraw: function(evt) { + + var lonLat; + + if (evt == null) { + this.reset(); + return; + } else { + if (this.lastXy == null || + Math.abs(evt.xy.x - this.lastXy.x) > this.granularity || + Math.abs(evt.xy.y - this.lastXy.y) > this.granularity) + { + this.lastXy = evt.xy; + return; + } + + lonLat = this.map.getLonLatFromPixel(evt.xy); + if (!lonLat) { + // map has not yet been properly initialized + return; + } + this.lastXy = evt.xy; + } + + var newHtml = this.formatOutput(lonLat); + + var layers = this.findLayers(); + if (layers.length > 0) { + var layer; + for (var i=0, len=layers.length; i= tileSize) || + (Math.floor(info.j) >= tileSize)) alert("TOO BIG"); + */ + var attrs = null; + var resolution = 4; + if (tile !== null && typeof(tile) !== 'undefined') { + var data = tile.json + if (data !== null) { + //val = data; + var code = this.resolveCode(data.grid[ + Math.floor((info.j) / resolution) + ].charCodeAt( + Math.floor((info.i) / resolution) + )); + attrs = data.data[data.keys[code]]; + this.callback(attrs); + } + } + } + } + }, + + /** + * Method: callback + * Takes the attrs and does somethings with them + * this is a default (intended to be overridden) + */ + callback: function(attrs) { + if (attrs !== null && typeof(attrs) !== 'undefined') { + val = "

Attributes

    "; + for(var index in attrs) { + val += "
  • " + index + " : " + attrs[index] + "
  • "; + } + val += "
"; + //var val = attrs.NAME + ": population " + attrs.POP2005; + this.element.innerHTML = val; + return true; + } else { + this.element.innerHTML = ''; + return false; + } + }, + + /** + * Method: resolveCode + * Resolve the UTF-8 encoding stored in grids to simple + * number values. + * See the [utfgrid section of the mbtiles spec](https://github.com/mapbox/mbtiles-spec/blob/master/1.1/utfgrid.md) + * for details. + */ + resolveCode: function(key) { + if (key >= 93) key--; + if (key >= 35) key--; + key -= 32; + return key; + }, + + /** + * Method: reset + */ + reset: function(evt) { + if (this.emptyString != null) { + this.element.innerHTML = this.emptyString; + } + }, + + /** + * Method: formatOutput + * Override to provide custom display output + * + * Parameters: + * lonLat - {} Location to display + */ + formatOutput: function(lonLat) { + var digits = parseInt(this.numDigits); + var newHtml = + this.prefix + + lonLat.lon.toFixed(digits) + + this.separator + + lonLat.lat.toFixed(digits) + + this.suffix; + return newHtml; + }, + + /** + * Method: findLayers + * Internal method to get the layers, independent of whether we are + * inspecting the map or using a client-provided array + */ + findLayers: function() { + var candidates = this.layers || this.map.layers; + var layers = []; + var layer; + for (var i=candidates.length-1; i>=0; --i) { + layer = candidates[i]; + if (layer instanceof OpenLayers.Layer.UTFGrid ) { + layers.push(layer); + } + } + return layers; + }, + + CLASS_NAME: "OpenLayers.Control.UTFGrid" +}); diff --git a/lib/OpenLayers/Layer/UTFGrid.js b/lib/OpenLayers/Layer/UTFGrid.js new file mode 100644 index 0000000000..850fd17650 --- /dev/null +++ b/lib/OpenLayers/Layer/UTFGrid.js @@ -0,0 +1,237 @@ +/* Copyright (c) 2006-2012 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/Layer/Grid.js + */ + +/** + * Class: OpenLayers.Layer.UTFGrid + * TODO + * + * Inherits from: + * - + */ +OpenLayers.Layer.UTFGrid = OpenLayers.Class(OpenLayers.Layer.Grid, { + + /** + * APIProperty: isBaseLayer + * Default is true, as this is designed to be a base tile source. + */ + isBaseLayer: false, + + /** + * APIProperty: sphericalMecator + * Whether the tile extents should be set to the defaults for + * spherical mercator. Useful for things like OpenStreetMap. + * Default is false, except for the OSM subclass. + */ + sphericalMercator: false, + + /** + * APIProperty: zoomOffset + * {Number} If your cache has more zoom levels than you want to provide + * access to with this layer, supply a zoomOffset. This zoom offset + * is added to the current map zoom level to determine the level + * for a requested tile. For example, if you supply a zoomOffset + * of 3, when the map is at the zoom 0, tiles will be requested from + * level 3 of your cache. Default is 0 (assumes cache level and map + * zoom are equivalent). Using is an alternative to + * setting if you only want to expose a subset + * of the server resolutions. + */ + zoomOffset: 0, + + /** + * APIProperty: serverResolutions + * {Array} A list of all resolutions available on the server. Only set this + * property if the map resolutions differ from the server. This + * property serves two purposes. (a) can include + * resolutions that the server supports and that you don't want to + * provide with this layer; you can also look at , which is + * an alternative to for that specific purpose. + * (b) The map can work with resolutions that aren't supported by + * the server, i.e. that aren't in . When the + * map is displayed in such a resolution data for the closest + * server-supported resolution is loaded and the layer div is + * stretched as necessary. + */ + serverResolutions: null, + + /** + * Constructor: OpenLayers.Layer.UTFGrid + * + * Parameters: + * name - {String} + * url - {String} + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, url, options) { + if (options && options.sphericalMercator || this.sphericalMercator) { + options = OpenLayers.Util.extend({ + maxExtent: new OpenLayers.Bounds( + -128 * 156543.03390625, + -128 * 156543.03390625, + 128 * 156543.03390625, + 128 * 156543.03390625 + ), + maxResolution: 156543.03390625, + numZoomLevels: 19, + units: "m", + projection: "EPSG:900913" + }, options); + } + url = url || this.url; + name = name || this.name; + var newArguments = [name, url, {}, options]; + OpenLayers.Layer.Grid.prototype.initialize.apply(this, newArguments); + }, + + /** + * APIMethod: clone + * Create a clone of this layer + * + * Parameters: + * obj - {Object} Is this ever used? + * + * Returns: + * {} An exact clone of this OpenLayers.Layer.UTFGrid + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer.UTFGrid(this.name, + this.url, + this.getOptions()); + } + + //get all additions from superclasses + obj = OpenLayers.Layer.Grid.prototype.clone.apply(this, [obj]); + + return obj; + }, + + /** + * Method: getURL + * + * Parameters: + * bounds - {} + * + * Returns: + * {String} A string with the layer's url and parameters and also the + * passed-in bounds and appropriate tile size specified as + * parameters + */ + getURL: function (bounds) { + var xyz = this.getXYZ(bounds); + var url = this.url; + if (OpenLayers.Util.isArray(url)) { + var s = '' + xyz.x + xyz.y + xyz.z; + url = this.selectUrl(s, url); + } + + return OpenLayers.String.format(url, xyz); + }, + + /** + * Method: getTileInfo + * Get tile information for a given location at the current map resolution. + * + * Parameters: + * loc - {} The tile class to use for this layer. + * Defaults is OpenLayers.Tile (not Tile.Image) + */ + tileClass: OpenLayers.Tile.UTFGrid, + + /** + * Method: getXYZ + * Calculates x, y and z for the given bounds. + * + * Parameters: + * bounds - {} + * + * Returns: + * {Object} - an object with x, y and z properties. + */ + getXYZ: function(bounds) { + var res = this.getServerResolution(); + var x = Math.round((bounds.left - this.maxExtent.left) / + (res * this.tileSize.w)); + var y = Math.round((this.maxExtent.top - bounds.top) / + (res * this.tileSize.h)); + var resolutions = this.serverResolutions || this.resolutions; + var z = this.zoomOffset == 0 ? + OpenLayers.Util.indexOf(resolutions, res) : + this.getServerZoom() + this.zoomOffset; + + var limit = Math.pow(2, z); + if (this.wrapDateLine) + { + x = ((x % limit) + limit) % limit; + } + + return {'x': x, 'y': y, 'z': z}; + }, + + /* APIMethod: setMap + * When the layer is added to a map, then we can fetch our origin + * (if we don't have one.) + * + * Parameters: + * map - {} + */ + setMap: function(map) { + OpenLayers.Layer.Grid.prototype.setMap.apply(this, arguments); + if (!this.tileOrigin) { + this.tileOrigin = new OpenLayers.LonLat(this.maxExtent.left, + this.maxExtent.top); + } + }, + + CLASS_NAME: "OpenLayers.Layer.UTFGrid" +}); diff --git a/lib/OpenLayers/Tile/UTFGrid.js b/lib/OpenLayers/Tile/UTFGrid.js new file mode 100644 index 0000000000..403c82b398 --- /dev/null +++ b/lib/OpenLayers/Tile/UTFGrid.js @@ -0,0 +1,399 @@ +/* Copyright (c) 2006-2012 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 + */ + +/** + * Class: OpenLayers.Tile.UTFGrid + * Instances of OpenLayers.Tile.UTFGrid are used to manage the image tiles + * TODO + * constructor. + * + * Inherits from: + * - + */ +OpenLayers.Tile.UTFGrid = OpenLayers.Class(OpenLayers.Tile, { + + /** + * Property: url + * {String} The URL of the image being requested. No default. Filled in by + * layer.getURL() function. + */ + url: null, + + /** + * Property: imgDiv + * TODO + * {HTMLImageElement} The image for this tile. + */ + json: null, + + /** + * Property: imageReloadAttempts + * {Integer} Attempts to load the image. + */ + imageReloadAttempts: 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, + + /** 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 instance. + * + * Parameters: + * layer - {} layer that the tile will go in. + * position - {} + * bounds - {} + * url - {} Deprecated. Remove me in 3.0. + * size - {} + * options - {Object} + */ + initialize: function(layer, position, bounds, url, size, options) { + OpenLayers.Tile.prototype.initialize.apply(this, arguments); + this.url = url; //deprecated remove me + }, + + /** + * 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? + */ + draw: function() { + var drawn = OpenLayers.Tile.prototype.draw.apply(this, arguments); + if (drawn) { + 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"); + } + this.url = this.layer.getURL(this.bounds); + + var resp = new OpenLayers.Protocol.Response({requestType: "read"}); + resp.priv = OpenLayers.Request.GET({ + url: this.url, + callback: this.parseData, + scope: this + }); + + /* + * TODO Investigate JSONP method to avoid xbrowser polucy + * + grid = function(e) { + console.log("-----------"); + console.log(e); + }; + + var ols = new OpenLayers.Protocol.Script({ + url: "http://tiles/world_utfgrid/2/2/1.json", + callback: grid, + scope: this + }); + var res = ols.read(); + console.log(res.priv); + */ + + this.positionTile(); + //this.renderTile(); + } else { + this.unload(); + } + return drawn; + }, + + /** + * Method: parseJSON + * Parse the JSON from a request + * + * Returns: + * {Object} parsed javascript data + */ + parseData: function(req) { + if (req.status == 200) { + var text = req.responseText; + this.json = JSON.parse(text); + } + }, + + /** + * Method: renderTile + * Internal function to actually initialize the image tile, + * position it correctly, and set its url. + */ + renderTile: function() { + this.layer.div.appendChild(this.getTile()); + if (this.layer.async) { + // 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 '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 { + // 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; + style.left = this.position.x + "%"; + style.top = this.position.y + "%"; + style.width = this.size.w + "%"; + style.height = this.size.h + "%"; + }, + + /** + * 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() { + var img = this.imgDiv; + if (img) { + OpenLayers.Event.stopObservingElement(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"); + } + }, + + /** + * Method: getImage + * Returns or creates and returns the tile image. + */ + getImage: function() { + if (!this.imgDiv) { + this.imgDiv = document.createElement("img"); + + this.imgDiv.className = "olTileImage"; + // avoid image gallery menu in IE6 + this.imgDiv.galleryImg = "no"; + + var style = this.imgDiv.style; + if (this.layer.gutter) { + var left = this.layer.gutter / this.layer.tileSize.w * 100; + var 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) + "%"; + } else { + style.width = "100%"; + style.height = "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; + }, + + /** + * Method: initImage + * Creates the content for the frame on the tile. + */ + initImage: function() { + var img = this.getImage(); + if (this.url && img.getAttribute("src") == this.url) { + this.onImageLoad(); + } else { + // We need to start with a blank image, to make sure that no + // loading image placeholder and no old image is displayed when we + // set the display style to "" in onImageLoad, which is called + // after the image is loaded, but before it is rendered. So we set + // a blank image with a data scheme URI, and register for the load + // event (for browsers that support data scheme) and the error + // event (for browsers that don't). In the event handler, we set + // the final src. + var load = OpenLayers.Function.bind(function() { + OpenLayers.Event.stopObservingElement(img); + 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); + }, this); + if (img.getAttribute("src") == this.blankImageUrl) { + load(); + } else { + OpenLayers.Event.observe(img, "load", load); + OpenLayers.Event.observe(img, "error", load); + img.src = this.blankImageUrl; + } + } + }, + + /** + * 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; + img.style.visibility = 'hidden'; + img.style.opacity = 0; + if (url) { + img.src = url; + } + }, + + /** + * 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; + OpenLayers.Event.stopObservingElement(img); + + img.style.visibility = 'inherit'; + img.style.opacity = this.layer.opacity; + + this.isLoading = false; + this.events.triggerEvent("loadend"); + + // IE<7 needs a reflow when the tiles are loaded because of the + // percentage based positioning. Otherwise nothing is shown + // until the user interacts with the map in some way. + if (parseFloat(navigator.appVersion.split("MSIE")[1]) < 7 && + this.layer && this.layer.div) { + var span = document.createElement("span"); + span.style.display = "none"; + var layerDiv = this.layer.div; + layerDiv.appendChild(span); + window.setTimeout(function() { + span.parentNode === layerDiv && span.parentNode.removeChild(span); + }, 0); + } + + 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.onImageLoad(); + } + } + }, + + CLASS_NAME: "OpenLayers.Tile.Image" + +});