]> dev.renevier.net Git - syp.git/blob - openlayers/lib/OpenLayers/Map.js
fixes notices
[syp.git] / openlayers / lib / OpenLayers / Map.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  * @requires OpenLayers/Util.js
7  * @requires OpenLayers/Events.js
8  * @requires OpenLayers/Tween.js
9  * @requires OpenLayers/Console.js
10  */
11
12 /**
13  * Class: OpenLayers.Map
14  * Instances of OpenLayers.Map are interactive maps embedded in a web page.
15  * Create a new map with the <OpenLayers.Map> constructor.
16  * 
17  * On their own maps do not provide much functionality.  To extend a map
18  * it's necessary to add controls (<OpenLayers.Control>) and 
19  * layers (<OpenLayers.Layer>) to the map. 
20  */
21 OpenLayers.Map = OpenLayers.Class({
22     
23     /**
24      * Constant: Z_INDEX_BASE
25      * {Object} Base z-indexes for different classes of thing 
26      */
27     Z_INDEX_BASE: {
28         BaseLayer: 100,
29         Overlay: 325,
30         Feature: 725,
31         Popup: 750,
32         Control: 1000
33     },
34
35     /**
36      * Constant: EVENT_TYPES
37      * {Array(String)} Supported application event types.  Register a listener
38      *     for a particular event with the following syntax:
39      * (code)
40      * map.events.register(type, obj, listener);
41      * (end)
42      *
43      * Listeners will be called with a reference to an event object.  The
44      *     properties of this event depends on exactly what happened.
45      *
46      * All event objects have at least the following properties:
47      *  - *object* {Object} A reference to map.events.object.
48      *  - *element* {DOMElement} A reference to map.events.element.
49      *
50      * Browser events have the following additional properties:
51      *  - *xy* {<OpenLayers.Pixel>} The pixel location of the event (relative
52      *      to the the map viewport).
53      *  - other properties that come with browser events
54      *
55      * Supported map event types:
56      *  - *preaddlayer* triggered before a layer has been added.  The event
57      *      object will include a *layer* property that references the layer  
58      *      to be added.
59      *  - *addlayer* triggered after a layer has been added.  The event object
60      *      will include a *layer* property that references the added layer.
61      *  - *removelayer* triggered after a layer has been removed.  The event
62      *      object will include a *layer* property that references the removed
63      *      layer.
64      *  - *changelayer* triggered after a layer name change, order change, or
65      *      visibility change (due to resolution thresholds).  Listeners will
66      *      receive an event object with *layer* and *property* properties.  The
67      *      *layer* property will be a reference to the changed layer.  The
68      *      *property* property will be a key to the changed property (name,
69      *      visibility, or order).
70      *  - *movestart* triggered after the start of a drag, pan, or zoom
71      *  - *move* triggered after each drag, pan, or zoom
72      *  - *moveend* triggered after a drag, pan, or zoom completes
73      *  - *zoomend* triggered after a zoom completes
74      *  - *addmarker* triggered after a marker has been added
75      *  - *removemarker* triggered after a marker has been removed
76      *  - *clearmarkers* triggered after markers have been cleared
77      *  - *mouseover* triggered after mouseover the map
78      *  - *mouseout* triggered after mouseout the map
79      *  - *mousemove* triggered after mousemove the map
80      *  - *dragstart* Does not work.  Register for movestart instead.
81      *  - *drag* Does not work.  Register for move instead.
82      *  - *dragend* Does not work.  Register for moveend instead.
83      *  - *changebaselayer* triggered after the base layer changes
84      */
85     EVENT_TYPES: [ 
86         "preaddlayer", "addlayer", "removelayer", "changelayer", "movestart",
87         "move", "moveend", "zoomend", "popupopen", "popupclose",
88         "addmarker", "removemarker", "clearmarkers", "mouseover",
89         "mouseout", "mousemove", "dragstart", "drag", "dragend",
90         "changebaselayer"],
91
92     /**
93      * Property: id
94      * {String} Unique identifier for the map
95      */
96     id: null,
97     
98     /**
99      * Property: fractionalZoom
100      * {Boolean} For a base layer that supports it, allow the map resolution
101      *     to be set to a value between one of the values in the resolutions
102      *     array.  Default is false.
103      *
104      * When fractionalZoom is set to true, it is possible to zoom to
105      *     an arbitrary extent.  This requires a base layer from a source
106      *     that supports requests for arbitrary extents (i.e. not cached
107      *     tiles on a regular lattice).  This means that fractionalZoom
108      *     will not work with commercial layers (Google, Yahoo, VE), layers
109      *     using TileCache, or any other pre-cached data sources.
110      *
111      * If you are using fractionalZoom, then you should also use
112      *     <getResolutionForZoom> instead of layer.resolutions[zoom] as the
113      *     former works for non-integer zoom levels.
114      */
115     fractionalZoom: false,
116     
117     /**
118      * APIProperty: events
119      * {<OpenLayers.Events>} An events object that handles all 
120      *                       events on the map
121      */
122     events: null,
123     
124     /**
125      * APIProperty: allOverlays
126      * {Boolean} Allow the map to function with "overlays" only.  Defaults to
127      *     false.  If true, the lowest layer in the draw order will act as
128      *     the base layer.  In addition, if set to true, all layers will
129      *     have isBaseLayer set to false when they are added to the map.
130      *
131      * Note:
132      * If you set map.allOverlays to true, then you *cannot* use
133      *     map.setBaseLayer or layer.setIsBaseLayer.  With allOverlays true,
134      *     the lowest layer in the draw layer is the base layer.  So, to change
135      *     the base layer, use <setLayerIndex> or <raiseLayer> to set the layer
136      *     index to 0.
137      */
138     allOverlays: false,
139
140     /**
141      * APIProperty: div
142      * {DOMElement|String} The element that contains the map (or an id for
143      *     that element).  If the <OpenLayers.Map> constructor is called
144      *     with two arguments, this should be provided as the first argument.
145      *     Alternatively, the map constructor can be called with the options
146      *     object as the only argument.  In this case (one argument), a
147      *     div property may or may not be provided.  If the div property
148      *     is not provided, the map can be rendered to a container later
149      *     using the <render> method.
150      *     
151      * Note: If you calling <render> after map construction, do not use
152      *     <maxResolution>  auto.  Instead, divide your <maxExtent> by your
153      *     maximum expected dimension.
154      */
155     div: null,
156     
157     /**
158      * Property: dragging
159      * {Boolean} The map is currently being dragged.
160      */
161     dragging: false,
162
163     /**
164      * Property: size
165      * {<OpenLayers.Size>} Size of the main div (this.div)
166      */
167     size: null,
168     
169     /**
170      * Property: viewPortDiv
171      * {HTMLDivElement} The element that represents the map viewport
172      */
173     viewPortDiv: null,
174
175     /**
176      * Property: layerContainerOrigin
177      * {<OpenLayers.LonLat>} The lonlat at which the later container was
178      *                       re-initialized (on-zoom)
179      */
180     layerContainerOrigin: null,
181
182     /**
183      * Property: layerContainerDiv
184      * {HTMLDivElement} The element that contains the layers.
185      */
186     layerContainerDiv: null,
187
188     /**
189      * APIProperty: layers
190      * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
191      */
192     layers: null,
193
194     /**
195      * Property: controls
196      * {Array(<OpenLayers.Control>)} List of controls associated with the map.
197      *
198      * If not provided in the map options at construction, the map will
199      *     be given the following controls by default:
200      *  - <OpenLayers.Control.Navigation>
201      *  - <OpenLayers.Control.PanZoom>
202      *  - <OpenLayers.Control.ArgParser>
203      *  - <OpenLayers.Control.Attribution>
204      */
205     controls: null,
206
207     /**
208      * Property: popups
209      * {Array(<OpenLayers.Popup>)} List of popups associated with the map
210      */
211     popups: null,
212
213     /**
214      * APIProperty: baseLayer
215      * {<OpenLayers.Layer>} The currently selected base layer.  This determines
216      * min/max zoom level, projection, etc.
217      */
218     baseLayer: null,
219     
220     /**
221      * Property: center
222      * {<OpenLayers.LonLat>} The current center of the map
223      */
224     center: null,
225
226     /**
227      * Property: resolution
228      * {Float} The resolution of the map.
229      */
230     resolution: null,
231
232     /**
233      * Property: zoom
234      * {Integer} The current zoom level of the map
235      */
236     zoom: 0,    
237
238     /**
239      * Property: panRatio
240      * {Float} The ratio of the current extent within
241      *         which panning will tween.
242      */
243     panRatio: 1.5,    
244
245     /**
246      * Property: viewRequestID
247      * {String} Used to store a unique identifier that changes when the map 
248      *          view changes. viewRequestID should be used when adding data 
249      *          asynchronously to the map: viewRequestID is incremented when 
250      *          you initiate your request (right now during changing of 
251      *          baselayers and changing of zooms). It is stored here in the 
252      *          map and also in the data that will be coming back 
253      *          asynchronously. Before displaying this data on request 
254      *          completion, we check that the viewRequestID of the data is 
255      *          still the same as that of the map. Fix for #480
256      */
257     viewRequestID: 0,
258
259   // Options
260
261     /**
262      * APIProperty: tileSize
263      * {<OpenLayers.Size>} Set in the map options to override the default tile
264      *                     size for this map.
265      */
266     tileSize: null,
267
268     /**
269      * APIProperty: projection
270      * {String} Set in the map options to override the default projection 
271      *          string this map - also set maxExtent, maxResolution, and 
272      *          units if appropriate.  Default is "EPSG:4326".
273      */
274     projection: "EPSG:4326",    
275         
276     /**
277      * APIProperty: units
278      * {String} The map units.  Defaults to 'degrees'.  Possible values are
279      *          'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
280      */
281     units: 'degrees',
282
283     /**
284      * APIProperty: resolutions
285      * {Array(Float)} A list of map resolutions (map units per pixel) in 
286      *     descending order.  If this is not set in the layer constructor, it 
287      *     will be set based on other resolution related properties 
288      *     (maxExtent, maxResolution, maxScale, etc.).
289      */
290     resolutions: null,
291
292     /**
293      * APIProperty: maxResolution
294      * {Float} Default max is 360 deg / 256 px, which corresponds to
295      *          zoom level 0 on gmaps.  Specify a different value in the map 
296      *          options if you are not using a geographic projection and 
297      *          displaying the whole world.
298      */
299     maxResolution: 1.40625,
300
301     /**
302      * APIProperty: minResolution
303      * {Float}
304      */
305     minResolution: null,
306
307     /**
308      * APIProperty: maxScale
309      * {Float}
310      */
311     maxScale: null,
312
313     /**
314      * APIProperty: minScale
315      * {Float}
316      */
317     minScale: null,
318
319     /**
320      * APIProperty: maxExtent
321      * {<OpenLayers.Bounds>} The maximum extent for the map.  Defaults to the
322      *                       whole world in decimal degrees 
323      *                       (-180, -90, 180, 90).  Specify a different
324      *                        extent in the map options if you are not using a 
325      *                        geographic projection and displaying the whole 
326      *                        world.
327      */
328     maxExtent: null,
329     
330     /**
331      * APIProperty: minExtent
332      * {<OpenLayers.Bounds>}
333      */
334     minExtent: null,
335     
336     /**
337      * APIProperty: restrictedExtent
338      * {<OpenLayers.Bounds>} Limit map navigation to this extent where possible.
339      *     If a non-null restrictedExtent is set, panning will be restricted
340      *     to the given bounds.  In addition, zooming to a resolution that
341      *     displays more than the restricted extent will center the map
342      *     on the restricted extent.  If you wish to limit the zoom level
343      *     or resolution, use maxResolution.
344      */
345     restrictedExtent: null,
346
347     /**
348      * APIProperty: numZoomLevels
349      * {Integer} Number of zoom levels for the map.  Defaults to 16.  Set a
350      *           different value in the map options if needed.
351      */
352     numZoomLevels: 16,
353
354     /**
355      * APIProperty: theme
356      * {String} Relative path to a CSS file from which to load theme styles.
357      *          Specify null in the map options (e.g. {theme: null}) if you 
358      *          want to get cascading style declarations - by putting links to 
359      *          stylesheets or style declarations directly in your page.
360      */
361     theme: null,
362     
363     /** 
364      * APIProperty: displayProjection
365      * {<OpenLayers.Projection>} Requires proj4js support.Projection used by
366      *     several controls to display data to user. If this property is set,
367      *     it will be set on any control which has a null displayProjection
368      *     property at the time the control is added to the map. 
369      */
370     displayProjection: null,
371
372     /**
373      * APIProperty: fallThrough
374      * {Boolean} Should OpenLayers allow events on the map to fall through to
375      *           other elements on the page, or should it swallow them? (#457)
376      *           Default is to fall through.
377      */
378     fallThrough: true,
379     
380     /**
381      * Property: panTween
382      * {OpenLayers.Tween} Animated panning tween object, see panTo()
383      */
384     panTween: null,
385
386     /**
387      * APIProperty: eventListeners
388      * {Object} If set as an option at construction, the eventListeners
389      *     object will be registered with <OpenLayers.Events.on>.  Object
390      *     structure must be a listeners object as shown in the example for
391      *     the events.on method.
392      */
393     eventListeners: null,
394
395     /**
396      * APIProperty: panMethod
397      * {Function} The Easing function to be used for tweening.  Default is
398      * OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
399      * animated panning.
400      */
401     panMethod: OpenLayers.Easing.Expo.easeOut,
402     
403     /**
404      * Property: panDuration
405      * {Integer} The number of steps to be passed to the
406      * OpenLayers.Tween.start() method when the map is
407      * panned.
408      * Default is 50.
409      */
410     panDuration: 50,
411     
412     /**
413      * Property: paddingForPopups
414      * {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent 
415      *     the popup from getting too close to the map border.
416      */
417     paddingForPopups : null,
418     
419     /**
420      * Constructor: OpenLayers.Map
421      * Constructor for a new OpenLayers.Map instance.  There are two possible
422      *     ways to call the map constructor.  See the examples below.
423      *
424      * Parameters:
425      * div - {String} Id of an element in your page that will contain the map.
426      *     May be omitted if the <div> option is provided or if you intend
427      *     to use <render> later.
428      * options - {Object} Optional object with properties to tag onto the map.
429      *
430      * Examples (method one):
431      * (code)
432      * // create a map with default options in an element with the id "map1"
433      * var map = new OpenLayers.Map("map1");
434      *
435      * // create a map with non-default options in an element with id "map2"
436      * var options = {
437      *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
438      *     maxResolution: 156543,
439      *     units: 'm',
440      *     projection: "EPSG:41001"
441      * };
442      * var map = new OpenLayers.Map("map2", options);
443      * (end)
444      *
445      * Examples (method two - single argument):
446      * (code)
447      * // create a map with non-default options
448      * var map = new OpenLayers.Map({
449      *     div: "map_id",
450      *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
451      *     maxResolution: 156543,
452      *     units: 'm',
453      *     projection: "EPSG:41001"
454      * });
455      *
456      * // create a map without a reference to a container - call render later
457      * var map = new OpenLayers.Map({
458      *     maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
459      *     maxResolution: 156543,
460      *     units: 'm',
461      *     projection: "EPSG:41001"
462      * });
463      */    
464     initialize: function (div, options) {
465         
466         // If only one argument is provided, check if it is an object.
467         if(arguments.length === 1 && typeof div === "object") {
468             options = div;
469             div = options && options.div;
470         }
471
472         // Simple-type defaults are set in class definition. 
473         //  Now set complex-type defaults 
474         this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
475                                             OpenLayers.Map.TILE_HEIGHT);
476         
477         this.maxExtent = new OpenLayers.Bounds(-180, -90, 180, 90);
478         
479         this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
480
481         this.theme = OpenLayers._getScriptLocation() + 
482                              'theme/default/style.css'; 
483
484         // now override default options 
485         OpenLayers.Util.extend(this, options);
486
487         this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
488
489         this.div = OpenLayers.Util.getElement(div);
490         if(!this.div) {
491             this.div = document.createElement("div");
492             this.div.style.height = "1px";
493             this.div.style.width = "1px";
494         }
495         
496         OpenLayers.Element.addClass(this.div, 'olMap');
497
498         // the viewPortDiv is the outermost div we modify
499         var id = this.div.id + "_OpenLayers_ViewPort";
500         this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
501                                                      "relative", null,
502                                                      "hidden");
503         this.viewPortDiv.style.width = "100%";
504         this.viewPortDiv.style.height = "100%";
505         this.viewPortDiv.className = "olMapViewport";
506         this.div.appendChild(this.viewPortDiv);
507
508         // the layerContainerDiv is the one that holds all the layers
509         id = this.div.id + "_OpenLayers_Container";
510         this.layerContainerDiv = OpenLayers.Util.createDiv(id);
511         this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
512         
513         this.viewPortDiv.appendChild(this.layerContainerDiv);
514
515         this.events = new OpenLayers.Events(this, 
516                                             this.div, 
517                                             this.EVENT_TYPES, 
518                                             this.fallThrough, 
519                                             {includeXY: true});
520         this.updateSize();
521         if(this.eventListeners instanceof Object) {
522             this.events.on(this.eventListeners);
523         }
524  
525         // update the map size and location before the map moves
526         this.events.register("movestart", this, this.updateSize);
527
528         // Because Mozilla does not support the "resize" event for elements 
529         // other than "window", we need to put a hack here. 
530         if (OpenLayers.String.contains(navigator.appName, "Microsoft")) {
531             // If IE, register the resize on the div
532             this.events.register("resize", this, this.updateSize);
533         } else {
534             // Else updateSize on catching the window's resize
535             //  Note that this is ok, as updateSize() does nothing if the 
536             //  map's size has not actually changed.
537             this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize, 
538                 this);
539             OpenLayers.Event.observe(window, 'resize',
540                             this.updateSizeDestroy);
541         }
542         
543         // only append link stylesheet if the theme property is set
544         if(this.theme) {
545             // check existing links for equivalent url
546             var addNode = true;
547             var nodes = document.getElementsByTagName('link');
548             for(var i=0, len=nodes.length; i<len; ++i) {
549                 if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
550                                                    this.theme)) {
551                     addNode = false;
552                     break;
553                 }
554             }
555             // only add a new node if one with an equivalent url hasn't already
556             // been added
557             if(addNode) {
558                 var cssNode = document.createElement('link');
559                 cssNode.setAttribute('rel', 'stylesheet');
560                 cssNode.setAttribute('type', 'text/css');
561                 cssNode.setAttribute('href', this.theme);
562                 document.getElementsByTagName('head')[0].appendChild(cssNode);
563             }
564         }
565
566         this.layers = [];
567         
568         if (this.controls == null) {
569             if (OpenLayers.Control != null) { // running full or lite?
570                 this.controls = [ new OpenLayers.Control.Navigation(),
571                                   new OpenLayers.Control.PanZoom(),
572                                   new OpenLayers.Control.ArgParser(),
573                                   new OpenLayers.Control.Attribution()
574                                 ];
575             } else {
576                 this.controls = [];
577             }
578         }
579
580         for(var i=0, len=this.controls.length; i<len; i++) {
581             this.addControlToMap(this.controls[i]);
582         }
583
584         this.popups = [];
585
586         this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
587         
588
589         // always call map.destroy()
590         OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
591     },
592     
593     /**
594      * APIMethod: render
595      * Render the map to a specified container.
596      * 
597      * Parameters:
598      * div - {String|DOMElement} The container that the map should be rendered
599      *     to. If different than the current container, the map viewport
600      *     will be moved from the current to the new container.
601      */
602     render: function(div) {
603         this.div = OpenLayers.Util.getElement(div);
604         OpenLayers.Element.addClass(this.div, 'olMap');
605         this.events.attachToElement(this.div);
606         this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);
607         this.div.appendChild(this.viewPortDiv);
608         this.updateSize();
609     },
610
611     /**
612      * Method: unloadDestroy
613      * Function that is called to destroy the map on page unload. stored here
614      *     so that if map is manually destroyed, we can unregister this.
615      */
616     unloadDestroy: null,
617     
618     /**
619      * Method: updateSizeDestroy
620      * When the map is destroyed, we need to stop listening to updateSize
621      *    events: this method stores the function we need to unregister in 
622      *    non-IE browsers.
623      */
624     updateSizeDestroy: null,
625
626     /**
627      * APIMethod: destroy
628      * Destroy this map
629      */
630     destroy:function() {
631         // if unloadDestroy is null, we've already been destroyed
632         if (!this.unloadDestroy) {
633             return false;
634         }
635
636         // map has been destroyed. dont do it again!
637         OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
638         this.unloadDestroy = null;
639
640         if (this.updateSizeDestroy) {
641             OpenLayers.Event.stopObserving(window, 'resize', 
642                                            this.updateSizeDestroy);
643         } else {
644             this.events.unregister("resize", this, this.updateSize);
645         }    
646         
647         this.paddingForPopups = null;    
648
649         if (this.controls != null) {
650             for (var i = this.controls.length - 1; i>=0; --i) {
651                 this.controls[i].destroy();
652             } 
653             this.controls = null;
654         }
655         if (this.layers != null) {
656             for (var i = this.layers.length - 1; i>=0; --i) {
657                 //pass 'false' to destroy so that map wont try to set a new 
658                 // baselayer after each baselayer is removed
659                 this.layers[i].destroy(false);
660             } 
661             this.layers = null;
662         }
663         if (this.viewPortDiv) {
664             this.div.removeChild(this.viewPortDiv);
665         }
666         this.viewPortDiv = null;
667
668         if(this.eventListeners) {
669             this.events.un(this.eventListeners);
670             this.eventListeners = null;
671         }
672         this.events.destroy();
673         this.events = null;
674
675     },
676
677     /**
678      * APIMethod: setOptions
679      * Change the map options
680      *
681      * Parameters:
682      * options - {Object} Hashtable of options to tag to the map
683      */
684     setOptions: function(options) {
685         OpenLayers.Util.extend(this, options);
686     },
687
688     /**
689      * APIMethod: getTileSize
690      * Get the tile size for the map
691      *
692      * Returns:
693      * {<OpenLayers.Size>}
694      */
695      getTileSize: function() {
696          return this.tileSize;
697      },
698
699
700     /**
701      * APIMethod: getBy
702      * Get a list of objects given a property and a match item.
703      *
704      * Parameters:
705      * array - {String} A property on the map whose value is an array.
706      * property - {String} A property on each item of the given array.
707      * match - {String | Object} A string to match.  Can also be a regular
708      *     expression literal or object.  In addition, it can be any object
709      *     with a method named test.  For reqular expressions or other, if
710      *     match.test(map[array][i][property]) evaluates to true, the item will
711      *     be included in the array returned.  If no items are found, an empty
712      *     array is returned.
713      *
714      * Returns:
715      * {Array} An array of items where the given property matches the given
716      *     criteria.
717      */
718     getBy: function(array, property, match) {
719         var test = (typeof match.test == "function");
720         var found = OpenLayers.Array.filter(this[array], function(item) {
721             return item[property] == match || (test && match.test(item[property]));
722         });
723         return found;
724     },
725
726     /**
727      * APIMethod: getLayersBy
728      * Get a list of layers with properties matching the given criteria.
729      *
730      * Parameter:
731      * property - {String} A layer property to be matched.
732      * match - {String | Object} A string to match.  Can also be a regular
733      *     expression literal or object.  In addition, it can be any object
734      *     with a method named test.  For reqular expressions or other, if
735      *     match.test(layer[property]) evaluates to true, the layer will be
736      *     included in the array returned.  If no layers are found, an empty
737      *     array is returned.
738      *
739      * Returns:
740      * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
741      *     An empty array is returned if no matches are found.
742      */
743     getLayersBy: function(property, match) {
744         return this.getBy("layers", property, match);
745     },
746
747     /**
748      * APIMethod: getLayersByName
749      * Get a list of layers with names matching the given name.
750      *
751      * Parameter:
752      * match - {String | Object} A layer name.  The name can also be a regular
753      *     expression literal or object.  In addition, it can be any object
754      *     with a method named test.  For reqular expressions or other, if
755      *     name.test(layer.name) evaluates to true, the layer will be included
756      *     in the list of layers returned.  If no layers are found, an empty
757      *     array is returned.
758      *
759      * Returns:
760      * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
761      *     An empty array is returned if no matches are found.
762      */
763     getLayersByName: function(match) {
764         return this.getLayersBy("name", match);
765     },
766
767     /**
768      * APIMethod: getLayersByClass
769      * Get a list of layers of a given class (CLASS_NAME).
770      *
771      * Parameter:
772      * match - {String | Object} A layer class name.  The match can also be a
773      *     regular expression literal or object.  In addition, it can be any
774      *     object with a method named test.  For reqular expressions or other,
775      *     if type.test(layer.CLASS_NAME) evaluates to true, the layer will
776      *     be included in the list of layers returned.  If no layers are
777      *     found, an empty array is returned.
778      *
779      * Returns:
780      * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
781      *     An empty array is returned if no matches are found.
782      */
783     getLayersByClass: function(match) {
784         return this.getLayersBy("CLASS_NAME", match);
785     },
786
787     /**
788      * APIMethod: getControlsBy
789      * Get a list of controls with properties matching the given criteria.
790      *
791      * Parameter:
792      * property - {String} A control property to be matched.
793      * match - {String | Object} A string to match.  Can also be a regular
794      *     expression literal or object.  In addition, it can be any object
795      *     with a method named test.  For reqular expressions or other, if
796      *     match.test(layer[property]) evaluates to true, the layer will be
797      *     included in the array returned.  If no layers are found, an empty
798      *     array is returned.
799      *
800      * Returns:
801      * {Array(<OpenLayers.Control>)} A list of controls matching the given
802      *     criteria.  An empty array is returned if no matches are found.
803      */
804     getControlsBy: function(property, match) {
805         return this.getBy("controls", property, match);
806     },
807
808     /**
809      * APIMethod: getControlsByClass
810      * Get a list of controls of a given class (CLASS_NAME).
811      *
812      * Parameter:
813      * match - {String | Object} A control class name.  The match can also be a
814      *     regular expression literal or object.  In addition, it can be any
815      *     object with a method named test.  For reqular expressions or other,
816      *     if type.test(control.CLASS_NAME) evaluates to true, the control will
817      *     be included in the list of controls returned.  If no controls are
818      *     found, an empty array is returned.
819      *
820      * Returns:
821      * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
822      *     An empty array is returned if no matches are found.
823      */
824     getControlsByClass: function(match) {
825         return this.getControlsBy("CLASS_NAME", match);
826     },
827
828   /********************************************************/
829   /*                                                      */
830   /*                  Layer Functions                     */
831   /*                                                      */
832   /*     The following functions deal with adding and     */
833   /*        removing Layers to and from the Map           */
834   /*                                                      */
835   /********************************************************/         
836
837     /**
838      * APIMethod: getLayer
839      * Get a layer based on its id
840      *
841      * Parameter:
842      * id - {String} A layer id
843      *
844      * Returns:
845      * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's 
846      *                      layer collection, or null if not found.
847      */
848     getLayer: function(id) {
849         var foundLayer = null;
850         for (var i=0, len=this.layers.length; i<len; i++) {
851             var layer = this.layers[i];
852             if (layer.id == id) {
853                 foundLayer = layer;
854                 break;
855             }
856         }
857         return foundLayer;
858     },
859
860     /**
861     * Method: setLayerZIndex
862     * 
863     * Parameters:
864     * layer - {<OpenLayers.Layer>} 
865     * zIdx - {int} 
866     */    
867     setLayerZIndex: function (layer, zIdx) {
868         layer.setZIndex(
869             this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
870             + zIdx * 5 );
871     },
872
873     /**
874      * Method: resetLayersZIndex
875      * Reset each layer's z-index based on layer's array index
876      */
877     resetLayersZIndex: function() {
878         for (var i=0, len=this.layers.length; i<len; i++) {
879             var layer = this.layers[i];
880             this.setLayerZIndex(layer, i);
881         }
882     },
883
884     /**
885     * APIMethod: addLayer
886     *
887     * Parameters:
888     * layer - {<OpenLayers.Layer>} 
889     */    
890     addLayer: function (layer) {
891         for(var i=0, len=this.layers.length; i <len; i++) {
892             if (this.layers[i] == layer) {
893                 var msg = OpenLayers.i18n('layerAlreadyAdded', 
894                                                       {'layerName':layer.name});
895                 OpenLayers.Console.warn(msg);
896                 return false;
897             }
898         }
899         if(this.allOverlays) {
900             layer.isBaseLayer = false;
901         }
902
903         this.events.triggerEvent("preaddlayer", {layer: layer});
904         
905         layer.div.className = "olLayerDiv";
906         layer.div.style.overflow = "";
907         this.setLayerZIndex(layer, this.layers.length);
908
909         if (layer.isFixed) {
910             this.viewPortDiv.appendChild(layer.div);
911         } else {
912             this.layerContainerDiv.appendChild(layer.div);
913         }
914         this.layers.push(layer);
915         layer.setMap(this);
916
917         if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer))  {
918             if (this.baseLayer == null) {
919                 // set the first baselaye we add as the baselayer
920                 this.setBaseLayer(layer);
921             } else {
922                 layer.setVisibility(false);
923             }
924         } else {
925             layer.redraw();
926         }
927
928         this.events.triggerEvent("addlayer", {layer: layer});
929         layer.afterAdd();
930     },
931
932     /**
933     * APIMethod: addLayers 
934     *
935     * Parameters:
936     * layers - {Array(<OpenLayers.Layer>)} 
937     */    
938     addLayers: function (layers) {
939         for (var i=0, len=layers.length; i<len; i++) {
940             this.addLayer(layers[i]);
941         }
942     },
943
944     /** 
945      * APIMethod: removeLayer
946      * Removes a layer from the map by removing its visual element (the 
947      *   layer.div property), then removing it from the map's internal list 
948      *   of layers, setting the layer's map property to null. 
949      * 
950      *   a "removelayer" event is triggered.
951      * 
952      *   very worthy of mention is that simply removing a layer from a map
953      *   will not cause the removal of any popups which may have been created
954      *   by the layer. this is due to the fact that it was decided at some
955      *   point that popups would not belong to layers. thus there is no way 
956      *   for us to know here to which layer the popup belongs.
957      *    
958      *     A simple solution to this is simply to call destroy() on the layer.
959      *     the default OpenLayers.Layer class's destroy() function
960      *     automatically takes care to remove itself from whatever map it has
961      *     been attached to. 
962      * 
963      *     The correct solution is for the layer itself to register an 
964      *     event-handler on "removelayer" and when it is called, if it 
965      *     recognizes itself as the layer being removed, then it cycles through
966      *     its own personal list of popups, removing them from the map.
967      * 
968      * Parameters:
969      * layer - {<OpenLayers.Layer>} 
970      * setNewBaseLayer - {Boolean} Default is true
971      */
972     removeLayer: function(layer, setNewBaseLayer) {
973         if (setNewBaseLayer == null) {
974             setNewBaseLayer = true;
975         }
976
977         if (layer.isFixed) {
978             this.viewPortDiv.removeChild(layer.div);
979         } else {
980             this.layerContainerDiv.removeChild(layer.div);
981         }
982         OpenLayers.Util.removeItem(this.layers, layer);
983         layer.removeMap(this);
984         layer.map = null;
985
986         // if we removed the base layer, need to set a new one
987         if(this.baseLayer == layer) {
988             this.baseLayer = null;
989             if(setNewBaseLayer) {
990                 for(var i=0, len=this.layers.length; i<len; i++) {
991                     var iLayer = this.layers[i];
992                     if (iLayer.isBaseLayer || this.allOverlays) {
993                         this.setBaseLayer(iLayer);
994                         break;
995                     }
996                 }
997             }
998         }
999
1000         this.resetLayersZIndex();
1001
1002         this.events.triggerEvent("removelayer", {layer: layer});
1003     },
1004
1005     /**
1006      * APIMethod: getNumLayers
1007      * 
1008      * Returns:
1009      * {Int} The number of layers attached to the map.
1010      */
1011     getNumLayers: function () {
1012         return this.layers.length;
1013     },
1014
1015     /** 
1016      * APIMethod: getLayerIndex
1017      *
1018      * Parameters:
1019      * layer - {<OpenLayers.Layer>}
1020      *
1021      * Returns:
1022      * {Integer} The current (zero-based) index of the given layer in the map's
1023      *           layer stack. Returns -1 if the layer isn't on the map.
1024      */
1025     getLayerIndex: function (layer) {
1026         return OpenLayers.Util.indexOf(this.layers, layer);
1027     },
1028     
1029     /** 
1030      * APIMethod: setLayerIndex
1031      * Move the given layer to the specified (zero-based) index in the layer
1032      *     list, changing its z-index in the map display. Use
1033      *     map.getLayerIndex() to find out the current index of a layer. Note
1034      *     that this cannot (or at least should not) be effectively used to
1035      *     raise base layers above overlays.
1036      *
1037      * Parameters:
1038      * layer - {<OpenLayers.Layer>} 
1039      * idx - {int} 
1040      */
1041     setLayerIndex: function (layer, idx) {
1042         var base = this.getLayerIndex(layer);
1043         if (idx < 0) {
1044             idx = 0;
1045         } else if (idx > this.layers.length) {
1046             idx = this.layers.length;
1047         }
1048         if (base != idx) {
1049             this.layers.splice(base, 1);
1050             this.layers.splice(idx, 0, layer);
1051             for (var i=0, len=this.layers.length; i<len; i++) {
1052                 this.setLayerZIndex(this.layers[i], i);
1053             }
1054             this.events.triggerEvent("changelayer", {
1055                 layer: layer, property: "order"
1056             });
1057             if(this.allOverlays) {
1058                 if(idx === 0) {
1059                     this.setBaseLayer(layer);
1060                 } else if(this.baseLayer !== this.layers[0]) {
1061                     this.setBaseLayer(this.layers[0]);
1062                 }
1063             }
1064         }
1065     },
1066
1067     /** 
1068      * APIMethod: raiseLayer
1069      * Change the index of the given layer by delta. If delta is positive, 
1070      *     the layer is moved up the map's layer stack; if delta is negative,
1071      *     the layer is moved down.  Again, note that this cannot (or at least
1072      *     should not) be effectively used to raise base layers above overlays.
1073      *
1074      * Paremeters:
1075      * layer - {<OpenLayers.Layer>} 
1076      * delta - {int} 
1077      */
1078     raiseLayer: function (layer, delta) {
1079         var idx = this.getLayerIndex(layer) + delta;
1080         this.setLayerIndex(layer, idx);
1081     },
1082     
1083     /** 
1084      * APIMethod: setBaseLayer
1085      * Allows user to specify one of the currently-loaded layers as the Map's
1086      *     new base layer.
1087      * 
1088      * Parameters:
1089      * newBaseLayer - {<OpenLayers.Layer>}
1090      */
1091     setBaseLayer: function(newBaseLayer) {
1092         var oldExtent = null;
1093         if (this.baseLayer) {
1094             oldExtent = this.baseLayer.getExtent();
1095         }
1096
1097         if (newBaseLayer != this.baseLayer) {
1098           
1099             // is newBaseLayer an already loaded layer?m
1100             if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
1101
1102                 // make the old base layer invisible 
1103                 if (this.baseLayer != null && !this.allOverlays) {
1104                     this.baseLayer.setVisibility(false);
1105                 }
1106
1107                 // set new baselayer
1108                 this.baseLayer = newBaseLayer;
1109                 
1110                 // Increment viewRequestID since the baseLayer is 
1111                 // changing. This is used by tiles to check if they should 
1112                 // draw themselves.
1113                 this.viewRequestID++;
1114                 if(!this.allOverlays) {
1115                     this.baseLayer.visibility = true;
1116                 }
1117
1118                 //redraw all layers
1119                 var center = this.getCenter();
1120                 if (center != null) {
1121
1122                     //either get the center from the old Extent or just from
1123                     // the current center of the map. 
1124                     var newCenter = (oldExtent) 
1125                         ? oldExtent.getCenterLonLat()
1126                         : center;
1127
1128                     //the new zoom will either come from the old Extent or 
1129                     // from the current resolution of the map                                                
1130                     var newZoom = (oldExtent) 
1131                         ? this.getZoomForExtent(oldExtent, true)
1132                         : this.getZoomForResolution(this.resolution, true);
1133
1134                     // zoom and force zoom change
1135                     this.setCenter(newCenter, newZoom, false, true);
1136                 }
1137
1138                 this.events.triggerEvent("changebaselayer", {
1139                     layer: this.baseLayer
1140                 });
1141             }        
1142         }
1143     },
1144
1145
1146   /********************************************************/
1147   /*                                                      */
1148   /*                 Control Functions                    */
1149   /*                                                      */
1150   /*     The following functions deal with adding and     */
1151   /*        removing Controls to and from the Map         */
1152   /*                                                      */
1153   /********************************************************/         
1154
1155     /**
1156      * APIMethod: addControl
1157      * 
1158      * Parameters:
1159      * control - {<OpenLayers.Control>}
1160      * px - {<OpenLayers.Pixel>}
1161      */    
1162     addControl: function (control, px) {
1163         this.controls.push(control);
1164         this.addControlToMap(control, px);
1165     },
1166
1167     /**
1168      * Method: addControlToMap
1169      * 
1170      * Parameters:
1171      * 
1172      * control - {<OpenLayers.Control>}
1173      * px - {<OpenLayers.Pixel>}
1174      */    
1175     addControlToMap: function (control, px) {
1176         // If a control doesn't have a div at this point, it belongs in the
1177         // viewport.
1178         control.outsideViewport = (control.div != null);
1179         
1180         // If the map has a displayProjection, and the control doesn't, set 
1181         // the display projection.
1182         if (this.displayProjection && !control.displayProjection) {
1183             control.displayProjection = this.displayProjection;
1184         }    
1185         
1186         control.setMap(this);
1187         var div = control.draw(px);
1188         if (div) {
1189             if(!control.outsideViewport) {
1190                 div.style.zIndex = this.Z_INDEX_BASE['Control'] +
1191                                     this.controls.length;
1192                 this.viewPortDiv.appendChild( div );
1193             }
1194         }
1195     },
1196     
1197     /**
1198      * APIMethod: getControl
1199      * 
1200      * Parameters:
1201      * id - {String} ID of the control to return.
1202      * 
1203      * Returns:
1204      * {<OpenLayers.Control>} The control from the map's list of controls 
1205      *                        which has a matching 'id'. If none found, 
1206      *                        returns null.
1207      */    
1208     getControl: function (id) {
1209         var returnControl = null;
1210         for(var i=0, len=this.controls.length; i<len; i++) {
1211             var control = this.controls[i];
1212             if (control.id == id) {
1213                 returnControl = control;
1214                 break;
1215             }
1216         }
1217         return returnControl;
1218     },
1219     
1220     /** 
1221      * APIMethod: removeControl
1222      * Remove a control from the map. Removes the control both from the map 
1223      *     object's internal array of controls, as well as from the map's 
1224      *     viewPort (assuming the control was not added outsideViewport)
1225      * 
1226      * Parameters:
1227      * control - {<OpenLayers.Control>} The control to remove.
1228      */    
1229     removeControl: function (control) {
1230         //make sure control is non-null and actually part of our map
1231         if ( (control) && (control == this.getControl(control.id)) ) {
1232             if (control.div && (control.div.parentNode == this.viewPortDiv)) {
1233                 this.viewPortDiv.removeChild(control.div);
1234             }
1235             OpenLayers.Util.removeItem(this.controls, control);
1236         }
1237     },
1238
1239   /********************************************************/
1240   /*                                                      */
1241   /*                  Popup Functions                     */
1242   /*                                                      */
1243   /*     The following functions deal with adding and     */
1244   /*        removing Popups to and from the Map           */
1245   /*                                                      */
1246   /********************************************************/         
1247
1248     /** 
1249      * APIMethod: addPopup
1250      * 
1251      * Parameters:
1252      * popup - {<OpenLayers.Popup>}
1253      * exclusive - {Boolean} If true, closes all other popups first
1254      */
1255     addPopup: function(popup, exclusive) {
1256
1257         if (exclusive) {
1258             //remove all other popups from screen
1259             for (var i = this.popups.length - 1; i >= 0; --i) {
1260                 this.removePopup(this.popups[i]);
1261             }
1262         }
1263
1264         popup.map = this;
1265         this.popups.push(popup);
1266         var popupDiv = popup.draw();
1267         if (popupDiv) {
1268             popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
1269                                     this.popups.length;
1270             this.layerContainerDiv.appendChild(popupDiv);
1271         }
1272     },
1273     
1274     /** 
1275     * APIMethod: removePopup
1276     * 
1277     * Parameters:
1278     * popup - {<OpenLayers.Popup>}
1279     */
1280     removePopup: function(popup) {
1281         OpenLayers.Util.removeItem(this.popups, popup);
1282         if (popup.div) {
1283             try { this.layerContainerDiv.removeChild(popup.div); }
1284             catch (e) { } // Popups sometimes apparently get disconnected
1285                       // from the layerContainerDiv, and cause complaints.
1286         }
1287         popup.map = null;
1288     },
1289
1290   /********************************************************/
1291   /*                                                      */
1292   /*              Container Div Functions                 */
1293   /*                                                      */
1294   /*   The following functions deal with the access to    */
1295   /*    and maintenance of the size of the container div  */
1296   /*                                                      */
1297   /********************************************************/     
1298
1299     /**
1300      * APIMethod: getSize
1301      * 
1302      * Returns:
1303      * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the 
1304      *                     size, in pixels, of the div into which OpenLayers 
1305      *                     has been loaded. 
1306      *                     Note - A clone() of this locally cached variable is
1307      *                     returned, so as not to allow users to modify it.
1308      */
1309     getSize: function () {
1310         var size = null;
1311         if (this.size != null) {
1312             size = this.size.clone();
1313         }
1314         return size;
1315     },
1316
1317     /**
1318      * APIMethod: updateSize
1319      * This function should be called by any external code which dynamically
1320      *     changes the size of the map div (because mozilla wont let us catch 
1321      *     the "onresize" for an element)
1322      */
1323     updateSize: function() {
1324         // the div might have moved on the page, also
1325         this.events.clearMouseCache();
1326         var newSize = this.getCurrentSize();
1327         var oldSize = this.getSize();
1328         if (oldSize == null) {
1329             this.size = oldSize = newSize;
1330         }
1331         if (!newSize.equals(oldSize)) {
1332             
1333             // store the new size
1334             this.size = newSize;
1335
1336             //notify layers of mapresize
1337             for(var i=0, len=this.layers.length; i<len; i++) {
1338                 this.layers[i].onMapResize();                
1339             }
1340
1341             if (this.baseLayer != null) {
1342                 var center = new OpenLayers.Pixel(newSize.w /2, newSize.h / 2);
1343                 var centerLL = this.getLonLatFromViewPortPx(center);
1344                 var zoom = this.getZoom();
1345                 this.zoom = null;
1346                 this.setCenter(this.getCenter(), zoom);
1347             }
1348
1349         }
1350     },
1351     
1352     /**
1353      * Method: getCurrentSize
1354      * 
1355      * Returns:
1356      * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions 
1357      *                     of the map div
1358      */
1359     getCurrentSize: function() {
1360
1361         var size = new OpenLayers.Size(this.div.clientWidth, 
1362                                        this.div.clientHeight);
1363
1364         // Workaround for the fact that hidden elements return 0 for size.
1365         if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
1366             var dim = OpenLayers.Element.getDimensions(this.div);
1367             size.w = dim.width;
1368             size.h = dim.height;
1369         }
1370         if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
1371             size.w = parseInt(this.div.style.width);
1372             size.h = parseInt(this.div.style.height);
1373         }
1374         return size;
1375     },
1376
1377     /** 
1378      * Method: calculateBounds
1379      * 
1380      * Parameters:
1381      * center - {<OpenLayers.LonLat>} Default is this.getCenter()
1382      * resolution - {float} Default is this.getResolution() 
1383      * 
1384      * Returns:
1385      * {<OpenLayers.Bounds>} A bounds based on resolution, center, and 
1386      *                       current mapsize.
1387      */
1388     calculateBounds: function(center, resolution) {
1389
1390         var extent = null;
1391         
1392         if (center == null) {
1393             center = this.getCenter();
1394         }                
1395         if (resolution == null) {
1396             resolution = this.getResolution();
1397         }
1398     
1399         if ((center != null) && (resolution != null)) {
1400
1401             var size = this.getSize();
1402             var w_deg = size.w * resolution;
1403             var h_deg = size.h * resolution;
1404         
1405             extent = new OpenLayers.Bounds(center.lon - w_deg / 2,
1406                                            center.lat - h_deg / 2,
1407                                            center.lon + w_deg / 2,
1408                                            center.lat + h_deg / 2);
1409         
1410         }
1411
1412         return extent;
1413     },
1414
1415
1416   /********************************************************/
1417   /*                                                      */
1418   /*            Zoom, Center, Pan Functions               */
1419   /*                                                      */
1420   /*    The following functions handle the validation,    */
1421   /*   getting and setting of the Zoom Level and Center   */
1422   /*       as well as the panning of the Map              */
1423   /*                                                      */
1424   /********************************************************/
1425     /**
1426      * APIMethod: getCenter
1427      * 
1428      * Returns:
1429      * {<OpenLayers.LonLat>}
1430      */
1431     getCenter: function () {
1432         var center = null;
1433         if (this.center) {
1434             center = this.center.clone();
1435         }
1436         return center;
1437     },
1438
1439
1440     /**
1441      * APIMethod: getZoom
1442      * 
1443      * Returns:
1444      * {Integer}
1445      */
1446     getZoom: function () {
1447         return this.zoom;
1448     },
1449     
1450     /** 
1451      * APIMethod: pan
1452      * Allows user to pan by a value of screen pixels
1453      * 
1454      * Parameters:
1455      * dx - {Integer}
1456      * dy - {Integer}
1457      * options - {Object} Options to configure panning:
1458      *  - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
1459      *  - *dragging* {Boolean} Call setCenter with dragging true.  Default is
1460      *    false.
1461      */
1462     pan: function(dx, dy, options) {
1463         options = OpenLayers.Util.applyDefaults(options, {
1464             animate: true,
1465             dragging: false
1466         });
1467         // getCenter
1468         var centerPx = this.getViewPortPxFromLonLat(this.getCenter());
1469
1470         // adjust
1471         var newCenterPx = centerPx.add(dx, dy);
1472         
1473         // only call setCenter if not dragging or there has been a change
1474         if (!options.dragging || !newCenterPx.equals(centerPx)) {
1475             var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
1476             if (options.animate) {
1477                 this.panTo(newCenterLonLat);
1478             } else {
1479                 this.setCenter(newCenterLonLat, null, options.dragging);
1480             }    
1481         }
1482
1483    },
1484    
1485    /** 
1486      * APIMethod: panTo
1487      * Allows user to pan to a new lonlat
1488      * If the new lonlat is in the current extent the map will slide smoothly
1489      * 
1490      * Parameters:
1491      * lonlat - {<OpenLayers.Lonlat>}
1492      */
1493     panTo: function(lonlat) {
1494         if (this.panMethod && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
1495             if (!this.panTween) {
1496                 this.panTween = new OpenLayers.Tween(this.panMethod);
1497             }
1498             var center = this.getCenter();
1499
1500             // center will not change, don't do nothing
1501             if (lonlat.lon == center.lon &&
1502                 lonlat.lat == center.lat) {
1503                 return;
1504             }
1505
1506             var from = {
1507                 lon: center.lon,
1508                 lat: center.lat
1509             };
1510             var to = {
1511                 lon: lonlat.lon,
1512                 lat: lonlat.lat
1513             };
1514             this.panTween.start(from, to, this.panDuration, {
1515                 callbacks: {
1516                     start: OpenLayers.Function.bind(function(lonlat) {
1517                         this.events.triggerEvent("movestart");
1518                     }, this),
1519                     eachStep: OpenLayers.Function.bind(function(lonlat) {
1520                         lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
1521                         this.moveTo(lonlat, this.zoom, {
1522                             'dragging': true,
1523                             'noEvent': true
1524                         });
1525                     }, this),
1526                     done: OpenLayers.Function.bind(function(lonlat) {
1527                         lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
1528                         this.moveTo(lonlat, this.zoom, {
1529                             'noEvent': true
1530                         });
1531                         this.events.triggerEvent("moveend");
1532                     }, this)
1533                 }
1534             });
1535         } else {
1536             this.setCenter(lonlat);
1537         }
1538     },
1539
1540     /**
1541      * APIMethod: setCenter
1542      * Set the map center (and optionally, the zoom level).
1543      * 
1544      * Parameters:
1545      * lonlat - {<OpenLayers.LonLat>} The new center location.
1546      * zoom - {Integer} Optional zoom level.
1547      * dragging - {Boolean} Specifies whether or not to trigger 
1548      *                      movestart/end events
1549      * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom 
1550      *                             change events (needed on baseLayer change)
1551      *
1552      * TBD: reconsider forceZoomChange in 3.0
1553      */
1554     setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
1555         this.moveTo(lonlat, zoom, {
1556             'dragging': dragging,
1557             'forceZoomChange': forceZoomChange,
1558             'caller': 'setCenter'
1559         });
1560     },
1561
1562     /**
1563      * Method: moveTo
1564      *
1565      * Parameters:
1566      * lonlat - {<OpenLayers.LonLat>}
1567      * zoom - {Integer}
1568      * options - {Object}
1569      */
1570     moveTo: function(lonlat, zoom, options) {
1571         if (!options) { 
1572             options = {};
1573         }    
1574         // dragging is false by default
1575         var dragging = options.dragging;
1576         // forceZoomChange is false by default
1577         var forceZoomChange = options.forceZoomChange;
1578         // noEvent is false by default
1579         var noEvent = options.noEvent;
1580
1581         if (this.panTween && options.caller == "setCenter") {
1582             this.panTween.stop();
1583         }    
1584              
1585         if (!this.center && !this.isValidLonLat(lonlat)) {
1586             lonlat = this.maxExtent.getCenterLonLat();
1587         }
1588
1589         if(this.restrictedExtent != null) {
1590             // In 3.0, decide if we want to change interpretation of maxExtent.
1591             if(lonlat == null) { 
1592                 lonlat = this.getCenter(); 
1593             }
1594             if(zoom == null) { 
1595                 zoom = this.getZoom(); 
1596             }
1597             var resolution = this.getResolutionForZoom(zoom);
1598             var extent = this.calculateBounds(lonlat, resolution); 
1599             if(!this.restrictedExtent.containsBounds(extent)) {
1600                 var maxCenter = this.restrictedExtent.getCenterLonLat(); 
1601                 if(extent.getWidth() > this.restrictedExtent.getWidth()) { 
1602                     lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat); 
1603                 } else if(extent.left < this.restrictedExtent.left) {
1604                     lonlat = lonlat.add(this.restrictedExtent.left -
1605                                         extent.left, 0); 
1606                 } else if(extent.right > this.restrictedExtent.right) { 
1607                     lonlat = lonlat.add(this.restrictedExtent.right -
1608                                         extent.right, 0); 
1609                 } 
1610                 if(extent.getHeight() > this.restrictedExtent.getHeight()) { 
1611                     lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat); 
1612                 } else if(extent.bottom < this.restrictedExtent.bottom) { 
1613                     lonlat = lonlat.add(0, this.restrictedExtent.bottom -
1614                                         extent.bottom); 
1615                 } 
1616                 else if(extent.top > this.restrictedExtent.top) { 
1617                     lonlat = lonlat.add(0, this.restrictedExtent.top -
1618                                         extent.top); 
1619                 } 
1620             }
1621         }
1622         
1623         var zoomChanged = forceZoomChange || (
1624                             (this.isValidZoomLevel(zoom)) && 
1625                             (zoom != this.getZoom()) );
1626
1627         var centerChanged = (this.isValidLonLat(lonlat)) && 
1628                             (!lonlat.equals(this.center));
1629
1630
1631         // if neither center nor zoom will change, no need to do anything
1632         if (zoomChanged || centerChanged || !dragging) {
1633
1634             if (!this.dragging && !noEvent) {
1635                 this.events.triggerEvent("movestart");
1636             }
1637
1638             if (centerChanged) {
1639                 if ((!zoomChanged) && (this.center)) { 
1640                     // if zoom hasnt changed, just slide layerContainer
1641                     //  (must be done before setting this.center to new value)
1642                     this.centerLayerContainer(lonlat);
1643                 }
1644                 this.center = lonlat.clone();
1645             }
1646
1647             // (re)set the layerContainerDiv's location
1648             if ((zoomChanged) || (this.layerContainerOrigin == null)) {
1649                 this.layerContainerOrigin = this.center.clone();
1650                 this.layerContainerDiv.style.left = "0px";
1651                 this.layerContainerDiv.style.top  = "0px";
1652             }
1653
1654             if (zoomChanged) {
1655                 this.zoom = zoom;
1656                 this.resolution = this.getResolutionForZoom(zoom);
1657                 // zoom level has changed, increment viewRequestID.
1658                 this.viewRequestID++;
1659             }    
1660             
1661             var bounds = this.getExtent();
1662             
1663             //send the move call to the baselayer and all the overlays    
1664
1665             if(this.baseLayer.visibility) {
1666                 this.baseLayer.moveTo(bounds, zoomChanged, dragging);
1667                 if(dragging) {
1668                     this.baseLayer.events.triggerEvent("move");
1669                 } else {
1670                     this.baseLayer.events.triggerEvent("moveend",
1671                         {"zoomChanged": zoomChanged}
1672                     );
1673                 }
1674             }
1675             
1676             bounds = this.baseLayer.getExtent();
1677             
1678             for (var i=0, len=this.layers.length; i<len; i++) {
1679                 var layer = this.layers[i];
1680                 if (layer !== this.baseLayer && !layer.isBaseLayer) {
1681                     var inRange = layer.calculateInRange();
1682                     if (layer.inRange != inRange) {
1683                         // the inRange property has changed. If the layer is
1684                         // no longer in range, we turn it off right away. If
1685                         // the layer is no longer out of range, the moveTo
1686                         // call below will turn on the layer.
1687                         layer.inRange = inRange;
1688                         if (!inRange) {
1689                             layer.display(false);
1690                         }
1691                         this.events.triggerEvent("changelayer", {
1692                             layer: layer, property: "visibility"
1693                         });
1694                     }
1695                     if (inRange && layer.visibility) {
1696                         layer.moveTo(bounds, zoomChanged, dragging);
1697                         if(dragging) {
1698                             layer.events.triggerEvent("move");
1699                         } else {
1700                             layer.events.triggerEvent("moveend",
1701                                 {"zoomChanged": zoomChanged}
1702                             );
1703                         }
1704                     }
1705                 }                
1706             }
1707             
1708             if (zoomChanged) {
1709                 //redraw popups
1710                 for (var i=0, len=this.popups.length; i<len; i++) {
1711                     this.popups[i].updatePosition();
1712                 }
1713             }    
1714             
1715             this.events.triggerEvent("move");
1716     
1717             if (zoomChanged) { this.events.triggerEvent("zoomend"); }
1718         }
1719
1720         // even if nothing was done, we want to notify of this
1721         if (!dragging && !noEvent) {
1722             this.events.triggerEvent("moveend");
1723         }
1724         
1725         // Store the map dragging state for later use
1726         this.dragging = !!dragging; 
1727
1728     },
1729
1730     /** 
1731      * Method: centerLayerContainer
1732      * This function takes care to recenter the layerContainerDiv.
1733      * 
1734      * Parameters:
1735      * lonlat - {<OpenLayers.LonLat>}
1736      */
1737     centerLayerContainer: function (lonlat) {
1738
1739         var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
1740         var newPx = this.getViewPortPxFromLonLat(lonlat);
1741
1742         if ((originPx != null) && (newPx != null)) {
1743             this.layerContainerDiv.style.left = Math.round(originPx.x - newPx.x) + "px";
1744             this.layerContainerDiv.style.top  = Math.round(originPx.y - newPx.y) + "px";
1745         }
1746     },
1747
1748     /**
1749      * Method: isValidZoomLevel
1750      * 
1751      * Parameters:
1752      * zoomLevel - {Integer}
1753      * 
1754      * Returns:
1755      * {Boolean} Whether or not the zoom level passed in is non-null and 
1756      *           within the min/max range of zoom levels.
1757      */
1758     isValidZoomLevel: function(zoomLevel) {
1759        return ( (zoomLevel != null) &&
1760                 (zoomLevel >= 0) && 
1761                 (zoomLevel < this.getNumZoomLevels()) );
1762     },
1763     
1764     /**
1765      * Method: isValidLonLat
1766      * 
1767      * Parameters:
1768      * lonlat - {<OpenLayers.LonLat>}
1769      * 
1770      * Returns:
1771      * {Boolean} Whether or not the lonlat passed in is non-null and within
1772      *           the maxExtent bounds
1773      */
1774     isValidLonLat: function(lonlat) {
1775         var valid = false;
1776         if (lonlat != null) {
1777             var maxExtent = this.getMaxExtent();
1778             valid = maxExtent.containsLonLat(lonlat);        
1779         }
1780         return valid;
1781     },
1782
1783   /********************************************************/
1784   /*                                                      */
1785   /*                 Layer Options                        */
1786   /*                                                      */
1787   /*    Accessor functions to Layer Options parameters    */
1788   /*                                                      */
1789   /********************************************************/
1790     
1791     /**
1792      * APIMethod: getProjection
1793      * This method returns a string representing the projection. In 
1794      *     the case of projection support, this will be the srsCode which
1795      *     is loaded -- otherwise it will simply be the string value that
1796      *     was passed to the projection at startup.
1797      *
1798      * FIXME: In 3.0, we will remove getProjectionObject, and instead
1799      *     return a Projection object from this function. 
1800      * 
1801      * Returns:
1802      * {String} The Projection string from the base layer or null. 
1803      */
1804     getProjection: function() {
1805         var projection = this.getProjectionObject();
1806         return projection ? projection.getCode() : null;
1807     },
1808     
1809     /**
1810      * APIMethod: getProjectionObject
1811      * Returns the projection obect from the baselayer.
1812      *
1813      * Returns:
1814      * {<OpenLayers.Projection>} The Projection of the base layer.
1815      */
1816     getProjectionObject: function() {
1817         var projection = null;
1818         if (this.baseLayer != null) {
1819             projection = this.baseLayer.projection;
1820         }
1821         return projection;
1822     },
1823     
1824     /**
1825      * APIMethod: getMaxResolution
1826      * 
1827      * Returns:
1828      * {String} The Map's Maximum Resolution
1829      */
1830     getMaxResolution: function() {
1831         var maxResolution = null;
1832         if (this.baseLayer != null) {
1833             maxResolution = this.baseLayer.maxResolution;
1834         }
1835         return maxResolution;
1836     },
1837         
1838     /**
1839      * APIMethod: getMaxExtent
1840      *
1841      * Parameters:
1842      * options - {Object} 
1843      * 
1844      * Allowed Options:
1845      * restricted - {Boolean} If true, returns restricted extent (if it is 
1846      *     available.)
1847      *
1848      * Returns:
1849      * {<OpenLayers.Bounds>} The maxExtent property as set on the current 
1850      *     baselayer, unless the 'restricted' option is set, in which case
1851      *     the 'restrictedExtent' option from the map is returned (if it
1852      *     is set).
1853      */
1854     getMaxExtent: function (options) {
1855         var maxExtent = null;
1856         if(options && options.restricted && this.restrictedExtent){
1857             maxExtent = this.restrictedExtent;
1858         } else if (this.baseLayer != null) {
1859             maxExtent = this.baseLayer.maxExtent;
1860         }        
1861         return maxExtent;
1862     },
1863     
1864     /**
1865      * APIMethod: getNumZoomLevels
1866      * 
1867      * Returns:
1868      * {Integer} The total number of zoom levels that can be displayed by the 
1869      *           current baseLayer.
1870      */
1871     getNumZoomLevels: function() {
1872         var numZoomLevels = null;
1873         if (this.baseLayer != null) {
1874             numZoomLevels = this.baseLayer.numZoomLevels;
1875         }
1876         return numZoomLevels;
1877     },
1878
1879   /********************************************************/
1880   /*                                                      */
1881   /*                 Baselayer Functions                  */
1882   /*                                                      */
1883   /*    The following functions, all publicly exposed     */
1884   /*       in the API?, are all merely wrappers to the    */
1885   /*       the same calls on whatever layer is set as     */
1886   /*                the current base layer                */
1887   /*                                                      */
1888   /********************************************************/
1889
1890     /**
1891      * APIMethod: getExtent
1892      * 
1893      * Returns:
1894      * {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat 
1895      *                       bounds of the current viewPort. 
1896      *                       If no baselayer is set, returns null.
1897      */
1898     getExtent: function () {
1899         var extent = null;
1900         if (this.baseLayer != null) {
1901             extent = this.baseLayer.getExtent();
1902         }
1903         return extent;
1904     },
1905
1906     /**
1907      * APIMethod: getResolution
1908      * 
1909      * Returns:
1910      * {Float} The current resolution of the map. 
1911      *         If no baselayer is set, returns null.
1912      */
1913     getResolution: function () {
1914         var resolution = null;
1915         if (this.baseLayer != null) {
1916             resolution = this.baseLayer.getResolution();
1917         }
1918         return resolution;
1919     },
1920
1921     /**
1922      * APIMethod: getUnits
1923      * 
1924      * Returns:
1925      * {Float} The current units of the map. 
1926      *         If no baselayer is set, returns null.
1927      */
1928     getUnits: function () {
1929         var units = null;
1930         if (this.baseLayer != null) {
1931             units = this.baseLayer.units;
1932         }
1933         return units;
1934     },
1935
1936      /**
1937       * APIMethod: getScale
1938       * 
1939       * Returns:
1940       * {Float} The current scale denominator of the map. 
1941       *         If no baselayer is set, returns null.
1942       */
1943     getScale: function () {
1944         var scale = null;
1945         if (this.baseLayer != null) {
1946             var res = this.getResolution();
1947             var units = this.baseLayer.units;
1948             scale = OpenLayers.Util.getScaleFromResolution(res, units);
1949         }
1950         return scale;
1951     },
1952
1953
1954     /**
1955      * APIMethod: getZoomForExtent
1956      * 
1957      * Parameters: 
1958      * bounds - {<OpenLayers.Bounds>}
1959      * closest - {Boolean} Find the zoom level that most closely fits the 
1960      *     specified bounds. Note that this may result in a zoom that does 
1961      *     not exactly contain the entire extent.
1962      *     Default is false.
1963      * 
1964      * Returns:
1965      * {Integer} A suitable zoom level for the specified bounds.
1966      *           If no baselayer is set, returns null.
1967      */
1968     getZoomForExtent: function (bounds, closest) {
1969         var zoom = null;
1970         if (this.baseLayer != null) {
1971             zoom = this.baseLayer.getZoomForExtent(bounds, closest);
1972         }
1973         return zoom;
1974     },
1975
1976     /**
1977      * APIMethod: getResolutionForZoom
1978      * 
1979      * Parameter:
1980      * zoom - {Float}
1981      * 
1982      * Returns:
1983      * {Float} A suitable resolution for the specified zoom.  If no baselayer
1984      *     is set, returns null.
1985      */
1986     getResolutionForZoom: function(zoom) {
1987         var resolution = null;
1988         if(this.baseLayer) {
1989             resolution = this.baseLayer.getResolutionForZoom(zoom);
1990         }
1991         return resolution;
1992     },
1993
1994     /**
1995      * APIMethod: getZoomForResolution
1996      * 
1997      * Parameter:
1998      * resolution - {Float}
1999      * closest - {Boolean} Find the zoom level that corresponds to the absolute 
2000      *     closest resolution, which may result in a zoom whose corresponding
2001      *     resolution is actually smaller than we would have desired (if this
2002      *     is being called from a getZoomForExtent() call, then this means that
2003      *     the returned zoom index might not actually contain the entire 
2004      *     extent specified... but it'll be close).
2005      *     Default is false.
2006      * 
2007      * Returns:
2008      * {Integer} A suitable zoom level for the specified resolution.
2009      *           If no baselayer is set, returns null.
2010      */
2011     getZoomForResolution: function(resolution, closest) {
2012         var zoom = null;
2013         if (this.baseLayer != null) {
2014             zoom = this.baseLayer.getZoomForResolution(resolution, closest);
2015         }
2016         return zoom;
2017     },
2018
2019   /********************************************************/
2020   /*                                                      */
2021   /*                  Zooming Functions                   */
2022   /*                                                      */
2023   /*    The following functions, all publicly exposed     */
2024   /*       in the API, are all merely wrappers to the     */
2025   /*               the setCenter() function               */
2026   /*                                                      */
2027   /********************************************************/
2028   
2029     /** 
2030      * APIMethod: zoomTo
2031      * Zoom to a specific zoom level
2032      * 
2033      * Parameters:
2034      * zoom - {Integer}
2035      */
2036     zoomTo: function(zoom) {
2037         if (this.isValidZoomLevel(zoom)) {
2038             this.setCenter(null, zoom);
2039         }
2040     },
2041     
2042     /**
2043      * APIMethod: zoomIn
2044      * 
2045      * Parameters:
2046      * zoom - {int}
2047      */
2048     zoomIn: function() {
2049         this.zoomTo(this.getZoom() + 1);
2050     },
2051     
2052     /**
2053      * APIMethod: zoomOut
2054      * 
2055      * Parameters:
2056      * zoom - {int}
2057      */
2058     zoomOut: function() {
2059         this.zoomTo(this.getZoom() - 1);
2060     },
2061
2062     /**
2063      * APIMethod: zoomToExtent
2064      * Zoom to the passed in bounds, recenter
2065      * 
2066      * Parameters:
2067      * bounds - {<OpenLayers.Bounds>}
2068      * closest - {Boolean} Find the zoom level that most closely fits the 
2069      *     specified bounds. Note that this may result in a zoom that does 
2070      *     not exactly contain the entire extent.
2071      *     Default is false.
2072      * 
2073      */
2074     zoomToExtent: function(bounds, closest) {
2075         var center = bounds.getCenterLonLat();
2076         if (this.baseLayer.wrapDateLine) {
2077             var maxExtent = this.getMaxExtent();
2078
2079             //fix straddling bounds (in the case of a bbox that straddles the 
2080             // dateline, it's left and right boundaries will appear backwards. 
2081             // we fix this by allowing a right value that is greater than the
2082             // max value at the dateline -- this allows us to pass a valid 
2083             // bounds to calculate zoom)
2084             //
2085             bounds = bounds.clone();
2086             while (bounds.right < bounds.left) {
2087                 bounds.right += maxExtent.getWidth();
2088             }
2089             //if the bounds was straddling (see above), then the center point 
2090             // we got from it was wrong. So we take our new bounds and ask it
2091             // for the center. Because our new bounds is at least partially 
2092             // outside the bounds of maxExtent, the new calculated center 
2093             // might also be. We don't want to pass a bad center value to 
2094             // setCenter, so we have it wrap itself across the date line.
2095             //
2096             center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
2097         }
2098         this.setCenter(center, this.getZoomForExtent(bounds, closest));
2099     },
2100
2101     /** 
2102      * APIMethod: zoomToMaxExtent
2103      * Zoom to the full extent and recenter.
2104      *
2105      * Parameters:
2106      * options - 
2107      * 
2108      * Allowed Options:
2109      * restricted - {Boolean} True to zoom to restricted extent if it is 
2110      *     set. Defaults to true.
2111      */
2112     zoomToMaxExtent: function(options) {
2113         //restricted is true by default
2114         var restricted = (options) ? options.restricted : true;
2115
2116         var maxExtent = this.getMaxExtent({
2117             'restricted': restricted 
2118         });
2119         this.zoomToExtent(maxExtent);
2120     },
2121
2122     /** 
2123      * APIMethod: zoomToScale
2124      * Zoom to a specified scale 
2125      * 
2126      * Parameters:
2127      * scale - {float}
2128      * closest - {Boolean} Find the zoom level that most closely fits the 
2129      *     specified scale. Note that this may result in a zoom that does 
2130      *     not exactly contain the entire extent.
2131      *     Default is false.
2132      * 
2133      */
2134     zoomToScale: function(scale, closest) {
2135         var res = OpenLayers.Util.getResolutionFromScale(scale, 
2136                                                          this.baseLayer.units);
2137         var size = this.getSize();
2138         var w_deg = size.w * res;
2139         var h_deg = size.h * res;
2140         var center = this.getCenter();
2141
2142         var extent = new OpenLayers.Bounds(center.lon - w_deg / 2,
2143                                            center.lat - h_deg / 2,
2144                                            center.lon + w_deg / 2,
2145                                            center.lat + h_deg / 2);
2146         this.zoomToExtent(extent, closest);
2147     },
2148     
2149   /********************************************************/
2150   /*                                                      */
2151   /*             Translation Functions                    */
2152   /*                                                      */
2153   /*      The following functions translate between       */
2154   /*           LonLat, LayerPx, and ViewPortPx            */
2155   /*                                                      */
2156   /********************************************************/
2157       
2158   //
2159   // TRANSLATION: LonLat <-> ViewPortPx
2160   //
2161
2162     /**
2163      * Method: getLonLatFromViewPortPx
2164      * 
2165      * Parameters:
2166      * viewPortPx - {<OpenLayers.Pixel>}
2167      * 
2168      * Returns:
2169      * {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view 
2170      *                       port <OpenLayers.Pixel>, translated into lon/lat
2171      *                       by the current base layer.
2172      */
2173     getLonLatFromViewPortPx: function (viewPortPx) {
2174         var lonlat = null; 
2175         if (this.baseLayer != null) {
2176             lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
2177         }
2178         return lonlat;
2179     },
2180
2181     /**
2182      * APIMethod: getViewPortPxFromLonLat
2183      * 
2184      * Parameters:
2185      * lonlat - {<OpenLayers.LonLat>}
2186      * 
2187      * Returns:
2188      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
2189      *                      <OpenLayers.LonLat>, translated into view port 
2190      *                      pixels by the current base layer.
2191      */
2192     getViewPortPxFromLonLat: function (lonlat) {
2193         var px = null; 
2194         if (this.baseLayer != null) {
2195             px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
2196         }
2197         return px;
2198     },
2199
2200     
2201   //
2202   // CONVENIENCE TRANSLATION FUNCTIONS FOR API
2203   //
2204
2205     /**
2206      * APIMethod: getLonLatFromPixel
2207      * 
2208      * Parameters:
2209      * px - {<OpenLayers.Pixel>}
2210      *
2211      * Returns:
2212      * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
2213      *                       OpenLayers.Pixel, translated into lon/lat by the 
2214      *                       current base layer
2215      */
2216     getLonLatFromPixel: function (px) {
2217         return this.getLonLatFromViewPortPx(px);
2218     },
2219
2220     /**
2221      * APIMethod: getPixelFromLonLat
2222      * Returns a pixel location given a map location.  The map location is
2223      *     translated to an integer pixel location (in viewport pixel
2224      *     coordinates) by the current base layer.
2225      * 
2226      * Parameters:
2227      * lonlat - {<OpenLayers.LonLat>} A map location.
2228      * 
2229      * Returns: 
2230      * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the 
2231      *     <OpenLayers.LonLat> translated into view port pixels by the current
2232      *     base layer.
2233      */
2234     getPixelFromLonLat: function (lonlat) {
2235         var px = this.getViewPortPxFromLonLat(lonlat);
2236         px.x = Math.round(px.x);
2237         px.y = Math.round(px.y);
2238         return px;
2239     },
2240
2241
2242
2243   //
2244   // TRANSLATION: ViewPortPx <-> LayerPx
2245   //
2246
2247     /**
2248      * APIMethod: getViewPortPxFromLayerPx
2249      * 
2250      * Parameters:
2251      * layerPx - {<OpenLayers.Pixel>}
2252      * 
2253      * Returns:
2254      * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel 
2255      *                      coordinates
2256      */
2257     getViewPortPxFromLayerPx:function(layerPx) {
2258         var viewPortPx = null;
2259         if (layerPx != null) {
2260             var dX = parseInt(this.layerContainerDiv.style.left);
2261             var dY = parseInt(this.layerContainerDiv.style.top);
2262             viewPortPx = layerPx.add(dX, dY);            
2263         }
2264         return viewPortPx;
2265     },
2266     
2267     /**
2268      * APIMethod: getLayerPxFromViewPortPx
2269      * 
2270      * Parameters:
2271      * viewPortPx - {<OpenLayers.Pixel>}
2272      * 
2273      * Returns:
2274      * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel 
2275      *                      coordinates
2276      */
2277     getLayerPxFromViewPortPx:function(viewPortPx) {
2278         var layerPx = null;
2279         if (viewPortPx != null) {
2280             var dX = -parseInt(this.layerContainerDiv.style.left);
2281             var dY = -parseInt(this.layerContainerDiv.style.top);
2282             layerPx = viewPortPx.add(dX, dY);
2283             if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
2284                 layerPx = null;
2285             }
2286         }
2287         return layerPx;
2288     },
2289     
2290   //
2291   // TRANSLATION: LonLat <-> LayerPx
2292   //
2293
2294     /**
2295      * Method: getLonLatFromLayerPx
2296      * 
2297      * Parameters:
2298      * px - {<OpenLayers.Pixel>}
2299      *
2300      * Returns:
2301      * {<OpenLayers.LonLat>}
2302      */
2303     getLonLatFromLayerPx: function (px) {
2304        //adjust for displacement of layerContainerDiv
2305        px = this.getViewPortPxFromLayerPx(px);
2306        return this.getLonLatFromViewPortPx(px);         
2307     },
2308     
2309     /**
2310      * APIMethod: getLayerPxFromLonLat
2311      * 
2312      * Parameters:
2313      * lonlat - {<OpenLayers.LonLat>} lonlat
2314      *
2315      * Returns:
2316      * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in 
2317      *                      <OpenLayers.LonLat>, translated into layer pixels 
2318      *                      by the current base layer
2319      */
2320     getLayerPxFromLonLat: function (lonlat) {
2321        //adjust for displacement of layerContainerDiv
2322        var px = this.getPixelFromLonLat(lonlat);
2323        return this.getLayerPxFromViewPortPx(px);         
2324     },
2325
2326     CLASS_NAME: "OpenLayers.Map"
2327 });
2328
2329 /**
2330  * Constant: TILE_WIDTH
2331  * {Integer} 256 Default tile width (unless otherwise specified)
2332  */
2333 OpenLayers.Map.TILE_WIDTH = 256;
2334 /**
2335  * Constant: TILE_HEIGHT
2336  * {Integer} 256 Default tile height (unless otherwise specified)
2337  */
2338 OpenLayers.Map.TILE_HEIGHT = 256;