/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD * license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the * full text of the license. */ /* * @requires OpenLayers/Util.js */ /* * Class: OpenLayers.Tile * This is a class designed to designate a single tile, however * it is explicitly designed to do relatively little. Tiles store * information about themselves -- such as the URL that they are related * to, and their size - but do not add themselves to the layer div * automatically, for example. Create a new tile with the * constructor, or a subclass. * * TBD 3.0 - remove reference to url in above paragraph * */ OpenLayers.Tile = OpenLayers.Class({ /** * Constant: EVENT_TYPES * {Array(String)} Supported application event types */ EVENT_TYPES: [ "loadstart", "loadend", "reload", "unload"], /** * APIProperty: events * {} An events object that handles all * events on the tile. */ events: null, /** * Property: id * {String} null */ id: null, /** * Property: layer * {} layer the tile is attached to */ layer: null, /** * Property: url * {String} url of the request. * * TBD 3.0 * Deprecated. The base tile class does not need an url. This should be * handled in subclasses. Does not belong here. */ url: null, /** * APIProperty: bounds * {} null */ bounds: null, /** * Property: size * {} null */ size: null, /** * Property: position * {} Top Left pixel of the tile */ position: null, /** * Property: isLoading * {Boolean} Is the tile loading? */ isLoading: false, /** * Property: isBackBuffer * {Boolean} Is this tile a back buffer tile? */ isBackBuffer: false, /** * Property: lastRatio * {Float} Used in transition code only. This is the previous ratio * of the back buffer tile resolution to the map resolution. Compared * with the current ratio to determine if zooming occurred. */ lastRatio: 1, /** * 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. */ backBufferTile: null, /** TBD 3.0 -- remove 'url' from the list of parameters to the constructor. * there is no need for the base tile class to have a url. * * Constructor: OpenLayers.Tile * Constructor for a new instance. * * Parameters: * layer - {} layer that the tile will go in. * position - {} * bounds - {} * url - {} * size - {} */ initialize: function(layer, position, bounds, url, size) { this.layer = layer; this.position = position.clone(); this.bounds = bounds.clone(); this.url = url; this.size = size.clone(); //give the tile a unique id based on its BBOX. this.id = OpenLayers.Util.createUniqueID("Tile_"); this.events = new OpenLayers.Events(this, null, this.EVENT_TYPES); }, /** * Method: unload * Call immediately before destroying if you are listening to tile * events, so that counters are properly handled if tile is still * loading at destroy-time. Will only fire an event if the tile is * still loading. */ unload: function() { if (this.isLoading) { this.isLoading = false; this.events.triggerEvent("unload"); } }, /** * APIMethod: destroy * Nullify references to prevent circular references and memory leaks. */ destroy:function() { if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) { this.layer.events.unregister("loadend", this, this.resetBackBuffer); this.events.unregister('loadend', this, this.resetBackBuffer); } else { this.events.unregister('loadend', this, this.showTile); } this.layer = null; this.bounds = null; this.size = null; this.position = null; this.events.destroy(); this.events = null; /* clean up the backBufferTile if it exists */ if (this.backBufferTile) { this.backBufferTile.destroy(); this.backBufferTile = null; } }, /** * 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(this.layer, this.position, this.bounds, this.url, this.size); } // catch any randomly tagged-on properties OpenLayers.Util.applyDefaults(obj, this); return obj; }, /** * Method: draw * Clear whatever is currently in the tile, then return whether or not * it should actually be re-drawn. * * 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. */ draw: 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. var drawTile = (withinMaxExtent || this.layer.displayOutsideMaxExtent); if (OpenLayers.Util.indexOf(this.layer.SUPPORTED_TRANSITIONS, this.layer.transitionEffect) != -1) { 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(); } else { // if we aren't going to draw the tile, then the backBuffer should // be hidden too! if (this.backBufferTile) { this.backBufferTile.clear(); } } } else { if (drawTile && this.isFirstDraw) { this.events.register('loadend', this, this.showTile); this.isFirstDraw = false; } } this.shouldDraw = drawTile; //clear tile's contents and mark as not drawn this.clear(); return drawTile; }, /** * Method: moveTo * Reposition the tile. * * Parameters: * bounds - {} * position - {} * redraw - {Boolean} Call draw method on tile after moving. * Default is true */ moveTo: function (bounds, position, redraw) { if (redraw == null) { redraw = true; } this.bounds = bounds.clone(); this.position = position.clone(); if (redraw) { this.draw(); } }, /** * Method: clear * 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() { // to be implemented by subclasses }, /** * Method: getBoundsFromBaseLayer * Take the pixel locations of the corner of the tile, and pass them to * the base layer and ask for the location of those pixels, so that * displaying tiles over Google works fine. * * Parameters: * position - {} * * Returns: * bounds - {} */ getBoundsFromBaseLayer: function(position) { var msg = OpenLayers.i18n('reprojectDeprecated', {'layerName':this.layer.name}); OpenLayers.Console.warn(msg); var topLeft = this.layer.map.getLonLatFromLayerPx(position); var bottomRightPx = position.clone(); bottomRightPx.x += this.size.w; bottomRightPx.y += this.size.h; var bottomRight = this.layer.map.getLonLatFromLayerPx(bottomRightPx); // Handle the case where the base layer wraps around the date line. // Google does this, and it breaks WMS servers to request bounds in // that fashion. if (topLeft.lon > bottomRight.lon) { if (topLeft.lon < 0) { topLeft.lon = -180 - (topLeft.lon+180); } else { bottomRight.lon = 180+bottomRight.lon+180; } } var bounds = new OpenLayers.Bounds(topLeft.lon, bottomRight.lat, bottomRight.lon, topLeft.lat); return bounds; }, /** * Method: startTransition * Prepare the tile for a transition effect. To be * implemented by subclasses. */ startTransition: function() {}, /** * 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.imageSize || this.size; this.backBufferTile.imageOffset = this.layer.imageOffset; this.backBufferTile.resolution = this.layer.getResolution(); this.backBufferTile.renderTile(); } } }, /** * 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" });