]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Layer/Grid.js
web interface to add co-administrators
[syp.git] / openlayers / lib / OpenLayers / Layer / Grid.js
1 /* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
2  * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
3  * full text of the license. */
4
5
6 /**
7  * @requires OpenLayers/Layer/HTTPRequest.js
8  * @requires OpenLayers/Console.js
9  */
10
11 /**
12  * Class: OpenLayers.Layer.Grid
13  * Base class for layers that use a lattice of tiles.  Create a new grid
14  * layer with the <OpenLayers.Layer.Grid> constructor.
15  *
16  * Inherits from:
17  *  - <OpenLayers.Layer.HTTPRequest>
18  */
19 OpenLayers.Layer.Grid = OpenLayers.Class(OpenLayers.Layer.HTTPRequest, {
20     
21     /**
22      * APIProperty: tileSize
23      * {<OpenLayers.Size>}
24      */
25     tileSize: null,
26     
27     /**
28      * Property: grid
29      * {Array(Array(<OpenLayers.Tile>))} This is an array of rows, each row is 
30      *     an array of tiles.
31      */
32     grid: null,
33
34     /**
35      * APIProperty: singleTile
36      * {Boolean} Moves the layer into single-tile mode, meaning that one tile 
37      *     will be loaded. The tile's size will be determined by the 'ratio'
38      *     property. When the tile is dragged such that it does not cover the 
39      *     entire viewport, it is reloaded.
40      */
41     singleTile: false,
42
43     /** APIProperty: ratio
44      *  {Float} Used only when in single-tile mode, this specifies the 
45      *          ratio of the size of the single tile to the size of the map.
46      */
47     ratio: 1.5,
48
49     /**
50      * APIProperty: buffer
51      * {Integer} Used only when in gridded mode, this specifies the number of 
52      *           extra rows and colums of tiles on each side which will
53      *           surround the minimum grid tiles to cover the map.
54      */
55     buffer: 2,
56
57     /**
58      * APIProperty: numLoadingTiles
59      * {Integer} How many tiles are still loading?
60      */
61     numLoadingTiles: 0,
62
63     /**
64      * Constructor: OpenLayers.Layer.Grid
65      * Create a new grid layer
66      *
67      * Parameters:
68      * name - {String}
69      * url - {String}
70      * params - {Object}
71      * options - {Object} Hashtable of extra options to tag onto the layer
72      */
73     initialize: function(name, url, params, options) {
74         OpenLayers.Layer.HTTPRequest.prototype.initialize.apply(this, 
75                                                                 arguments);
76         
77         //grid layers will trigger 'tileloaded' when each new tile is 
78         // loaded, as a means of progress update to listeners.
79         // listeners can access 'numLoadingTiles' if they wish to keep track
80         // of the loading progress
81         //
82         this.events.addEventType("tileloaded");
83
84         this.grid = [];
85     },
86
87     /**
88      * APIMethod: destroy
89      * Deconstruct the layer and clear the grid.
90      */
91     destroy: function() {
92         this.clearGrid();
93         this.grid = null;
94         this.tileSize = null;
95         OpenLayers.Layer.HTTPRequest.prototype.destroy.apply(this, arguments); 
96     },
97
98     /**
99      * Method: clearGrid
100      * Go through and remove all tiles from the grid, calling
101      *    destroy() on each of them to kill circular references
102      */
103     clearGrid:function() {
104         if (this.grid) {
105             for(var iRow=0, len=this.grid.length; iRow<len; iRow++) {
106                 var row = this.grid[iRow];
107                 for(var iCol=0, clen=row.length; iCol<clen; iCol++) {
108                     var tile = row[iCol];
109                     this.removeTileMonitoringHooks(tile);
110                     tile.destroy();
111                 }
112             }
113             this.grid = [];
114         }
115     },
116
117     /**
118      * APIMethod: clone
119      * Create a clone of this layer
120      *
121      * Parameters:
122      * obj - {Object} Is this ever used?
123      * 
124      * Returns:
125      * {<OpenLayers.Layer.Grid>} An exact clone of this OpenLayers.Layer.Grid
126      */
127     clone: function (obj) {
128         
129         if (obj == null) {
130             obj = new OpenLayers.Layer.Grid(this.name,
131                                             this.url,
132                                             this.params,
133                                             this.options);
134         }
135
136         //get all additions from superclasses
137         obj = OpenLayers.Layer.HTTPRequest.prototype.clone.apply(this, [obj]);
138
139         // copy/set any non-init, non-simple values here
140         if (this.tileSize != null) {
141             obj.tileSize = this.tileSize.clone();
142         }
143         
144         // we do not want to copy reference to grid, so we make a new array
145         obj.grid = [];
146
147         return obj;
148     },    
149
150     /**
151      * Method: moveTo
152      * This function is called whenever the map is moved. All the moving
153      * of actual 'tiles' is done by the map, but moveTo's role is to accept
154      * a bounds and make sure the data that that bounds requires is pre-loaded.
155      *
156      * Parameters:
157      * bounds - {<OpenLayers.Bounds>}
158      * zoomChanged - {Boolean}
159      * dragging - {Boolean}
160      */
161     moveTo:function(bounds, zoomChanged, dragging) {
162         OpenLayers.Layer.HTTPRequest.prototype.moveTo.apply(this, arguments);
163         
164         bounds = bounds || this.map.getExtent();
165
166         if (bounds != null) {
167              
168             // if grid is empty or zoom has changed, we *must* re-tile
169             var forceReTile = !this.grid.length || zoomChanged;
170
171             // total bounds of the tiles
172             var tilesBounds = this.getTilesBounds();            
173       
174             if (this.singleTile) {
175                 
176                 // We want to redraw whenever even the slightest part of the 
177                 //  current bounds is not contained by our tile.
178                 //  (thus, we do not specify partial -- its default is false)
179                 if ( forceReTile || 
180                      (!dragging && !tilesBounds.containsBounds(bounds))) {
181                     this.initSingleTile(bounds);
182                 }
183             } else {
184              
185                 // if the bounds have changed such that they are not even 
186                 //  *partially* contained by our tiles (IE user has 
187                 //  programmatically panned to the other side of the earth) 
188                 //  then we want to reTile (thus, partial true).  
189                 //
190                 if (forceReTile || !tilesBounds.containsBounds(bounds, true)) {
191                     this.initGriddedTiles(bounds);
192                 } else {
193                     //we might have to shift our buffer tiles
194                     this.moveGriddedTiles(bounds);
195                 }
196             }
197         }
198     },
199     
200     /**
201      * APIMethod: setTileSize
202      * Check if we are in singleTile mode and if so, set the size as a ratio
203      *     of the map size (as specified by the layer's 'ratio' property).
204      * 
205      * Parameters:
206      * size - {<OpenLayers.Size>}
207      */
208     setTileSize: function(size) { 
209         if (this.singleTile) {
210             size = this.map.getSize().clone();
211             size.h = parseInt(size.h * this.ratio);
212             size.w = parseInt(size.w * this.ratio);
213         } 
214         OpenLayers.Layer.HTTPRequest.prototype.setTileSize.apply(this, [size]);
215     },
216         
217     /**
218      * Method: getGridBounds
219      * Deprecated. This function will be removed in 3.0. Please use 
220      *     getTilesBounds() instead.
221      * 
222      * Returns:
223      * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
224      * currently loaded tiles (including those partially or not at all seen 
225      * onscreen)
226      */
227     getGridBounds: function() {
228         var msg = "The getGridBounds() function is deprecated. It will be " +
229                   "removed in 3.0. Please use getTilesBounds() instead.";
230         OpenLayers.Console.warn(msg);
231         return this.getTilesBounds();
232     },
233
234     /**
235      * APIMethod: getTilesBounds
236      * Return the bounds of the tile grid.
237      *
238      * Returns:
239      * {<OpenLayers.Bounds>} A Bounds object representing the bounds of all the
240      *     currently loaded tiles (including those partially or not at all seen 
241      *     onscreen).
242      */
243     getTilesBounds: function() {    
244         var bounds = null; 
245         
246         if (this.grid.length) {
247             var bottom = this.grid.length - 1;
248             var bottomLeftTile = this.grid[bottom][0];
249     
250             var right = this.grid[0].length - 1; 
251             var topRightTile = this.grid[0][right];
252     
253             bounds = new OpenLayers.Bounds(bottomLeftTile.bounds.left, 
254                                            bottomLeftTile.bounds.bottom,
255                                            topRightTile.bounds.right, 
256                                            topRightTile.bounds.top);
257             
258         }   
259         return bounds;
260     },
261
262     /**
263      * Method: initSingleTile
264      * 
265      * Parameters: 
266      * bounds - {<OpenLayers.Bounds>}
267      */
268     initSingleTile: function(bounds) {
269
270         //determine new tile bounds
271         var center = bounds.getCenterLonLat();
272         var tileWidth = bounds.getWidth() * this.ratio;
273         var tileHeight = bounds.getHeight() * this.ratio;
274                                        
275         var tileBounds = 
276             new OpenLayers.Bounds(center.lon - (tileWidth/2),
277                                   center.lat - (tileHeight/2),
278                                   center.lon + (tileWidth/2),
279                                   center.lat + (tileHeight/2));
280   
281         var ul = new OpenLayers.LonLat(tileBounds.left, tileBounds.top);
282         var px = this.map.getLayerPxFromLonLat(ul);
283
284         if (!this.grid.length) {
285             this.grid[0] = [];
286         }
287
288         var tile = this.grid[0][0];
289         if (!tile) {
290             tile = this.addTile(tileBounds, px);
291             
292             this.addTileMonitoringHooks(tile);
293             tile.draw();
294             this.grid[0][0] = tile;
295         } else {
296             tile.moveTo(tileBounds, px);
297         }           
298         
299         //remove all but our single tile
300         this.removeExcessTiles(1,1);
301     },
302
303     /** 
304      * Method: calculateGridLayout
305      * Generate parameters for the grid layout. This  
306      *
307      * Parameters:
308      * bounds - {<OpenLayers.Bound>}
309      * extent - {<OpenLayers.Bounds>}
310      * resolution - {Number}
311      *
312      * Returns:
313      * Object containing properties tilelon, tilelat, tileoffsetlat,
314      * tileoffsetlat, tileoffsetx, tileoffsety
315      */
316     calculateGridLayout: function(bounds, extent, resolution) {
317         var tilelon = resolution * this.tileSize.w;
318         var tilelat = resolution * this.tileSize.h;
319         
320         var offsetlon = bounds.left - extent.left;
321         var tilecol = Math.floor(offsetlon/tilelon) - this.buffer;
322         var tilecolremain = offsetlon/tilelon - tilecol;
323         var tileoffsetx = -tilecolremain * this.tileSize.w;
324         var tileoffsetlon = extent.left + tilecol * tilelon;
325         
326         var offsetlat = bounds.top - (extent.bottom + tilelat);  
327         var tilerow = Math.ceil(offsetlat/tilelat) + this.buffer;
328         var tilerowremain = tilerow - offsetlat/tilelat;
329         var tileoffsety = -tilerowremain * this.tileSize.h;
330         var tileoffsetlat = extent.bottom + tilerow * tilelat;
331         
332         return { 
333           tilelon: tilelon, tilelat: tilelat,
334           tileoffsetlon: tileoffsetlon, tileoffsetlat: tileoffsetlat,
335           tileoffsetx: tileoffsetx, tileoffsety: tileoffsety
336         };
337
338     },
339
340     /**
341      * Method: initGriddedTiles
342      * 
343      * Parameters:
344      * bounds - {<OpenLayers.Bounds>}
345      */
346     initGriddedTiles:function(bounds) {
347         
348         // work out mininum number of rows and columns; this is the number of
349         // tiles required to cover the viewport plus at least one for panning
350
351         var viewSize = this.map.getSize();
352         var minRows = Math.ceil(viewSize.h/this.tileSize.h) + 
353                       Math.max(1, 2 * this.buffer);
354         var minCols = Math.ceil(viewSize.w/this.tileSize.w) +
355                       Math.max(1, 2 * this.buffer);
356         
357         var extent = this.maxExtent;
358         var resolution = this.map.getResolution();
359         
360         var tileLayout = this.calculateGridLayout(bounds, extent, resolution);
361
362         var tileoffsetx = Math.round(tileLayout.tileoffsetx); // heaven help us
363         var tileoffsety = Math.round(tileLayout.tileoffsety);
364
365         var tileoffsetlon = tileLayout.tileoffsetlon;
366         var tileoffsetlat = tileLayout.tileoffsetlat;
367         
368         var tilelon = tileLayout.tilelon;
369         var tilelat = tileLayout.tilelat;
370
371         this.origin = new OpenLayers.Pixel(tileoffsetx, tileoffsety);
372
373         var startX = tileoffsetx; 
374         var startLon = tileoffsetlon;
375
376         var rowidx = 0;
377         
378         var layerContainerDivLeft = parseInt(this.map.layerContainerDiv.style.left);
379         var layerContainerDivTop = parseInt(this.map.layerContainerDiv.style.top);
380         
381     
382         do {
383             var row = this.grid[rowidx++];
384             if (!row) {
385                 row = [];
386                 this.grid.push(row);
387             }
388
389             tileoffsetlon = startLon;
390             tileoffsetx = startX;
391             var colidx = 0;
392  
393             do {
394                 var tileBounds = 
395                     new OpenLayers.Bounds(tileoffsetlon, 
396                                           tileoffsetlat, 
397                                           tileoffsetlon + tilelon,
398                                           tileoffsetlat + tilelat);
399
400                 var x = tileoffsetx;
401                 x -= layerContainerDivLeft;
402
403                 var y = tileoffsety;
404                 y -= layerContainerDivTop;
405
406                 var px = new OpenLayers.Pixel(x, y);
407                 var tile = row[colidx++];
408                 if (!tile) {
409                     tile = this.addTile(tileBounds, px);
410                     this.addTileMonitoringHooks(tile);
411                     row.push(tile);
412                 } else {
413                     tile.moveTo(tileBounds, px, false);
414                 }
415      
416                 tileoffsetlon += tilelon;       
417                 tileoffsetx += this.tileSize.w;
418             } while ((tileoffsetlon <= bounds.right + tilelon * this.buffer)
419                      || colidx < minCols);
420              
421             tileoffsetlat -= tilelat;
422             tileoffsety += this.tileSize.h;
423         } while((tileoffsetlat >= bounds.bottom - tilelat * this.buffer)
424                 || rowidx < minRows);
425         
426         //shave off exceess rows and colums
427         this.removeExcessTiles(rowidx, colidx);
428
429         //now actually draw the tiles
430         this.spiralTileLoad();
431     },
432     
433     /**
434      * Method: spiralTileLoad
435      *   Starts at the top right corner of the grid and proceeds in a spiral 
436      *    towards the center, adding tiles one at a time to the beginning of a 
437      *    queue. 
438      * 
439      *   Once all the grid's tiles have been added to the queue, we go back 
440      *    and iterate through the queue (thus reversing the spiral order from 
441      *    outside-in to inside-out), calling draw() on each tile. 
442      */
443     spiralTileLoad: function() {
444         var tileQueue = [];
445  
446         var directions = ["right", "down", "left", "up"];
447
448         var iRow = 0;
449         var iCell = -1;
450         var direction = OpenLayers.Util.indexOf(directions, "right");
451         var directionsTried = 0;
452         
453         while( directionsTried < directions.length) {
454
455             var testRow = iRow;
456             var testCell = iCell;
457
458             switch (directions[direction]) {
459                 case "right":
460                     testCell++;
461                     break;
462                 case "down":
463                     testRow++;
464                     break;
465                 case "left":
466                     testCell--;
467                     break;
468                 case "up":
469                     testRow--;
470                     break;
471             } 
472     
473             // if the test grid coordinates are within the bounds of the 
474             //  grid, get a reference to the tile.
475             var tile = null;
476             if ((testRow < this.grid.length) && (testRow >= 0) &&
477                 (testCell < this.grid[0].length) && (testCell >= 0)) {
478                 tile = this.grid[testRow][testCell];
479             }
480             
481             if ((tile != null) && (!tile.queued)) {
482                 //add tile to beginning of queue, mark it as queued.
483                 tileQueue.unshift(tile);
484                 tile.queued = true;
485                 
486                 //restart the directions counter and take on the new coords
487                 directionsTried = 0;
488                 iRow = testRow;
489                 iCell = testCell;
490             } else {
491                 //need to try to load a tile in a different direction
492                 direction = (direction + 1) % 4;
493                 directionsTried++;
494             }
495         } 
496         
497         // now we go through and draw the tiles in forward order
498         for(var i=0, len=tileQueue.length; i<len; i++) {
499             var tile = tileQueue[i];
500             tile.draw();
501             //mark tile as unqueued for the next time (since tiles are reused)
502             tile.queued = false;       
503         }
504     },
505
506     /**
507      * APIMethod: addTile
508      * Gives subclasses of Grid the opportunity to create an 
509      * OpenLayer.Tile of their choosing. The implementer should initialize 
510      * the new tile and take whatever steps necessary to display it.
511      *
512      * Parameters
513      * bounds - {<OpenLayers.Bounds>}
514      * position - {<OpenLayers.Pixel>}
515      *
516      * Returns:
517      * {<OpenLayers.Tile>} The added OpenLayers.Tile
518      */
519     addTile:function(bounds, position) {
520         // Should be implemented by subclasses
521     },
522     
523     /** 
524      * Method: addTileMonitoringHooks
525      * This function takes a tile as input and adds the appropriate hooks to 
526      *     the tile so that the layer can keep track of the loading tiles.
527      * 
528      * Parameters: 
529      * tile - {<OpenLayers.Tile>}
530      */
531     addTileMonitoringHooks: function(tile) {
532         
533         tile.onLoadStart = function() {
534             //if that was first tile then trigger a 'loadstart' on the layer
535             if (this.numLoadingTiles == 0) {
536                 this.events.triggerEvent("loadstart");
537             }
538             this.numLoadingTiles++;
539         };
540         tile.events.register("loadstart", this, tile.onLoadStart);
541       
542         tile.onLoadEnd = function() {
543             this.numLoadingTiles--;
544             this.events.triggerEvent("tileloaded");
545             //if that was the last tile, then trigger a 'loadend' on the layer
546             if (this.numLoadingTiles == 0) {
547                 this.events.triggerEvent("loadend");
548             }
549         };
550         tile.events.register("loadend", this, tile.onLoadEnd);
551         tile.events.register("unload", this, tile.onLoadEnd);
552     },
553
554     /** 
555      * Method: removeTileMonitoringHooks
556      * This function takes a tile as input and removes the tile hooks 
557      *     that were added in addTileMonitoringHooks()
558      * 
559      * Parameters: 
560      * tile - {<OpenLayers.Tile>}
561      */
562     removeTileMonitoringHooks: function(tile) {
563         tile.unload();
564         tile.events.un({
565             "loadstart": tile.onLoadStart,
566             "loadend": tile.onLoadEnd,
567             "unload": tile.onLoadEnd,
568             scope: this
569         });
570     },
571     
572     /**
573      * Method: moveGriddedTiles
574      * 
575      * Parameters:
576      * bounds - {<OpenLayers.Bounds>}
577      */
578     moveGriddedTiles: function(bounds) {
579         var buffer = this.buffer || 1;
580         while (true) {
581             var tlLayer = this.grid[0][0].position;
582             var tlViewPort = 
583                 this.map.getViewPortPxFromLayerPx(tlLayer);
584             if (tlViewPort.x > -this.tileSize.w * (buffer - 1)) {
585                 this.shiftColumn(true);
586             } else if (tlViewPort.x < -this.tileSize.w * buffer) {
587                 this.shiftColumn(false);
588             } else if (tlViewPort.y > -this.tileSize.h * (buffer - 1)) {
589                 this.shiftRow(true);
590             } else if (tlViewPort.y < -this.tileSize.h * buffer) {
591                 this.shiftRow(false);
592             } else {
593                 break;
594             }
595         };
596     },
597
598     /**
599      * Method: shiftRow
600      * Shifty grid work
601      *
602      * Parameters:
603      * prepend - {Boolean} if true, prepend to beginning.
604      *                          if false, then append to end
605      */
606     shiftRow:function(prepend) {
607         var modelRowIndex = (prepend) ? 0 : (this.grid.length - 1);
608         var grid = this.grid;
609         var modelRow = grid[modelRowIndex];
610
611         var resolution = this.map.getResolution();
612         var deltaY = (prepend) ? -this.tileSize.h : this.tileSize.h;
613         var deltaLat = resolution * -deltaY;
614
615         var row = (prepend) ? grid.pop() : grid.shift();
616
617         for (var i=0, len=modelRow.length; i<len; i++) {
618             var modelTile = modelRow[i];
619             var bounds = modelTile.bounds.clone();
620             var position = modelTile.position.clone();
621             bounds.bottom = bounds.bottom + deltaLat;
622             bounds.top = bounds.top + deltaLat;
623             position.y = position.y + deltaY;
624             row[i].moveTo(bounds, position);
625         }
626
627         if (prepend) {
628             grid.unshift(row);
629         } else {
630             grid.push(row);
631         }
632     },
633
634     /**
635      * Method: shiftColumn
636      * Shift grid work in the other dimension
637      *
638      * Parameters:
639      * prepend - {Boolean} if true, prepend to beginning.
640      *                          if false, then append to end
641      */
642     shiftColumn: function(prepend) {
643         var deltaX = (prepend) ? -this.tileSize.w : this.tileSize.w;
644         var resolution = this.map.getResolution();
645         var deltaLon = resolution * deltaX;
646
647         for (var i=0, len=this.grid.length; i<len; i++) {
648             var row = this.grid[i];
649             var modelTileIndex = (prepend) ? 0 : (row.length - 1);
650             var modelTile = row[modelTileIndex];
651             
652             var bounds = modelTile.bounds.clone();
653             var position = modelTile.position.clone();
654             bounds.left = bounds.left + deltaLon;
655             bounds.right = bounds.right + deltaLon;
656             position.x = position.x + deltaX;
657
658             var tile = prepend ? this.grid[i].pop() : this.grid[i].shift();
659             tile.moveTo(bounds, position);
660             if (prepend) {
661                 row.unshift(tile);
662             } else {
663                 row.push(tile);
664             }
665         }
666     },
667     
668     /**
669      * Method: removeExcessTiles
670      * When the size of the map or the buffer changes, we may need to
671      *     remove some excess rows and columns.
672      * 
673      * Parameters:
674      * rows - {Integer} Maximum number of rows we want our grid to have.
675      * colums - {Integer} Maximum number of columns we want our grid to have.
676      */
677     removeExcessTiles: function(rows, columns) {
678         
679         // remove extra rows
680         while (this.grid.length > rows) {
681             var row = this.grid.pop();
682             for (var i=0, l=row.length; i<l; i++) {
683                 var tile = row[i];
684                 this.removeTileMonitoringHooks(tile);
685                 tile.destroy();
686             }
687         }
688         
689         // remove extra columns
690         while (this.grid[0].length > columns) {
691             for (var i=0, l=this.grid.length; i<l; i++) {
692                 var row = this.grid[i];
693                 var tile = row.pop();
694                 this.removeTileMonitoringHooks(tile);
695                 tile.destroy();
696             }
697         }
698     },
699
700     /**
701      * Method: onMapResize
702      * For singleTile layers, this will set a new tile size according to the
703      * dimensions of the map pane.
704      */
705     onMapResize: function() {
706         if (this.singleTile) {
707             this.clearGrid();
708             this.setTileSize();
709         }
710     },
711     
712     /**
713      * APIMethod: getTileBounds
714      * Returns The tile bounds for a layer given a pixel location.
715      *
716      * Parameters:
717      * viewPortPx - {<OpenLayers.Pixel>} The location in the viewport.
718      *
719      * Returns:
720      * {<OpenLayers.Bounds>} Bounds of the tile at the given pixel location.
721      */
722     getTileBounds: function(viewPortPx) {
723         var maxExtent = this.maxExtent;
724         var resolution = this.getResolution();
725         var tileMapWidth = resolution * this.tileSize.w;
726         var tileMapHeight = resolution * this.tileSize.h;
727         var mapPoint = this.getLonLatFromViewPortPx(viewPortPx);
728         var tileLeft = maxExtent.left + (tileMapWidth *
729                                          Math.floor((mapPoint.lon -
730                                                      maxExtent.left) /
731                                                     tileMapWidth));
732         var tileBottom = maxExtent.bottom + (tileMapHeight *
733                                              Math.floor((mapPoint.lat -
734                                                          maxExtent.bottom) /
735                                                         tileMapHeight));
736         return new OpenLayers.Bounds(tileLeft, tileBottom,
737                                      tileLeft + tileMapWidth,
738                                      tileBottom + tileMapHeight);
739     },
740     
741     CLASS_NAME: "OpenLayers.Layer.Grid"
742 });