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. */
6 * @requires OpenLayers/Util.js
7 * @requires OpenLayers/Events.js
8 * @requires OpenLayers/Tween.js
9 * @requires OpenLayers/Console.js
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.
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.
21 OpenLayers.Map = OpenLayers.Class({
24 * Constant: Z_INDEX_BASE
25 * {Object} Base z-indexes for different classes of thing
36 * Constant: EVENT_TYPES
37 * {Array(String)} Supported application event types. Register a listener
38 * for a particular event with the following syntax:
40 * map.events.register(type, obj, listener);
43 * Listeners will be called with a reference to an event object. The
44 * properties of this event depends on exactly what happened.
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.
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
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
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
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
86 "preaddlayer", "addlayer", "removelayer", "changelayer", "movestart",
87 "move", "moveend", "zoomend", "popupopen", "popupclose",
88 "addmarker", "removemarker", "clearmarkers", "mouseover",
89 "mouseout", "mousemove", "dragstart", "drag", "dragend",
94 * {String} Unique identifier for the map
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.
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.
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.
115 fractionalZoom: false,
118 * APIProperty: events
119 * {<OpenLayers.Events>} An events object that handles all
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.
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
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.
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.
159 * {Boolean} The map is currently being dragged.
165 * {<OpenLayers.Size>} Size of the main div (this.div)
170 * Property: viewPortDiv
171 * {HTMLDivElement} The element that represents the map viewport
176 * Property: layerContainerOrigin
177 * {<OpenLayers.LonLat>} The lonlat at which the later container was
178 * re-initialized (on-zoom)
180 layerContainerOrigin: null,
183 * Property: layerContainerDiv
184 * {HTMLDivElement} The element that contains the layers.
186 layerContainerDiv: null,
189 * APIProperty: layers
190 * {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
196 * {Array(<OpenLayers.Control>)} List of controls associated with the map.
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>
209 * {Array(<OpenLayers.Popup>)} List of popups associated with the map
214 * APIProperty: baseLayer
215 * {<OpenLayers.Layer>} The currently selected base layer. This determines
216 * min/max zoom level, projection, etc.
222 * {<OpenLayers.LonLat>} The current center of the map
227 * Property: resolution
228 * {Float} The resolution of the map.
234 * {Integer} The current zoom level of the map
240 * {Float} The ratio of the current extent within
241 * which panning will tween.
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
262 * APIProperty: tileSize
263 * {<OpenLayers.Size>} Set in the map options to override the default tile
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".
274 projection: "EPSG:4326",
278 * {String} The map units. Defaults to 'degrees'. Possible values are
279 * 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
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.).
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.
299 maxResolution: 1.40625,
302 * APIProperty: minResolution
308 * APIProperty: maxScale
314 * APIProperty: minScale
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
331 * APIProperty: minExtent
332 * {<OpenLayers.Bounds>}
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.
345 restrictedExtent: null,
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.
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.
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.
370 displayProjection: null,
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.
382 * {OpenLayers.Tween} Animated panning tween object, see panTo()
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.
393 eventListeners: null,
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
401 panMethod: OpenLayers.Easing.Expo.easeOut,
404 * Property: panDuration
405 * {Integer} The number of steps to be passed to the
406 * OpenLayers.Tween.start() method when the map is
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.
417 paddingForPopups : null,
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.
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.
430 * Examples (method one):
432 * // create a map with default options in an element with the id "map1"
433 * var map = new OpenLayers.Map("map1");
435 * // create a map with non-default options in an element with id "map2"
437 * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
438 * maxResolution: 156543,
440 * projection: "EPSG:41001"
442 * var map = new OpenLayers.Map("map2", options);
445 * Examples (method two - single argument):
447 * // create a map with non-default options
448 * var map = new OpenLayers.Map({
450 * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
451 * maxResolution: 156543,
453 * projection: "EPSG:41001"
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,
461 * projection: "EPSG:41001"
464 initialize: function (div, options) {
466 // If only one argument is provided, check if it is an object.
467 if(arguments.length === 1 && typeof div === "object") {
469 div = options && options.div;
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);
477 this.maxExtent = new OpenLayers.Bounds(-180, -90, 180, 90);
479 this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
481 this.theme = OpenLayers._getScriptLocation() +
482 'theme/default/style.css';
484 // now override default options
485 OpenLayers.Util.extend(this, options);
487 this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
489 this.div = OpenLayers.Util.getElement(div);
491 this.div = document.createElement("div");
492 this.div.style.height = "1px";
493 this.div.style.width = "1px";
496 OpenLayers.Element.addClass(this.div, 'olMap');
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,
503 this.viewPortDiv.style.width = "100%";
504 this.viewPortDiv.style.height = "100%";
505 this.viewPortDiv.className = "olMapViewport";
506 this.div.appendChild(this.viewPortDiv);
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;
513 this.viewPortDiv.appendChild(this.layerContainerDiv);
515 this.events = new OpenLayers.Events(this,
521 if(this.eventListeners instanceof Object) {
522 this.events.on(this.eventListeners);
525 // update the map size and location before the map moves
526 this.events.register("movestart", this, this.updateSize);
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);
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,
539 OpenLayers.Event.observe(window, 'resize',
540 this.updateSizeDestroy);
543 // only append link stylesheet if the theme property is set
545 // check existing links for equivalent url
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,
555 // only add a new node if one with an equivalent url hasn't already
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);
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()
580 for(var i=0, len=this.controls.length; i<len; i++) {
581 this.addControlToMap(this.controls[i]);
586 this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
589 // always call map.destroy()
590 OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
595 * Render the map to a specified container.
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.
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);
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.
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
624 updateSizeDestroy: null,
631 // if unloadDestroy is null, we've already been destroyed
632 if (!this.unloadDestroy) {
636 // map has been destroyed. dont do it again!
637 OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
638 this.unloadDestroy = null;
640 if (this.updateSizeDestroy) {
641 OpenLayers.Event.stopObserving(window, 'resize',
642 this.updateSizeDestroy);
644 this.events.unregister("resize", this, this.updateSize);
647 this.paddingForPopups = null;
649 if (this.controls != null) {
650 for (var i = this.controls.length - 1; i>=0; --i) {
651 this.controls[i].destroy();
653 this.controls = null;
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);
663 if (this.viewPortDiv) {
664 this.div.removeChild(this.viewPortDiv);
666 this.viewPortDiv = null;
668 if(this.eventListeners) {
669 this.events.un(this.eventListeners);
670 this.eventListeners = null;
672 this.events.destroy();
678 * APIMethod: setOptions
679 * Change the map options
682 * options - {Object} Hashtable of options to tag to the map
684 setOptions: function(options) {
685 OpenLayers.Util.extend(this, options);
689 * APIMethod: getTileSize
690 * Get the tile size for the map
693 * {<OpenLayers.Size>}
695 getTileSize: function() {
696 return this.tileSize;
702 * Get a list of objects given a property and a match item.
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
715 * {Array} An array of items where the given property matches the given
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]));
727 * APIMethod: getLayersBy
728 * Get a list of layers with properties matching the given criteria.
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
740 * {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
741 * An empty array is returned if no matches are found.
743 getLayersBy: function(property, match) {
744 return this.getBy("layers", property, match);
748 * APIMethod: getLayersByName
749 * Get a list of layers with names matching the given name.
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
760 * {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
761 * An empty array is returned if no matches are found.
763 getLayersByName: function(match) {
764 return this.getLayersBy("name", match);
768 * APIMethod: getLayersByClass
769 * Get a list of layers of a given class (CLASS_NAME).
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.
780 * {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
781 * An empty array is returned if no matches are found.
783 getLayersByClass: function(match) {
784 return this.getLayersBy("CLASS_NAME", match);
788 * APIMethod: getControlsBy
789 * Get a list of controls with properties matching the given criteria.
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
801 * {Array(<OpenLayers.Control>)} A list of controls matching the given
802 * criteria. An empty array is returned if no matches are found.
804 getControlsBy: function(property, match) {
805 return this.getBy("controls", property, match);
809 * APIMethod: getControlsByClass
810 * Get a list of controls of a given class (CLASS_NAME).
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.
821 * {Array(<OpenLayers.Control>)} A list of controls matching the given class.
822 * An empty array is returned if no matches are found.
824 getControlsByClass: function(match) {
825 return this.getControlsBy("CLASS_NAME", match);
828 /********************************************************/
830 /* Layer Functions */
832 /* The following functions deal with adding and */
833 /* removing Layers to and from the Map */
835 /********************************************************/
838 * APIMethod: getLayer
839 * Get a layer based on its id
842 * id - {String} A layer id
845 * {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
846 * layer collection, or null if not found.
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) {
861 * Method: setLayerZIndex
864 * layer - {<OpenLayers.Layer>}
867 setLayerZIndex: function (layer, zIdx) {
869 this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
874 * Method: resetLayersZIndex
875 * Reset each layer's z-index based on layer's array index
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);
885 * APIMethod: addLayer
888 * layer - {<OpenLayers.Layer>}
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);
899 if(this.allOverlays) {
900 layer.isBaseLayer = false;
903 this.events.triggerEvent("preaddlayer", {layer: layer});
905 layer.div.className = "olLayerDiv";
906 layer.div.style.overflow = "";
907 this.setLayerZIndex(layer, this.layers.length);
910 this.viewPortDiv.appendChild(layer.div);
912 this.layerContainerDiv.appendChild(layer.div);
914 this.layers.push(layer);
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);
922 layer.setVisibility(false);
928 this.events.triggerEvent("addlayer", {layer: layer});
933 * APIMethod: addLayers
936 * layers - {Array(<OpenLayers.Layer>)}
938 addLayers: function (layers) {
939 for (var i=0, len=layers.length; i<len; i++) {
940 this.addLayer(layers[i]);
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.
950 * a "removelayer" event is triggered.
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.
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
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.
969 * layer - {<OpenLayers.Layer>}
970 * setNewBaseLayer - {Boolean} Default is true
972 removeLayer: function(layer, setNewBaseLayer) {
973 if (setNewBaseLayer == null) {
974 setNewBaseLayer = true;
978 this.viewPortDiv.removeChild(layer.div);
980 this.layerContainerDiv.removeChild(layer.div);
982 OpenLayers.Util.removeItem(this.layers, layer);
983 layer.removeMap(this);
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);
1000 this.resetLayersZIndex();
1002 this.events.triggerEvent("removelayer", {layer: layer});
1006 * APIMethod: getNumLayers
1009 * {Int} The number of layers attached to the map.
1011 getNumLayers: function () {
1012 return this.layers.length;
1016 * APIMethod: getLayerIndex
1019 * layer - {<OpenLayers.Layer>}
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.
1025 getLayerIndex: function (layer) {
1026 return OpenLayers.Util.indexOf(this.layers, layer);
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.
1038 * layer - {<OpenLayers.Layer>}
1041 setLayerIndex: function (layer, idx) {
1042 var base = this.getLayerIndex(layer);
1045 } else if (idx > this.layers.length) {
1046 idx = this.layers.length;
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);
1054 this.events.triggerEvent("changelayer", {
1055 layer: layer, property: "order"
1057 if(this.allOverlays) {
1059 this.setBaseLayer(layer);
1060 } else if(this.baseLayer !== this.layers[0]) {
1061 this.setBaseLayer(this.layers[0]);
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.
1075 * layer - {<OpenLayers.Layer>}
1078 raiseLayer: function (layer, delta) {
1079 var idx = this.getLayerIndex(layer) + delta;
1080 this.setLayerIndex(layer, idx);
1084 * APIMethod: setBaseLayer
1085 * Allows user to specify one of the currently-loaded layers as the Map's
1089 * newBaseLayer - {<OpenLayers.Layer>}
1091 setBaseLayer: function(newBaseLayer) {
1092 var oldExtent = null;
1093 if (this.baseLayer) {
1094 oldExtent = this.baseLayer.getExtent();
1097 if (newBaseLayer != this.baseLayer) {
1099 // is newBaseLayer an already loaded layer?m
1100 if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
1102 // make the old base layer invisible
1103 if (this.baseLayer != null && !this.allOverlays) {
1104 this.baseLayer.setVisibility(false);
1107 // set new baselayer
1108 this.baseLayer = newBaseLayer;
1110 // Increment viewRequestID since the baseLayer is
1111 // changing. This is used by tiles to check if they should
1113 this.viewRequestID++;
1114 if(!this.allOverlays) {
1115 this.baseLayer.visibility = true;
1119 var center = this.getCenter();
1120 if (center != null) {
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()
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);
1134 // zoom and force zoom change
1135 this.setCenter(newCenter, newZoom, false, true);
1138 this.events.triggerEvent("changebaselayer", {
1139 layer: this.baseLayer
1146 /********************************************************/
1148 /* Control Functions */
1150 /* The following functions deal with adding and */
1151 /* removing Controls to and from the Map */
1153 /********************************************************/
1156 * APIMethod: addControl
1159 * control - {<OpenLayers.Control>}
1160 * px - {<OpenLayers.Pixel>}
1162 addControl: function (control, px) {
1163 this.controls.push(control);
1164 this.addControlToMap(control, px);
1168 * Method: addControlToMap
1172 * control - {<OpenLayers.Control>}
1173 * px - {<OpenLayers.Pixel>}
1175 addControlToMap: function (control, px) {
1176 // If a control doesn't have a div at this point, it belongs in the
1178 control.outsideViewport = (control.div != null);
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;
1186 control.setMap(this);
1187 var div = control.draw(px);
1189 if(!control.outsideViewport) {
1190 div.style.zIndex = this.Z_INDEX_BASE['Control'] +
1191 this.controls.length;
1192 this.viewPortDiv.appendChild( div );
1198 * APIMethod: getControl
1201 * id - {String} ID of the control to return.
1204 * {<OpenLayers.Control>} The control from the map's list of controls
1205 * which has a matching 'id'. If none found,
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;
1217 return returnControl;
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)
1227 * control - {<OpenLayers.Control>} The control to remove.
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);
1235 OpenLayers.Util.removeItem(this.controls, control);
1239 /********************************************************/
1241 /* Popup Functions */
1243 /* The following functions deal with adding and */
1244 /* removing Popups to and from the Map */
1246 /********************************************************/
1249 * APIMethod: addPopup
1252 * popup - {<OpenLayers.Popup>}
1253 * exclusive - {Boolean} If true, closes all other popups first
1255 addPopup: function(popup, 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]);
1265 this.popups.push(popup);
1266 var popupDiv = popup.draw();
1268 popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
1270 this.layerContainerDiv.appendChild(popupDiv);
1275 * APIMethod: removePopup
1278 * popup - {<OpenLayers.Popup>}
1280 removePopup: function(popup) {
1281 OpenLayers.Util.removeItem(this.popups, popup);
1283 try { this.layerContainerDiv.removeChild(popup.div); }
1284 catch (e) { } // Popups sometimes apparently get disconnected
1285 // from the layerContainerDiv, and cause complaints.
1290 /********************************************************/
1292 /* Container Div Functions */
1294 /* The following functions deal with the access to */
1295 /* and maintenance of the size of the container div */
1297 /********************************************************/
1300 * APIMethod: getSize
1303 * {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
1304 * size, in pixels, of the div into which OpenLayers
1306 * Note - A clone() of this locally cached variable is
1307 * returned, so as not to allow users to modify it.
1309 getSize: function () {
1311 if (this.size != null) {
1312 size = this.size.clone();
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)
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;
1331 if (!newSize.equals(oldSize)) {
1333 // store the new size
1334 this.size = newSize;
1336 //notify layers of mapresize
1337 for(var i=0, len=this.layers.length; i<len; i++) {
1338 this.layers[i].onMapResize();
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();
1346 this.setCenter(this.getCenter(), zoom);
1353 * Method: getCurrentSize
1356 * {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
1359 getCurrentSize: function() {
1361 var size = new OpenLayers.Size(this.div.clientWidth,
1362 this.div.clientHeight);
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);
1368 size.h = dim.height;
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);
1378 * Method: calculateBounds
1381 * center - {<OpenLayers.LonLat>} Default is this.getCenter()
1382 * resolution - {float} Default is this.getResolution()
1385 * {<OpenLayers.Bounds>} A bounds based on resolution, center, and
1388 calculateBounds: function(center, resolution) {
1392 if (center == null) {
1393 center = this.getCenter();
1395 if (resolution == null) {
1396 resolution = this.getResolution();
1399 if ((center != null) && (resolution != null)) {
1401 var size = this.getSize();
1402 var w_deg = size.w * resolution;
1403 var h_deg = size.h * resolution;
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);
1416 /********************************************************/
1418 /* Zoom, Center, Pan Functions */
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 */
1424 /********************************************************/
1426 * APIMethod: getCenter
1429 * {<OpenLayers.LonLat>}
1431 getCenter: function () {
1434 center = this.center.clone();
1441 * APIMethod: getZoom
1446 getZoom: function () {
1452 * Allows user to pan by a value of screen pixels
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
1462 pan: function(dx, dy, options) {
1463 options = OpenLayers.Util.applyDefaults(options, {
1468 var centerPx = this.getViewPortPxFromLonLat(this.getCenter());
1471 var newCenterPx = centerPx.add(dx, dy);
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);
1479 this.setCenter(newCenterLonLat, null, options.dragging);
1487 * Allows user to pan to a new lonlat
1488 * If the new lonlat is in the current extent the map will slide smoothly
1491 * lonlat - {<OpenLayers.Lonlat>}
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);
1498 var center = this.getCenter();
1500 // center will not change, don't do nothing
1501 if (lonlat.lon == center.lon &&
1502 lonlat.lat == center.lat) {
1514 this.panTween.start(from, to, this.panDuration, {
1516 start: OpenLayers.Function.bind(function(lonlat) {
1517 this.events.triggerEvent("movestart");
1519 eachStep: OpenLayers.Function.bind(function(lonlat) {
1520 lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
1521 this.moveTo(lonlat, this.zoom, {
1526 done: OpenLayers.Function.bind(function(lonlat) {
1527 lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
1528 this.moveTo(lonlat, this.zoom, {
1531 this.events.triggerEvent("moveend");
1536 this.setCenter(lonlat);
1541 * APIMethod: setCenter
1542 * Set the map center (and optionally, the zoom level).
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)
1552 * TBD: reconsider forceZoomChange in 3.0
1554 setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
1555 this.moveTo(lonlat, zoom, {
1556 'dragging': dragging,
1557 'forceZoomChange': forceZoomChange,
1558 'caller': 'setCenter'
1566 * lonlat - {<OpenLayers.LonLat>}
1568 * options - {Object}
1570 moveTo: function(lonlat, zoom, options) {
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;
1581 if (this.panTween && options.caller == "setCenter") {
1582 this.panTween.stop();
1585 if (!this.center && !this.isValidLonLat(lonlat)) {
1586 lonlat = this.maxExtent.getCenterLonLat();
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();
1595 zoom = this.getZoom();
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 -
1606 } else if(extent.right > this.restrictedExtent.right) {
1607 lonlat = lonlat.add(this.restrictedExtent.right -
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 -
1616 else if(extent.top > this.restrictedExtent.top) {
1617 lonlat = lonlat.add(0, this.restrictedExtent.top -
1623 var zoomChanged = forceZoomChange || (
1624 (this.isValidZoomLevel(zoom)) &&
1625 (zoom != this.getZoom()) );
1627 var centerChanged = (this.isValidLonLat(lonlat)) &&
1628 (!lonlat.equals(this.center));
1631 // if neither center nor zoom will change, no need to do anything
1632 if (zoomChanged || centerChanged || !dragging) {
1634 if (!this.dragging && !noEvent) {
1635 this.events.triggerEvent("movestart");
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);
1644 this.center = lonlat.clone();
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";
1656 this.resolution = this.getResolutionForZoom(zoom);
1657 // zoom level has changed, increment viewRequestID.
1658 this.viewRequestID++;
1661 var bounds = this.getExtent();
1663 //send the move call to the baselayer and all the overlays
1665 if(this.baseLayer.visibility) {
1666 this.baseLayer.moveTo(bounds, zoomChanged, dragging);
1668 this.baseLayer.events.triggerEvent("move");
1670 this.baseLayer.events.triggerEvent("moveend",
1671 {"zoomChanged": zoomChanged}
1676 bounds = this.baseLayer.getExtent();
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;
1689 layer.display(false);
1691 this.events.triggerEvent("changelayer", {
1692 layer: layer, property: "visibility"
1695 if (inRange && layer.visibility) {
1696 layer.moveTo(bounds, zoomChanged, dragging);
1698 layer.events.triggerEvent("move");
1700 layer.events.triggerEvent("moveend",
1701 {"zoomChanged": zoomChanged}
1710 for (var i=0, len=this.popups.length; i<len; i++) {
1711 this.popups[i].updatePosition();
1715 this.events.triggerEvent("move");
1717 if (zoomChanged) { this.events.triggerEvent("zoomend"); }
1720 // even if nothing was done, we want to notify of this
1721 if (!dragging && !noEvent) {
1722 this.events.triggerEvent("moveend");
1725 // Store the map dragging state for later use
1726 this.dragging = !!dragging;
1731 * Method: centerLayerContainer
1732 * This function takes care to recenter the layerContainerDiv.
1735 * lonlat - {<OpenLayers.LonLat>}
1737 centerLayerContainer: function (lonlat) {
1739 var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
1740 var newPx = this.getViewPortPxFromLonLat(lonlat);
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";
1749 * Method: isValidZoomLevel
1752 * zoomLevel - {Integer}
1755 * {Boolean} Whether or not the zoom level passed in is non-null and
1756 * within the min/max range of zoom levels.
1758 isValidZoomLevel: function(zoomLevel) {
1759 return ( (zoomLevel != null) &&
1761 (zoomLevel < this.getNumZoomLevels()) );
1765 * Method: isValidLonLat
1768 * lonlat - {<OpenLayers.LonLat>}
1771 * {Boolean} Whether or not the lonlat passed in is non-null and within
1772 * the maxExtent bounds
1774 isValidLonLat: function(lonlat) {
1776 if (lonlat != null) {
1777 var maxExtent = this.getMaxExtent();
1778 valid = maxExtent.containsLonLat(lonlat);
1783 /********************************************************/
1787 /* Accessor functions to Layer Options parameters */
1789 /********************************************************/
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.
1798 * FIXME: In 3.0, we will remove getProjectionObject, and instead
1799 * return a Projection object from this function.
1802 * {String} The Projection string from the base layer or null.
1804 getProjection: function() {
1805 var projection = this.getProjectionObject();
1806 return projection ? projection.getCode() : null;
1810 * APIMethod: getProjectionObject
1811 * Returns the projection obect from the baselayer.
1814 * {<OpenLayers.Projection>} The Projection of the base layer.
1816 getProjectionObject: function() {
1817 var projection = null;
1818 if (this.baseLayer != null) {
1819 projection = this.baseLayer.projection;
1825 * APIMethod: getMaxResolution
1828 * {String} The Map's Maximum Resolution
1830 getMaxResolution: function() {
1831 var maxResolution = null;
1832 if (this.baseLayer != null) {
1833 maxResolution = this.baseLayer.maxResolution;
1835 return maxResolution;
1839 * APIMethod: getMaxExtent
1842 * options - {Object}
1845 * restricted - {Boolean} If true, returns restricted extent (if it is
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
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;
1865 * APIMethod: getNumZoomLevels
1868 * {Integer} The total number of zoom levels that can be displayed by the
1869 * current baseLayer.
1871 getNumZoomLevels: function() {
1872 var numZoomLevels = null;
1873 if (this.baseLayer != null) {
1874 numZoomLevels = this.baseLayer.numZoomLevels;
1876 return numZoomLevels;
1879 /********************************************************/
1881 /* Baselayer Functions */
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 */
1888 /********************************************************/
1891 * APIMethod: getExtent
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.
1898 getExtent: function () {
1900 if (this.baseLayer != null) {
1901 extent = this.baseLayer.getExtent();
1907 * APIMethod: getResolution
1910 * {Float} The current resolution of the map.
1911 * If no baselayer is set, returns null.
1913 getResolution: function () {
1914 var resolution = null;
1915 if (this.baseLayer != null) {
1916 resolution = this.baseLayer.getResolution();
1922 * APIMethod: getUnits
1925 * {Float} The current units of the map.
1926 * If no baselayer is set, returns null.
1928 getUnits: function () {
1930 if (this.baseLayer != null) {
1931 units = this.baseLayer.units;
1937 * APIMethod: getScale
1940 * {Float} The current scale denominator of the map.
1941 * If no baselayer is set, returns null.
1943 getScale: function () {
1945 if (this.baseLayer != null) {
1946 var res = this.getResolution();
1947 var units = this.baseLayer.units;
1948 scale = OpenLayers.Util.getScaleFromResolution(res, units);
1955 * APIMethod: getZoomForExtent
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.
1965 * {Integer} A suitable zoom level for the specified bounds.
1966 * If no baselayer is set, returns null.
1968 getZoomForExtent: function (bounds, closest) {
1970 if (this.baseLayer != null) {
1971 zoom = this.baseLayer.getZoomForExtent(bounds, closest);
1977 * APIMethod: getResolutionForZoom
1983 * {Float} A suitable resolution for the specified zoom. If no baselayer
1984 * is set, returns null.
1986 getResolutionForZoom: function(zoom) {
1987 var resolution = null;
1988 if(this.baseLayer) {
1989 resolution = this.baseLayer.getResolutionForZoom(zoom);
1995 * APIMethod: getZoomForResolution
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).
2008 * {Integer} A suitable zoom level for the specified resolution.
2009 * If no baselayer is set, returns null.
2011 getZoomForResolution: function(resolution, closest) {
2013 if (this.baseLayer != null) {
2014 zoom = this.baseLayer.getZoomForResolution(resolution, closest);
2019 /********************************************************/
2021 /* Zooming Functions */
2023 /* The following functions, all publicly exposed */
2024 /* in the API, are all merely wrappers to the */
2025 /* the setCenter() function */
2027 /********************************************************/
2031 * Zoom to a specific zoom level
2036 zoomTo: function(zoom) {
2037 if (this.isValidZoomLevel(zoom)) {
2038 this.setCenter(null, zoom);
2048 zoomIn: function() {
2049 this.zoomTo(this.getZoom() + 1);
2053 * APIMethod: zoomOut
2058 zoomOut: function() {
2059 this.zoomTo(this.getZoom() - 1);
2063 * APIMethod: zoomToExtent
2064 * Zoom to the passed in bounds, recenter
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.
2074 zoomToExtent: function(bounds, closest) {
2075 var center = bounds.getCenterLonLat();
2076 if (this.baseLayer.wrapDateLine) {
2077 var maxExtent = this.getMaxExtent();
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)
2085 bounds = bounds.clone();
2086 while (bounds.right < bounds.left) {
2087 bounds.right += maxExtent.getWidth();
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.
2096 center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
2098 this.setCenter(center, this.getZoomForExtent(bounds, closest));
2102 * APIMethod: zoomToMaxExtent
2103 * Zoom to the full extent and recenter.
2109 * restricted - {Boolean} True to zoom to restricted extent if it is
2110 * set. Defaults to true.
2112 zoomToMaxExtent: function(options) {
2113 //restricted is true by default
2114 var restricted = (options) ? options.restricted : true;
2116 var maxExtent = this.getMaxExtent({
2117 'restricted': restricted
2119 this.zoomToExtent(maxExtent);
2123 * APIMethod: zoomToScale
2124 * Zoom to a specified scale
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.
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();
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);
2149 /********************************************************/
2151 /* Translation Functions */
2153 /* The following functions translate between */
2154 /* LonLat, LayerPx, and ViewPortPx */
2156 /********************************************************/
2159 // TRANSLATION: LonLat <-> ViewPortPx
2163 * Method: getLonLatFromViewPortPx
2166 * viewPortPx - {<OpenLayers.Pixel>}
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.
2173 getLonLatFromViewPortPx: function (viewPortPx) {
2175 if (this.baseLayer != null) {
2176 lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
2182 * APIMethod: getViewPortPxFromLonLat
2185 * lonlat - {<OpenLayers.LonLat>}
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.
2192 getViewPortPxFromLonLat: function (lonlat) {
2194 if (this.baseLayer != null) {
2195 px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
2202 // CONVENIENCE TRANSLATION FUNCTIONS FOR API
2206 * APIMethod: getLonLatFromPixel
2209 * px - {<OpenLayers.Pixel>}
2212 * {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
2213 * OpenLayers.Pixel, translated into lon/lat by the
2214 * current base layer
2216 getLonLatFromPixel: function (px) {
2217 return this.getLonLatFromViewPortPx(px);
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.
2227 * lonlat - {<OpenLayers.LonLat>} A map location.
2230 * {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
2231 * <OpenLayers.LonLat> translated into view port pixels by the current
2234 getPixelFromLonLat: function (lonlat) {
2235 var px = this.getViewPortPxFromLonLat(lonlat);
2236 px.x = Math.round(px.x);
2237 px.y = Math.round(px.y);
2244 // TRANSLATION: ViewPortPx <-> LayerPx
2248 * APIMethod: getViewPortPxFromLayerPx
2251 * layerPx - {<OpenLayers.Pixel>}
2254 * {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
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);
2268 * APIMethod: getLayerPxFromViewPortPx
2271 * viewPortPx - {<OpenLayers.Pixel>}
2274 * {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
2277 getLayerPxFromViewPortPx:function(viewPortPx) {
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)) {
2291 // TRANSLATION: LonLat <-> LayerPx
2295 * Method: getLonLatFromLayerPx
2298 * px - {<OpenLayers.Pixel>}
2301 * {<OpenLayers.LonLat>}
2303 getLonLatFromLayerPx: function (px) {
2304 //adjust for displacement of layerContainerDiv
2305 px = this.getViewPortPxFromLayerPx(px);
2306 return this.getLonLatFromViewPortPx(px);
2310 * APIMethod: getLayerPxFromLonLat
2313 * lonlat - {<OpenLayers.LonLat>} lonlat
2316 * {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
2317 * <OpenLayers.LonLat>, translated into layer pixels
2318 * by the current base layer
2320 getLayerPxFromLonLat: function (lonlat) {
2321 //adjust for displacement of layerContainerDiv
2322 var px = this.getPixelFromLonLat(lonlat);
2323 return this.getLayerPxFromViewPortPx(px);
2326 CLASS_NAME: "OpenLayers.Map"
2330 * Constant: TILE_WIDTH
2331 * {Integer} 256 Default tile width (unless otherwise specified)
2333 OpenLayers.Map.TILE_WIDTH = 256;
2335 * Constant: TILE_HEIGHT
2336 * {Integer} 256 Default tile height (unless otherwise specified)
2338 OpenLayers.Map.TILE_HEIGHT = 256;