/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for * full list of contributors). Published under the 2-clause BSD license. * See license.txt in the OpenLayers distribution or repository for the * full text of the license. */ /** * @requires OpenLayers/BaseTypes/Class.js * @requires OpenLayers/Util.js * @requires OpenLayers/Util/vendorPrefix.js * @requires OpenLayers/Events.js * @requires OpenLayers/Tween.js * @requires OpenLayers/Projection.js */ /** * Class: OpenLayers.Map * Instances of OpenLayers.Map are interactive maps embedded in a web page. * Create a new map with the constructor. * * On their own maps do not provide much functionality. To extend a map * it's necessary to add controls () and * layers () 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, Feature: 725, Popup: 750, Control: 1000 }, /** * APIProperty: events * {} * * 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 - {} The pixel location of the event (relative * to the the map viewport). * * 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. When a listener returns "false" the adding will be * aborted. * addlayer - triggered after a layer has been added. The event object * will include a *layer* property that references the added layer. * preremovelayer - triggered before a layer has been removed. The event * object will include a *layer* property that references the layer * to be removed. When a listener returns "false" the removal will be * aborted. * 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, * opacity change, params change, visibility change (actual visibility, * not the layer's visibility property) or attribution change (due to * extent change). 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, order, opacity, params, visibility or * attribution). * movestart - triggered after the start of a drag, pan, or zoom. The event * object may include a *zoomChanged* property that tells whether the * zoom has changed. * move - triggered after each drag, pan, or zoom * moveend - triggered after a drag, pan, or zoom completes * zoomend - triggered after a zoom completes * mouseover - triggered after mouseover the map * mouseout - triggered after mouseout the map * mousemove - triggered after mousemove the map * changebaselayer - triggered after the base layer changes * updatesize - triggered after the method was executed */ /** * 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 * instead of layer.resolutions[zoom] as the * former works for non-integer zoom levels. */ fractionalZoom: false, /** * APIProperty: events * {} An events object that handles all * events on the map */ events: null, /** * APIProperty: allOverlays * {Boolean} Allow the map to function with "overlays" only. Defaults to * false. If true, the lowest layer in the draw order will act as * the base layer. In addition, if set to true, all layers will * have isBaseLayer set to false when they are added to the map. * * Note: * If you set map.allOverlays to true, then you *cannot* use * map.setBaseLayer or layer.setIsBaseLayer. With allOverlays true, * the lowest layer in the draw layer is the base layer. So, to change * the base layer, use or to set the layer * index to 0. */ allOverlays: false, /** * APIProperty: div * {DOMElement|String} The element that contains the map (or an id for * that element). If the constructor is called * with two arguments, this should be provided as the first argument. * Alternatively, the map constructor can be called with the options * object as the only argument. In this case (one argument), a * div property may or may not be provided. If the div property * is not provided, the map can be rendered to a container later * using the method. * * Note: * If you are calling after map construction, do not use * auto. Instead, divide your by your * maximum expected dimension. */ div: null, /** * Property: dragging * {Boolean} The map is currently being dragged. */ dragging: false, /** * Property: size * {} Size of the main div (this.div) */ size: null, /** * Property: viewPortDiv * {HTMLDivElement} The element that represents the map viewport */ viewPortDiv: null, /** * Property: layerContainerOrigin * {} 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()} Ordered list of layers in the map */ layers: null, /** * APIProperty: controls * {Array()} List of controls associated with the map. * * If not provided in the map options at construction, the map will * by default be given the following controls if present in the build: * - or * - or * - * - */ controls: null, /** * Property: popups * {Array()} List of popups associated with the map */ popups: null, /** * APIProperty: baseLayer * {} The currently selected base layer. This determines * min/max zoom level, projection, etc. */ baseLayer: null, /** * Property: center * {} 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, /** * APIProperty: options * {Object} The options object passed to the class constructor. Read-only. */ options: null, // Options /** * APIProperty: tileSize * {} 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 specify the default projection * for layers added to this map. When using a projection other than EPSG:4326 * (CRS:84, Geographic) or EPSG:3857 (EPSG:900913, Web Mercator), * also set maxExtent, maxResolution or resolutions. Default is "EPSG:4326". * Note that the projection of the map is usually determined * by that of the current baseLayer (see and ). */ projection: "EPSG:4326", /** * APIProperty: units * {String} The map units. Possible values are 'degrees' (or 'dd'), 'm', * 'ft', 'km', 'mi', 'inches'. Normally taken from the projection. * Only required if both map and layers do not define a projection, * or if they define a projection which does not define units */ units: null, /** * 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} Required if you are not displaying the whole world on a tile * with the size specified in . */ maxResolution: null, /** * APIProperty: minResolution * {Float} */ minResolution: null, /** * APIProperty: maxScale * {Float} */ maxScale: null, /** * APIProperty: minScale * {Float} */ minScale: null, /** * APIProperty: maxExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The maximum extent for the map. * Default depends on projection; if this is one of those defined in OpenLayers.Projection.defaults * (EPSG:4326 or web mercator), maxExtent will be set to the value defined there; * else, defaults to null. * To restrict user panning and zooming of the map, use instead. * The value for will change calculations for tile URLs. */ maxExtent: null, /** * APIProperty: minExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * The minimum extent for the map. Defaults to null. */ minExtent: null, /** * APIProperty: restrictedExtent * {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * 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 * {} Requires proj4js support for projections other * than EPSG:4326 or EPSG:900913/EPSG:3857. 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: tileManager * {|Object} By default, and if the build contains * TileManager.js, the map will use the TileManager to queue image requests * and to cache tile image elements. To create a map without a TileManager * configure the map with tileManager: null. To create a TileManager with * non-default options, supply the options instead or alternatively supply * an instance of {}. */ /** * 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 swallow. */ fallThrough: false, /** * APIProperty: autoUpdateSize * {Boolean} Should OpenLayers automatically update the size of the map * when the resize event is fired. Default is true. */ autoUpdateSize: true, /** * APIProperty: eventListeners * {Object} If set as an option at construction, the eventListeners * object will be registered with . Object * structure must be a listeners object as shown in the example for * the events.on method. */ eventListeners: null, /** * Property: panTween * {} Animated panning tween object, see panTo() */ panTween: null, /** * APIProperty: 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: panDuration * {Integer} The number of steps to be passed to the * OpenLayers.Tween.start() method when the map is * panned. * Default is 50. */ panDuration: 50, /** * Property: zoomTween * {} Animated zooming tween object, see zoomTo() */ zoomTween: null, /** * APIProperty: zoomMethod * {Function} The Easing function to be used for tweening. Default is * OpenLayers.Easing.Quad.easeOut. Setting this to 'null' turns off * animated zooming. */ zoomMethod: OpenLayers.Easing.Quad.easeOut, /** * Property: zoomDuration * {Integer} The number of steps to be passed to the * OpenLayers.Tween.start() method when the map is zoomed. * Default is 20. */ zoomDuration: 20, /** * Property: paddingForPopups * {} Outside margin of the popup. Used to prevent * the popup from getting too close to the map border. */ paddingForPopups : null, /** * Property: layerContainerOriginPx * {Object} Cached object representing the layer container origin (in pixels). */ layerContainerOriginPx: null, /** * Property: minPx * {Object} An object with a 'x' and 'y' values that is the lower * left of maxExtent in viewport pixel space. * Used to verify in moveByPx that the new location we're moving to * is valid. It is also used in the getLonLatFromViewPortPx function * of Layer. */ minPx: null, /** * Property: maxPx * {Object} An object with a 'x' and 'y' values that is the top * right of maxExtent in viewport pixel space. * Used to verify in moveByPx that the new location we're moving to * is valid. */ maxPx: null, /** * Constructor: OpenLayers.Map * Constructor for a new OpenLayers.Map instance. There are two possible * ways to call the map constructor. See the examples below. * * Parameters: * div - {DOMElement|String} The element or id of an element in your page * that will contain the map. May be omitted if the
option is * provided or if you intend to call the method later. * options - {Object} Optional object with properties to tag onto the map. * * Valid options (in addition to the listed API properties): * center - {|Array} The default initial center of the map. * If provided as array, the first value is the x coordinate, * and the 2nd value is the y coordinate. * Only specify if is provided. * Note that if an ArgParser/Permalink control is present, * and the querystring contains coordinates, center will be set * by that, and this option will be ignored. * zoom - {Number} The initial zoom level for the map. Only specify if * is provided. * Note that if an ArgParser/Permalink control is present, * and the querystring contains a zoom level, zoom will be set * by that, and this option will be ignored. * extent - {|Array} The initial extent of the map. * If provided as an array, the array should consist of * four values (left, bottom, right, top). * Only specify if
and are not provided. * * 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 = { * projection: "EPSG:3857", * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000), * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095) * }; * var map = new OpenLayers.Map("map2", options); * * // map with non-default options - same as above but with a single argument, * // a restricted extent, and using arrays for bounds and center * var map = new OpenLayers.Map({ * div: "map_id", * projection: "EPSG:3857", * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146], * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962], * center: [-12356463.476333, 5621521.4854095] * }); * * // create a map without a reference to a container - call render later * var map = new OpenLayers.Map({ * projection: "EPSG:3857", * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000) * }); * (end) */ initialize: function (div, options) { // If only one argument is provided, check if it is an object. if(arguments.length === 1 && typeof div === "object") { options = div; div = options && options.div; } // 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.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15); this.theme = OpenLayers._getScriptLocation() + 'theme/default/style.css'; // backup original options this.options = OpenLayers.Util.extend({}, options); // now override default options OpenLayers.Util.extend(this, options); var projCode = this.projection instanceof OpenLayers.Projection ? this.projection.projCode : this.projection; OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]); // allow extents and center to be arrays if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) { this.maxExtent = new OpenLayers.Bounds(this.maxExtent); } if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) { this.minExtent = new OpenLayers.Bounds(this.minExtent); } if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) { this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent); } if (this.center && !(this.center instanceof OpenLayers.LonLat)) { this.center = new OpenLayers.LonLat(this.center); } // initialize layers array this.layers = []; this.id = OpenLayers.Util.createUniqueID("OpenLayers.Map_"); this.div = OpenLayers.Util.getElement(div); if(!this.div) { this.div = document.createElement("div"); this.div.style.height = "1px"; this.div.style.width = "1px"; } OpenLayers.Element.addClass(this.div, 'olMap'); // the viewPortDiv is the outermost div we modify var id = this.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); this.events = new OpenLayers.Events( this, this.viewPortDiv, null, this.fallThrough, {includeXY: true} ); if (OpenLayers.TileManager && this.tileManager !== null) { if (!(this.tileManager instanceof OpenLayers.TileManager)) { this.tileManager = new OpenLayers.TileManager(this.tileManager); } this.tileManager.addMap(this); } // the layerContainerDiv is the one that holds all the layers id = this.id + "_OpenLayers_Container"; this.layerContainerDiv = OpenLayers.Util.createDiv(id); this.layerContainerDiv.style.zIndex=this.Z_INDEX_BASE['Popup']-1; this.layerContainerOriginPx = {x: 0, y: 0}; this.applyTransform(); this.viewPortDiv.appendChild(this.layerContainerDiv); this.updateSize(); if(this.eventListeners instanceof Object) { this.events.on(this.eventListeners); } if (this.autoUpdateSize === true) { // 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=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.viewPortDiv.parentNode) { this.viewPortDiv.parentNode.removeChild(this.viewPortDiv); } this.viewPortDiv = null; if (this.tileManager) { this.tileManager.removeMap(this); this.tileManager = null; } if(this.eventListeners) { this.events.un(this.eventListeners); this.eventListeners = null; } this.events.destroy(); this.events = null; this.options = null; }, /** * APIMethod: setOptions * Change the map options * * Parameters: * options - {Object} Hashtable of options to tag to the map */ setOptions: function(options) { var updatePxExtent = this.minPx && options.restrictedExtent != this.restrictedExtent; OpenLayers.Util.extend(this, options); // force recalculation of minPx and maxPx updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, { forceZoomChange: true }); }, /** * APIMethod: getTileSize * Get the tile size for the map * * Returns: * {} */ 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. * * Parameters: * 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()} 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. * * Parameters: * 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()} 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). * * Parameters: * 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()} 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. * * Parameters: * 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()} 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). * * Parameters: * 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()} 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 * * Parameters: * id - {String} A layer id * * Returns: * {} 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} * 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} * * Returns: * {Boolean} True if the layer has been added to the map. */ addLayer: function (layer) { for(var i = 0, len = this.layers.length; i < len; i++) { if (this.layers[i] == layer) { return false; } } if (this.events.triggerEvent("preaddlayer", {layer: layer}) === false) { return false; } if(this.allOverlays) { layer.isBaseLayer = false; } 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 || (this.allOverlays && !this.baseLayer)) { 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}); layer.events.triggerEvent("added", {map: this, layer: layer}); layer.afterAdd(); return true; }, /** * APIMethod: addLayers * * Parameters: * layers - {Array()} */ addLayers: function (layers) { for (var i=0, len=layers.length; i} * setNewBaseLayer - {Boolean} Default is true */ removeLayer: function(layer, setNewBaseLayer) { if (this.events.triggerEvent("preremovelayer", {layer: layer}) === false) { return; } 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} * * 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 - {} * 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} * 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 - {} */ setBaseLayer: function(newBaseLayer) { if (newBaseLayer != this.baseLayer) { // ensure newBaseLayer is already loaded if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) { // preserve center and scale when changing base layers var center = this.getCachedCenter(); var newResolution = OpenLayers.Util.getResolutionFromScale( this.getScale(), newBaseLayer.units ); // make the old base layer invisible if (this.baseLayer != null && !this.allOverlays) { this.baseLayer.setVisibility(false); } // set new baselayer this.baseLayer = newBaseLayer; if(!this.allOverlays || this.baseLayer.visibility) { this.baseLayer.setVisibility(true); // Layer may previously have been visible but not in range. // In this case we need to redraw it to make it visible. if (this.baseLayer.inRange === false) { this.baseLayer.redraw(); } } // recenter the map if (center != null) { // new zoom level derived from old scale var newZoom = this.getZoomForResolution( newResolution || this.resolution, true ); // zoom and force zoom change this.setCenter(center, 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 * Add the passed over control to the map. Optionally * position the control at the given pixel. * * Parameters: * control - {} * px - {} */ addControl: function (control, px) { this.controls.push(control); this.addControlToMap(control, px); }, /** * APIMethod: addControls * Add all of the passed over controls to the map. * You can pass over an optional second array * with pixel-objects to position the controls. * The indices of the two arrays should match and * you can add null as pixel for those controls * you want to be autopositioned. * * Parameters: * controls - {Array()} * pixels - {Array()} */ addControls: function (controls, pixels) { var pxs = (arguments.length === 1) ? [] : pixels; for (var i=0, len=controls.length; i} * px - {} */ 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 ); } } if(control.autoActivate) { control.activate(); } }, /** * APIMethod: getControl * * Parameters: * id - {String} ID of the control to return. * * Returns: * {} 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} 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 - {} * 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 - {} */ 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: * {} An 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 var newSize = this.getCurrentSize(); if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) { this.events.clearMouseCache(); 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} A new object with the dimensions * of the map div */ getCurrentSize: function() { var size = new OpenLayers.Size(this.div.clientWidth, this.div.clientHeight); if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) { size.w = this.div.offsetWidth; size.h = this.div.offsetHeight; } 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 - {} Default is this.getCenter() * resolution - {float} Default is this.getResolution() * * Returns: * {} A bounds based on resolution, center, and * current mapsize. */ calculateBounds: function(center, resolution) { var extent = null; if (center == null) { center = this.getCachedCenter(); } if (resolution == null) { resolution = this.getResolution(); } if ((center != null) && (resolution != null)) { var halfWDeg = (this.size.w * resolution) / 2; var halfHDeg = (this.size.h * resolution) / 2; extent = new OpenLayers.Bounds(center.lon - halfWDeg, center.lat - halfHDeg, center.lon + halfWDeg, center.lat + halfHDeg); } 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: * {} */ getCenter: function () { var center = null; var cachedCenter = this.getCachedCenter(); if (cachedCenter) { center = cachedCenter.clone(); } return center; }, /** * Method: getCachedCenter * * Returns: * {} */ getCachedCenter: function() { if (!this.center && this.size) { this.center = this.getLonLatFromViewPortPx({ x: this.size.w / 2, y: this.size.h / 2 }); } 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 }); if (options.dragging) { if (dx != 0 || dy != 0) { this.moveByPx(dx, dy); } } else { // getCenter var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter()); // adjust var newCenterPx = centerPx.add(dx, dy); if (this.dragging || !newCenterPx.equals(centerPx)) { var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx); if (options.animate) { this.panTo(newCenterLonLat); } else { this.moveTo(newCenterLonLat); if(this.dragging) { this.dragging = false; this.events.triggerEvent("moveend"); } } } } }, /** * 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 - {} */ panTo: function(lonlat) { if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) { var center = this.getCachedCenter(); // center will not change, don't do nothing if (lonlat.equals(center)) { return; } var from = this.getPixelFromLonLat(center); var to = this.getPixelFromLonLat(lonlat); var vector = { x: to.x - from.x, y: to.y - from.y }; var last = { x: 0, y: 0 }; this.panTween.start( { x: 0, y: 0 }, vector, this.panDuration, { callbacks: { eachStep: OpenLayers.Function.bind(function(px) { var x = px.x - last.x, y = px.y - last.y; this.moveByPx(x, y); last.x = Math.round(px.x); last.y = Math.round(px.y); }, this), done: OpenLayers.Function.bind(function(px) { this.moveTo(lonlat); this.dragging = false; this.events.triggerEvent("moveend"); }, this) } }); } else { this.setCenter(lonlat); } }, /** * APIMethod: setCenter * Set the map center (and optionally, the zoom level). * * Parameters: * lonlat - {|Array} The new center location. * If provided as array, the first value is the x coordinate, * and the 2nd value is the y coordinate. * zoom - {Integer} Optional zoom level. * 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) { if (this.panTween) { this.panTween.stop(); } if (this.zoomTween) { this.zoomTween.stop(); } this.moveTo(lonlat, zoom, { 'dragging': dragging, 'forceZoomChange': forceZoomChange }); }, /** * Method: moveByPx * Drag the map by pixels. * * Parameters: * dx - {Number} * dy - {Number} */ moveByPx: function(dx, dy) { var hw = this.size.w / 2; var hh = this.size.h / 2; var x = hw + dx; var y = hh + dy; var wrapDateLine = this.baseLayer.wrapDateLine; var xRestriction = 0; var yRestriction = 0; if (this.restrictedExtent) { xRestriction = hw; yRestriction = hh; // wrapping the date line makes no sense for restricted extents wrapDateLine = false; } dx = wrapDateLine || x <= this.maxPx.x - xRestriction && x >= this.minPx.x + xRestriction ? Math.round(dx) : 0; dy = y <= this.maxPx.y - yRestriction && y >= this.minPx.y + yRestriction ? Math.round(dy) : 0; if (dx || dy) { if (!this.dragging) { this.dragging = true; this.events.triggerEvent("movestart"); } this.center = null; if (dx) { this.layerContainerOriginPx.x -= dx; this.minPx.x -= dx; this.maxPx.x -= dx; } if (dy) { this.layerContainerOriginPx.y -= dy; this.minPx.y -= dy; this.maxPx.y -= dy; } this.applyTransform(); var layer, i, len; for (i=0, len=this.layers.length; i's maxExtent. */ adjustZoom: function(zoom) { if (this.baseLayer && this.baseLayer.wrapDateLine) { var resolution, resolutions = this.baseLayer.resolutions, maxResolution = this.getMaxExtent().getWidth() / this.size.w; if (this.getResolutionForZoom(zoom) > maxResolution) { if (this.fractionalZoom) { zoom = this.getZoomForResolution(maxResolution); } else { for (var i=zoom|0, ii=resolutions.length; i set to true, this will be the * first zoom level that shows no more than one world width in the current * map viewport. Components that rely on this value (e.g. zoom sliders) * should also listen to the map's "updatesize" event and call this method * in the "updatesize" listener. * * Returns: * {Number} Minimum zoom level that shows a map not wider than its * 's maxExtent. This is an Integer value, unless the map is * configured with set to true. */ getMinZoom: function() { return this.adjustZoom(0); }, /** * Method: moveTo * * Parameters: * lonlat - {} * zoom - {Integer} * options - {Object} */ moveTo: function(lonlat, zoom, options) { if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) { lonlat = new OpenLayers.LonLat(lonlat); } if (!options) { options = {}; } if (zoom != null) { zoom = parseFloat(zoom); if (!this.fractionalZoom) { zoom = Math.round(zoom); } } var requestedZoom = zoom; zoom = this.adjustZoom(zoom); if (zoom !== requestedZoom) { // zoom was adjusted, so keep old lonlat to avoid panning lonlat = this.getCenter(); } // dragging is false by default var dragging = options.dragging || this.dragging; // forceZoomChange is false by default var forceZoomChange = options.forceZoomChange; if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) { lonlat = this.maxExtent.getCenterLonLat(); this.center = lonlat.clone(); } if(this.restrictedExtent != null) { // In 3.0, decide if we want to change interpretation of maxExtent. if(lonlat == null) { lonlat = this.center; } 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) { dragging || this.events.triggerEvent("movestart", { zoomChanged: zoomChanged }); 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(); } var res = zoomChanged ? this.getResolutionForZoom(zoom) : this.getResolution(); // (re)set the layerContainerDiv's location if (zoomChanged || this.layerContainerOrigin == null) { this.layerContainerOrigin = this.getCachedCenter(); this.layerContainerOriginPx.x = 0; this.layerContainerOriginPx.y = 0; this.applyTransform(); var maxExtent = this.getMaxExtent({restricted: true}); var maxExtentCenter = maxExtent.getCenterLonLat(); var lonDelta = this.center.lon - maxExtentCenter.lon; var latDelta = maxExtentCenter.lat - this.center.lat; var extentWidth = Math.round(maxExtent.getWidth() / res); var extentHeight = Math.round(maxExtent.getHeight() / res); this.minPx = { x: (this.size.w - extentWidth) / 2 - lonDelta / res, y: (this.size.h - extentHeight) / 2 - latDelta / res }; this.maxPx = { x: this.minPx.x + Math.round(maxExtent.getWidth() / res), y: this.minPx.y + Math.round(maxExtent.getHeight() / res) }; } if (zoomChanged) { this.zoom = zoom; this.resolution = res; } var bounds = this.getExtent(); //send the move call to the baselayer and all the overlays if(this.baseLayer.visibility) { this.baseLayer.moveTo(bounds, zoomChanged, options.dragging); options.dragging || this.baseLayer.events.triggerEvent( "moveend", {zoomChanged: zoomChanged} ); } bounds = this.baseLayer.getExtent(); for (var i=this.layers.length-1; i>=0; --i) { var layer = this.layers[i]; if (layer !== this.baseLayer && !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, options.dragging); options.dragging || layer.events.triggerEvent( "moveend", {zoomChanged: zoomChanged} ); } } } this.events.triggerEvent("move"); dragging || this.events.triggerEvent("moveend"); if (zoomChanged) { //redraw popups for (var i=0, len=this.popups.length; i} */ centerLayerContainer: function (lonlat) { var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin); var newPx = this.getViewPortPxFromLonLat(lonlat); if ((originPx != null) && (newPx != null)) { var oldLeft = this.layerContainerOriginPx.x; var oldTop = this.layerContainerOriginPx.y; var newLeft = Math.round(originPx.x - newPx.x); var newTop = Math.round(originPx.y - newPx.y); this.applyTransform( (this.layerContainerOriginPx.x = newLeft), (this.layerContainerOriginPx.y = newTop)); var dx = oldLeft - newLeft; var dy = oldTop - newTop; this.minPx.x -= dx; this.maxPx.x -= dx; this.minPx.y -= dy; this.maxPx.y -= dy; } }, /** * 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 - {} * * 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(); var worldBounds = this.baseLayer.wrapDateLine && maxExtent; valid = maxExtent.containsLonLat(lonlat, {worldBounds: worldBounds}); } 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: * {} 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: * {} 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: * {} 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(); } else if(this.allOverlays === true && this.layers.length > 0) { // while adding the 1st layer to the map in allOverlays mode, // this.baseLayer is not set yet when we need the resolution // for calculateInRange. resolution = this.layers[0].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 - {} * 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 * * Parameters: * 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 * * Parameters: * 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. Zooming will be animated unless the map * is configured with {zoomMethod: null}. To zoom without animation, use * without a lonlat argument. * * Parameters: * zoom - {Integer} */ zoomTo: function(zoom, xy) { // non-API arguments: // xy - {} optional zoom origin var map = this; if (map.isValidZoomLevel(zoom)) { if (map.baseLayer.wrapDateLine) { zoom = map.adjustZoom(zoom); } if (map.zoomTween) { var currentRes = map.getResolution(), targetRes = map.getResolutionForZoom(zoom), start = {scale: 1}, end = {scale: currentRes / targetRes}; if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) { // update the end scale, and reuse the running zoomTween map.zoomTween.finish = { scale: map.zoomTween.finish.scale * end.scale }; } else { if (!xy) { var size = map.getSize(); xy = {x: size.w / 2, y: size.h / 2}; } map.zoomTween.start(start, end, map.zoomDuration, { minFrameRate: 50, // don't spend much time zooming callbacks: { eachStep: function(data) { var containerOrigin = map.layerContainerOriginPx, scale = data.scale, dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0, dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0; map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale); }, done: function(data) { map.applyTransform(); var resolution = map.getResolution() / data.scale, zoom = map.getZoomForResolution(resolution, true) map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true); } } }); } } else { var center = xy ? map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) : null; map.setCenter(center, zoom); } } }, /** * APIMethod: zoomIn * */ zoomIn: function() { this.zoomTo(this.getZoom() + 1); }, /** * APIMethod: zoomOut * */ zoomOut: function() { this.zoomTo(this.getZoom() - 1); }, /** * APIMethod: zoomToExtent * Zoom to the passed in bounds, recenter * * Parameters: * bounds - {|Array} If provided as an array, the array * should consist of four values (left, bottom, right, top). * 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. * */ zoomToExtent: function(bounds, closest) { if (!(bounds instanceof OpenLayers.Bounds)) { bounds = new OpenLayers.Bounds(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. // center = bounds.getCenterLonLat().wrapDateLine(maxExtent); } this.setCenter(center, this.getZoomForExtent(bounds, closest)); }, /** * APIMethod: zoomToMaxExtent * Zoom to the full extent and recenter. * * Parameters: * options - {Object} * * 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} * closest - {Boolean} Find the zoom level that most closely fits the * specified scale. Note that this may result in a zoom that does * not exactly contain the entire extent. * Default is false. * */ zoomToScale: function(scale, closest) { var res = OpenLayers.Util.getResolutionFromScale(scale, this.baseLayer.units); var halfWDeg = (this.size.w * res) / 2; var halfHDeg = (this.size.h * res) / 2; var center = this.getCachedCenter(); var extent = new OpenLayers.Bounds(center.lon - halfWDeg, center.lat - halfHDeg, center.lon + halfWDeg, center.lat + halfHDeg); this.zoomToExtent(extent, closest); }, /********************************************************/ /* */ /* Translation Functions */ /* */ /* The following functions translate between */ /* LonLat, LayerPx, and ViewPortPx */ /* */ /********************************************************/ // // TRANSLATION: LonLat <-> ViewPortPx // /** * Method: getLonLatFromViewPortPx * * Parameters: * viewPortPx - {|Object} An OpenLayers.Pixel or * an object with a 'x' * and 'y' properties. * * Returns: * {} An OpenLayers.LonLat which is the passed-in view * port , 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 - {} * * Returns: * {} An OpenLayers.Pixel which is the passed-in * , 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; }, /** * Method: getZoomTargetCenter * * Parameters: * xy - {} The zoom origin pixel location on the screen * resolution - {Float} The resolution we want to get the center for * * Returns: * {} The location of the map center after the * transformation described by the origin xy and the target resolution. */ getZoomTargetCenter: function (xy, resolution) { var lonlat = null, size = this.getSize(), deltaX = size.w/2 - xy.x, deltaY = xy.y - size.h/2, zoomPoint = this.getLonLatFromPixel(xy); if (zoomPoint) { lonlat = new OpenLayers.LonLat( zoomPoint.lon + deltaX * resolution, zoomPoint.lat + deltaY * resolution ); } return lonlat; }, // // CONVENIENCE TRANSLATION FUNCTIONS FOR API // /** * APIMethod: getLonLatFromPixel * * Parameters: * px - {|Object} An OpenLayers.Pixel or an object with * a 'x' and 'y' properties. * * Returns: * {} 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 - {} A map location. * * Returns: * {} An OpenLayers.Pixel corresponding to the * 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; }, /** * Method: getGeodesicPixelSize * * Parameters: * px - {} The pixel to get the geodesic length for. If * not provided, the center pixel of the map viewport will be used. * * Returns: * {} The geodesic size of the pixel in kilometers. */ getGeodesicPixelSize: function(px) { var lonlat = px ? this.getLonLatFromPixel(px) : ( this.getCachedCenter() || new OpenLayers.LonLat(0, 0)); var res = this.getResolution(); var left = lonlat.add(-res / 2, 0); var right = lonlat.add(res / 2, 0); var bottom = lonlat.add(0, -res / 2); var top = lonlat.add(0, res / 2); var dest = new OpenLayers.Projection("EPSG:4326"); var source = this.getProjectionObject() || dest; if(!source.equals(dest)) { left.transform(source, dest); right.transform(source, dest); bottom.transform(source, dest); top.transform(source, dest); } return new OpenLayers.Size( OpenLayers.Util.distVincenty(left, right), OpenLayers.Util.distVincenty(bottom, top) ); }, // // TRANSLATION: ViewPortPx <-> LayerPx // /** * APIMethod: getViewPortPxFromLayerPx * * Parameters: * layerPx - {} * * Returns: * {} Layer Pixel translated into ViewPort Pixel * coordinates */ getViewPortPxFromLayerPx:function(layerPx) { var viewPortPx = null; if (layerPx != null) { var dX = this.layerContainerOriginPx.x; var dY = this.layerContainerOriginPx.y; viewPortPx = layerPx.add(dX, dY); } return viewPortPx; }, /** * APIMethod: getLayerPxFromViewPortPx * * Parameters: * viewPortPx - {} * * Returns: * {} ViewPort Pixel translated into Layer Pixel * coordinates */ getLayerPxFromViewPortPx:function(viewPortPx) { var layerPx = null; if (viewPortPx != null) { var dX = -this.layerContainerOriginPx.x; var dY = -this.layerContainerOriginPx.y; layerPx = viewPortPx.add(dX, dY); if (isNaN(layerPx.x) || isNaN(layerPx.y)) { layerPx = null; } } return layerPx; }, // // TRANSLATION: LonLat <-> LayerPx // /** * Method: getLonLatFromLayerPx * * Parameters: * px - {} * * Returns: * {} */ getLonLatFromLayerPx: function (px) { //adjust for displacement of layerContainerDiv px = this.getViewPortPxFromLayerPx(px); return this.getLonLatFromViewPortPx(px); }, /** * APIMethod: getLayerPxFromLonLat * * Parameters: * lonlat - {} lonlat * * Returns: * {} An OpenLayers.Pixel which is the passed-in * , 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); }, /** * Method: applyTransform * Applies the given transform to the . This method has * a 2-stage fallback from translate3d/scale3d via translate/scale to plain * style.left/style.top, in which case no scaling is supported. * * Parameters: * x - {Number} x parameter for the translation. Defaults to the x value of * the map's * y - {Number} y parameter for the translation. Defaults to the y value of * the map's * scale - {Number} scale. Defaults to 1 if not provided. */ applyTransform: function(x, y, scale) { scale = scale || 1; var origin = this.layerContainerOriginPx, needTransform = scale !== 1; x = x || origin.x; y = y || origin.y; var style = this.layerContainerDiv.style, transform = this.applyTransform.transform, template = this.applyTransform.template; if (transform === undefined) { transform = OpenLayers.Util.vendorPrefix.style('transform'); this.applyTransform.transform = transform; if (transform) { // Try translate3d, but only if the viewPortDiv has a transform // defined in a stylesheet var computedStyle = OpenLayers.Element.getStyle(this.viewPortDiv, OpenLayers.Util.vendorPrefix.css('transform')); if (!computedStyle || computedStyle !== 'none') { template = ['translate3d(', ',0) ', 'scale3d(', ',1)']; style[transform] = [template[0], '0,0', template[1]].join(''); } // If no transform is defined in the stylesheet or translate3d // does not stick, use translate and scale if (!template || !~style[transform].indexOf(template[0])) { template = ['translate(', ') ', 'scale(', ')']; } this.applyTransform.template = template; } } // If we do 3d transforms, we always want to use them. If we do 2d // transforms, we only use them when we need to. if (transform !== null && (template[0] === 'translate3d(' || needTransform === true)) { // Our 2d transforms are combined with style.left and style.top, so // adjust x and y values and set the origin as left and top if (needTransform === true && template[0] === 'translate(') { x -= origin.x; y -= origin.y; style.left = origin.x + 'px'; style.top = origin.y + 'px'; } style[transform] = [ template[0], x, 'px,', y, 'px', template[1], template[2], scale, ',', scale, template[3] ].join(''); } else { style.left = x + 'px'; style.top = y + 'px'; // We previously might have had needTransform, so remove transform if (transform !== null) { style[transform] = ''; } } }, 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;