--- a/labs/openlayers/lib/OpenLayers/Layer.js +++ b/labs/openlayers/lib/OpenLayers/Layer.js @@ -1,1 +1,1305 @@ - +/* Copyright (c) 2006-2010 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/Map.js + * @requires OpenLayers/Projection.js + */ + +/** + * Class: OpenLayers.Layer + */ +OpenLayers.Layer = OpenLayers.Class({ + + /** + * APIProperty: id + * {String} + */ + id: null, + + /** + * APIProperty: name + * {String} + */ + name: null, + + /** + * APIProperty: div + * {DOMElement} + */ + div: null, + + /** + * Property: opacity + * {Float} The layer's opacity. Float number between 0.0 and 1.0. + */ + opacity: null, + + /** + * APIProperty: alwaysInRange + * {Boolean} If a layer's display should not be scale-based, this should + * be set to true. This will cause the layer, as an overlay, to always + * be 'active', by always returning true from the calculateInRange() + * function. + * + * If not explicitly specified for a layer, its value will be + * determined on startup in initResolutions() based on whether or not + * any scale-specific properties have been set as options on the + * layer. If no scale-specific options have been set on the layer, we + * assume that it should always be in range. + * + * See #987 for more info. + */ + alwaysInRange: null, + + /** + * Constant: EVENT_TYPES + * {Array(String)} Supported application event types. Register a listener + * for a particular event with the following syntax: + * (code) + * layer.events.register(type, obj, listener); + * (end) + * + * Listeners will be called with a reference to an event object. The + * properties of this event depends on exactly what happened. + * + * All event objects have at least the following properties: + * object - {Object} A reference to layer.events.object. + * element - {DOMElement} A reference to layer.events.element. + * + * Supported map event types: + * loadstart - Triggered when layer loading starts. + * loadend - Triggered when layer loading ends. + * loadcancel - Triggered when layer loading is canceled. + * visibilitychanged - Triggered when layer visibility is changed. + * move - Triggered when layer moves (triggered with every mousemove + * during a drag). + * moveend - Triggered when layer is done moving, object passed as + * argument has a zoomChanged boolean property which tells that the + * zoom has changed. + */ + EVENT_TYPES: ["loadstart", "loadend", "loadcancel", "visibilitychanged", + "move", "moveend"], + + /** + * Constant: RESOLUTION_PROPERTIES + * {Array} The properties that are used for calculating resolutions + * information. + */ + RESOLUTION_PROPERTIES: [ + 'scales', 'resolutions', + 'maxScale', 'minScale', + 'maxResolution', 'minResolution', + 'numZoomLevels', 'maxZoomLevel' + ], + + /** + * APIProperty: events + * {} + */ + events: null, + + /** + * APIProperty: map + * {} This variable is set when the layer is added to + * the map, via the accessor function setMap(). + */ + map: null, + + /** + * APIProperty: isBaseLayer + * {Boolean} Whether or not the layer is a base layer. This should be set + * individually by all subclasses. Default is false + */ + isBaseLayer: false, + + /** + * Property: alpha + * {Boolean} The layer's images have an alpha channel. Default is false. + */ + alpha: false, + + /** + * APIProperty: displayInLayerSwitcher + * {Boolean} Display the layer's name in the layer switcher. Default is + * true. + */ + displayInLayerSwitcher: true, + + /** + * APIProperty: visibility + * {Boolean} The layer should be displayed in the map. Default is true. + */ + visibility: true, + + /** + * APIProperty: attribution + * {String} Attribution string, displayed when an + * has been added to the map. + */ + attribution: null, + + /** + * Property: inRange + * {Boolean} The current map resolution is within the layer's min/max + * range. This is set in whenever the zoom + * changes. + */ + inRange: false, + + /** + * Propery: imageSize + * {} For layers with a gutter, the image is larger than + * the tile by twice the gutter in each dimension. + */ + imageSize: null, + + /** + * Property: imageOffset + * {} For layers with a gutter, the image offset + * represents displacement due to the gutter. + */ + imageOffset: null, + + // OPTIONS + + /** + * Property: options + * {Object} An optional object whose properties will be set on the layer. + * Any of the layer properties can be set as a property of the options + * object and sent to the constructor when the layer is created. + */ + options: null, + + /** + * APIProperty: eventListeners + * {Object} If set as an option at construction, the eventListeners + * object will be registered with . Object + * structure must be a listeners object as shown in the example for + * the events.on method. + */ + eventListeners: null, + + /** + * APIProperty: gutter + * {Integer} Determines the width (in pixels) of the gutter around image + * tiles to ignore. By setting this property to a non-zero value, + * images will be requested that are wider and taller than the tile + * size by a value of 2 x gutter. This allows artifacts of rendering + * at tile edges to be ignored. Set a gutter value that is equal to + * half the size of the widest symbol that needs to be displayed. + * Defaults to zero. Non-tiled layers always have zero gutter. + */ + gutter: 0, + + /** + * APIProperty: projection + * {} or {} Set in the layer options to + * override the default projection string this layer - also set maxExtent, + * maxResolution, and units if appropriate. Can be either a string or + * an object when created -- will be converted + * to an object when setMap is called if a string is passed. + */ + projection: null, + + /** + * APIProperty: units + * {String} The layer map units. Defaults to 'degrees'. Possible values + * are 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'. + */ + units: null, + + /** + * APIProperty: scales + * {Array} An array of map scales in descending order. The values in the + * array correspond to the map scale denominator. Note that these + * values only make sense if the display (monitor) resolution of the + * client is correctly guessed by whomever is configuring the + * application. In addition, the units property must also be set. + * Use instead wherever possible. + */ + scales: null, + + /** + * APIProperty: resolutions + * {Array} A list of map resolutions (map units per pixel) in descending + * order. If this is not set in the layer constructor, it will be set + * based on other resolution related properties (maxExtent, + * maxResolution, maxScale, etc.). + */ + resolutions: null, + + /** + * APIProperty: maxExtent + * {} The center of these bounds will not stray outside + * of the viewport extent during panning. In addition, if + * is set to false, data will not be + * requested that falls completely outside of these bounds. + */ + maxExtent: null, + + /** + * APIProperty: minExtent + * {} + */ + minExtent: null, + + /** + * APIProperty: maxResolution + * {Float} Default max is 360 deg / 256 px, which corresponds to + * zoom level 0 on gmaps. Specify a different value in the layer + * options if you are not using a geographic projection and + * displaying the whole world. + */ + maxResolution: null, + + /** + * APIProperty: minResolution + * {Float} + */ + minResolution: null, + + /** + * APIProperty: numZoomLevels + * {Integer} + */ + numZoomLevels: null, + + /** + * APIProperty: minScale + * {Float} + */ + minScale: null, + + /** + * APIProperty: maxScale + * {Float} + */ + maxScale: null, + + /** + * APIProperty: displayOutsideMaxExtent + * {Boolean} Request map tiles that are completely outside of the max + * extent for this layer. Defaults to false. + */ + displayOutsideMaxExtent: false, + + /** + * APIProperty: wrapDateLine + * {Boolean} #487 for more info. + */ + wrapDateLine: false, + + /** + * APIProperty: transitionEffect + * {String} The transition effect to use when the map is panned or + * zoomed. + * + * There are currently two supported values: + * - *null* No transition effect (the default). + * - *resize* Existing tiles are resized on zoom to provide a visual + * effect of the zoom having taken place immediately. As the + * new tiles become available, they are drawn over top of the + * resized tiles. + */ + transitionEffect: null, + + /** + * Property: SUPPORTED_TRANSITIONS + * {Array} An immutable (that means don't change it!) list of supported + * transitionEffect values. + */ + SUPPORTED_TRANSITIONS: ['resize'], + + /** + * Property: metadata + * {Object} This object can be used to store additional information on a + * layer object. + */ + metadata: {}, + + /** + * Constructor: OpenLayers.Layer + * + * Parameters: + * name - {String} The layer name + * options - {Object} Hashtable of extra options to tag onto the layer + */ + initialize: function(name, options) { + + this.addOptions(options); + + this.name = name; + + if (this.id == null) { + + this.id = OpenLayers.Util.createUniqueID(this.CLASS_NAME + "_"); + + this.div = OpenLayers.Util.createDiv(this.id); + this.div.style.width = "100%"; + this.div.style.height = "100%"; + this.div.dir = "ltr"; + + this.events = new OpenLayers.Events(this, this.div, + this.EVENT_TYPES); + if(this.eventListeners instanceof Object) { + this.events.on(this.eventListeners); + } + + } + + if (this.wrapDateLine) { + this.displayOutsideMaxExtent = true; + } + }, + + /** + * Method: destroy + * Destroy is a destructor: this is to alleviate cyclic references which + * the Javascript garbage cleaner can not take care of on its own. + * + * Parameters: + * setNewBaseLayer - {Boolean} Set a new base layer when this layer has + * been destroyed. Default is true. + */ + destroy: function(setNewBaseLayer) { + if (setNewBaseLayer == null) { + setNewBaseLayer = true; + } + if (this.map != null) { + this.map.removeLayer(this, setNewBaseLayer); + } + this.projection = null; + this.map = null; + this.name = null; + this.div = null; + this.options = null; + + if (this.events) { + if(this.eventListeners) { + this.events.un(this.eventListeners); + } + this.events.destroy(); + } + this.eventListeners = null; + this.events = null; + }, + + /** + * Method: clone + * + * Parameters: + * obj - {} The layer to be cloned + * + * Returns: + * {} An exact clone of this + */ + clone: function (obj) { + + if (obj == null) { + obj = new OpenLayers.Layer(this.name, this.getOptions()); + } + + // catch any randomly tagged-on properties + OpenLayers.Util.applyDefaults(obj, this); + + // a cloned layer should never have its map property set + // because it has not been added to a map yet. + obj.map = null; + + return obj; + }, + + /** + * Method: getOptions + * Extracts an object from the layer with the properties that were set as + * options, but updates them with the values currently set on the + * instance. + * + * Returns: + * {Object} the of the layer, representing the current state. + */ + getOptions: function() { + var options = {}; + for(var o in this.options) { + options[o] = this[o]; + } + return options; + }, + + /** + * APIMethod: setName + * Sets the new layer name for this layer. Can trigger a changelayer event + * on the map. + * + * Parameters: + * newName - {String} The new name. + */ + setName: function(newName) { + if (newName != this.name) { + this.name = newName; + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "name" + }); + } + } + }, + + /** + * APIMethod: addOptions + * + * Parameters: + * newOptions - {Object} + */ + addOptions: function (newOptions) { + + if (this.options == null) { + this.options = {}; + } + + // update our copy for clone + OpenLayers.Util.extend(this.options, newOptions); + + // add new options to this + OpenLayers.Util.extend(this, newOptions); + + // make sure this.projection references a projection object + if(typeof this.projection == "string") { + this.projection = new OpenLayers.Projection(this.projection); + } + + // get the units from the projection, if we have a projection + // and it it has units + if(this.projection && this.projection.getUnits()) { + this.units = this.projection.getUnits(); + } + + // re-initialize resolutions if necessary, i.e. if any of the + // properties of the "properties" array defined below is set + // in the new options + if(this.map) { + var properties = this.RESOLUTION_PROPERTIES.concat( + ["projection", "units", "minExtent", "maxExtent"] + ); + for(var o in newOptions) { + if(newOptions.hasOwnProperty(o) && + OpenLayers.Util.indexOf(properties, o) >= 0) { + + this.initResolutions(); + break; + } + } + } + }, + + /** + * APIMethod: onMapResize + * This function can be implemented by subclasses + */ + onMapResize: function() { + //this function can be implemented by subclasses + }, + + /** + * APIMethod: redraw + * Redraws the layer. Returns true if the layer was redrawn, false if not. + * + * Returns: + * {Boolean} The layer was redrawn. + */ + redraw: function() { + var redrawn = false; + if (this.map) { + + // min/max Range may have changed + this.inRange = this.calculateInRange(); + + // map's center might not yet be set + var extent = this.getExtent(); + + if (extent && this.inRange && this.visibility) { + var zoomChanged = true; + this.moveTo(extent, zoomChanged, false); + this.events.triggerEvent("moveend", + {"zoomChanged": zoomChanged}); + redrawn = true; + } + } + return redrawn; + }, + + /** + * Method: moveTo + * + * Parameters: + * bound - {} + * zoomChanged - {Boolean} Tells when zoom has changed, as layers have to + * do some init work in that case. + * dragging - {Boolean} + */ + moveTo:function(bounds, zoomChanged, dragging) { + var display = this.visibility; + if (!this.isBaseLayer) { + display = display && this.inRange; + } + this.display(display); + }, + + /** + * Method: setMap + * Set the map property for the layer. This is done through an accessor + * so that subclasses can override this and take special action once + * they have their map variable set. + * + * Here we take care to bring over any of the necessary default + * properties from the map. + * + * Parameters: + * map - {} + */ + setMap: function(map) { + if (this.map == null) { + + this.map = map; + + // grab some essential layer data from the map if it hasn't already + // been set + this.maxExtent = this.maxExtent || this.map.maxExtent; + this.minExtent = this.minExtent || this.map.minExtent; + + this.projection = this.projection || this.map.projection; + if (typeof this.projection == "string") { + this.projection = new OpenLayers.Projection(this.projection); + } + + // Check the projection to see if we can get units -- if not, refer + // to properties. + this.units = this.projection.getUnits() || + this.units || this.map.units; + + this.initResolutions(); + + if (!this.isBaseLayer) { + this.inRange = this.calculateInRange(); + var show = ((this.visibility) && (this.inRange)); + this.div.style.display = show ? "" : "none"; + } + + // deal with gutters + this.setTileSize(); + } + }, + + /** + * Method: afterAdd + * Called at the end of the map.addLayer sequence. At this point, the map + * will have a base layer. To be overridden by subclasses. + */ + afterAdd: function() { + }, + + /** + * APIMethod: removeMap + * Just as setMap() allows each layer the possibility to take a + * personalized action on being added to the map, removeMap() allows + * each layer to take a personalized action on being removed from it. + * For now, this will be mostly unused, except for the EventPane layer, + * which needs this hook so that it can remove the special invisible + * pane. + * + * Parameters: + * map - {} + */ + removeMap: function(map) { + //to be overridden by subclasses + }, + + /** + * APIMethod: getImageSize + * + * Parameters: + * bounds - {} optional tile bounds, can be used + * by subclasses that have to deal with different tile sizes at the + * layer extent edges (e.g. Zoomify) + * + * Returns: + * {} The size that the image should be, taking into + * account gutters. + */ + getImageSize: function(bounds) { + return (this.imageSize || this.tileSize); + }, + + /** + * APIMethod: setTileSize + * Set the tile size based on the map size. This also sets layer.imageSize + * and layer.imageOffset for use by Tile.Image. + * + * Parameters: + * size - {} + */ + setTileSize: function(size) { + var tileSize = (size) ? size : + ((this.tileSize) ? this.tileSize : + this.map.getTileSize()); + this.tileSize = tileSize; + if(this.gutter) { + // layers with gutters need non-null tile sizes + //if(tileSize == null) { + // OpenLayers.console.error("Error in layer.setMap() for " + + // this.name + ": layers with " + + // "gutters need non-null tile sizes"); + //} + this.imageOffset = new OpenLayers.Pixel(-this.gutter, + -this.gutter); + this.imageSize = new OpenLayers.Size(tileSize.w + (2*this.gutter), + tileSize.h + (2*this.gutter)); + } + }, + + /** + * APIMethod: getVisibility + * + * Returns: + * {Boolean} The layer should be displayed (if in range). + */ + getVisibility: function() { + return this.visibility; + }, + + /** + * APIMethod: setVisibility + * Set the visibility flag for the layer and hide/show & redraw + * accordingly. Fire event unless otherwise specified + * + * Note that visibility is no longer simply whether or not the layer's + * style.display is set to "block". Now we store a 'visibility' state + * property on the layer class, this allows us to remember whether or + * not we *desire* for a layer to be visible. In the case where the + * map's resolution is out of the layer's range, this desire may be + * subverted. + * + * Parameters: + * visible - {Boolean} Whether or not to display the layer (if in range) + */ + setVisibility: function(visibility) { + if (visibility != this.visibility) { + this.visibility = visibility; + this.display(visibility); + this.redraw(); + if (this.map != null) { + this.map.events.triggerEvent("changelayer", { + layer: this, + property: "visibility" + }); + } + this.events.triggerEvent("visibilitychanged"); + } + }, + + /** + * APIMethod: display + * Hide or show the Layer + * + * Parameters: + * display - {Boolean} + */ + display: function(display) { + if (display != (this.div.style.display != "none")) { + this.div.style.display = (display && this.calculateInRange()) ? "block" : "none"; + } + }, + + /** + * APIMethod: calculateInRange + * + * Returns: + * {Boolean} The layer is displayable at the current map's current + * resolution. Note that if 'alwaysInRange' is true for the layer, + * this function will always return true. + */ + calculateInRange: function() { + var inRange = false; + + if (this.alwaysInRange) { + inRange = true; + } else { + if (this.map) { + var resolution = this.map.getResolution(); + inRange = ( (resolution >= this.minResolution) && + (resolution <= this.maxResolution) ); + } + } + return inRange; + }, + + /** + * APIMethod: setIsBaseLayer + * + * Parameters: + * isBaseLayer - {Boolean} + */ + setIsBaseLayer: function(isBaseLayer) { + if (isBaseLayer != this.isBaseLayer) { + this.isBaseLayer = isBaseLayer; + if (this.map != null) { + this.map.events.triggerEvent("changebaselayer", { + layer: this + }); + } + } + }, + + /********************************************************/ + /* */ + /* Baselayer Functions */ + /* */ + /********************************************************/ + + /** + * Method: initResolutions + * This method's responsibility is to set up the 'resolutions' array + * for the layer -- this array is what the layer will use to interface + * between the zoom levels of the map and the resolution display + * of the layer. + * + * The user has several options that determine how the array is set up. + * + * For a detailed explanation, see the following wiki from the + * openlayers.org homepage: + * http://trac.openlayers.org/wiki/SettingZoomLevels + */ + initResolutions: function() { + + // ok we want resolutions, here's our strategy: + // + // 1. if resolutions are defined in the layer config, use them + // 2. else, if scales are defined in the layer config then derive + // resolutions from these scales + // 3. else, attempt to calculate resolutions from maxResolution, + // minResolution, numZoomLevels, maxZoomLevel set in the + // layer config + // 4. if we still don't have resolutions, and if resolutions + // are defined in the same, use them + // 5. else, if scales are defined in the map then derive + // resolutions from these scales + // 6. else, attempt to calculate resolutions from maxResolution, + // minResolution, numZoomLevels, maxZoomLevel set in the + // map + // 7. hope for the best! + + var i, len; + var props = {}, alwaysInRange = true; + + // get resolution data from layer config + // (we also set alwaysInRange in the layer as appropriate) + for(i=0, len=this.RESOLUTION_PROPERTIES.length; i} A Bounds object which represents the lon/lat + * bounds of the current viewPort. + */ + getExtent: function() { + // just use stock map calculateBounds function -- passing no arguments + // means it will user map's current center & resolution + // + return this.map.calculateBounds(); + }, + + /** + * APIMethod: getZoomForExtent + * + * Parameters: + * bounds - {} + * closest - {Boolean} Find the zoom level that most closely fits the + * specified bounds. Note that this may result in a zoom that does + * not exactly contain the entire extent. + * Default is false. + * + * Returns: + * {Integer} The index of the zoomLevel (entry in the resolutions array) + * for the passed-in extent. We do this by calculating the ideal + * resolution for the given extent (based on the map size) and then + * calling getZoomForResolution(), passing along the 'closest' + * parameter. + */ + getZoomForExtent: function(extent, closest) { + var viewSize = this.map.getSize(); + var idealResolution = Math.max( extent.getWidth() / viewSize.w, + extent.getHeight() / viewSize.h ); + + return this.getZoomForResolution(idealResolution, closest); + }, + + /** + * Method: getDataExtent + * Calculates the max extent which includes all of the data for the layer. + * This function is to be implemented by subclasses. + * + * Returns: + * {} + */ + getDataExtent: function () { + //to be implemented by subclasses + }, + + /** + * APIMethod: getResolutionForZoom + * + * Parameter: + * zoom - {Float} + * + * Returns: + * {Float} A suitable resolution for the specified zoom. + */ + getResolutionForZoom: function(zoom) { + zoom = Math.max(0, Math.min(zoom, this.resolutions.length - 1)); + var resolution; + if(this.map.fractionalZoom) { + var low = Math.floor(zoom); + var high = Math.ceil(zoom); + resolution = this.resolutions[low] - + ((zoom-low) * (this.resolutions[low]-this.resolutions[high])); + } else { + resolution = this.resolutions[Math.round(zoom)]; + } + return resolution; + }, + + /** + * APIMethod: getZoomForResolution + * + * Parameters: + * resolution - {Float} + * closest - {Boolean} Find the zoom level that corresponds to the absolute + * closest resolution, which may result in a zoom whose corresponding + * resolution is actually smaller than we would have desired (if this + * is being called from a getZoomForExtent() call, then this means that + * the returned zoom index might not actually contain the entire + * extent specified... but it'll be close). + * Default is false. + * + * Returns: + * {Integer} The index of the zoomLevel (entry in the resolutions array) + * that corresponds to the best fit resolution given the passed in + * value and the 'closest' specification. + */ + getZoomForResolution: function(resolution, closest) { + var zoom; + if(this.map.fractionalZoom) { + var lowZoom = 0; + var highZoom = this.resolutions.length - 1; + var highRes = this.resolutions[lowZoom]; + var lowRes = this.resolutions[highZoom]; + var res; + for(var i=0, len=this.resolutions.length; i= resolution) { + highRes = res; + lowZoom = i; + } + if(res <= resolution) { + lowRes = res; + highZoom = i; + break; + } + } + var dRes = highRes - lowRes; + if(dRes > 0) { + zoom = lowZoom + ((highRes - resolution) / dRes); + } else { + zoom = lowZoom; + } + } else { + var diff; + var minDiff = Number.POSITIVE_INFINITY; + for(var i=0, len=this.resolutions.length; i minDiff) { + break; + } + minDiff = diff; + } else { + if (this.resolutions[i] < resolution) { + break; + } + } + } + zoom = Math.max(0, i-1); + } + return zoom; + }, + + /** + * APIMethod: getLonLatFromViewPortPx + * + * Parameters: + * viewPortPx - {} + * + * Returns: + * {} An OpenLayers.LonLat which is the passed-in + * view port , translated into lon/lat by the layer. + */ + getLonLatFromViewPortPx: function (viewPortPx) { + var lonlat = null; + if (viewPortPx != null) { + var size = this.map.getSize(); + var center = this.map.getCenter(); + if (center) { + var res = this.map.getResolution(); + + var delta_x = viewPortPx.x - (size.w / 2); + var delta_y = viewPortPx.y - (size.h / 2); + + lonlat = new OpenLayers.LonLat(center.lon + delta_x * res , + center.lat - delta_y * res); + + if (this.wrapDateLine) { + lonlat = lonlat.wrapDateLine(this.maxExtent); + } + } // else { DEBUG STATEMENT } + } + return lonlat; + }, + + /** + * APIMethod: getViewPortPxFromLonLat + * Returns a pixel location given a map location. This method will return + * fractional pixel values. + * + * Parameters: + * lonlat - {} + * + * Returns: + * {} An which is the passed-in + * ,translated into view port pixels. + */ + getViewPortPxFromLonLat: function (lonlat) { + var px = null; + if (lonlat != null) { + var resolution = this.map.getResolution(); + var extent = this.map.getExtent(); + px = new OpenLayers.Pixel( + (1/resolution * (lonlat.lon - extent.left)), + (1/resolution * (extent.top - lonlat.lat)) + ); + } + return px; + }, + + /** + * APIMethod: setOpacity + * Sets the opacity for the entire layer (all images) + * + * Parameter: + * opacity - {Float} + */ + setOpacity: function(opacity) { + if (opacity != this.opacity) { + this.opacity = opacity; + for(var i=0, len=this.div.childNodes.length; i} + */ + adjustBounds: function (bounds) { + + if (this.gutter) { + // Adjust the extent of a bounds in map units by the + // layer's gutter in pixels. + var mapGutter = this.gutter * this.map.getResolution(); + bounds = new OpenLayers.Bounds(bounds.left - mapGutter, + bounds.bottom - mapGutter, + bounds.right + mapGutter, + bounds.top + mapGutter); + } + + if (this.wrapDateLine) { + // wrap around the date line, within the limits of rounding error + var wrappingOptions = { + 'rightTolerance':this.getResolution() + }; + bounds = bounds.wrapDateLine(this.maxExtent, wrappingOptions); + + } + return bounds; + }, + + CLASS_NAME: "OpenLayers.Layer" +}); +