Files
openlayers/lib/OpenLayers/Map.js
Éric Lemoine 8e0876488b Add a moveend event to layer. By registering to that event (instead of that at
the map level) strategies need to check that the layer is in range and active
before fetching new features. r=ahocevar (closes #1678)


git-svn-id: http://svn.openlayers.org/trunk/openlayers@7874 dc9f47b5-9b13-0410-9fdd-eb0c1a62fdaf
2008-08-27 06:27:22 +00:00

2198 lines
74 KiB
JavaScript

/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
* license. See http://svn.openlayers.org/trunk/openlayers/license.txt for the
* full text of the license. */
/**
* @requires OpenLayers/Util.js
* @requires OpenLayers/Events.js
* @requires OpenLayers/Tween.js
*/
/**
* Class: OpenLayers.Map
* Instances of OpenLayers.Map are interactive maps embedded in a web page.
* Create a new map with the <OpenLayers.Map> constructor.
*
* On their own maps do not provide much functionality. To extend a map
* it's necessary to add controls (<OpenLayers.Control>) and
* layers (<OpenLayers.Layer>) to the map.
*/
OpenLayers.Map = OpenLayers.Class({
/**
* Constant: Z_INDEX_BASE
* {Object} Base z-indexes for different classes of thing
*/
Z_INDEX_BASE: { BaseLayer: 100, Overlay: 325, Popup: 750, Control: 1000 },
/**
* Constant: EVENT_TYPES
* {Array(String)} Supported application event types. Register a listener
* for a particular event with the following syntax:
* (code)
* map.events.register(type, obj, listener);
* (end)
*
* Listeners will be called with a reference to an event object. The
* properties of this event depends on exactly what happened.
*
* All event objects have at least the following properties:
* - *object* {Object} A reference to map.events.object.
* - *element* {DOMElement} A reference to map.events.element.
*
* Browser events have the following additional properties:
* - *xy* {<OpenLayers.Pixel>} The pixel location of the event (relative
* to the the map viewport).
* - other properties that come with browser events
*
* Supported map event types:
* - *preaddlayer* triggered before a layer has been added. The event
* object will include a *layer* property that references the layer
* to be added.
* - *addlayer* triggered after a layer has been added. The event object
* will include a *layer* property that references the added layer.
* - *removelayer* triggered after a layer has been removed. The event
* object will include a *layer* property that references the removed
* layer.
* - *changelayer* triggered after a layer name change, order change, or
* visibility change (due to resolution thresholds). Listeners will
* receive an event object with *layer* and *property* properties. The
* *layer* property will be a reference to the changed layer. The
* *property* property will be a key to the changed property (name,
* visibility, or order).
* - *movestart* triggered after the start of a drag, pan, or zoom
* - *move* triggered after each drag, pan, or zoom
* - *moveend* triggered after a drag, pan, or zoom completes
* - *zoomend* triggered after a zoom completes
* - *popupopen* triggered after a popup opens
* - *popupclose* triggered after a popup opens
* - *addmarker* triggered after a marker has been added
* - *removemarker* triggered after a marker has been removed
* - *clearmarkers* triggered after markers have been cleared
* - *mouseover* triggered after mouseover the map
* - *mouseout* triggered after mouseout the map
* - *mousemove* triggered after mousemove the map
* - *dragstart* triggered after the start of a drag
* - *drag* triggered after a drag
* - *dragend* triggered after the end of a drag
* - *changebaselayer* triggered after the base layer changes
*/
EVENT_TYPES: [
"preaddlayer", "addlayer", "removelayer", "changelayer", "movestart",
"move", "moveend", "zoomend", "popupopen", "popupclose",
"addmarker", "removemarker", "clearmarkers", "mouseover",
"mouseout", "mousemove", "dragstart", "drag", "dragend",
"changebaselayer"],
/**
* Property: id
* {String} Unique identifier for the map
*/
id: null,
/**
* Property: fractionalZoom
* {Boolean} For a base layer that supports it, allow the map resolution
* to be set to a value between one of the values in the resolutions
* array. Default is false.
*
* When fractionalZoom is set to true, it is possible to zoom to
* an arbitrary extent. This requires a base layer from a source
* that supports requests for arbitrary extents (i.e. not cached
* tiles on a regular lattice). This means that fractionalZoom
* will not work with commercial layers (Google, Yahoo, VE), layers
* using TileCache, or any other pre-cached data sources.
*
* If you are using fractionalZoom, then you should also use
* <getResolutionForZoom> instead of layer.resolutions[zoom] as the
* former works for non-integer zoom levels.
*/
fractionalZoom: false,
/**
* APIProperty: events
* {<OpenLayers.Events>} An events object that handles all
* events on the map
*/
events: null,
/**
* APIProperty: div
* {DOMElement} The element that contains the map
*/
div: null,
/**
* Property: dragging
* {Boolean} The map is currently being dragged.
*/
dragging: false,
/**
* Property: size
* {<OpenLayers.Size>} Size of the main div (this.div)
*/
size: null,
/**
* Property: viewPortDiv
* {HTMLDivElement} The element that represents the map viewport
*/
viewPortDiv: null,
/**
* Property: layerContainerOrigin
* {<OpenLayers.LonLat>} The lonlat at which the later container was
* re-initialized (on-zoom)
*/
layerContainerOrigin: null,
/**
* Property: layerContainerDiv
* {HTMLDivElement} The element that contains the layers.
*/
layerContainerDiv: null,
/**
* APIProperty: layers
* {Array(<OpenLayers.Layer>)} Ordered list of layers in the map
*/
layers: null,
/**
* Property: controls
* {Array(<OpenLayers.Control>)} List of controls associated with the map
*/
controls: null,
/**
* Property: popups
* {Array(<OpenLayers.Popup>)} List of popups associated with the map
*/
popups: null,
/**
* APIProperty: baseLayer
* {<OpenLayers.Layer>} The currently selected base layer. This determines
* min/max zoom level, projection, etc.
*/
baseLayer: null,
/**
* Property: center
* {<OpenLayers.LonLat>} The current center of the map
*/
center: null,
/**
* Property: resolution
* {Float} The resolution of the map.
*/
resolution: null,
/**
* Property: zoom
* {Integer} The current zoom level of the map
*/
zoom: 0,
/**
* Property: panRatio
* {Float} The ratio of the current extent within
* which panning will tween.
*/
panRatio: 1.5,
/**
* Property: viewRequestID
* {String} Used to store a unique identifier that changes when the map
* view changes. viewRequestID should be used when adding data
* asynchronously to the map: viewRequestID is incremented when
* you initiate your request (right now during changing of
* baselayers and changing of zooms). It is stored here in the
* map and also in the data that will be coming back
* asynchronously. Before displaying this data on request
* completion, we check that the viewRequestID of the data is
* still the same as that of the map. Fix for #480
*/
viewRequestID: 0,
// Options
/**
* APIProperty: tileSize
* {<OpenLayers.Size>} Set in the map options to override the default tile
* size for this map.
*/
tileSize: null,
/**
* APIProperty: projection
* {String} Set in the map options to override the default projection
* string this map - also set maxExtent, maxResolution, and
* units if appropriate.
*/
projection: "EPSG:4326",
/**
* APIProperty: units
* {String} The map units. Defaults to 'degrees'. Possible values are
* 'degrees' (or 'dd'), 'm', 'ft', 'km', 'mi', 'inches'.
*/
units: 'degrees',
/**
* APIProperty: resolutions
* {Array(Float)} A list of map resolutions (map units per pixel) in
* descending order. If this is not set in the layer constructor, it
* will be set based on other resolution related properties
* (maxExtent, maxResolution, maxScale, etc.).
*/
resolutions: null,
/**
* APIProperty: maxResolution
* {Float} Default max is 360 deg / 256 px, which corresponds to
* zoom level 0 on gmaps. Specify a different value in the map
* options if you are not using a geographic projection and
* displaying the whole world.
*/
maxResolution: 1.40625,
/**
* APIProperty: minResolution
* {Float}
*/
minResolution: null,
/**
* APIProperty: maxScale
* {Float}
*/
maxScale: null,
/**
* APIProperty: minScale
* {Float}
*/
minScale: null,
/**
* APIProperty: maxExtent
* {<OpenLayers.Bounds>} The maximum extent for the map. Defaults to the
* whole world in decimal degrees
* (-180, -90, 180, 90). Specify a different
* extent in the map options if you are not using a
* geographic projection and displaying the whole
* world.
*/
maxExtent: null,
/**
* APIProperty: minExtent
* {<OpenLayers.Bounds>}
*/
minExtent: null,
/**
* APIProperty: restrictedExtent
* {<OpenLayers.Bounds>} Limit map navigation to this extent where possible.
* If a non-null restrictedExtent is set, panning will be restricted
* to the given bounds. In addition, zooming to a resolution that
* displays more than the restricted extent will center the map
* on the restricted extent. If you wish to limit the zoom level
* or resolution, use maxResolution.
*/
restrictedExtent: null,
/**
* APIProperty: numZoomLevels
* {Integer} Number of zoom levels for the map. Defaults to 16. Set a
* different value in the map options if needed.
*/
numZoomLevels: 16,
/**
* APIProperty: theme
* {String} Relative path to a CSS file from which to load theme styles.
* Specify null in the map options (e.g. {theme: null}) if you
* want to get cascading style declarations - by putting links to
* stylesheets or style declarations directly in your page.
*/
theme: null,
/**
* APIProperty: displayProjection
* {<OpenLayers.Projection>} Requires proj4js support.Projection used by
* several controls to display data to user. If this property is set,
* it will be set on any control which has a null displayProjection
* property at the time the control is added to the map.
*/
displayProjection: null,
/**
* APIProperty: fallThrough
* {Boolean} Should OpenLayers allow events on the map to fall through to
* other elements on the page, or should it swallow them? (#457)
* Default is to fall through.
*/
fallThrough: true,
/**
* Property: panTween
* {OpenLayers.Tween} Animated panning tween object, see panTo()
*/
panTween: null,
/**
* APIProperty: eventListeners
* {Object} If set as an option at construction, the eventListeners
* object will be registered with <OpenLayers.Events.on>. Object
* structure must be a listeners object as shown in the example for
* the events.on method.
*/
eventListeners: null,
/**
* Property: panMethod
* {Function} The Easing function to be used for tweening. Default is
* OpenLayers.Easing.Expo.easeOut. Setting this to 'null' turns off
* animated panning.
*/
panMethod: OpenLayers.Easing.Expo.easeOut,
/**
* Property: paddingForPopups
* {<OpenLayers.Bounds>} Outside margin of the popup. Used to prevent
* the popup from getting too close to the map border.
*/
paddingForPopups : null,
/**
* Constructor: OpenLayers.Map
* Constructor for a new OpenLayers.Map instance.
*
* Parameters:
* div - {String} Id of an element in your page that will contain the map.
* options - {Object} Optional object with properties to tag onto the map.
*
* Examples:
* (code)
* // create a map with default options in an element with the id "map1"
* var map = new OpenLayers.Map("map1");
*
* // create a map with non-default options in an element with id "map2"
* var options = {
* maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),
* maxResolution: 156543,
* units: 'm',
* projection: "EPSG:41001"
* };
* var map = new OpenLayers.Map("map2", options);
* (end)
*/
initialize: function (div, options) {
// Simple-type defaults are set in class definition.
// Now set complex-type defaults
this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,
OpenLayers.Map.TILE_HEIGHT);
this.maxExtent = new OpenLayers.Bounds(-180, -90, 180, 90);
this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);
this.theme = OpenLayers._getScriptLocation() +
'theme/default/style.css';
// now override default options
OpenLayers.Util.extend(this, options);
this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_");
this.div = OpenLayers.Util.getElement(div);
OpenLayers.Element.addClass(this.div, 'olMap');
// the viewPortDiv is the outermost div we modify
var id = this.div.id + "_OpenLayers_ViewPort";
this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,
"relative", null,
"hidden");
this.viewPortDiv.style.width = "100%";
this.viewPortDiv.style.height = "100%";
this.viewPortDiv.className = "olMapViewport";
this.div.appendChild(this.viewPortDiv);
// the layerContainerDiv is the one that holds all the layers
id = this.div.id + "_OpenLayers_Container";
this.layerContainerDiv = OpenLayers.Util.createDiv(id);
this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1;
this.viewPortDiv.appendChild(this.layerContainerDiv);
this.events = new OpenLayers.Events(this,
this.div,
this.EVENT_TYPES,
this.fallThrough,
{includeXY: true});
this.updateSize();
if(this.eventListeners instanceof Object) {
this.events.on(this.eventListeners);
}
// update the map size and location before the map moves
this.events.register("movestart", this, this.updateSize);
// Because Mozilla does not support the "resize" event for elements
// other than "window", we need to put a hack here.
if (OpenLayers.String.contains(navigator.appName, "Microsoft")) {
// If IE, register the resize on the div
this.events.register("resize", this, this.updateSize);
} else {
// Else updateSize on catching the window's resize
// Note that this is ok, as updateSize() does nothing if the
// map's size has not actually changed.
this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,
this);
OpenLayers.Event.observe(window, 'resize',
this.updateSizeDestroy);
}
// only append link stylesheet if the theme property is set
if(this.theme) {
// check existing links for equivalent url
var addNode = true;
var nodes = document.getElementsByTagName('link');
for(var i=0, len=nodes.length; i<len; ++i) {
if(OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,
this.theme)) {
addNode = false;
break;
}
}
// only add a new node if one with an equivalent url hasn't already
// been added
if(addNode) {
var cssNode = document.createElement('link');
cssNode.setAttribute('rel', 'stylesheet');
cssNode.setAttribute('type', 'text/css');
cssNode.setAttribute('href', this.theme);
document.getElementsByTagName('head')[0].appendChild(cssNode);
}
}
this.layers = [];
if (this.controls == null) {
if (OpenLayers.Control != null) { // running full or lite?
this.controls = [ new OpenLayers.Control.Navigation(),
new OpenLayers.Control.PanZoom(),
new OpenLayers.Control.ArgParser(),
new OpenLayers.Control.Attribution()
];
} else {
this.controls = [];
}
}
for(var i=0, len=this.controls.length; i<len; i++) {
this.addControlToMap(this.controls[i]);
}
this.popups = [];
this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);
// always call map.destroy()
OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);
},
/**
* Method: unloadDestroy
* Function that is called to destroy the map on page unload. stored here
* so that if map is manually destroyed, we can unregister this.
*/
unloadDestroy: null,
/**
* Method: updateSizeDestroy
* When the map is destroyed, we need to stop listening to updateSize
* events: this method stores the function we need to unregister in
* non-IE browsers.
*/
updateSizeDestroy: null,
/**
* APIMethod: destroy
* Destroy this map
*/
destroy:function() {
// if unloadDestroy is null, we've already been destroyed
if (!this.unloadDestroy) {
return false;
}
// map has been destroyed. dont do it again!
OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);
this.unloadDestroy = null;
if (this.updateSizeDestroy) {
OpenLayers.Event.stopObserving(window, 'resize',
this.updateSizeDestroy);
} else {
this.events.unregister("resize", this, this.updateSize);
}
this.paddingForPopups = null;
if (this.controls != null) {
for (var i = this.controls.length - 1; i>=0; --i) {
this.controls[i].destroy();
}
this.controls = null;
}
if (this.layers != null) {
for (var i = this.layers.length - 1; i>=0; --i) {
//pass 'false' to destroy so that map wont try to set a new
// baselayer after each baselayer is removed
this.layers[i].destroy(false);
}
this.layers = null;
}
if (this.viewPortDiv) {
this.div.removeChild(this.viewPortDiv);
}
this.viewPortDiv = null;
if(this.eventListeners) {
this.events.un(this.eventListeners);
this.eventListeners = null;
}
this.events.destroy();
this.events = null;
},
/**
* APIMethod: setOptions
* Change the map options
*
* Parameters:
* options - {Object} Hashtable of options to tag to the map
*/
setOptions: function(options) {
OpenLayers.Util.extend(this, options);
},
/**
* APIMethod: getTileSize
* Get the tile size for the map
*
* Returns:
* {<OpenLayers.Size>}
*/
getTileSize: function() {
return this.tileSize;
},
/**
* APIMethod: getBy
* Get a list of objects given a property and a match item.
*
* Parameters:
* array - {String} A property on the map whose value is an array.
* property - {String} A property on each item of the given array.
* match - {String | Object} A string to match. Can also be a regular
* expression literal or object. In addition, it can be any object
* with a method named test. For reqular expressions or other, if
* match.test(map[array][i][property]) evaluates to true, the item will
* be included in the array returned. If no items are found, an empty
* array is returned.
*
* Returns:
* {Array} An array of items where the given property matches the given
* criteria.
*/
getBy: function(array, property, match) {
var test = (typeof match.test == "function");
var found = OpenLayers.Array.filter(this[array], function(item) {
return item[property] == match || (test && match.test(item[property]));
});
return found;
},
/**
* APIMethod: getLayersBy
* Get a list of layers with properties matching the given criteria.
*
* Parameter:
* property - {String} A layer property to be matched.
* match - {String | Object} A string to match. Can also be a regular
* expression literal or object. In addition, it can be any object
* with a method named test. For reqular expressions or other, if
* match.test(layer[property]) evaluates to true, the layer will be
* included in the array returned. If no layers are found, an empty
* array is returned.
*
* Returns:
* {Array(<OpenLayers.Layer>)} A list of layers matching the given criteria.
* An empty array is returned if no matches are found.
*/
getLayersBy: function(property, match) {
return this.getBy("layers", property, match);
},
/**
* APIMethod: getLayersByName
* Get a list of layers with names matching the given name.
*
* Parameter:
* match - {String | Object} A layer name. The name can also be a regular
* expression literal or object. In addition, it can be any object
* with a method named test. For reqular expressions or other, if
* name.test(layer.name) evaluates to true, the layer will be included
* in the list of layers returned. If no layers are found, an empty
* array is returned.
*
* Returns:
* {Array(<OpenLayers.Layer>)} A list of layers matching the given name.
* An empty array is returned if no matches are found.
*/
getLayersByName: function(match) {
return this.getLayersBy("name", match);
},
/**
* APIMethod: getLayersByClass
* Get a list of layers of a given class (CLASS_NAME).
*
* Parameter:
* match - {String | Object} A layer class name. The match can also be a
* regular expression literal or object. In addition, it can be any
* object with a method named test. For reqular expressions or other,
* if type.test(layer.CLASS_NAME) evaluates to true, the layer will
* be included in the list of layers returned. If no layers are
* found, an empty array is returned.
*
* Returns:
* {Array(<OpenLayers.Layer>)} A list of layers matching the given class.
* An empty array is returned if no matches are found.
*/
getLayersByClass: function(match) {
return this.getLayersBy("CLASS_NAME", match);
},
/**
* APIMethod: getControlsBy
* Get a list of controls with properties matching the given criteria.
*
* Parameter:
* property - {String} A control property to be matched.
* match - {String | Object} A string to match. Can also be a regular
* expression literal or object. In addition, it can be any object
* with a method named test. For reqular expressions or other, if
* match.test(layer[property]) evaluates to true, the layer will be
* included in the array returned. If no layers are found, an empty
* array is returned.
*
* Returns:
* {Array(<OpenLayers.Control>)} A list of controls matching the given
* criteria. An empty array is returned if no matches are found.
*/
getControlsBy: function(property, match) {
return this.getBy("controls", property, match);
},
/**
* APIMethod: getControlsByClass
* Get a list of controls of a given class (CLASS_NAME).
*
* Parameter:
* match - {String | Object} A control class name. The match can also be a
* regular expression literal or object. In addition, it can be any
* object with a method named test. For reqular expressions or other,
* if type.test(control.CLASS_NAME) evaluates to true, the control will
* be included in the list of controls returned. If no controls are
* found, an empty array is returned.
*
* Returns:
* {Array(<OpenLayers.Control>)} A list of controls matching the given class.
* An empty array is returned if no matches are found.
*/
getControlsByClass: function(match) {
return this.getControlsBy("CLASS_NAME", match);
},
/********************************************************/
/* */
/* Layer Functions */
/* */
/* The following functions deal with adding and */
/* removing Layers to and from the Map */
/* */
/********************************************************/
/**
* APIMethod: getLayer
* Get a layer based on its id
*
* Parameter:
* id - {String} A layer id
*
* Returns:
* {<OpenLayers.Layer>} The Layer with the corresponding id from the map's
* layer collection, or null if not found.
*/
getLayer: function(id) {
var foundLayer = null;
for (var i=0, len=this.layers.length; i<len; i++) {
var layer = this.layers[i];
if (layer.id == id) {
foundLayer = layer;
break;
}
}
return foundLayer;
},
/**
* Method: setLayerZIndex
*
* Parameters:
* layer - {<OpenLayers.Layer>}
* zIdx - {int}
*/
setLayerZIndex: function (layer, zIdx) {
layer.setZIndex(
this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay']
+ zIdx * 5 );
},
/**
* Method: resetLayersZIndex
* Reset each layer's z-index based on layer's array index
*/
resetLayersZIndex: function() {
for (var i=0, len=this.layers.length; i<len; i++) {
var layer = this.layers[i];
this.setLayerZIndex(layer, i);
}
},
/**
* APIMethod: addLayer
*
* Parameters:
* layer - {<OpenLayers.Layer>}
*/
addLayer: function (layer) {
for(var i=0, len=this.layers.length; i <len; i++) {
if (this.layers[i] == layer) {
var msg = OpenLayers.i18n('layerAlreadyAdded',
{'layerName':layer.name});
OpenLayers.Console.warn(msg);
return false;
}
}
this.events.triggerEvent("preaddlayer", {layer: layer});
layer.div.className = "olLayerDiv";
layer.div.style.overflow = "";
this.setLayerZIndex(layer, this.layers.length);
if (layer.isFixed) {
this.viewPortDiv.appendChild(layer.div);
} else {
this.layerContainerDiv.appendChild(layer.div);
}
this.layers.push(layer);
layer.setMap(this);
if (layer.isBaseLayer) {
if (this.baseLayer == null) {
// set the first baselaye we add as the baselayer
this.setBaseLayer(layer);
} else {
layer.setVisibility(false);
}
} else {
layer.redraw();
}
this.events.triggerEvent("addlayer", {layer: layer});
},
/**
* APIMethod: addLayers
*
* Parameters:
* layers - {Array(<OpenLayers.Layer>)}
*/
addLayers: function (layers) {
for (var i=0, len=layers.length; i<len; i++) {
this.addLayer(layers[i]);
}
},
/**
* APIMethod: removeLayer
* Removes a layer from the map by removing its visual element (the
* layer.div property), then removing it from the map's internal list
* of layers, setting the layer's map property to null.
*
* a "removelayer" event is triggered.
*
* very worthy of mention is that simply removing a layer from a map
* will not cause the removal of any popups which may have been created
* by the layer. this is due to the fact that it was decided at some
* point that popups would not belong to layers. thus there is no way
* for us to know here to which layer the popup belongs.
*
* A simple solution to this is simply to call destroy() on the layer.
* the default OpenLayers.Layer class's destroy() function
* automatically takes care to remove itself from whatever map it has
* been attached to.
*
* The correct solution is for the layer itself to register an
* event-handler on "removelayer" and when it is called, if it
* recognizes itself as the layer being removed, then it cycles through
* its own personal list of popups, removing them from the map.
*
* Parameters:
* layer - {<OpenLayers.Layer>}
* setNewBaseLayer - {Boolean} Default is true
*/
removeLayer: function(layer, setNewBaseLayer) {
if (setNewBaseLayer == null) {
setNewBaseLayer = true;
}
if (layer.isFixed) {
this.viewPortDiv.removeChild(layer.div);
} else {
this.layerContainerDiv.removeChild(layer.div);
}
OpenLayers.Util.removeItem(this.layers, layer);
layer.removeMap(this);
layer.map = null;
// if we removed the base layer, need to set a new one
if(this.baseLayer == layer) {
this.baseLayer = null;
if(setNewBaseLayer) {
for(var i=0, len=this.layers.length; i<len; i++) {
var iLayer = this.layers[i];
if (iLayer.isBaseLayer) {
this.setBaseLayer(iLayer);
break;
}
}
}
}
this.resetLayersZIndex();
this.events.triggerEvent("removelayer", {layer: layer});
},
/**
* APIMethod: getNumLayers
*
* Returns:
* {Int} The number of layers attached to the map.
*/
getNumLayers: function () {
return this.layers.length;
},
/**
* APIMethod: getLayerIndex
*
* Parameters:
* layer - {<OpenLayers.Layer>}
*
* Returns:
* {Integer} The current (zero-based) index of the given layer in the map's
* layer stack. Returns -1 if the layer isn't on the map.
*/
getLayerIndex: function (layer) {
return OpenLayers.Util.indexOf(this.layers, layer);
},
/**
* APIMethod: setLayerIndex
* Move the given layer to the specified (zero-based) index in the layer
* list, changing its z-index in the map display. Use
* map.getLayerIndex() to find out the current index of a layer. Note
* that this cannot (or at least should not) be effectively used to
* raise base layers above overlays.
*
* Parameters:
* layer - {<OpenLayers.Layer>}
* idx - {int}
*/
setLayerIndex: function (layer, idx) {
var base = this.getLayerIndex(layer);
if (idx < 0) {
idx = 0;
} else if (idx > this.layers.length) {
idx = this.layers.length;
}
if (base != idx) {
this.layers.splice(base, 1);
this.layers.splice(idx, 0, layer);
for (var i=0, len=this.layers.length; i<len; i++) {
this.setLayerZIndex(this.layers[i], i);
}
this.events.triggerEvent("changelayer", {
layer: layer, property: "order"
});
}
},
/**
* APIMethod: raiseLayer
* Change the index of the given layer by delta. If delta is positive,
* the layer is moved up the map's layer stack; if delta is negative,
* the layer is moved down. Again, note that this cannot (or at least
* should not) be effectively used to raise base layers above overlays.
*
* Paremeters:
* layer - {<OpenLayers.Layer>}
* delta - {int}
*/
raiseLayer: function (layer, delta) {
var idx = this.getLayerIndex(layer) + delta;
this.setLayerIndex(layer, idx);
},
/**
* APIMethod: setBaseLayer
* Allows user to specify one of the currently-loaded layers as the Map's
* new base layer.
*
* Parameters:
* newBaseLayer - {<OpenLayers.Layer>}
*/
setBaseLayer: function(newBaseLayer) {
var oldExtent = null;
if (this.baseLayer) {
oldExtent = this.baseLayer.getExtent();
}
if (newBaseLayer != this.baseLayer) {
// is newBaseLayer an already loaded layer?m
if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {
// make the old base layer invisible
if (this.baseLayer != null) {
this.baseLayer.setVisibility(false);
}
// set new baselayer
this.baseLayer = newBaseLayer;
// Increment viewRequestID since the baseLayer is
// changing. This is used by tiles to check if they should
// draw themselves.
this.viewRequestID++;
this.baseLayer.visibility = true;
//redraw all layers
var center = this.getCenter();
if (center != null) {
//either get the center from the old Extent or just from
// the current center of the map.
var newCenter = (oldExtent)
? oldExtent.getCenterLonLat()
: center;
//the new zoom will either come from the old Extent or
// from the current resolution of the map
var newZoom = (oldExtent)
? this.getZoomForExtent(oldExtent, true)
: this.getZoomForResolution(this.resolution, true);
// zoom and force zoom change
this.setCenter(newCenter, newZoom, false, true);
}
this.events.triggerEvent("changebaselayer", {
layer: this.baseLayer
});
}
}
},
/********************************************************/
/* */
/* Control Functions */
/* */
/* The following functions deal with adding and */
/* removing Controls to and from the Map */
/* */
/********************************************************/
/**
* APIMethod: addControl
*
* Parameters:
* control - {<OpenLayers.Control>}
* px - {<OpenLayers.Pixel>}
*/
addControl: function (control, px) {
this.controls.push(control);
this.addControlToMap(control, px);
},
/**
* Method: addControlToMap
*
* Parameters:
*
* control - {<OpenLayers.Control>}
* px - {<OpenLayers.Pixel>}
*/
addControlToMap: function (control, px) {
// If a control doesn't have a div at this point, it belongs in the
// viewport.
control.outsideViewport = (control.div != null);
// If the map has a displayProjection, and the control doesn't, set
// the display projection.
if (this.displayProjection && !control.displayProjection) {
control.displayProjection = this.displayProjection;
}
control.setMap(this);
var div = control.draw(px);
if (div) {
if(!control.outsideViewport) {
div.style.zIndex = this.Z_INDEX_BASE['Control'] +
this.controls.length;
this.viewPortDiv.appendChild( div );
}
}
},
/**
* APIMethod: getControl
*
* Parameters:
* id - {String} ID of the control to return.
*
* Returns:
* {<OpenLayers.Control>} The control from the map's list of controls
* which has a matching 'id'. If none found,
* returns null.
*/
getControl: function (id) {
var returnControl = null;
for(var i=0, len=this.controls.length; i<len; i++) {
var control = this.controls[i];
if (control.id == id) {
returnControl = control;
break;
}
}
return returnControl;
},
/**
* APIMethod: removeControl
* Remove a control from the map. Removes the control both from the map
* object's internal array of controls, as well as from the map's
* viewPort (assuming the control was not added outsideViewport)
*
* Parameters:
* control - {<OpenLayers.Control>} The control to remove.
*/
removeControl: function (control) {
//make sure control is non-null and actually part of our map
if ( (control) && (control == this.getControl(control.id)) ) {
if (control.div && (control.div.parentNode == this.viewPortDiv)) {
this.viewPortDiv.removeChild(control.div);
}
OpenLayers.Util.removeItem(this.controls, control);
}
},
/********************************************************/
/* */
/* Popup Functions */
/* */
/* The following functions deal with adding and */
/* removing Popups to and from the Map */
/* */
/********************************************************/
/**
* APIMethod: addPopup
*
* Parameters:
* popup - {<OpenLayers.Popup>}
* exclusive - {Boolean} If true, closes all other popups first
*/
addPopup: function(popup, exclusive) {
if (exclusive) {
//remove all other popups from screen
for (var i = this.popups.length - 1; i >= 0; --i) {
this.removePopup(this.popups[i]);
}
}
popup.map = this;
this.popups.push(popup);
var popupDiv = popup.draw();
if (popupDiv) {
popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +
this.popups.length;
this.layerContainerDiv.appendChild(popupDiv);
}
},
/**
* APIMethod: removePopup
*
* Parameters:
* popup - {<OpenLayers.Popup>}
*/
removePopup: function(popup) {
OpenLayers.Util.removeItem(this.popups, popup);
if (popup.div) {
try { this.layerContainerDiv.removeChild(popup.div); }
catch (e) { } // Popups sometimes apparently get disconnected
// from the layerContainerDiv, and cause complaints.
}
popup.map = null;
},
/********************************************************/
/* */
/* Container Div Functions */
/* */
/* The following functions deal with the access to */
/* and maintenance of the size of the container div */
/* */
/********************************************************/
/**
* APIMethod: getSize
*
* Returns:
* {<OpenLayers.Size>} An <OpenLayers.Size> object that represents the
* size, in pixels, of the div into which OpenLayers
* has been loaded.
* Note - A clone() of this locally cached variable is
* returned, so as not to allow users to modify it.
*/
getSize: function () {
var size = null;
if (this.size != null) {
size = this.size.clone();
}
return size;
},
/**
* APIMethod: updateSize
* This function should be called by any external code which dynamically
* changes the size of the map div (because mozilla wont let us catch
* the "onresize" for an element)
*/
updateSize: function() {
// the div might have moved on the page, also
this.events.clearMouseCache();
var newSize = this.getCurrentSize();
var oldSize = this.getSize();
if (oldSize == null) {
this.size = oldSize = newSize;
}
if (!newSize.equals(oldSize)) {
// store the new size
this.size = newSize;
//notify layers of mapresize
for(var i=0, len=this.layers.length; i<len; i++) {
this.layers[i].onMapResize();
}
if (this.baseLayer != null) {
var center = new OpenLayers.Pixel(newSize.w /2, newSize.h / 2);
var centerLL = this.getLonLatFromViewPortPx(center);
var zoom = this.getZoom();
this.zoom = null;
this.setCenter(this.getCenter(), zoom);
}
}
},
/**
* Method: getCurrentSize
*
* Returns:
* {<OpenLayers.Size>} A new <OpenLayers.Size> object with the dimensions
* of the map div
*/
getCurrentSize: function() {
var size = new OpenLayers.Size(this.div.clientWidth,
this.div.clientHeight);
// Workaround for the fact that hidden elements return 0 for size.
if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
var dim = OpenLayers.Element.getDimensions(this.div);
size.w = dim.width;
size.h = dim.height;
}
if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {
size.w = parseInt(this.div.style.width);
size.h = parseInt(this.div.style.height);
}
return size;
},
/**
* Method: calculateBounds
*
* Parameters:
* center - {<OpenLayers.LonLat>} Default is this.getCenter()
* resolution - {float} Default is this.getResolution()
*
* Returns:
* {<OpenLayers.Bounds>} A bounds based on resolution, center, and
* current mapsize.
*/
calculateBounds: function(center, resolution) {
var extent = null;
if (center == null) {
center = this.getCenter();
}
if (resolution == null) {
resolution = this.getResolution();
}
if ((center != null) && (resolution != null)) {
var size = this.getSize();
var w_deg = size.w * resolution;
var h_deg = size.h * resolution;
extent = new OpenLayers.Bounds(center.lon - w_deg / 2,
center.lat - h_deg / 2,
center.lon + w_deg / 2,
center.lat + h_deg / 2);
}
return extent;
},
/********************************************************/
/* */
/* Zoom, Center, Pan Functions */
/* */
/* The following functions handle the validation, */
/* getting and setting of the Zoom Level and Center */
/* as well as the panning of the Map */
/* */
/********************************************************/
/**
* APIMethod: getCenter
*
* Returns:
* {<OpenLayers.LonLat>}
*/
getCenter: function () {
return this.center;
},
/**
* APIMethod: getZoom
*
* Returns:
* {Integer}
*/
getZoom: function () {
return this.zoom;
},
/**
* APIMethod: pan
* Allows user to pan by a value of screen pixels
*
* Parameters:
* dx - {Integer}
* dy - {Integer}
* options - {Object} Options to configure panning:
* - *animate* {Boolean} Use panTo instead of setCenter. Default is true.
* - *dragging* {Boolean} Call setCenter with dragging true. Default is
* false.
*/
pan: function(dx, dy, options) {
options = OpenLayers.Util.applyDefaults(options, {
animate: true,
dragging: false
});
// getCenter
var centerPx = this.getViewPortPxFromLonLat(this.getCenter());
// adjust
var newCenterPx = centerPx.add(dx, dy);
// only call setCenter if not dragging or there has been a change
if (!options.dragging || !newCenterPx.equals(centerPx)) {
var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);
if (options.animate) {
this.panTo(newCenterLonLat);
} else {
this.setCenter(newCenterLonLat, null, options.dragging);
}
}
},
/**
* APIMethod: panTo
* Allows user to pan to a new lonlat
* If the new lonlat is in the current extent the map will slide smoothly
*
* Parameters:
* lonlat - {<OpenLayers.Lonlat>}
*/
panTo: function(lonlat) {
if (this.panMethod && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {
if (!this.panTween) {
this.panTween = new OpenLayers.Tween(this.panMethod);
}
var center = this.getCenter();
// center will not change, don't do nothing
if (lonlat.lon == center.lon &&
lonlat.lat == center.lat) {
return;
}
var from = {
lon: center.lon,
lat: center.lat
};
var to = {
lon: lonlat.lon,
lat: lonlat.lat
};
this.panTween.start(from, to, 50, {
callbacks: {
start: OpenLayers.Function.bind(function(lonlat) {
this.events.triggerEvent("movestart");
}, this),
eachStep: OpenLayers.Function.bind(function(lonlat) {
lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
this.moveTo(lonlat, this.zoom, {
'dragging': true,
'noEvent': true
});
}, this),
done: OpenLayers.Function.bind(function(lonlat) {
lonlat = new OpenLayers.LonLat(lonlat.lon, lonlat.lat);
this.moveTo(lonlat, this.zoom, {
'noEvent': true
});
this.events.triggerEvent("moveend");
}, this)
}
});
} else {
this.setCenter(lonlat);
}
},
/**
* APIMethod: setCenter
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
* zoom - {Integer}
* dragging - {Boolean} Specifies whether or not to trigger
* movestart/end events
* forceZoomChange - {Boolean} Specifies whether or not to trigger zoom
* change events (needed on baseLayer change)
*
* TBD: reconsider forceZoomChange in 3.0
*/
setCenter: function(lonlat, zoom, dragging, forceZoomChange) {
this.moveTo(lonlat, zoom, {
'dragging': dragging,
'forceZoomChange': forceZoomChange,
'caller': 'setCenter'
});
},
/**
* Method: moveTo
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
* zoom - {Integer}
* options - {Object}
*/
moveTo: function(lonlat, zoom, options) {
if (!options) {
options = {};
}
// dragging is false by default
var dragging = options.dragging;
// forceZoomChange is false by default
var forceZoomChange = options.forceZoomChange;
// noEvent is false by default
var noEvent = options.noEvent;
if (this.panTween && options.caller == "setCenter") {
this.panTween.stop();
}
if (!this.center && !this.isValidLonLat(lonlat)) {
lonlat = this.maxExtent.getCenterLonLat();
}
if(this.restrictedExtent != null) {
// In 3.0, decide if we want to change interpretation of maxExtent.
if(lonlat == null) {
lonlat = this.getCenter();
}
if(zoom == null) {
zoom = this.getZoom();
}
var resolution = this.getResolutionForZoom(zoom);
var extent = this.calculateBounds(lonlat, resolution);
if(!this.restrictedExtent.containsBounds(extent)) {
var maxCenter = this.restrictedExtent.getCenterLonLat();
if(extent.getWidth() > this.restrictedExtent.getWidth()) {
lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);
} else if(extent.left < this.restrictedExtent.left) {
lonlat = lonlat.add(this.restrictedExtent.left -
extent.left, 0);
} else if(extent.right > this.restrictedExtent.right) {
lonlat = lonlat.add(this.restrictedExtent.right -
extent.right, 0);
}
if(extent.getHeight() > this.restrictedExtent.getHeight()) {
lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);
} else if(extent.bottom < this.restrictedExtent.bottom) {
lonlat = lonlat.add(0, this.restrictedExtent.bottom -
extent.bottom);
}
else if(extent.top > this.restrictedExtent.top) {
lonlat = lonlat.add(0, this.restrictedExtent.top -
extent.top);
}
}
}
var zoomChanged = forceZoomChange || (
(this.isValidZoomLevel(zoom)) &&
(zoom != this.getZoom()) );
var centerChanged = (this.isValidLonLat(lonlat)) &&
(!lonlat.equals(this.center));
// if neither center nor zoom will change, no need to do anything
if (zoomChanged || centerChanged || !dragging) {
if (!this.dragging && !noEvent) {
this.events.triggerEvent("movestart");
}
if (centerChanged) {
if ((!zoomChanged) && (this.center)) {
// if zoom hasnt changed, just slide layerContainer
// (must be done before setting this.center to new value)
this.centerLayerContainer(lonlat);
}
this.center = lonlat.clone();
}
// (re)set the layerContainerDiv's location
if ((zoomChanged) || (this.layerContainerOrigin == null)) {
this.layerContainerOrigin = this.center.clone();
this.layerContainerDiv.style.left = "0px";
this.layerContainerDiv.style.top = "0px";
}
if (zoomChanged) {
this.zoom = zoom;
this.resolution = this.getResolutionForZoom(zoom);
// zoom level has changed, increment viewRequestID.
this.viewRequestID++;
}
var bounds = this.getExtent();
//send the move call to the baselayer and all the overlays
this.baseLayer.moveTo(bounds, zoomChanged, dragging);
bounds = this.baseLayer.getExtent();
for (var i=0, len=this.layers.length; i<len; i++) {
var layer = this.layers[i];
if (!layer.isBaseLayer) {
var inRange = layer.calculateInRange();
if (layer.inRange != inRange) {
// the inRange property has changed. If the layer is
// no longer in range, we turn it off right away. If
// the layer is no longer out of range, the moveTo
// call below will turn on the layer.
layer.inRange = inRange;
if (!inRange) {
layer.display(false);
}
this.events.triggerEvent("changelayer", {
layer: layer, property: "visibility"
});
}
if (inRange && layer.visibility) {
layer.moveTo(bounds, zoomChanged, dragging);
layer.events.triggerEvent("moveend",
{"zoomChanged": zoomChanged}
);
}
}
}
if (zoomChanged) {
//redraw popups
for (var i=0, len=this.popups.length; i<len; i++) {
this.popups[i].updatePosition();
}
}
this.events.triggerEvent("move");
if (zoomChanged) { this.events.triggerEvent("zoomend"); }
}
// even if nothing was done, we want to notify of this
if (!dragging && !noEvent) {
this.events.triggerEvent("moveend");
}
// Store the map dragging state for later use
this.dragging = !!dragging;
},
/**
* Method: centerLayerContainer
* This function takes care to recenter the layerContainerDiv.
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
*/
centerLayerContainer: function (lonlat) {
var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);
var newPx = this.getViewPortPxFromLonLat(lonlat);
if ((originPx != null) && (newPx != null)) {
this.layerContainerDiv.style.left = Math.round(originPx.x - newPx.x) + "px";
this.layerContainerDiv.style.top = Math.round(originPx.y - newPx.y) + "px";
}
},
/**
* Method: isValidZoomLevel
*
* Parameters:
* zoomLevel - {Integer}
*
* Returns:
* {Boolean} Whether or not the zoom level passed in is non-null and
* within the min/max range of zoom levels.
*/
isValidZoomLevel: function(zoomLevel) {
return ( (zoomLevel != null) &&
(zoomLevel >= 0) &&
(zoomLevel < this.getNumZoomLevels()) );
},
/**
* Method: isValidLonLat
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
*
* Returns:
* {Boolean} Whether or not the lonlat passed in is non-null and within
* the maxExtent bounds
*/
isValidLonLat: function(lonlat) {
var valid = false;
if (lonlat != null) {
var maxExtent = this.getMaxExtent();
valid = maxExtent.containsLonLat(lonlat);
}
return valid;
},
/********************************************************/
/* */
/* Layer Options */
/* */
/* Accessor functions to Layer Options parameters */
/* */
/********************************************************/
/**
* APIMethod: getProjection
* This method returns a string representing the projection. In
* the case of projection support, this will be the srsCode which
* is loaded -- otherwise it will simply be the string value that
* was passed to the projection at startup.
*
* FIXME: In 3.0, we will remove getProjectionObject, and instead
* return a Projection object from this function.
*
* Returns:
* {String} The Projection string from the base layer or null.
*/
getProjection: function() {
var projection = this.getProjectionObject();
return projection ? projection.getCode() : null;
},
/**
* APIMethod: getProjectionObject
* Returns the projection obect from the baselayer.
*
* Returns:
* {<OpenLayers.Projection>} The Projection of the base layer.
*/
getProjectionObject: function() {
var projection = null;
if (this.baseLayer != null) {
projection = this.baseLayer.projection;
}
return projection;
},
/**
* APIMethod: getMaxResolution
*
* Returns:
* {String} The Map's Maximum Resolution
*/
getMaxResolution: function() {
var maxResolution = null;
if (this.baseLayer != null) {
maxResolution = this.baseLayer.maxResolution;
}
return maxResolution;
},
/**
* APIMethod: getMaxExtent
*
* Parameters:
* options - {Object}
*
* Allowed Options:
* restricted - {Boolean} If true, returns restricted extent (if it is
* available.)
*
* Returns:
* {<OpenLayers.Bounds>} The maxExtent property as set on the current
* baselayer, unless the 'restricted' option is set, in which case
* the 'restrictedExtent' option from the map is returned (if it
* is set).
*/
getMaxExtent: function (options) {
var maxExtent = null;
if(options && options.restricted && this.restrictedExtent){
maxExtent = this.restrictedExtent;
} else if (this.baseLayer != null) {
maxExtent = this.baseLayer.maxExtent;
}
return maxExtent;
},
/**
* APIMethod: getNumZoomLevels
*
* Returns:
* {Integer} The total number of zoom levels that can be displayed by the
* current baseLayer.
*/
getNumZoomLevels: function() {
var numZoomLevels = null;
if (this.baseLayer != null) {
numZoomLevels = this.baseLayer.numZoomLevels;
}
return numZoomLevels;
},
/********************************************************/
/* */
/* Baselayer Functions */
/* */
/* The following functions, all publicly exposed */
/* in the API?, are all merely wrappers to the */
/* the same calls on whatever layer is set as */
/* the current base layer */
/* */
/********************************************************/
/**
* APIMethod: getExtent
*
* Returns:
* {<OpenLayers.Bounds>} A Bounds object which represents the lon/lat
* bounds of the current viewPort.
* If no baselayer is set, returns null.
*/
getExtent: function () {
var extent = null;
if (this.baseLayer != null) {
extent = this.baseLayer.getExtent();
}
return extent;
},
/**
* APIMethod: getResolution
*
* Returns:
* {Float} The current resolution of the map.
* If no baselayer is set, returns null.
*/
getResolution: function () {
var resolution = null;
if (this.baseLayer != null) {
resolution = this.baseLayer.getResolution();
}
return resolution;
},
/**
* APIMethod: getUnits
*
* Returns:
* {Float} The current units of the map.
* If no baselayer is set, returns null.
*/
getUnits: function () {
var units = null;
if (this.baseLayer != null) {
units = this.baseLayer.units;
}
return units;
},
/**
* APIMethod: getScale
*
* Returns:
* {Float} The current scale denominator of the map.
* If no baselayer is set, returns null.
*/
getScale: function () {
var scale = null;
if (this.baseLayer != null) {
var res = this.getResolution();
var units = this.baseLayer.units;
scale = OpenLayers.Util.getScaleFromResolution(res, units);
}
return scale;
},
/**
* APIMethod: getZoomForExtent
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
* closest - {Boolean} Find the zoom level that most closely fits the
* specified bounds. Note that this may result in a zoom that does
* not exactly contain the entire extent.
* Default is false.
*
* Returns:
* {Integer} A suitable zoom level for the specified bounds.
* If no baselayer is set, returns null.
*/
getZoomForExtent: function (bounds, closest) {
var zoom = null;
if (this.baseLayer != null) {
zoom = this.baseLayer.getZoomForExtent(bounds, closest);
}
return zoom;
},
/**
* APIMethod: getResolutionForZoom
*
* Parameter:
* zoom - {Float}
*
* Returns:
* {Float} A suitable resolution for the specified zoom. If no baselayer
* is set, returns null.
*/
getResolutionForZoom: function(zoom) {
var resolution = null;
if(this.baseLayer) {
resolution = this.baseLayer.getResolutionForZoom(zoom);
}
return resolution;
},
/**
* APIMethod: getZoomForResolution
*
* Parameter:
* resolution - {Float}
* closest - {Boolean} Find the zoom level that corresponds to the absolute
* closest resolution, which may result in a zoom whose corresponding
* resolution is actually smaller than we would have desired (if this
* is being called from a getZoomForExtent() call, then this means that
* the returned zoom index might not actually contain the entire
* extent specified... but it'll be close).
* Default is false.
*
* Returns:
* {Integer} A suitable zoom level for the specified resolution.
* If no baselayer is set, returns null.
*/
getZoomForResolution: function(resolution, closest) {
var zoom = null;
if (this.baseLayer != null) {
zoom = this.baseLayer.getZoomForResolution(resolution, closest);
}
return zoom;
},
/********************************************************/
/* */
/* Zooming Functions */
/* */
/* The following functions, all publicly exposed */
/* in the API, are all merely wrappers to the */
/* the setCenter() function */
/* */
/********************************************************/
/**
* APIMethod: zoomTo
* Zoom to a specific zoom level
*
* Parameters:
* zoom - {Integer}
*/
zoomTo: function(zoom) {
if (this.isValidZoomLevel(zoom)) {
this.setCenter(null, zoom);
}
},
/**
* APIMethod: zoomIn
*
* Parameters:
* zoom - {int}
*/
zoomIn: function() {
this.zoomTo(this.getZoom() + 1);
},
/**
* APIMethod: zoomOut
*
* Parameters:
* zoom - {int}
*/
zoomOut: function() {
this.zoomTo(this.getZoom() - 1);
},
/**
* APIMethod: zoomToExtent
* Zoom to the passed in bounds, recenter
*
* Parameters:
* bounds - {<OpenLayers.Bounds>}
*/
zoomToExtent: function(bounds) {
var center = bounds.getCenterLonLat();
if (this.baseLayer.wrapDateLine) {
var maxExtent = this.getMaxExtent();
//fix straddling bounds (in the case of a bbox that straddles the
// dateline, it's left and right boundaries will appear backwards.
// we fix this by allowing a right value that is greater than the
// max value at the dateline -- this allows us to pass a valid
// bounds to calculate zoom)
//
bounds = bounds.clone();
while (bounds.right < bounds.left) {
bounds.right += maxExtent.getWidth();
}
//if the bounds was straddling (see above), then the center point
// we got from it was wrong. So we take our new bounds and ask it
// for the center. Because our new bounds is at least partially
// outside the bounds of maxExtent, the new calculated center
// might also be. We don't want to pass a bad center value to
// setCenter, so we have it wrap itself across the date line.
//
center = bounds.getCenterLonLat().wrapDateLine(maxExtent);
}
this.setCenter(center, this.getZoomForExtent(bounds));
},
/**
* APIMethod: zoomToMaxExtent
* Zoom to the full extent and recenter.
*
* Parameters:
* options -
*
* Allowed Options:
* restricted - {Boolean} True to zoom to restricted extent if it is
* set. Defaults to true.
*/
zoomToMaxExtent: function(options) {
//restricted is true by default
var restricted = (options) ? options.restricted : true;
var maxExtent = this.getMaxExtent({
'restricted': restricted
});
this.zoomToExtent(maxExtent);
},
/**
* APIMethod: zoomToScale
* Zoom to a specified scale
*
* Parameters:
* scale - {float}
*/
zoomToScale: function(scale) {
var res = OpenLayers.Util.getResolutionFromScale(scale,
this.baseLayer.units);
var size = this.getSize();
var w_deg = size.w * res;
var h_deg = size.h * res;
var center = this.getCenter();
var extent = new OpenLayers.Bounds(center.lon - w_deg / 2,
center.lat - h_deg / 2,
center.lon + w_deg / 2,
center.lat + h_deg / 2);
this.zoomToExtent(extent);
},
/********************************************************/
/* */
/* Translation Functions */
/* */
/* The following functions translate between */
/* LonLat, LayerPx, and ViewPortPx */
/* */
/********************************************************/
//
// TRANSLATION: LonLat <-> ViewPortPx
//
/**
* Method: getLonLatFromViewPortPx
*
* Parameters:
* viewPortPx - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.LonLat>} An OpenLayers.LonLat which is the passed-in view
* port <OpenLayers.Pixel>, translated into lon/lat
* by the current base layer.
*/
getLonLatFromViewPortPx: function (viewPortPx) {
var lonlat = null;
if (this.baseLayer != null) {
lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);
}
return lonlat;
},
/**
* APIMethod: getViewPortPxFromLonLat
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>}
*
* Returns:
* {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
* <OpenLayers.LonLat>, translated into view port
* pixels by the current base layer.
*/
getViewPortPxFromLonLat: function (lonlat) {
var px = null;
if (this.baseLayer != null) {
px = this.baseLayer.getViewPortPxFromLonLat(lonlat);
}
return px;
},
//
// CONVENIENCE TRANSLATION FUNCTIONS FOR API
//
/**
* APIMethod: getLonLatFromPixel
*
* Parameters:
* px - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.LonLat>} An OpenLayers.LonLat corresponding to the given
* OpenLayers.Pixel, translated into lon/lat by the
* current base layer
*/
getLonLatFromPixel: function (px) {
return this.getLonLatFromViewPortPx(px);
},
/**
* APIMethod: getPixelFromLonLat
* Returns a pixel location given a map location. The map location is
* translated to an integer pixel location (in viewport pixel
* coordinates) by the current base layer.
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>} A map location.
*
* Returns:
* {<OpenLayers.Pixel>} An OpenLayers.Pixel corresponding to the
* <OpenLayers.LonLat> translated into view port pixels by the current
* base layer.
*/
getPixelFromLonLat: function (lonlat) {
var px = this.getViewPortPxFromLonLat(lonlat);
px.x = Math.round(px.x);
px.y = Math.round(px.y);
return px;
},
//
// TRANSLATION: ViewPortPx <-> LayerPx
//
/**
* APIMethod: getViewPortPxFromLayerPx
*
* Parameters:
* layerPx - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.Pixel>} Layer Pixel translated into ViewPort Pixel
* coordinates
*/
getViewPortPxFromLayerPx:function(layerPx) {
var viewPortPx = null;
if (layerPx != null) {
var dX = parseInt(this.layerContainerDiv.style.left);
var dY = parseInt(this.layerContainerDiv.style.top);
viewPortPx = layerPx.add(dX, dY);
}
return viewPortPx;
},
/**
* APIMethod: getLayerPxFromViewPortPx
*
* Parameters:
* viewPortPx - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.Pixel>} ViewPort Pixel translated into Layer Pixel
* coordinates
*/
getLayerPxFromViewPortPx:function(viewPortPx) {
var layerPx = null;
if (viewPortPx != null) {
var dX = -parseInt(this.layerContainerDiv.style.left);
var dY = -parseInt(this.layerContainerDiv.style.top);
layerPx = viewPortPx.add(dX, dY);
if (isNaN(layerPx.x) || isNaN(layerPx.y)) {
layerPx = null;
}
}
return layerPx;
},
//
// TRANSLATION: LonLat <-> LayerPx
//
/**
* Method: getLonLatFromLayerPx
*
* Parameters:
* px - {<OpenLayers.Pixel>}
*
* Returns:
* {<OpenLayers.LonLat>}
*/
getLonLatFromLayerPx: function (px) {
//adjust for displacement of layerContainerDiv
px = this.getViewPortPxFromLayerPx(px);
return this.getLonLatFromViewPortPx(px);
},
/**
* APIMethod: getLayerPxFromLonLat
*
* Parameters:
* lonlat - {<OpenLayers.LonLat>} lonlat
*
* Returns:
* {<OpenLayers.Pixel>} An OpenLayers.Pixel which is the passed-in
* <OpenLayers.LonLat>, translated into layer pixels
* by the current base layer
*/
getLayerPxFromLonLat: function (lonlat) {
//adjust for displacement of layerContainerDiv
var px = this.getPixelFromLonLat(lonlat);
return this.getLayerPxFromViewPortPx(px);
},
CLASS_NAME: "OpenLayers.Map"
});
/**
* Constant: TILE_WIDTH
* {Integer} 256 Default tile width (unless otherwise specified)
*/
OpenLayers.Map.TILE_WIDTH = 256;
/**
* Constant: TILE_HEIGHT
* {Integer} 256 Default tile height (unless otherwise specified)
*/
OpenLayers.Map.TILE_HEIGHT = 256;