} The geometry to test. \n+ * div - {DOMElement|String} The element or id of an element in your page\n+ * that will contain the map. May be omitted if the option is\n+ * provided or if you intend to call the method later.\n+ * options - {Object} Optional object with properties to tag onto the map.\n *\n- * Returns:\n- * {Boolean} The supplied geometry is equivalent to this geometry.\n+ * Valid options (in addition to the listed API properties):\n+ * center - {|Array} The default initial center of the map.\n+ * If provided as array, the first value is the x coordinate,\n+ * and the 2nd value is the y coordinate.\n+ * Only specify if is provided.\n+ * Note that if an ArgParser/Permalink control is present,\n+ * and the querystring contains coordinates, center will be set\n+ * by that, and this option will be ignored.\n+ * zoom - {Number} The initial zoom level for the map. Only specify if\n+ * is provided.\n+ * Note that if an ArgParser/Permalink control is present,\n+ * and the querystring contains a zoom level, zoom will be set\n+ * by that, and this option will be ignored.\n+ * extent - {|Array} The initial extent of the map.\n+ * If provided as an array, the array should consist of\n+ * four values (left, bottom, right, top).\n+ * Only specify if and are not provided.\n+ * \n+ * Examples:\n+ * (code)\n+ * // create a map with default options in an element with the id \"map1\"\n+ * var map = new OpenLayers.Map(\"map1\");\n+ *\n+ * // create a map with non-default options in an element with id \"map2\"\n+ * var options = {\n+ * projection: \"EPSG:3857\",\n+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000),\n+ * center: new OpenLayers.LonLat(-12356463.476333, 5621521.4854095)\n+ * };\n+ * var map = new OpenLayers.Map(\"map2\", options);\n+ *\n+ * // map with non-default options - same as above but with a single argument,\n+ * // a restricted extent, and using arrays for bounds and center\n+ * var map = new OpenLayers.Map({\n+ * div: \"map_id\",\n+ * projection: \"EPSG:3857\",\n+ * maxExtent: [-18924313.432222, -15538711.094146, 18924313.432222, 15538711.094146],\n+ * restrictedExtent: [-13358338.893333, -9608371.5085962, 13358338.893333, 9608371.5085962],\n+ * center: [-12356463.476333, 5621521.4854095]\n+ * });\n+ *\n+ * // create a map without a reference to a container - call render later\n+ * var map = new OpenLayers.Map({\n+ * projection: \"EPSG:3857\",\n+ * maxExtent: new OpenLayers.Bounds(-200000, -200000, 200000, 200000)\n+ * });\n+ * (end)\n */\n- equals: function(geom) {\n- var equals = false;\n- if (geom != null) {\n- equals = ((this.x == geom.x && this.y == geom.y) ||\n- (isNaN(this.x) && isNaN(this.y) && isNaN(geom.x) && isNaN(geom.y)));\n+ initialize: function(div, options) {\n+\n+ // If only one argument is provided, check if it is an object.\n+ if (arguments.length === 1 && typeof div === \"object\") {\n+ options = div;\n+ div = options && options.div;\n+ }\n+\n+ // Simple-type defaults are set in class definition. \n+ // Now set complex-type defaults \n+ this.tileSize = new OpenLayers.Size(OpenLayers.Map.TILE_WIDTH,\n+ OpenLayers.Map.TILE_HEIGHT);\n+\n+ this.paddingForPopups = new OpenLayers.Bounds(15, 15, 15, 15);\n+\n+ this.theme = OpenLayers._getScriptLocation() +\n+ 'theme/default/style.css';\n+\n+ // backup original options\n+ this.options = OpenLayers.Util.extend({}, options);\n+\n+ // now override default options \n+ OpenLayers.Util.extend(this, options);\n+\n+ var projCode = this.projection instanceof OpenLayers.Projection ?\n+ this.projection.projCode : this.projection;\n+ OpenLayers.Util.applyDefaults(this, OpenLayers.Projection.defaults[projCode]);\n+\n+ // allow extents and center to be arrays\n+ if (this.maxExtent && !(this.maxExtent instanceof OpenLayers.Bounds)) {\n+ this.maxExtent = new OpenLayers.Bounds(this.maxExtent);\n+ }\n+ if (this.minExtent && !(this.minExtent instanceof OpenLayers.Bounds)) {\n+ this.minExtent = new OpenLayers.Bounds(this.minExtent);\n+ }\n+ if (this.restrictedExtent && !(this.restrictedExtent instanceof OpenLayers.Bounds)) {\n+ this.restrictedExtent = new OpenLayers.Bounds(this.restrictedExtent);\n+ }\n+ if (this.center && !(this.center instanceof OpenLayers.LonLat)) {\n+ this.center = new OpenLayers.LonLat(this.center);\n+ }\n+\n+ // initialize layers array\n+ this.layers = [];\n+\n+ this.id = OpenLayers.Util.createUniqueID(\"OpenLayers.Map_\");\n+\n+ this.div = OpenLayers.Util.getElement(div);\n+ if (!this.div) {\n+ this.div = document.createElement(\"div\");\n+ this.div.style.height = \"1px\";\n+ this.div.style.width = \"1px\";\n+ }\n+\n+ OpenLayers.Element.addClass(this.div, 'olMap');\n+\n+ // the viewPortDiv is the outermost div we modify\n+ var id = this.id + \"_OpenLayers_ViewPort\";\n+ this.viewPortDiv = OpenLayers.Util.createDiv(id, null, null, null,\n+ \"relative\", null,\n+ \"hidden\");\n+ this.viewPortDiv.style.width = \"100%\";\n+ this.viewPortDiv.style.height = \"100%\";\n+ this.viewPortDiv.className = \"olMapViewport\";\n+ this.div.appendChild(this.viewPortDiv);\n+\n+ this.events = new OpenLayers.Events(\n+ this, this.viewPortDiv, null, this.fallThrough, {\n+ includeXY: true\n+ }\n+ );\n+\n+ if (OpenLayers.TileManager && this.tileManager !== null) {\n+ if (!(this.tileManager instanceof OpenLayers.TileManager)) {\n+ this.tileManager = new OpenLayers.TileManager(this.tileManager);\n+ }\n+ this.tileManager.addMap(this);\n+ }\n+\n+ // the layerContainerDiv is the one that holds all the layers\n+ id = this.id + \"_OpenLayers_Container\";\n+ this.layerContainerDiv = OpenLayers.Util.createDiv(id);\n+ this.layerContainerDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] - 1;\n+ this.layerContainerOriginPx = {\n+ x: 0,\n+ y: 0\n+ };\n+ this.applyTransform();\n+\n+ this.viewPortDiv.appendChild(this.layerContainerDiv);\n+\n+ this.updateSize();\n+ if (this.eventListeners instanceof Object) {\n+ this.events.on(this.eventListeners);\n+ }\n+\n+ if (this.autoUpdateSize === true) {\n+ // updateSize on catching the window's resize\n+ // Note that this is ok, as updateSize() does nothing if the \n+ // map's size has not actually changed.\n+ this.updateSizeDestroy = OpenLayers.Function.bind(this.updateSize,\n+ this);\n+ OpenLayers.Event.observe(window, 'resize',\n+ this.updateSizeDestroy);\n+ }\n+\n+ // only append link stylesheet if the theme property is set\n+ if (this.theme) {\n+ // check existing links for equivalent url\n+ var addNode = true;\n+ var nodes = document.getElementsByTagName('link');\n+ for (var i = 0, len = nodes.length; i < len; ++i) {\n+ if (OpenLayers.Util.isEquivalentUrl(nodes.item(i).href,\n+ this.theme)) {\n+ addNode = false;\n+ break;\n+ }\n+ }\n+ // only add a new node if one with an equivalent url hasn't already\n+ // been added\n+ if (addNode) {\n+ var cssNode = document.createElement('link');\n+ cssNode.setAttribute('rel', 'stylesheet');\n+ cssNode.setAttribute('type', 'text/css');\n+ cssNode.setAttribute('href', this.theme);\n+ document.getElementsByTagName('head')[0].appendChild(cssNode);\n+ }\n+ }\n+\n+ if (this.controls == null) { // default controls\n+ this.controls = [];\n+ if (OpenLayers.Control != null) { // running full or lite?\n+ // Navigation or TouchNavigation depending on what is in build\n+ if (OpenLayers.Control.Navigation) {\n+ this.controls.push(new OpenLayers.Control.Navigation());\n+ } else if (OpenLayers.Control.TouchNavigation) {\n+ this.controls.push(new OpenLayers.Control.TouchNavigation());\n+ }\n+ if (OpenLayers.Control.Zoom) {\n+ this.controls.push(new OpenLayers.Control.Zoom());\n+ } else if (OpenLayers.Control.PanZoom) {\n+ this.controls.push(new OpenLayers.Control.PanZoom());\n+ }\n+\n+ if (OpenLayers.Control.ArgParser) {\n+ this.controls.push(new OpenLayers.Control.ArgParser());\n+ }\n+ if (OpenLayers.Control.Attribution) {\n+ this.controls.push(new OpenLayers.Control.Attribution());\n+ }\n+ }\n+ }\n+\n+ for (var i = 0, len = this.controls.length; i < len; i++) {\n+ this.addControlToMap(this.controls[i]);\n+ }\n+\n+ this.popups = [];\n+\n+ this.unloadDestroy = OpenLayers.Function.bind(this.destroy, this);\n+\n+\n+ // always call map.destroy()\n+ OpenLayers.Event.observe(window, 'unload', this.unloadDestroy);\n+\n+ // add any initial layers\n+ if (options && options.layers) {\n+ /** \n+ * If you have set options.center, the map center property will be\n+ * set at this point. However, since setCenter has not been called,\n+ * addLayers gets confused. So we delete the map center in this \n+ * case. Because the check below uses options.center, it will\n+ * be properly set below.\n+ */\n+ delete this.center;\n+ delete this.zoom;\n+ this.addLayers(options.layers);\n+ // set center (and optionally zoom)\n+ if (options.center && !this.getCenter()) {\n+ // zoom can be undefined here\n+ this.setCenter(options.center, options.zoom);\n+ }\n+ }\n+\n+ if (this.panMethod) {\n+ this.panTween = new OpenLayers.Tween(this.panMethod);\n+ }\n+ if (this.zoomMethod && this.applyTransform.transform) {\n+ this.zoomTween = new OpenLayers.Tween(this.zoomMethod);\n }\n- return equals;\n },\n \n- /**\n- * Method: toShortString\n+ /** \n+ * APIMethod: getViewport\n+ * Get the DOMElement representing the view port.\n *\n * Returns:\n- * {String} Shortened String representation of Point object. \n- * (ex. \"5, 42\")\n+ * {DOMElement}\n */\n- toShortString: function() {\n- return (this.x + \", \" + this.y);\n+ getViewport: function() {\n+ return this.viewPortDiv;\n },\n \n /**\n- * APIMethod: move\n- * Moves a geometry by the given displacement along positive x and y axes.\n- * This modifies the position of the geometry and clears the cached\n- * bounds.\n- *\n+ * APIMethod: render\n+ * Render the map to a specified container.\n+ * \n * Parameters:\n- * x - {Float} Distance to move geometry in positive x direction. \n- * y - {Float} Distance to move geometry in positive y direction.\n+ * div - {String|DOMElement} The container that the map should be rendered\n+ * to. If different than the current container, the map viewport\n+ * will be moved from the current to the new container.\n */\n- move: function(x, y) {\n- this.x = this.x + x;\n- this.y = this.y + y;\n- this.clearBounds();\n+ render: function(div) {\n+ this.div = OpenLayers.Util.getElement(div);\n+ OpenLayers.Element.addClass(this.div, 'olMap');\n+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);\n+ this.div.appendChild(this.viewPortDiv);\n+ this.updateSize();\n },\n \n /**\n- * APIMethod: rotate\n- * Rotate a point around another.\n+ * Method: unloadDestroy\n+ * Function that is called to destroy the map on page unload. stored here\n+ * so that if map is manually destroyed, we can unregister this.\n+ */\n+ unloadDestroy: null,\n+\n+ /**\n+ * Method: updateSizeDestroy\n+ * When the map is destroyed, we need to stop listening to updateSize\n+ * events: this method stores the function we need to unregister in \n+ * non-IE browsers.\n+ */\n+ updateSizeDestroy: null,\n+\n+ /**\n+ * APIMethod: destroy\n+ * Destroy this map.\n+ * Note that if you are using an application which removes a container\n+ * of the map from the DOM, you need to ensure that you destroy the\n+ * map *before* this happens; otherwise, the page unload handler\n+ * will fail because the DOM elements that map.destroy() wants\n+ * to clean up will be gone. (See \n+ * http://trac.osgeo.org/openlayers/ticket/2277 for more information).\n+ * This will apply to GeoExt and also to other applications which\n+ * modify the DOM of the container of the OpenLayers Map.\n+ */\n+ destroy: function() {\n+ // if unloadDestroy is null, we've already been destroyed\n+ if (!this.unloadDestroy) {\n+ return false;\n+ }\n+\n+ // make sure panning doesn't continue after destruction\n+ if (this.panTween) {\n+ this.panTween.stop();\n+ this.panTween = null;\n+ }\n+ // make sure zooming doesn't continue after destruction\n+ if (this.zoomTween) {\n+ this.zoomTween.stop();\n+ this.zoomTween = null;\n+ }\n+\n+ // map has been destroyed. dont do it again!\n+ OpenLayers.Event.stopObserving(window, 'unload', this.unloadDestroy);\n+ this.unloadDestroy = null;\n+\n+ if (this.updateSizeDestroy) {\n+ OpenLayers.Event.stopObserving(window, 'resize',\n+ this.updateSizeDestroy);\n+ }\n+\n+ this.paddingForPopups = null;\n+\n+ if (this.controls != null) {\n+ for (var i = this.controls.length - 1; i >= 0; --i) {\n+ this.controls[i].destroy();\n+ }\n+ this.controls = null;\n+ }\n+ if (this.layers != null) {\n+ for (var i = this.layers.length - 1; i >= 0; --i) {\n+ //pass 'false' to destroy so that map wont try to set a new \n+ // baselayer after each baselayer is removed\n+ this.layers[i].destroy(false);\n+ }\n+ this.layers = null;\n+ }\n+ if (this.viewPortDiv && this.viewPortDiv.parentNode) {\n+ this.viewPortDiv.parentNode.removeChild(this.viewPortDiv);\n+ }\n+ this.viewPortDiv = null;\n+\n+ if (this.tileManager) {\n+ this.tileManager.removeMap(this);\n+ this.tileManager = null;\n+ }\n+\n+ if (this.eventListeners) {\n+ this.events.un(this.eventListeners);\n+ this.eventListeners = null;\n+ }\n+ this.events.destroy();\n+ this.events = null;\n+\n+ this.options = null;\n+ },\n+\n+ /**\n+ * APIMethod: setOptions\n+ * Change the map options\n *\n * Parameters:\n- * angle - {Float} Rotation angle in degrees (measured counterclockwise\n- * from the positive x-axis)\n- * origin - {} Center point for the rotation\n+ * options - {Object} Hashtable of options to tag to the map\n */\n- rotate: function(angle, origin) {\n- angle *= Math.PI / 180;\n- var radius = this.distanceTo(origin);\n- var theta = angle + Math.atan2(this.y - origin.y, this.x - origin.x);\n- this.x = origin.x + (radius * Math.cos(theta));\n- this.y = origin.y + (radius * Math.sin(theta));\n- this.clearBounds();\n+ setOptions: function(options) {\n+ var updatePxExtent = this.minPx &&\n+ options.restrictedExtent != this.restrictedExtent;\n+ OpenLayers.Util.extend(this, options);\n+ // force recalculation of minPx and maxPx\n+ updatePxExtent && this.moveTo(this.getCachedCenter(), this.zoom, {\n+ forceZoomChange: true\n+ });\n },\n \n /**\n- * APIMethod: getCentroid\n+ * APIMethod: getTileSize\n+ * Get the tile size for the map\n *\n * Returns:\n- * {} The centroid of the collection\n+ * {}\n */\n- getCentroid: function() {\n- return new OpenLayers.Geometry.Point(this.x, this.y);\n+ getTileSize: function() {\n+ return this.tileSize;\n },\n \n+\n /**\n- * APIMethod: resize\n- * Resize a point relative to some origin. For points, this has the effect\n- * of scaling a vector (from the origin to the point). This method is\n- * more useful on geometry collection subclasses.\n+ * APIMethod: getBy\n+ * Get a list of objects given a property and a match item.\n *\n * Parameters:\n- * scale - {Float} Ratio of the new distance from the origin to the old\n- * distance from the origin. A scale of 2 doubles the\n- * distance between the point and origin.\n- * origin - {} Point of origin for resizing\n- * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.\n- * \n+ * array - {String} A property on the map whose value is an array.\n+ * property - {String} A property on each item of the given array.\n+ * match - {String | Object} A string to match. Can also be a regular\n+ * expression literal or object. In addition, it can be any object\n+ * with a method named test. For reqular expressions or other, if\n+ * match.test(map[array][i][property]) evaluates to true, the item will\n+ * be included in the array returned. If no items are found, an empty\n+ * array is returned.\n+ *\n * Returns:\n- * {} - The current geometry. \n+ * {Array} An array of items where the given property matches the given\n+ * criteria.\n */\n- resize: function(scale, origin, ratio) {\n- ratio = (ratio == undefined) ? 1 : ratio;\n- this.x = origin.x + (scale * ratio * (this.x - origin.x));\n- this.y = origin.y + (scale * (this.y - origin.y));\n- this.clearBounds();\n- return this;\n+ getBy: function(array, property, match) {\n+ var test = (typeof match.test == \"function\");\n+ var found = OpenLayers.Array.filter(this[array], function(item) {\n+ return item[property] == match || (test && match.test(item[property]));\n+ });\n+ return found;\n },\n \n /**\n- * APIMethod: intersects\n- * Determine if the input geometry intersects this one.\n+ * APIMethod: getLayersBy\n+ * Get a list of layers with properties matching the given criteria.\n *\n * Parameters:\n- * geometry - {} Any type of geometry.\n+ * property - {String} A layer property to be matched.\n+ * match - {String | Object} A string to match. Can also be a regular\n+ * expression literal or object. In addition, it can be any object\n+ * with a method named test. For reqular expressions or other, if\n+ * match.test(layer[property]) evaluates to true, the layer will be\n+ * included in the array returned. If no layers are found, an empty\n+ * array is returned.\n *\n * Returns:\n- * {Boolean} The input geometry intersects this one.\n+ * {Array()} A list of layers matching the given criteria.\n+ * An empty array is returned if no matches are found.\n */\n- intersects: function(geometry) {\n- var intersect = false;\n- if (geometry.CLASS_NAME == \"OpenLayers.Geometry.Point\") {\n- intersect = this.equals(geometry);\n- } else {\n- intersect = geometry.intersects(this);\n- }\n- return intersect;\n+ getLayersBy: function(property, match) {\n+ return this.getBy(\"layers\", property, match);\n },\n \n /**\n- * APIMethod: transform\n- * Translate the x,y properties of the point from source to dest.\n- * \n+ * APIMethod: getLayersByName\n+ * Get a list of layers with names matching the given name.\n+ *\n * Parameters:\n- * source - {} \n- * dest - {}\n- * \n+ * match - {String | Object} A layer name. The name can also be a regular\n+ * expression literal or object. In addition, it can be any object\n+ * with a method named test. For reqular expressions or other, if\n+ * name.test(layer.name) evaluates to true, the layer will be included\n+ * in the list of layers returned. If no layers are found, an empty\n+ * array is returned.\n+ *\n * Returns:\n- * {} \n+ * {Array()} A list of layers matching the given name.\n+ * An empty array is returned if no matches are found.\n */\n- transform: function(source, dest) {\n- if ((source && dest)) {\n- OpenLayers.Projection.transform(\n- this, source, dest);\n- this.bounds = null;\n- }\n- return this;\n+ getLayersByName: function(match) {\n+ return this.getLayersBy(\"name\", match);\n },\n \n /**\n- * APIMethod: getVertices\n- * Return a list of all points in this geometry.\n+ * APIMethod: getLayersByClass\n+ * Get a list of layers of a given class (CLASS_NAME).\n *\n * Parameters:\n- * nodes - {Boolean} For lines, only return vertices that are\n- * endpoints. If false, for lines, only vertices that are not\n- * endpoints will be returned. If not provided, all vertices will\n- * be returned.\n+ * match - {String | Object} A layer class name. The match can also be a\n+ * regular expression literal or object. In addition, it can be any\n+ * object with a method named test. For reqular expressions or other,\n+ * if type.test(layer.CLASS_NAME) evaluates to true, the layer will\n+ * be included in the list of layers returned. If no layers are\n+ * found, an empty array is returned.\n *\n * Returns:\n- * {Array} A list of all vertices in the geometry.\n+ * {Array()} A list of layers matching the given class.\n+ * An empty array is returned if no matches are found.\n */\n- getVertices: function(nodes) {\n- return [this];\n+ getLayersByClass: function(match) {\n+ return this.getLayersBy(\"CLASS_NAME\", match);\n },\n \n- CLASS_NAME: \"OpenLayers.Geometry.Point\"\n-});\n-/* ======================================================================\n- OpenLayers/Geometry/Collection.js\n- ====================================================================== */\n-\n-/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for\n- * full list of contributors). Published under the 2-clause BSD license.\n- * See license.txt in the OpenLayers distribution or repository for the\n- * full text of the license. */\n-\n-/**\n- * @requires OpenLayers/Geometry.js\n- */\n-\n-/**\n- * Class: OpenLayers.Geometry.Collection\n- * A Collection is exactly what it sounds like: A collection of different \n- * Geometries. These are stored in the local parameter (which\n- * can be passed as a parameter to the constructor). \n- * \n- * As new geometries are added to the collection, they are NOT cloned. \n- * When removing geometries, they need to be specified by reference (ie you \n- * have to pass in the *exact* geometry to be removed).\n- * \n- * The and functions here merely iterate through\n- * the components, summing their respective areas and lengths.\n- *\n- * Create a new instance with the constructor.\n- *\n- * Inherits from:\n- * - \n- */\n-OpenLayers.Geometry.Collection = OpenLayers.Class(OpenLayers.Geometry, {\n-\n /**\n- * APIProperty: components\n- * {Array()} The component parts of this geometry\n+ * APIMethod: getControlsBy\n+ * Get a list of controls with properties matching the given criteria.\n+ *\n+ * Parameters:\n+ * property - {String} A control property to be matched.\n+ * match - {String | Object} A string to match. Can also be a regular\n+ * expression literal or object. In addition, it can be any object\n+ * with a method named test. For reqular expressions or other, if\n+ * match.test(layer[property]) evaluates to true, the layer will be\n+ * included in the array returned. If no layers are found, an empty\n+ * array is returned.\n+ *\n+ * Returns:\n+ * {Array()} A list of controls matching the given\n+ * criteria. An empty array is returned if no matches are found.\n */\n- components: null,\n+ getControlsBy: function(property, match) {\n+ return this.getBy(\"controls\", property, match);\n+ },\n \n /**\n- * Property: componentTypes\n- * {Array(String)} An array of class names representing the types of\n- * components that the collection can include. A null value means the\n- * component types are not restricted.\n+ * APIMethod: getControlsByClass\n+ * Get a list of controls of a given class (CLASS_NAME).\n+ *\n+ * Parameters:\n+ * match - {String | Object} A control class name. The match can also be a\n+ * regular expression literal or object. In addition, it can be any\n+ * object with a method named test. For reqular expressions or other,\n+ * if type.test(control.CLASS_NAME) evaluates to true, the control will\n+ * be included in the list of controls returned. If no controls are\n+ * found, an empty array is returned.\n+ *\n+ * Returns:\n+ * {Array()} A list of controls matching the given class.\n+ * An empty array is returned if no matches are found.\n */\n- componentTypes: null,\n+ getControlsByClass: function(match) {\n+ return this.getControlsBy(\"CLASS_NAME\", match);\n+ },\n+\n+ /********************************************************/\n+ /* */\n+ /* Layer Functions */\n+ /* */\n+ /* The following functions deal with adding and */\n+ /* removing Layers to and from the Map */\n+ /* */\n+ /********************************************************/\n \n /**\n- * Constructor: OpenLayers.Geometry.Collection\n- * Creates a Geometry Collection -- a list of geoms.\n+ * APIMethod: getLayer\n+ * Get a layer based on its id\n *\n- * Parameters: \n- * components - {Array()} Optional array of geometries\n+ * Parameters:\n+ * id - {String} A layer id\n *\n+ * Returns:\n+ * {} The Layer with the corresponding id from the map's \n+ * layer collection, or null if not found.\n */\n- initialize: function(components) {\n- OpenLayers.Geometry.prototype.initialize.apply(this, arguments);\n- this.components = [];\n- if (components != null) {\n- this.addComponents(components);\n+ getLayer: function(id) {\n+ var foundLayer = null;\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ var layer = this.layers[i];\n+ if (layer.id == id) {\n+ foundLayer = layer;\n+ break;\n+ }\n }\n+ return foundLayer;\n },\n \n /**\n- * APIMethod: destroy\n- * Destroy this geometry.\n+ * Method: setLayerZIndex\n+ * \n+ * Parameters:\n+ * layer - {} \n+ * zIdx - {int} \n */\n- destroy: function() {\n- this.components.length = 0;\n- this.components = null;\n- OpenLayers.Geometry.prototype.destroy.apply(this, arguments);\n+ setLayerZIndex: function(layer, zIdx) {\n+ layer.setZIndex(\n+ this.Z_INDEX_BASE[layer.isBaseLayer ? 'BaseLayer' : 'Overlay'] +\n+ zIdx * 5);\n },\n \n /**\n- * APIMethod: clone\n- * Clone this geometry.\n- *\n- * Returns:\n- * {} An exact clone of this collection\n+ * Method: resetLayersZIndex\n+ * Reset each layer's z-index based on layer's array index\n */\n- clone: function() {\n- var geometry = eval(\"new \" + this.CLASS_NAME + \"()\");\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- geometry.addComponent(this.components[i].clone());\n+ resetLayersZIndex: function() {\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ var layer = this.layers[i];\n+ this.setLayerZIndex(layer, i);\n }\n-\n- // catch any randomly tagged-on properties\n- OpenLayers.Util.applyDefaults(geometry, this);\n-\n- return geometry;\n },\n \n /**\n- * Method: getComponentsString\n- * Get a string representing the components for this collection\n- * \n+ * APIMethod: addLayer\n+ *\n+ * Parameters:\n+ * layer - {} \n+ *\n * Returns:\n- * {String} A string representation of the components of this geometry\n+ * {Boolean} True if the layer has been added to the map.\n */\n- getComponentsString: function() {\n- var strings = [];\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- strings.push(this.components[i].toShortString());\n+ addLayer: function(layer) {\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ if (this.layers[i] == layer) {\n+ return false;\n+ }\n+ }\n+ if (this.events.triggerEvent(\"preaddlayer\", {\n+ layer: layer\n+ }) === false) {\n+ return false;\n+ }\n+ if (this.allOverlays) {\n+ layer.isBaseLayer = false;\n }\n- return strings.join(\",\");\n- },\n \n- /**\n- * APIMethod: calculateBounds\n- * Recalculate the bounds by iterating through the components and \n- * calling calling extendBounds() on each item.\n- */\n- calculateBounds: function() {\n- this.bounds = null;\n- var bounds = new OpenLayers.Bounds();\n- var components = this.components;\n- if (components) {\n- for (var i = 0, len = components.length; i < len; i++) {\n- bounds.extend(components[i].getBounds());\n- }\n+ layer.div.className = \"olLayerDiv\";\n+ layer.div.style.overflow = \"\";\n+ this.setLayerZIndex(layer, this.layers.length);\n+\n+ if (layer.isFixed) {\n+ this.viewPortDiv.appendChild(layer.div);\n+ } else {\n+ this.layerContainerDiv.appendChild(layer.div);\n }\n- // to preserve old behavior, we only set bounds if non-null\n- // in the future, we could add bounds.isEmpty()\n- if (bounds.left != null && bounds.bottom != null &&\n- bounds.right != null && bounds.top != null) {\n- this.setBounds(bounds);\n+ this.layers.push(layer);\n+ layer.setMap(this);\n+\n+ if (layer.isBaseLayer || (this.allOverlays && !this.baseLayer)) {\n+ if (this.baseLayer == null) {\n+ // set the first baselaye we add as the baselayer\n+ this.setBaseLayer(layer);\n+ } else {\n+ layer.setVisibility(false);\n+ }\n+ } else {\n+ layer.redraw();\n }\n+\n+ this.events.triggerEvent(\"addlayer\", {\n+ layer: layer\n+ });\n+ layer.events.triggerEvent(\"added\", {\n+ map: this,\n+ layer: layer\n+ });\n+ layer.afterAdd();\n+\n+ return true;\n },\n \n /**\n- * APIMethod: addComponents\n- * Add components to this geometry.\n+ * APIMethod: addLayers \n *\n * Parameters:\n- * components - {Array()} An array of geometries to add\n+ * layers - {Array()} \n */\n- addComponents: function(components) {\n- if (!(OpenLayers.Util.isArray(components))) {\n- components = [components];\n- }\n- for (var i = 0, len = components.length; i < len; i++) {\n- this.addComponent(components[i]);\n+ addLayers: function(layers) {\n+ for (var i = 0, len = layers.length; i < len; i++) {\n+ this.addLayer(layers[i]);\n }\n },\n \n- /**\n- * Method: addComponent\n- * Add a new component (geometry) to the collection. If this.componentTypes\n- * is set, then the component class name must be in the componentTypes array.\n- *\n- * The bounds cache is reset.\n+ /** \n+ * APIMethod: removeLayer\n+ * Removes a layer from the map by removing its visual element (the \n+ * layer.div property), then removing it from the map's internal list \n+ * of layers, setting the layer's map property to null. \n+ * \n+ * a \"removelayer\" event is triggered.\n+ * \n+ * very worthy of mention is that simply removing a layer from a map\n+ * will not cause the removal of any popups which may have been created\n+ * by the layer. this is due to the fact that it was decided at some\n+ * point that popups would not belong to layers. thus there is no way \n+ * for us to know here to which layer the popup belongs.\n+ * \n+ * A simple solution to this is simply to call destroy() on the layer.\n+ * the default OpenLayers.Layer class's destroy() function\n+ * automatically takes care to remove itself from whatever map it has\n+ * been attached to. \n+ * \n+ * The correct solution is for the layer itself to register an \n+ * event-handler on \"removelayer\" and when it is called, if it \n+ * recognizes itself as the layer being removed, then it cycles through\n+ * its own personal list of popups, removing them from the map.\n * \n * Parameters:\n- * component - {} A geometry to add\n- * index - {int} Optional index into the array to insert the component\n- *\n- * Returns:\n- * {Boolean} The component geometry was successfully added\n+ * layer - {} \n+ * setNewBaseLayer - {Boolean} Default is true\n */\n- addComponent: function(component, index) {\n- var added = false;\n- if (component) {\n- if (this.componentTypes == null ||\n- (OpenLayers.Util.indexOf(this.componentTypes,\n- component.CLASS_NAME) > -1)) {\n+ removeLayer: function(layer, setNewBaseLayer) {\n+ if (this.events.triggerEvent(\"preremovelayer\", {\n+ layer: layer\n+ }) === false) {\n+ return;\n+ }\n+ if (setNewBaseLayer == null) {\n+ setNewBaseLayer = true;\n+ }\n \n- if (index != null && (index < this.components.length)) {\n- var components1 = this.components.slice(0, index);\n- var components2 = this.components.slice(index,\n- this.components.length);\n- components1.push(component);\n- this.components = components1.concat(components2);\n- } else {\n- this.components.push(component);\n+ if (layer.isFixed) {\n+ this.viewPortDiv.removeChild(layer.div);\n+ } else {\n+ this.layerContainerDiv.removeChild(layer.div);\n+ }\n+ OpenLayers.Util.removeItem(this.layers, layer);\n+ layer.removeMap(this);\n+ layer.map = null;\n+\n+ // if we removed the base layer, need to set a new one\n+ if (this.baseLayer == layer) {\n+ this.baseLayer = null;\n+ if (setNewBaseLayer) {\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ var iLayer = this.layers[i];\n+ if (iLayer.isBaseLayer || this.allOverlays) {\n+ this.setBaseLayer(iLayer);\n+ break;\n+ }\n }\n- component.parent = this;\n- this.clearBounds();\n- added = true;\n }\n }\n- return added;\n+\n+ this.resetLayersZIndex();\n+\n+ this.events.triggerEvent(\"removelayer\", {\n+ layer: layer\n+ });\n+ layer.events.triggerEvent(\"removed\", {\n+ map: this,\n+ layer: layer\n+ });\n },\n \n /**\n- * APIMethod: removeComponents\n- * Remove components from this geometry.\n+ * APIMethod: getNumLayers\n+ * \n+ * Returns:\n+ * {Int} The number of layers attached to the map.\n+ */\n+ getNumLayers: function() {\n+ return this.layers.length;\n+ },\n+\n+ /** \n+ * APIMethod: getLayerIndex\n *\n * Parameters:\n- * components - {Array()} The components to be removed\n+ * layer - {}\n *\n- * Returns: \n- * {Boolean} A component was removed.\n+ * Returns:\n+ * {Integer} The current (zero-based) index of the given layer in the map's\n+ * layer stack. Returns -1 if the layer isn't on the map.\n */\n- removeComponents: function(components) {\n- var removed = false;\n+ getLayerIndex: function(layer) {\n+ return OpenLayers.Util.indexOf(this.layers, layer);\n+ },\n \n- if (!(OpenLayers.Util.isArray(components))) {\n- components = [components];\n+ /** \n+ * APIMethod: setLayerIndex\n+ * Move the given layer to the specified (zero-based) index in the layer\n+ * list, changing its z-index in the map display. Use\n+ * map.getLayerIndex() to find out the current index of a layer. Note\n+ * that this cannot (or at least should not) be effectively used to\n+ * raise base layers above overlays.\n+ *\n+ * Parameters:\n+ * layer - {} \n+ * idx - {int} \n+ */\n+ setLayerIndex: function(layer, idx) {\n+ var base = this.getLayerIndex(layer);\n+ if (idx < 0) {\n+ idx = 0;\n+ } else if (idx > this.layers.length) {\n+ idx = this.layers.length;\n }\n- for (var i = components.length - 1; i >= 0; --i) {\n- removed = this.removeComponent(components[i]) || removed;\n+ if (base != idx) {\n+ this.layers.splice(base, 1);\n+ this.layers.splice(idx, 0, layer);\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ this.setLayerZIndex(this.layers[i], i);\n+ }\n+ this.events.triggerEvent(\"changelayer\", {\n+ layer: layer,\n+ property: \"order\"\n+ });\n+ if (this.allOverlays) {\n+ if (idx === 0) {\n+ this.setBaseLayer(layer);\n+ } else if (this.baseLayer !== this.layers[0]) {\n+ this.setBaseLayer(this.layers[0]);\n+ }\n+ }\n }\n- return removed;\n },\n \n- /**\n- * Method: removeComponent\n- * Remove a component from this geometry.\n+ /** \n+ * APIMethod: raiseLayer\n+ * Change the index of the given layer by delta. If delta is positive, \n+ * the layer is moved up the map's layer stack; if delta is negative,\n+ * the layer is moved down. Again, note that this cannot (or at least\n+ * should not) be effectively used to raise base layers above overlays.\n *\n+ * Paremeters:\n+ * layer - {} \n+ * delta - {int} \n+ */\n+ raiseLayer: function(layer, delta) {\n+ var idx = this.getLayerIndex(layer) + delta;\n+ this.setLayerIndex(layer, idx);\n+ },\n+\n+ /** \n+ * APIMethod: setBaseLayer\n+ * Allows user to specify one of the currently-loaded layers as the Map's\n+ * new base layer.\n+ * \n * Parameters:\n- * component - {} \n- *\n- * Returns: \n- * {Boolean} The component was removed.\n+ * newBaseLayer - {}\n */\n- removeComponent: function(component) {\n+ setBaseLayer: function(newBaseLayer) {\n \n- OpenLayers.Util.removeItem(this.components, component);\n+ if (newBaseLayer != this.baseLayer) {\n \n- // clearBounds() so that it gets recalculated on the next call\n- // to this.getBounds();\n- this.clearBounds();\n- return true;\n+ // ensure newBaseLayer is already loaded\n+ if (OpenLayers.Util.indexOf(this.layers, newBaseLayer) != -1) {\n+\n+ // preserve center and scale when changing base layers\n+ var center = this.getCachedCenter();\n+ var newResolution = OpenLayers.Util.getResolutionFromScale(\n+ this.getScale(), newBaseLayer.units\n+ );\n+\n+ // make the old base layer invisible \n+ if (this.baseLayer != null && !this.allOverlays) {\n+ this.baseLayer.setVisibility(false);\n+ }\n+\n+ // set new baselayer\n+ this.baseLayer = newBaseLayer;\n+\n+ if (!this.allOverlays || this.baseLayer.visibility) {\n+ this.baseLayer.setVisibility(true);\n+ // Layer may previously have been visible but not in range.\n+ // In this case we need to redraw it to make it visible.\n+ if (this.baseLayer.inRange === false) {\n+ this.baseLayer.redraw();\n+ }\n+ }\n+\n+ // recenter the map\n+ if (center != null) {\n+ // new zoom level derived from old scale\n+ var newZoom = this.getZoomForResolution(\n+ newResolution || this.resolution, true\n+ );\n+ // zoom and force zoom change\n+ this.setCenter(center, newZoom, false, true);\n+ }\n+\n+ this.events.triggerEvent(\"changebaselayer\", {\n+ layer: this.baseLayer\n+ });\n+ }\n+ }\n },\n \n+\n+ /********************************************************/\n+ /* */\n+ /* Control Functions */\n+ /* */\n+ /* The following functions deal with adding and */\n+ /* removing Controls to and from the Map */\n+ /* */\n+ /********************************************************/\n+\n /**\n- * APIMethod: getLength\n- * Calculate the length of this geometry\n- *\n- * Returns:\n- * {Float} The length of the geometry\n+ * APIMethod: addControl\n+ * Add the passed over control to the map. Optionally \n+ * position the control at the given pixel.\n+ * \n+ * Parameters:\n+ * control - {}\n+ * px - {}\n */\n- getLength: function() {\n- var length = 0.0;\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- length += this.components[i].getLength();\n- }\n- return length;\n+ addControl: function(control, px) {\n+ this.controls.push(control);\n+ this.addControlToMap(control, px);\n },\n \n /**\n- * APIMethod: getArea\n- * Calculate the area of this geometry. Note how this function is overridden\n- * in .\n- *\n- * Returns:\n- * {Float} The area of the collection by summing its parts\n+ * APIMethod: addControls\n+ * Add all of the passed over controls to the map. \n+ * You can pass over an optional second array\n+ * with pixel-objects to position the controls.\n+ * The indices of the two arrays should match and\n+ * you can add null as pixel for those controls \n+ * you want to be autopositioned. \n+ * \n+ * Parameters:\n+ * controls - {Array()}\n+ * pixels - {Array()}\n */\n- getArea: function() {\n- var area = 0.0;\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- area += this.components[i].getArea();\n+ addControls: function(controls, pixels) {\n+ var pxs = (arguments.length === 1) ? [] : pixels;\n+ for (var i = 0, len = controls.length; i < len; i++) {\n+ var ctrl = controls[i];\n+ var px = (pxs[i]) ? pxs[i] : null;\n+ this.addControl(ctrl, px);\n }\n- return area;\n },\n \n- /** \n- * APIMethod: getGeodesicArea\n- * Calculate the approximate area of the polygon were it projected onto\n- * the earth.\n- *\n+ /**\n+ * Method: addControlToMap\n+ * \n * Parameters:\n- * projection - {} The spatial reference system\n- * for the geometry coordinates. If not provided, Geographic/WGS84 is\n- * assumed.\n * \n- * Reference:\n- * Robert. G. Chamberlain and William H. Duquette, \"Some Algorithms for\n- * Polygons on a Sphere\", JPL Publication 07-03, Jet Propulsion\n- * Laboratory, Pasadena, CA, June 2007 http://trs-new.jpl.nasa.gov/dspace/handle/2014/40409\n- *\n- * Returns:\n- * {float} The approximate geodesic area of the geometry in square meters.\n+ * control - {}\n+ * px - {}\n */\n- getGeodesicArea: function(projection) {\n- var area = 0.0;\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- area += this.components[i].getGeodesicArea(projection);\n+ addControlToMap: function(control, px) {\n+ // If a control doesn't have a div at this point, it belongs in the\n+ // viewport.\n+ control.outsideViewport = (control.div != null);\n+\n+ // If the map has a displayProjection, and the control doesn't, set \n+ // the display projection.\n+ if (this.displayProjection && !control.displayProjection) {\n+ control.displayProjection = this.displayProjection;\n+ }\n+\n+ control.setMap(this);\n+ var div = control.draw(px);\n+ if (div) {\n+ if (!control.outsideViewport) {\n+ div.style.zIndex = this.Z_INDEX_BASE['Control'] +\n+ this.controls.length;\n+ this.viewPortDiv.appendChild(div);\n+ }\n+ }\n+ if (control.autoActivate) {\n+ control.activate();\n }\n- return area;\n },\n \n /**\n- * APIMethod: getCentroid\n- *\n- * Compute the centroid for this geometry collection.\n- *\n+ * APIMethod: getControl\n+ * \n * Parameters:\n- * weighted - {Boolean} Perform the getCentroid computation recursively,\n- * returning an area weighted average of all geometries in this collection.\n- *\n+ * id - {String} ID of the control to return.\n+ * \n * Returns:\n- * {} The centroid of the collection\n+ * {} The control from the map's list of controls \n+ * which has a matching 'id'. If none found, \n+ * returns null.\n */\n- getCentroid: function(weighted) {\n- if (!weighted) {\n- return this.components.length && this.components[0].getCentroid();\n- }\n- var len = this.components.length;\n- if (!len) {\n- return false;\n+ getControl: function(id) {\n+ var returnControl = null;\n+ for (var i = 0, len = this.controls.length; i < len; i++) {\n+ var control = this.controls[i];\n+ if (control.id == id) {\n+ returnControl = control;\n+ break;\n+ }\n }\n+ return returnControl;\n+ },\n \n- var areas = [];\n- var centroids = [];\n- var areaSum = 0;\n- var minArea = Number.MAX_VALUE;\n- var component;\n- for (var i = 0; i < len; ++i) {\n- component = this.components[i];\n- var area = component.getArea();\n- var centroid = component.getCentroid(true);\n- if (isNaN(area) || isNaN(centroid.x) || isNaN(centroid.y)) {\n- continue;\n+ /** \n+ * APIMethod: removeControl\n+ * Remove a control from the map. Removes the control both from the map \n+ * object's internal array of controls, as well as from the map's \n+ * viewPort (assuming the control was not added outsideViewport)\n+ * \n+ * Parameters:\n+ * control - {} The control to remove.\n+ */\n+ removeControl: function(control) {\n+ //make sure control is non-null and actually part of our map\n+ if ((control) && (control == this.getControl(control.id))) {\n+ if (control.div && (control.div.parentNode == this.viewPortDiv)) {\n+ this.viewPortDiv.removeChild(control.div);\n }\n- areas.push(area);\n- areaSum += area;\n- minArea = (area < minArea && area > 0) ? area : minArea;\n- centroids.push(centroid);\n+ OpenLayers.Util.removeItem(this.controls, control);\n }\n- len = areas.length;\n- if (areaSum === 0) {\n- // all the components in this collection have 0 area\n- // probably a collection of points -- weight all the points the same\n- for (var i = 0; i < len; ++i) {\n- areas[i] = 1;\n- }\n- areaSum = areas.length;\n- } else {\n- // normalize all the areas where the smallest area will get\n- // a value of 1\n- for (var i = 0; i < len; ++i) {\n- areas[i] /= minArea;\n+ },\n+\n+ /********************************************************/\n+ /* */\n+ /* Popup Functions */\n+ /* */\n+ /* The following functions deal with adding and */\n+ /* removing Popups to and from the Map */\n+ /* */\n+ /********************************************************/\n+\n+ /** \n+ * APIMethod: addPopup\n+ * \n+ * Parameters:\n+ * popup - {}\n+ * exclusive - {Boolean} If true, closes all other popups first\n+ */\n+ addPopup: function(popup, exclusive) {\n+\n+ if (exclusive) {\n+ //remove all other popups from screen\n+ for (var i = this.popups.length - 1; i >= 0; --i) {\n+ this.removePopup(this.popups[i]);\n }\n- areaSum /= minArea;\n }\n \n- var xSum = 0,\n- ySum = 0,\n- centroid, area;\n- for (var i = 0; i < len; ++i) {\n- centroid = centroids[i];\n- area = areas[i];\n- xSum += centroid.x * area;\n- ySum += centroid.y * area;\n+ popup.map = this;\n+ this.popups.push(popup);\n+ var popupDiv = popup.draw();\n+ if (popupDiv) {\n+ popupDiv.style.zIndex = this.Z_INDEX_BASE['Popup'] +\n+ this.popups.length;\n+ this.layerContainerDiv.appendChild(popupDiv);\n }\n+ },\n \n- return new OpenLayers.Geometry.Point(xSum / areaSum, ySum / areaSum);\n+ /** \n+ * APIMethod: removePopup\n+ * \n+ * Parameters:\n+ * popup - {}\n+ */\n+ removePopup: function(popup) {\n+ OpenLayers.Util.removeItem(this.popups, popup);\n+ if (popup.div) {\n+ try {\n+ this.layerContainerDiv.removeChild(popup.div);\n+ } catch (e) {} // Popups sometimes apparently get disconnected\n+ // from the layerContainerDiv, and cause complaints.\n+ }\n+ popup.map = null;\n },\n \n+ /********************************************************/\n+ /* */\n+ /* Container Div Functions */\n+ /* */\n+ /* The following functions deal with the access to */\n+ /* and maintenance of the size of the container div */\n+ /* */\n+ /********************************************************/\n+\n /**\n- * APIMethod: getGeodesicLength\n- * Calculate the approximate length of the geometry were it projected onto\n- * the earth.\n- *\n- * projection - {} The spatial reference system\n- * for the geometry coordinates. If not provided, Geographic/WGS84 is\n- * assumed.\n+ * APIMethod: getSize\n * \n * Returns:\n- * {Float} The appoximate geodesic length of the geometry in meters.\n+ * {} An object that represents the \n+ * size, in pixels, of the div into which OpenLayers \n+ * has been loaded. \n+ * Note - A clone() of this locally cached variable is\n+ * returned, so as not to allow users to modify it.\n */\n- getGeodesicLength: function(projection) {\n- var length = 0.0;\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- length += this.components[i].getGeodesicLength(projection);\n+ getSize: function() {\n+ var size = null;\n+ if (this.size != null) {\n+ size = this.size.clone();\n }\n- return length;\n+ return size;\n },\n \n /**\n- * APIMethod: move\n- * Moves a geometry by the given displacement along positive x and y axes.\n- * This modifies the position of the geometry and clears the cached\n- * bounds.\n- *\n- * Parameters:\n- * x - {Float} Distance to move geometry in positive x direction. \n- * y - {Float} Distance to move geometry in positive y direction.\n+ * APIMethod: updateSize\n+ * This function should be called by any external code which dynamically\n+ * changes the size of the map div (because mozilla wont let us catch \n+ * the \"onresize\" for an element)\n */\n- move: function(x, y) {\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- this.components[i].move(x, y);\n+ updateSize: function() {\n+ // the div might have moved on the page, also\n+ var newSize = this.getCurrentSize();\n+ if (newSize && !isNaN(newSize.h) && !isNaN(newSize.w)) {\n+ this.events.clearMouseCache();\n+ var oldSize = this.getSize();\n+ if (oldSize == null) {\n+ this.size = oldSize = newSize;\n+ }\n+ if (!newSize.equals(oldSize)) {\n+\n+ // store the new size\n+ this.size = newSize;\n+\n+ //notify layers of mapresize\n+ for (var i = 0, len = this.layers.length; i < len; i++) {\n+ this.layers[i].onMapResize();\n+ }\n+\n+ var center = this.getCachedCenter();\n+\n+ if (this.baseLayer != null && center != null) {\n+ var zoom = this.getZoom();\n+ this.zoom = null;\n+ this.setCenter(center, zoom);\n+ }\n+\n+ }\n }\n+ this.events.triggerEvent(\"updatesize\");\n },\n \n /**\n- * APIMethod: rotate\n- * Rotate a geometry around some origin\n- *\n+ * Method: getCurrentSize\n+ * \n+ * Returns:\n+ * {} A new object with the dimensions \n+ * of the map div\n+ */\n+ getCurrentSize: function() {\n+\n+ var size = new OpenLayers.Size(this.div.clientWidth,\n+ this.div.clientHeight);\n+\n+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {\n+ size.w = this.div.offsetWidth;\n+ size.h = this.div.offsetHeight;\n+ }\n+ if (size.w == 0 && size.h == 0 || isNaN(size.w) && isNaN(size.h)) {\n+ size.w = parseInt(this.div.style.width);\n+ size.h = parseInt(this.div.style.height);\n+ }\n+ return size;\n+ },\n+\n+ /** \n+ * Method: calculateBounds\n+ * \n * Parameters:\n- * angle - {Float} Rotation angle in degrees (measured counterclockwise\n- * from the positive x-axis)\n- * origin - {} Center point for the rotation\n+ * center - {} Default is this.getCenter()\n+ * resolution - {float} Default is this.getResolution() \n+ * \n+ * Returns:\n+ * {} A bounds based on resolution, center, and \n+ * current mapsize.\n */\n- rotate: function(angle, origin) {\n- for (var i = 0, len = this.components.length; i < len; ++i) {\n- this.components[i].rotate(angle, origin);\n+ calculateBounds: function(center, resolution) {\n+\n+ var extent = null;\n+\n+ if (center == null) {\n+ center = this.getCachedCenter();\n+ }\n+ if (resolution == null) {\n+ resolution = this.getResolution();\n+ }\n+\n+ if ((center != null) && (resolution != null)) {\n+ var halfWDeg = (this.size.w * resolution) / 2;\n+ var halfHDeg = (this.size.h * resolution) / 2;\n+\n+ extent = new OpenLayers.Bounds(center.lon - halfWDeg,\n+ center.lat - halfHDeg,\n+ center.lon + halfWDeg,\n+ center.lat + halfHDeg);\n }\n+\n+ return extent;\n },\n \n+\n+ /********************************************************/\n+ /* */\n+ /* Zoom, Center, Pan Functions */\n+ /* */\n+ /* The following functions handle the validation, */\n+ /* getting and setting of the Zoom Level and Center */\n+ /* as well as the panning of the Map */\n+ /* */\n+ /********************************************************/\n /**\n- * APIMethod: resize\n- * Resize a geometry relative to some origin. Use this method to apply\n- * a uniform scaling to a geometry.\n- *\n- * Parameters:\n- * scale - {Float} Factor by which to scale the geometry. A scale of 2\n- * doubles the size of the geometry in each dimension\n- * (lines, for example, will be twice as long, and polygons\n- * will have four times the area).\n- * origin - {} Point of origin for resizing\n- * ratio - {Float} Optional x:y ratio for resizing. Default ratio is 1.\n+ * APIMethod: getCenter\n * \n * Returns:\n- * {} - The current geometry. \n+ * {}\n */\n- resize: function(scale, origin, ratio) {\n- for (var i = 0; i < this.components.length; ++i) {\n- this.components[i].resize(scale, origin, ratio);\n+ getCenter: function() {\n+ var center = null;\n+ var cachedCenter = this.getCachedCenter();\n+ if (cachedCenter) {\n+ center = cachedCenter.clone();\n }\n- return this;\n+ return center;\n },\n \n /**\n- * APIMethod: distanceTo\n- * Calculate the closest distance between two geometries (on the x-y plane).\n- *\n- * Parameters:\n- * geometry - {} The target geometry.\n- * options - {Object} Optional properties for configuring the distance\n- * calculation.\n- *\n- * Valid options:\n- * details - {Boolean} Return details from the distance calculation.\n- * Default is false.\n- * edge - {Boolean} Calculate the distance from this geometry to the\n- * nearest edge of the target geometry. Default is true. If true,\n- * calling distanceTo from a geometry that is wholly contained within\n- * the target will result in a non-zero distance. If false, whenever\n- * geometries intersect, calling distanceTo will return 0. If false,\n- * details cannot be returned.\n+ * Method: getCachedCenter\n *\n * Returns:\n- * {Number | Object} The distance between this geometry and the target.\n- * If details is true, the return will be an object with distance,\n- * x0, y0, x1, and y1 properties. The x0 and y0 properties represent\n- * the coordinates of the closest point on this geometry. The x1 and y1\n- * properties represent the coordinates of the closest point on the\n- * target geometry.\n+ * {}\n */\n- distanceTo: function(geometry, options) {\n- var edge = !(options && options.edge === false);\n- var details = edge && options && options.details;\n- var result, best, distance;\n- var min = Number.POSITIVE_INFINITY;\n- for (var i = 0, len = this.components.length; i < len; ++i) {\n- result = this.components[i].distanceTo(geometry, options);\n- distance = details ? result.distance : result;\n- if (distance < min) {\n- min = distance;\n- best = result;\n- if (min == 0) {\n- break;\n- }\n- }\n+ getCachedCenter: function() {\n+ if (!this.center && this.size) {\n+ this.center = this.getLonLatFromViewPortPx({\n+ x: this.size.w / 2,\n+ y: this.size.h / 2\n+ });\n }\n- return best;\n+ return this.center;\n+ },\n+\n+ /**\n+ * APIMethod: getZoom\n+ * \n+ * Returns:\n+ * {Integer}\n+ */\n+ getZoom: function() {\n+ return this.zoom;\n },\n \n /** \n- * APIMethod: equals\n- * Determine whether another geometry is equivalent to this one. Geometries\n- * are considered equivalent if all components have the same coordinates.\n+ * APIMethod: pan\n+ * Allows user to pan by a value of screen pixels\n * \n * Parameters:\n- * geometry - {} The geometry to test. \n- *\n- * Returns:\n- * {Boolean} The supplied geometry is equivalent to this geometry.\n+ * dx - {Integer}\n+ * dy - {Integer}\n+ * options - {Object} Options to configure panning:\n+ * - *animate* {Boolean} Use panTo instead of setCenter. Default is true.\n+ * - *dragging* {Boolean} Call setCenter with dragging true. Default is\n+ * false.\n */\n- equals: function(geometry) {\n- var equivalent = true;\n- if (!geometry || !geometry.CLASS_NAME ||\n- (this.CLASS_NAME != geometry.CLASS_NAME)) {\n- equivalent = false;\n- } else if (!(OpenLayers.Util.isArray(geometry.components)) ||\n- (geometry.components.length != this.components.length)) {\n- equivalent = false;\n+ pan: function(dx, dy, options) {\n+ options = OpenLayers.Util.applyDefaults(options, {\n+ animate: true,\n+ dragging: false\n+ });\n+ if (options.dragging) {\n+ if (dx != 0 || dy != 0) {\n+ this.moveByPx(dx, dy);\n+ }\n } else {\n- for (var i = 0, len = this.components.length; i < len; ++i) {\n- if (!this.components[i].equals(geometry.components[i])) {\n- equivalent = false;\n- break;\n+ // getCenter\n+ var centerPx = this.getViewPortPxFromLonLat(this.getCachedCenter());\n+\n+ // adjust\n+ var newCenterPx = centerPx.add(dx, dy);\n+\n+ if (this.dragging || !newCenterPx.equals(centerPx)) {\n+ var newCenterLonLat = this.getLonLatFromViewPortPx(newCenterPx);\n+ if (options.animate) {\n+ this.panTo(newCenterLonLat);\n+ } else {\n+ this.moveTo(newCenterLonLat);\n+ if (this.dragging) {\n+ this.dragging = false;\n+ this.events.triggerEvent(\"moveend\");\n+ }\n }\n }\n }\n- return equivalent;\n+\n },\n \n- /**\n- * APIMethod: transform\n- * Reproject the components geometry from source to dest.\n+ /** \n+ * APIMethod: panTo\n+ * Allows user to pan to a new lonlat\n+ * If the new lonlat is in the current extent the map will slide smoothly\n * \n * Parameters:\n- * source - {} \n- * dest - {}\n- * \n- * Returns:\n- * {} \n+ * lonlat - {}\n */\n- transform: function(source, dest) {\n- if (source && dest) {\n- for (var i = 0, len = this.components.length; i < len; i++) {\n- var component = this.components[i];\n- component.transform(source, dest);\n+ panTo: function(lonlat) {\n+ if (this.panTween && this.getExtent().scale(this.panRatio).containsLonLat(lonlat)) {\n+ var center = this.getCachedCenter();\n+\n+ // center will not change, don't do nothing\n+ if (lonlat.equals(center)) {\n+ return;\n }\n- this.bounds = null;\n+\n+ var from = this.getPixelFromLonLat(center);\n+ var to = this.getPixelFromLonLat(lonlat);\n+ var vector = {\n+ x: to.x - from.x,\n+ y: to.y - from.y\n+ };\n+ var last = {\n+ x: 0,\n+ y: 0\n+ };\n+\n+ this.panTween.start({\n+ x: 0,\n+ y: 0\n+ }, vector, this.panDuration, {\n+ callbacks: {\n+ eachStep: OpenLayers.Function.bind(function(px) {\n+ var x = px.x - last.x,\n+ y = px.y - last.y;\n+ this.moveByPx(x, y);\n+ last.x = Math.round(px.x);\n+ last.y = Math.round(px.y);\n+ }, this),\n+ done: OpenLayers.Function.bind(function(px) {\n+ this.moveTo(lonlat);\n+ this.dragging = false;\n+ this.events.triggerEvent(\"moveend\");\n+ }, this)\n+ }\n+ });\n+ } else {\n+ this.setCenter(lonlat);\n }\n- return this;\n },\n \n /**\n- * APIMethod: intersects\n- * Determine if the input geometry intersects this one.\n- *\n+ * APIMethod: setCenter\n+ * Set the map center (and optionally, the zoom level).\n+ * \n * Parameters:\n- * geometry - {} Any type of geometry.\n+ * lonlat - {|Array} The new center location.\n+ * If provided as array, the first value is the x coordinate,\n+ * and the 2nd value is the y coordinate.\n+ * zoom - {Integer} Optional zoom level.\n+ * dragging - {Boolean} Specifies whether or not to trigger \n+ * movestart/end events\n+ * forceZoomChange - {Boolean} Specifies whether or not to trigger zoom \n+ * change events (needed on baseLayer change)\n *\n- * Returns:\n- * {Boolean} The input geometry intersects this one.\n+ * TBD: reconsider forceZoomChange in 3.0\n */\n- intersects: function(geometry) {\n- var intersect = false;\n- for (var i = 0, len = this.components.length; i < len; ++i) {\n- intersect = geometry.intersects(this.components[i]);\n- if (intersect) {\n- break;\n+ setCenter: function(lonlat, zoom, dragging, forceZoomChange) {\n+ if (this.panTween) {\n+ this.panTween.stop();\n+ }\n+ if (this.zoomTween) {\n+ this.zoomTween.stop();\n+ }\n+ this.moveTo(lonlat, zoom, {\n+ 'dragging': dragging,\n+ 'forceZoomChange': forceZoomChange\n+ });\n+ },\n+\n+ /** \n+ * Method: moveByPx\n+ * Drag the map by pixels.\n+ *\n+ * Parameters:\n+ * dx - {Number}\n+ * dy - {Number}\n+ */\n+ moveByPx: function(dx, dy) {\n+ var hw = this.size.w / 2;\n+ var hh = this.size.h / 2;\n+ var x = hw + dx;\n+ var y = hh + dy;\n+ var wrapDateLine = this.baseLayer.wrapDateLine;\n+ var xRestriction = 0;\n+ var yRestriction = 0;\n+ if (this.restrictedExtent) {\n+ xRestriction = hw;\n+ yRestriction = hh;\n+ // wrapping the date line makes no sense for restricted extents\n+ wrapDateLine = false;\n+ }\n+ dx = wrapDateLine ||\n+ x <= this.maxPx.x - xRestriction &&\n+ x >= this.minPx.x + xRestriction ? Math.round(dx) : 0;\n+ dy = y <= this.maxPx.y - yRestriction &&\n+ y >= this.minPx.y + yRestriction ? Math.round(dy) : 0;\n+ if (dx || dy) {\n+ if (!this.dragging) {\n+ this.dragging = true;\n+ this.events.triggerEvent(\"movestart\");\n+ }\n+ this.center = null;\n+ if (dx) {\n+ this.layerContainerOriginPx.x -= dx;\n+ this.minPx.x -= dx;\n+ this.maxPx.x -= dx;\n+ }\n+ if (dy) {\n+ this.layerContainerOriginPx.y -= dy;\n+ this.minPx.y -= dy;\n+ this.maxPx.y -= dy;\n+ }\n+ this.applyTransform();\n+ var layer, i, len;\n+ for (i = 0, len = this.layers.length; i < len; ++i) {\n+ layer = this.layers[i];\n+ if (layer.visibility &&\n+ (layer === this.baseLayer || layer.inRange)) {\n+ layer.moveByPx(dx, dy);\n+ layer.events.triggerEvent(\"move\");\n+ }\n }\n+ this.events.triggerEvent(\"move\");\n }\n- return intersect;\n },\n \n /**\n- * APIMethod: getVertices\n- * Return a list of all points in this geometry.\n+ * Method: adjustZoom\n *\n * Parameters:\n- * nodes - {Boolean} For lines, only return vertices that are\n- * endpoints. If false, for lines, only vertices that are not\n- * endpoints will be returned. If not provided, all vertices will\n- * be returned.\n+ * zoom - {Number} The zoom level to adjust\n *\n * Returns:\n- * {Array} A list of all vertices in the geometry.\n+ * {Integer} Adjusted zoom level that shows a map not wider than its\n+ * 's maxExtent.\n */\n- getVertices: function(nodes) {\n- var vertices = [];\n- for (var i = 0, len = this.components.length; i < len; ++i) {\n- Array.prototype.push.apply(\n- vertices, this.components[i].getVertices(nodes)\n- );\n+ adjustZoom: function(zoom) {\n+ if (this.baseLayer && this.baseLayer.wrapDateLine) {\n+ var resolution, resolutions = this.baseLayer.resolutions,\n+ maxResolution = this.getMaxExtent().getWidth() / this.size.w;\n+ if (this.getResolutionForZoom(zoom) > maxResolution) {\n+ if (this.fractionalZoom) {\n+ zoom = this.getZoomForResolution(maxResolution);\n+ } else {\n+ for (var i = zoom | 0, ii = resolutions.length; i < ii; ++i) {\n+ if (resolutions[i] <= maxResolution) {\n+ zoom = i;\n+ break;\n+ }\n+ }\n+ }\n+ }\n }\n- return vertices;\n+ return zoom;\n },\n \n+ /**\n+ * APIMethod: getMinZoom\n+ * Returns the minimum zoom level for the current map view. If the base\n+ * layer is configured with set to true, this will be the\n+ * first zoom level that shows no more than one world width in the current\n+ * map viewport. Components that rely on this value (e.g. zoom sliders)\n+ * should also listen to the map's \"updatesize\" event and call this method\n+ * in the \"updatesize\" listener.\n+ *\n+ * Returns:\n+ * {Number} Minimum zoom level that shows a map not wider than its\n+ * 's maxExtent. This is an Integer value, unless the map is\n+ * configured with set to true.\n+ */\n+ getMinZoom: function() {\n+ return this.adjustZoom(0);\n+ },\n \n- CLASS_NAME: \"OpenLayers.Geometry.Collection\"\n-});\n-/* ======================================================================\n- OpenLayers/Geometry/MultiPoint.js\n- ====================================================================== */\n+ /**\n+ * Method: moveTo\n+ *\n+ * Parameters:\n+ * lonlat - {}\n+ * zoom - {Integer}\n+ * options - {Object}\n+ */\n+ moveTo: function(lonlat, zoom, options) {\n+ if (lonlat != null && !(lonlat instanceof OpenLayers.LonLat)) {\n+ lonlat = new OpenLayers.LonLat(lonlat);\n+ }\n+ if (!options) {\n+ options = {};\n+ }\n+ if (zoom != null) {\n+ zoom = parseFloat(zoom);\n+ if (!this.fractionalZoom) {\n+ zoom = Math.round(zoom);\n+ }\n+ }\n+ var requestedZoom = zoom;\n+ zoom = this.adjustZoom(zoom);\n+ if (zoom !== requestedZoom) {\n+ // zoom was adjusted, so keep old lonlat to avoid panning\n+ lonlat = this.getCenter();\n+ }\n+ // dragging is false by default\n+ var dragging = options.dragging || this.dragging;\n+ // forceZoomChange is false by default\n+ var forceZoomChange = options.forceZoomChange;\n \n-/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for\n- * full list of contributors). Published under the 2-clause BSD license.\n- * See license.txt in the OpenLayers distribution or repository for the\n- * full text of the license. */\n+ if (!this.getCachedCenter() && !this.isValidLonLat(lonlat)) {\n+ lonlat = this.maxExtent.getCenterLonLat();\n+ this.center = lonlat.clone();\n+ }\n \n-/**\n- * @requires OpenLayers/Geometry/Collection.js\n- * @requires OpenLayers/Geometry/Point.js\n- */\n+ if (this.restrictedExtent != null) {\n+ // In 3.0, decide if we want to change interpretation of maxExtent.\n+ if (lonlat == null) {\n+ lonlat = this.center;\n+ }\n+ if (zoom == null) {\n+ zoom = this.getZoom();\n+ }\n+ var resolution = this.getResolutionForZoom(zoom);\n+ var extent = this.calculateBounds(lonlat, resolution);\n+ if (!this.restrictedExtent.containsBounds(extent)) {\n+ var maxCenter = this.restrictedExtent.getCenterLonLat();\n+ if (extent.getWidth() > this.restrictedExtent.getWidth()) {\n+ lonlat = new OpenLayers.LonLat(maxCenter.lon, lonlat.lat);\n+ } else if (extent.left < this.restrictedExtent.left) {\n+ lonlat = lonlat.add(this.restrictedExtent.left -\n+ extent.left, 0);\n+ } else if (extent.right > this.restrictedExtent.right) {\n+ lonlat = lonlat.add(this.restrictedExtent.right -\n+ extent.right, 0);\n+ }\n+ if (extent.getHeight() > this.restrictedExtent.getHeight()) {\n+ lonlat = new OpenLayers.LonLat(lonlat.lon, maxCenter.lat);\n+ } else if (extent.bottom < this.restrictedExtent.bottom) {\n+ lonlat = lonlat.add(0, this.restrictedExtent.bottom -\n+ extent.bottom);\n+ } else if (extent.top > this.restrictedExtent.top) {\n+ lonlat = lonlat.add(0, this.restrictedExtent.top -\n+ extent.top);\n+ }\n+ }\n+ }\n \n-/**\n- * Class: OpenLayers.Geometry.MultiPoint\n- * MultiPoint is a collection of Points. Create a new instance with the\n- * constructor.\n- *\n- * Inherits from:\n- * - \n- * - \n- */\n-OpenLayers.Geometry.MultiPoint = OpenLayers.Class(\n- OpenLayers.Geometry.Collection, {\n+ var zoomChanged = forceZoomChange || (\n+ (this.isValidZoomLevel(zoom)) &&\n+ (zoom != this.getZoom()));\n \n- /**\n- * Property: componentTypes\n- * {Array(String)} An array of class names representing the types of\n- * components that the collection can include. A null value means the\n- * component types are not restricted.\n- */\n- componentTypes: [\"OpenLayers.Geometry.Point\"],\n+ var centerChanged = (this.isValidLonLat(lonlat)) &&\n+ (!lonlat.equals(this.center));\n \n- /**\n- * Constructor: OpenLayers.Geometry.MultiPoint\n- * Create a new MultiPoint Geometry\n- *\n- * Parameters:\n- * components - {Array()} \n- *\n- * Returns:\n- * {}\n- */\n+ // if neither center nor zoom will change, no need to do anything\n+ if (zoomChanged || centerChanged || dragging) {\n+ dragging || this.events.triggerEvent(\"movestart\", {\n+ zoomChanged: zoomChanged\n+ });\n \n- /**\n- * APIMethod: addPoint\n- * Wrapper for \n- *\n- * Parameters:\n- * point - {} Point to be added\n- * index - {Integer} Optional index\n- */\n- addPoint: function(point, index) {\n- this.addComponent(point, index);\n- },\n+ if (centerChanged) {\n+ if (!zoomChanged && this.center) {\n+ // if zoom hasnt changed, just slide layerContainer\n+ // (must be done before setting this.center to new value)\n+ this.centerLayerContainer(lonlat);\n+ }\n+ this.center = lonlat.clone();\n+ }\n \n- /**\n- * APIMethod: removePoint\n- * Wrapper for \n- *\n- * Parameters:\n- * point - {} Point to be removed\n- */\n- removePoint: function(point) {\n- this.removeComponent(point);\n- },\n+ var res = zoomChanged ?\n+ this.getResolutionForZoom(zoom) : this.getResolution();\n+ // (re)set the layerContainerDiv's location\n+ if (zoomChanged || this.layerContainerOrigin == null) {\n+ this.layerContainerOrigin = this.getCachedCenter();\n+ this.layerContainerOriginPx.x = 0;\n+ this.layerContainerOriginPx.y = 0;\n+ this.applyTransform();\n+ var maxExtent = this.getMaxExtent({\n+ restricted: true\n+ });\n+ var maxExtentCenter = maxExtent.getCenterLonLat();\n+ var lonDelta = this.center.lon - maxExtentCenter.lon;\n+ var latDelta = maxExtentCenter.lat - this.center.lat;\n+ var extentWidth = Math.round(maxExtent.getWidth() / res);\n+ var extentHeight = Math.round(maxExtent.getHeight() / res);\n+ this.minPx = {\n+ x: (this.size.w - extentWidth) / 2 - lonDelta / res,\n+ y: (this.size.h - extentHeight) / 2 - latDelta / res\n+ };\n+ this.maxPx = {\n+ x: this.minPx.x + Math.round(maxExtent.getWidth() / res),\n+ y: this.minPx.y + Math.round(maxExtent.getHeight() / res)\n+ };\n+ }\n \n- CLASS_NAME: \"OpenLayers.Geometry.MultiPoint\"\n- });\n-/* ======================================================================\n- OpenLayers/Geometry/Curve.js\n- ====================================================================== */\n+ if (zoomChanged) {\n+ this.zoom = zoom;\n+ this.resolution = res;\n+ }\n \n-/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for\n- * full list of contributors). Published under the 2-clause BSD license.\n- * See license.txt in the OpenLayers distribution or repository for the\n- * full text of the license. */\n+ var bounds = this.getExtent();\n \n-/**\n- * @requires OpenLayers/Geometry/MultiPoint.js\n- */\n+ //send the move call to the baselayer and all the overlays \n \n-/**\n- * Class: OpenLayers.Geometry.Curve\n- * A Curve is a MultiPoint, whose points are assumed to be connected. To \n- * this end, we provide a \"getLength()\" function, which iterates through \n- * the points, summing the distances between them. \n- * \n- * Inherits: \n- * - \n- */\n-OpenLayers.Geometry.Curve = OpenLayers.Class(OpenLayers.Geometry.MultiPoint, {\n+ if (this.baseLayer.visibility) {\n+ this.baseLayer.moveTo(bounds, zoomChanged, options.dragging);\n+ options.dragging || this.baseLayer.events.triggerEvent(\n+ \"moveend\", {\n+ zoomChanged: zoomChanged\n+ }\n+ );\n+ }\n \n- /**\n- * Property: componentTypes\n- * {Array(String)} An array of class names representing the types of \n- * components that the collection can include. A null \n- * value means the component types are not restricted.\n+ bounds = this.baseLayer.getExtent();\n+\n+ for (var i = this.layers.length - 1; i >= 0; --i) {\n+ var layer = this.layers[i];\n+ if (layer !== this.baseLayer && !layer.isBaseLayer) {\n+ var inRange = layer.calculateInRange();\n+ if (layer.inRange != inRange) {\n+ // the inRange property has changed. If the layer is\n+ // no longer in range, we turn it off right away. If\n+ // the layer is no longer out of range, the moveTo\n+ // call below will turn on the layer.\n+ layer.inRange = inRange;\n+ if (!inRange) {\n+ layer.display(false);\n+ }\n+ this.events.triggerEvent(\"changelayer\", {\n+ layer: layer,\n+ property: \"visibility\"\n+ });\n+ }\n+ if (inRange && layer.visibility) {\n+ layer.moveTo(bounds, zoomChanged, options.dragging);\n+ options.dragging || layer.events.triggerEvent(\n+ \"moveend\", {\n+ zoomChanged: zoomChanged\n+ }\n+ );\n+ }\n+ }\n+ }\n+\n+ this.events.triggerEvent(\"move\");\n+ dragging || this.events.triggerEvent(\"moveend\");\n+\n+ if (zoomChanged) {\n+ //redraw popups\n+ for (var i = 0, len = this.popups.length; i < len; i++) {\n+ this.popups[i].updatePosition();\n+ }\n+ this.events.triggerEvent(\"zoomend\");\n+ }\n+ }\n+ },\n+\n+ /** \n+ * Method: centerLayerContainer\n+ * This function takes care to recenter the layerContainerDiv.\n+ * \n+ * Parameters:\n+ * lonlat - {}\n */\n- componentTypes: [\"OpenLayers.Geometry.Point\"],\n+ centerLayerContainer: function(lonlat) {\n+ var originPx = this.getViewPortPxFromLonLat(this.layerContainerOrigin);\n+ var newPx = this.getViewPortPxFromLonLat(lonlat);\n+\n+ if ((originPx != null) && (newPx != null)) {\n+ var oldLeft = this.layerContainerOriginPx.x;\n+ var oldTop = this.layerContainerOriginPx.y;\n+ var newLeft = Math.round(originPx.x - newPx.x);\n+ var newTop = Math.round(originPx.y - newPx.y);\n+ this.applyTransform(\n+ (this.layerContainerOriginPx.x = newLeft),\n+ (this.layerContainerOriginPx.y = newTop));\n+ var dx = oldLeft - newLeft;\n+ var dy = oldTop - newTop;\n+ this.minPx.x -= dx;\n+ this.maxPx.x -= dx;\n+ this.minPx.y -= dy;\n+ this.maxPx.y -= dy;\n+ }\n+ },\n \n /**\n- * Constructor: OpenLayers.Geometry.Curve\n+ * Method: isValidZoomLevel\n * \n * Parameters:\n- * point - {Array()}\n+ * zoomLevel - {Integer}\n+ * \n+ * Returns:\n+ * {Boolean} Whether or not the zoom level passed in is non-null and \n+ * within the min/max range of zoom levels.\n */\n+ isValidZoomLevel: function(zoomLevel) {\n+ return ((zoomLevel != null) &&\n+ (zoomLevel >= 0) &&\n+ (zoomLevel < this.getNumZoomLevels()));\n+ },\n \n /**\n- * APIMethod: getLength\n+ * Method: isValidLonLat\n+ * \n+ * Parameters:\n+ * lonlat - {}\n * \n * Returns:\n- * {Float} The length of the curve\n+ * {Boolean} Whether or not the lonlat passed in is non-null and within\n+ * the maxExtent bounds\n */\n- getLength: function() {\n- var length = 0.0;\n- if (this.components && (this.components.length > 1)) {\n- for (var i = 1, len = this.components.length; i < len; i++) {\n- length += this.components[i - 1].distanceTo(this.components[i]);\n- }\n+ isValidLonLat: function(lonlat) {\n+ var valid = false;\n+ if (lonlat != null) {\n+ var maxExtent = this.getMaxExtent();\n+ var worldBounds = this.baseLayer.wrapDateLine && maxExtent;\n+ valid = maxExtent.containsLonLat(lonlat, {\n+ worldBounds: worldBounds\n+ });\n }\n- return length;\n+ return valid;\n },\n \n+ /********************************************************/\n+ /* */\n+ /* Layer Options */\n+ /* */\n+ /* Accessor functions to Layer Options parameters */\n+ /* */\n+ /********************************************************/\n+\n /**\n- * APIMethod: getGeodesicLength\n- * Calculate the approximate length of the geometry were it projected onto\n- * the earth.\n+ * APIMethod: getProjection\n+ * This method returns a string representing the projection. In \n+ * the case of projection support, this will be the srsCode which\n+ * is loaded -- otherwise it will simply be the string value that\n+ * was passed to the projection at startup.\n *\n- * projection - {} The spatial reference system\n- * for the geometry coordinates. If not provided, Geographic/WGS84 is\n- * assumed.\n+ * FIXME: In 3.0, we will remove getProjectionObject, and instead\n+ * return a Projection object from this function. \n * \n * Returns:\n- * {Float} The appoximate geodesic length of the geometry in meters.\n+ * {String} The Projection string from the base layer or null. \n */\n- getGeodesicLength: function(projection) {\n- var geom = this; // so we can work with a clone if needed\n- if (projection) {\n- var gg = new OpenLayers.Projection(\"EPSG:4326\");\n- if (!gg.equals(projection)) {\n- geom = this.clone().transform(projection, gg);\n- }\n- }\n- var length = 0.0;\n- if (geom.components && (geom.components.length > 1)) {\n- var p1, p2;\n- for (var i = 1, len = geom.components.length; i < len; i++) {\n- p1 = geom.components[i - 1];\n- p2 = geom.components[i];\n- // this returns km and requires lon/lat properties\n- length += OpenLayers.Util.distVincenty({\n- lon: p1.x,\n- lat: p1.y\n- }, {\n- lon: p2.x,\n- lat: p2.y\n- });\n- }\n- }\n- // convert to m\n- return length * 1000;\n+ getProjection: function() {\n+ var projection = this.getProjectionObject();\n+ return projection ? projection.getCode() : null;\n },\n \n- CLASS_NAME: \"OpenLayers.Geometry.Curve\"\n-});\n-/* ======================================================================\n- OpenLayers/Geometry/LineString.js\n- ====================================================================== */\n-\n-/* Copyright (c) 2006-2013 by OpenLayers Contributors (see authors.txt for\n- * full list of contributors). Published under the 2-clause BSD license.\n- * See license.txt in the OpenLayers distribution or repository for the\n- * full text of the license. */\n-\n-/**\n- * @requires OpenLayers/Geometry/Curve.js\n- */\n+ /**\n+ * APIMethod: getProjectionObject\n+ * Returns the projection obect from the baselayer.\n+ *\n+ * Returns:\n+ * {} The Projection of the base layer.\n+ */\n+ getProjectionObject: function() {\n+ var projection = null;\n+ if (this.baseLayer != null) {\n+ projection = this.baseLayer.projection;\n+ }\n+ return projection;\n+ },\n \n-/**\n- * Class: OpenLayers.Geometry.LineString\n- * A LineString is a Curve which, once two points have been added to it, can \n- * never be less than two points long.\n- * \n- * Inherits from:\n- * - \n- */\n-OpenLayers.Geometry.LineString = OpenLayers.Class(OpenLayers.Geometry.Curve, {\n+ /**\n+ * APIMethod: getMaxResolution\n+ * \n+ * Returns:\n+ * {String} The Map's Maximum Resolution\n+ */\n+ getMaxResolution: function() {\n+ var maxResolution = null;\n+ if (this.baseLayer != null) {\n+ maxResolution = this.baseLayer.maxResolution;\n+ }\n+ return maxResolution;\n+ },\n \n /**\n- * Constructor: OpenLayers.Geometry.LineString\n- * Create a new LineString geometry\n+ * APIMethod: getMaxExtent\n *\n * Parameters:\n- * points - {Array()} An array of points used to\n- * generate the linestring\n+ * options - {Object} \n+ * \n+ * Allowed Options:\n+ * restricted - {Boolean} If true, returns restricted extent (if it is \n+ * available.)\n *\n+ * Returns:\n+ * {} The maxExtent property as set on the current \n+ * baselayer, unless the 'restricted' option is set, in which case\n+ * the 'restrictedExtent' option from the map is returned (if it\n+ * is set).\n */\n+ getMaxExtent: function(options) {\n+ var maxExtent = null;\n+ if (options && options.restricted && this.restrictedExtent) {\n+ maxExtent = this.restrictedExtent;\n+ } else if (this.baseLayer != null) {\n+ maxExtent = this.baseLayer.maxExtent;\n+ }\n+ return maxExtent;\n+ },\n \n /**\n- * APIMethod: removeComponent\n- * Only allows removal of a point if there are three or more points in \n- * the linestring. (otherwise the result would be just a single point)\n- *\n- * Parameters: \n- * point - {} The point to be removed\n- *\n- * Returns: \n- * {Boolean} The component was removed.\n+ * APIMethod: getNumZoomLevels\n+ * \n+ * Returns:\n+ * {Integer} The total number of zoom levels that can be displayed by the \n+ * current baseLayer.\n */\n- removeComponent: function(point) {\n- var removed = this.components && (this.components.length > 2);\n- if (removed) {\n- OpenLayers.Geometry.Collection.prototype.removeComponent.apply(this,\n- arguments);\n+ getNumZoomLevels: function() {\n+ var numZoomLevels = null;\n+ if (this.baseLayer != null) {\n+ numZoomLevels = this.baseLayer.numZoomLevels;\n }\n- return removed;\n+ return numZoomLevels;\n },\n \n+ /********************************************************/\n+ /* */\n+ /* Baselayer Functions */\n+ /* */\n+ /* The following functions, all publicly exposed */\n+ /* in the API?, are all merely wrappers to the */\n+ /* the same calls on whatever layer is set as */\n+ /* the current base layer */\n+ /* */\n+ /********************************************************/\n+\n /**\n- * APIMethod: intersects\n- * Test for instersection between two geometries. This is a cheapo\n- * implementation of the Bently-Ottmann algorigithm. It doesn't\n- * really keep track of a sweep line data structure. It is closer\n- * to the brute force method, except that segments are sorted and\n- * potential intersections are only calculated when bounding boxes\n- * intersect.\n- *\n- * Parameters:\n- * geometry - {}\n- *\n+ * APIMethod: getExtent\n+ * \n * Returns:\n- * {Boolean} The input geometry intersects this geometry.\n+ * {} A Bounds object which represents the lon/lat \n+ * bounds of the current viewPort. \n+ * If no baselayer is set, returns null.\n */\n- intersects: function(geometry) {\n- var intersect = false;\n- var type = geometry.CLASS_NAME;\n- if (type == \"OpenLayers.Geometry.LineString\" ||\n- type == \"OpenLayers.Geometry.LinearRing\" ||\n- type == \"OpenLayers.Geometry.Point\") {\n- var segs1 = this.getSortedSegments();\n- var segs2;\n- if (type == \"OpenLayers.Geometry.Point\") {\n- segs2 = [{\n- x1: geometry.x,\n- y1: geometry.y,\n- x2: geometry.x,\n- y2: geometry.y\n- }];\n- } else {\n- segs2 = geometry.getSortedSegments();\n- }\n- var seg1, seg1x1, seg1x2, seg1y1, seg1y2,\n- seg2, seg2y1, seg2y2;\n- // sweep right\n- outer: for (var i = 0, len = segs1.length; i < len; ++i) {\n- seg1 = segs1[i];\n- seg1x1 = seg1.x1;\n- seg1x2 = seg1.x2;\n- seg1y1 = seg1.y1;\n- seg1y2 = seg1.y2;\n- inner: for (var j = 0, jlen = segs2.length; j < jlen; ++j) {\n- seg2 = segs2[j];\n- if (seg2.x1 > seg1x2) {\n- // seg1 still left of seg2\n- break;\n- }\n- if (seg2.x2 < seg1x1) {\n- // seg2 still left of seg1\n- continue;\n- }\n- seg2y1 = seg2.y1;\n- seg2y2 = seg2.y2;\n- if (Math.min(seg2y1, seg2y2) > Math.max(seg1y1, seg1y2)) {\n- // seg2 above seg1\n- continue;\n- }\n- if (Math.max(seg2y1, seg2y2) < Math.min(seg1y1, seg1y2)) {\n- // seg2 below seg1\n- continue;\n- }\n- if (OpenLayers.Geometry.segmentsIntersect(seg1, seg2)) {\n- intersect = true;\n- break outer;\n- }\n- }\n- }\n- } else {\n- intersect = geometry.intersects(this);\n+ getExtent: function() {\n+ var extent = null;\n+ if (this.baseLayer != null) {\n+ extent = this.baseLayer.getExtent();\n }\n- return intersect;\n+ return extent;\n },\n \n /**\n- * Method: getSortedSegments\n- *\n+ * APIMethod: getResolution\n+ * \n * Returns:\n- * {Array} An array of segment objects. Segment objects have properties\n- * x1, y1, x2, and y2. The start point is represented by x1 and y1.\n- * The end point is represented by x2 and y2. Start and end are\n- * ordered so that x1 < x2.\n+ * {Float} The current resolution of the map. \n+ * If no baselayer is set, returns null.\n */\n- getSortedSegments: function() {\n- var numSeg = this.components.length - 1;\n- var segments = new Array(numSeg),\n- point1, point2;\n- for (var i = 0; i < numSeg; ++i) {\n- point1 = this.components[i];\n- point2 = this.components[i + 1];\n- if (point1.x < point2.x) {\n- segments[i] = {\n- x1: point1.x,\n- y1: point1.y,\n- x2: point2.x,\n- y2: point2.y\n- };\n- } else {\n- segments[i] = {\n- x1: point2.x,\n- y1: point2.y,\n- x2: point1.x,\n- y2: point1.y\n- };\n- }\n+ getResolution: function() {\n+ var resolution = null;\n+ if (this.baseLayer != null) {\n+ resolution = this.baseLayer.getResolution();\n+ } else if (this.allOverlays === true && this.layers.length > 0) {\n+ // while adding the 1st layer to the map in allOverlays mode,\n+ // this.baseLayer is not set yet when we need the resolution\n+ // for calculateInRange.\n+ resolution = this.layers[0].getResolution();\n }\n- // more efficient to define this somewhere static\n- function byX1(seg1, seg2) {\n- return seg1.x1 - seg2.x1;\n+ return resolution;\n+ },\n+\n+ /**\n+ * APIMethod: getUnits\n+ * \n+ * Returns:\n+ * {Float} The current units of the map. \n+ * If no baselayer is set, returns null.\n+ */\n+ getUnits: function() {\n+ var units = null;\n+ if (this.baseLayer != null) {\n+ units = this.baseLayer.units;\n }\n- return segments.sort(byX1);\n+ return units;\n },\n \n /**\n- * Method: splitWithSegment\n- * Split this geometry with the given segment.\n- *\n- * Parameters:\n- * seg - {Object} An object with x1, y1, x2, and y2 properties referencing\n- * segment endpoint coordinates.\n- * options - {Object} Properties of this object will be used to determine\n- * how the split is conducted.\n- *\n- * Valid options:\n- * edge - {Boolean} Allow splitting when only edges intersect. Default is\n- * true. If false, a vertex on the source segment must be within the\n- * tolerance distance of the intersection to be considered a split.\n- * tolerance - {Number} If a non-null value is provided, intersections\n- * within the tolerance distance of one of the source segment's\n- * endpoints will be assumed to occur at the endpoint.\n- *\n+ * APIMethod: getScale\n+ * \n * Returns:\n- * {Object} An object with *lines* and *points* properties. If the given\n- * segment intersects this linestring, the lines array will reference\n- * geometries that result from the split. The points array will contain\n- * all intersection points. Intersection points are sorted along the\n- * segment (in order from x1,y1 to x2,y2).\n+ * {Float} The current scale denominator of the map. \n+ * If no baselayer is set, returns null.\n */\n- splitWithSegment: function(seg, options) {\n- var edge = !(options && options.edge === false);\n- var tolerance = options && options.tolerance;\n- var lines = [];\n- var verts = this.getVertices();\n- var points = [];\n- var intersections = [];\n- var split = false;\n- var vert1, vert2, point;\n- var node, vertex, target;\n- var interOptions = {\n- point: true,\n- tolerance: tolerance\n- };\n- var result = null;\n- for (var i = 0, stop = verts.length - 2; i <= stop; ++i) {\n- vert1 = verts[i];\n- points.push(vert1.clone());\n- vert2 = verts[i + 1];\n- target = {\n- x1: vert1.x,\n- y1: vert1.y,\n- x2: vert2.x,\n- y2: vert2.y\n- };\n- point = OpenLayers.Geometry.segmentsIntersect(\n- seg, target, interOptions\n- );\n- if (point instanceof OpenLayers.Geometry.Point) {\n- if ((point.x === seg.x1 && point.y === seg.y1) ||\n- (point.x === seg.x2 && point.y === seg.y2) ||\n- point.equals(vert1) || point.equals(vert2)) {\n- vertex = true;\n- } else {\n- vertex = false;\n- }\n- if (vertex || edge) {\n- // push intersections different than the previous\n- if (!point.equals(intersections[intersections.length - 1])) {\n- intersections.push(point.clone());\n- }\n- if (i === 0) {\n- if (point.equals(vert1)) {\n- continue;\n- }\n- }\n- if (point.equals(vert2)) {\n- continue;\n- }\n- split = true;\n- if (!point.equals(vert1)) {\n- points.push(point);\n- }\n- lines.push(new OpenLayers.Geometry.LineString(points));\n- points = [point.clone()];\n- }\n- }\n+ getScale: function() {\n+ var scale = null;\n+ if (this.baseLayer != null) {\n+ var res = this.getResolution();\n+ var units = this.baseLayer.units;\n+ scale = OpenLayers.Util.getScaleFromResolution(res, units);\n }\n- if (split) {\n- points.push(vert2.clone());\n- lines.push(new OpenLayers.Geometry.LineString(points));\n+ return scale;\n+ },\n+\n+\n+ /**\n+ * APIMethod: getZoomForExtent\n+ * \n+ * Parameters: \n+ * bounds - {}\n+ * closest - {Boolean} Find the zoom level that most closely fits the \n+ * specified bounds. Note that this may result in a zoom that does \n+ * not exactly contain the entire extent.\n+ * Default is false.\n+ * \n+ * Returns:\n+ * {Integer} A suitable zoom level for the specified bounds.\n+ * If no baselayer is set, returns null.\n+ */\n+ getZoomForExtent: function(bounds, closest) {\n+ var zoom = null;\n+ if (this.baseLayer != null) {\n+ zoom = this.baseLayer.getZoomForExtent(bounds, closest);\n }\n- if (intersections.length > 0) {\n- // sort intersections along segment\n- var xDir = seg.x1 < seg.x2 ? 1 : -1;\n- var yDir = seg.y1 < seg.y2 ? 1 : -1;\n- result = {\n- lines: lines,\n- points: intersections.sort(function(p1, p2) {\n- return (xDir * p1.x - xDir * p2.x) || (yDir * p1.y - yDir * p2.y);\n- })\n- };\n+ return zoom;\n+ },\n+\n+ /**\n+ * APIMethod: getResolutionForZoom\n+ * \n+ * Parameters:\n+ * zoom - {Float}\n+ * \n+ * Returns:\n+ * {Float} A suitable resolution for the specified zoom. If no baselayer\n+ * is set, returns null.\n+ */\n+ getResolutionForZoom: function(zoom) {\n+ var resolution = null;\n+ if (this.baseLayer) {\n+ resolution = this.baseLayer.getResolutionForZoom(zoom);\n }\n- return result;\n+ return resolution;\n },\n \n /**\n- * Method: split\n- * Use this geometry (the source) to attempt to split a target geometry.\n+ * APIMethod: getZoomForResolution\n * \n * Parameters:\n- * target - {} The target geometry.\n- * options - {Object} Properties of this object will be used to determine\n- * how the split is conducted.\n- *\n- * Valid options:\n- * mutual - {Boolean} Split the source geometry in addition to the target\n- * geometry. Default is false.\n- * edge - {Boolean} Allow splitting when only edges intersect. Default is\n- * true. If false, a vertex on the source must be within the tolerance\n- * distance of the intersection to be considered a split.\n- * tolerance - {Number} If a non-null value is provided, intersections\n- * within the tolerance distance of an existing vertex on the source\n- * will be assumed to occur at the vertex.\n+ * resolution - {Float}\n+ * closest - {Boolean} Find the zoom level that corresponds to the absolute \n+ * closest resolution, which may result in a zoom whose corresponding\n+ * resolution is actually smaller than we would have desired (if this\n+ * is being called from a getZoomForExtent() call, then this means that\n+ * the returned zoom index might not actually contain the entire \n+ * extent specified... but it'll be close).\n+ * Default is false.\n * \n * Returns:\n- * {Array} A list of geometries (of this same type as the target) that\n- * result from splitting the target with the source geometry. The\n- * source and target geometry will remain unmodified. If no split\n- * results, null will be returned. If mutual is true and a split\n- * results, return will be an array of two arrays - the first will be\n- * all geometries that result from splitting the source geometry and\n- * the second will be all geometries that result from splitting the\n- * target geometry.\n+ * {Integer} A suitable zoom level for the specified resolution.\n+ * If no baselayer is set, returns null.\n */\n- split: function(target, options) {\n- var results = null;\n- var mutual = options && options.mutual;\n- var sourceSplit, targetSplit, sourceParts, targetParts;\n- if (target instanceof OpenLayers.Geometry.LineString) {\n- var verts = this.getVertices();\n- var vert1, vert2, seg, splits, lines, point;\n- var points = [];\n- sourceParts = [];\n- for (var i = 0, stop = verts.length - 2; i <= stop; ++i) {\n- vert1 = verts[i];\n- vert2 = verts[i + 1];\n- seg = {\n- x1: vert1.x,\n- y1: vert1.y,\n- x2: vert2.x,\n- y2: vert2.y\n- };\n- targetParts = targetParts || [target];\n- if (mutual) {\n- points.push(vert1.clone());\n- }\n- for (var j = 0; j < targetParts.length; ++j) {\n- splits = targetParts[j].splitWithSegment(seg, options);\n- if (splits) {\n- // splice in new features\n- lines = splits.lines;\n- if (lines.length > 0) {\n- lines.unshift(j, 1);\n- Array.prototype.splice.apply(targetParts, lines);\n- j += lines.length - 2;\n- }\n- if (mutual) {\n- for (var k = 0, len = splits.points.length; k < len; ++k) {\n- point = splits.points[k];\n- if (!point.equals(vert1)) {\n- points.push(point);\n- sourceParts.push(new OpenLayers.Geometry.LineString(points));\n- if (point.equals(vert2)) {\n- points = [];\n- } else {\n- points = [point.clone()];\n- }\n- }\n+ getZoomForResolution: function(resolution, closest) {\n+ var zoom = null;\n+ if (this.baseLayer != null) {\n+ zoom = this.baseLayer.getZoomForResolution(resolution, closest);\n+ }\n+ return zoom;\n+ },\n+\n+ /********************************************************/\n+ /* */\n+ /* Zooming Functions */\n+ /* */\n+ /* The following functions, all publicly exposed */\n+ /* in the API, are all merely wrappers to the */\n+ /* the setCenter() function */\n+ /* */\n+ /********************************************************/\n+\n+ /** \n+ * APIMethod: zoomTo\n+ * Zoom to a specific zoom level. Zooming will be animated unless the map\n+ * is configured with {zoomMethod: null}. To zoom without animation, use\n+ * without a lonlat argument.\n+ * \n+ * Parameters:\n+ * zoom - {Integer}\n+ */\n+ zoomTo: function(zoom, xy) {\n+ // non-API arguments:\n+ // xy - {} optional zoom origin\n+\n+ var map = this;\n+ if (map.isValidZoomLevel(zoom)) {\n+ if (map.baseLayer.wrapDateLine) {\n+ zoom = map.adjustZoom(zoom);\n+ }\n+ if (map.zoomTween) {\n+ var currentRes = map.getResolution(),\n+ targetRes = map.getResolutionForZoom(zoom),\n+ start = {\n+ scale: 1\n+ },\n+ end = {\n+ scale: currentRes / targetRes\n+ };\n+ if (map.zoomTween.playing && map.zoomTween.duration < 3 * map.zoomDuration) {\n+ // update the end scale, and reuse the running zoomTween\n+ map.zoomTween.finish = {\n+ scale: map.zoomTween.finish.scale * end.scale\n+ };\n+ } else {\n+ if (!xy) {\n+ var size = map.getSize();\n+ xy = {\n+ x: size.w / 2,\n+ y: size.h / 2\n+ };\n+ }\n+ map.zoomTween.start(start, end, map.zoomDuration, {\n+ minFrameRate: 50, // don't spend much time zooming\n+ callbacks: {\n+ eachStep: function(data) {\n+ var containerOrigin = map.layerContainerOriginPx,\n+ scale = data.scale,\n+ dx = ((scale - 1) * (containerOrigin.x - xy.x)) | 0,\n+ dy = ((scale - 1) * (containerOrigin.y - xy.y)) | 0;\n+ map.applyTransform(containerOrigin.x + dx, containerOrigin.y + dy, scale);\n+ },\n+ done: function(data) {\n+ map.applyTransform();\n+ var resolution = map.getResolution() / data.scale,\n+ zoom = map.getZoomForResolution(resolution, true)\n+ map.moveTo(map.getZoomTargetCenter(xy, resolution), zoom, true);\n }\n }\n- }\n+ });\n }\n+ } else {\n+ var center = xy ?\n+ map.getZoomTargetCenter(xy, map.getResolutionForZoom(zoom)) :\n+ null;\n+ map.setCenter(center, zoom);\n }\n- if (mutual && sourceParts.length > 0 && points.length > 0) {\n- points.push(vert2.clone());\n- sourceParts.push(new OpenLayers.Geometry.LineString(points));\n- }\n- } else {\n- results = target.splitWith(this, options);\n- }\n- if (targetParts && targetParts.length > 1) {\n- targetSplit = true;\n- } else {\n- targetParts = [];\n }\n- if (sourceParts && sourceParts.length > 1) {\n- sourceSplit = true;\n- } else {\n- sourceParts = [];\n+ },\n+\n+ /**\n+ * APIMethod: zoomIn\n+ * \n+ */\n+ zoomIn: function() {\n+ this.zoomTo(this.getZoom() + 1);\n+ },\n+\n+ /**\n+ * APIMethod: zoomOut\n+ * \n+ */\n+ zoomOut: function() {\n+ this.zoomTo(this.getZoom() - 1);\n+ },\n+\n+ /**\n+ * APIMethod: zoomToExtent\n+ * Zoom to the passed in bounds, recenter\n+ * \n+ * Parameters:\n+ * bounds - {|Array} If provided as an array, the array\n+ * should consist of four values (left, bottom, right, top).\n+ * closest - {Boolean} Find the zoom level that most closely fits the \n+ * specified bounds. Note that this may result in a zoom that does \n+ * not exactly contain the entire extent.\n+ * Default is false.\n+ * \n+ */\n+ zoomToExtent: function(bounds, closest) {\n+ if (!(bounds instanceof OpenLayers.Bounds)) {\n+ bounds = new OpenLayers.Bounds(bounds);\n }\n- if (targetSplit || sourceSplit) {\n- if (mutual) {\n- results = [sourceParts, targetParts];\n- } else {\n- results = targetParts;\n+ var center = bounds.getCenterLonLat();\n+ if (this.baseLayer.wrapDateLine) {\n+ var maxExtent = this.getMaxExtent();\n+\n+ //fix straddling bounds (in the case of a bbox that straddles the \n+ // dateline, it's left and right boundaries will appear backwards. \n+ // we fix this by allowing a right value that is greater than the\n+ // max value at the dateline -- this allows us to pass a valid \n+ // bounds to calculate zoom)\n+ //\n+ bounds = bounds.clone();\n+ while (bounds.right < bounds.left) {\n+ bounds.right += maxExtent.getWidth();\n }\n+ //if the bounds was straddling (see above), then the center point \n+ // we got from it was wrong. So we take our new bounds and ask it\n+ // for the center.\n+ //\n+ center = bounds.getCenterLonLat().wrapDateLine(maxExtent);\n }\n- return results;\n+ this.setCenter(center, this.getZoomForExtent(bounds, closest));\n },\n \n- /**\n- * Method: splitWith\n- * Split this geometry (the target) with the given geometry (the source).\n+ /** \n+ * APIMethod: zoomToMaxExtent\n+ * Zoom to the full extent and recenter.\n *\n * Parameters:\n- * geometry - {} A geometry used to split this\n- * geometry (the source).\n- * options - {Object} Properties of this object will be used to determine\n- * how the split is conducted.\n- *\n- * Valid options:\n- * mutual - {Boolean} Split the source geometry in addition to the target\n- * geometry. Default is false.\n- * edge - {Boolean} Allow splitting when only edges intersect. Default is\n- * true. If false, a vertex on the source must be within the tolerance\n- * distance of the intersection to be considered a split.\n- * tolerance - {Number} If a non-null value is provided, intersections\n- * within the tolerance distance of an existing vertex on the source\n- * will be assumed to occur at the vertex.\n+ * options - {Object}\n * \n- * Returns:\n- * {Array} A list of geometries (of this same type as the target) that\n- * result from splitting the target with the source geometry. The\n- * source and target geometry will remain unmodified. If no split\n- * results, null will be returned. If mutual is true and a split\n- * results, return will be an array of two arrays - the first will be\n- * all geometries that result from splitting the source geometry and\n- * the second will be all geometries that result from splitting the\n- * target geometry.\n+ * Allowed Options:\n+ * restricted - {Boolean} True to zoom to restricted extent if it is \n+ * set. Defaults to true.\n */\n- splitWith: function(geometry, options) {\n- return geometry.split(this, options);\n+ zoomToMaxExtent: function(options) {\n+ //restricted is true by default\n+ var restricted = (options) ? options.restricted : true;\n+\n+ var maxExtent = this.getMaxExtent({\n+ 'restricted': restricted\n+ });\n+ this.zoomToExtent(maxExtent);\n+ },\n+\n+ /** \n+ * APIMethod: zoomToScale\n+ * Zoom to a specified scale \n+ * \n+ * Parameters:\n+ * scale - {float}\n+ * closest - {Boolean} Find the zoom level that most closely fits the \n+ * specified scale. Note that this may result in a zoom that does \n+ * not exactly contain the entire extent.\n+ * Default is false.\n+ * \n+ */\n+ zoomToScale: function(scale, closest) {\n+ var res = OpenLayers.Util.getResolutionFromScale(scale,\n+ this.baseLayer.units);\n+\n+ var halfWDeg = (this.size.w * res) / 2;\n+ var halfHDeg = (this.size.h * res) / 2;\n+ var center = this.getCachedCenter();\n \n+ var extent = new OpenLayers.Bounds(center.lon - halfWDeg,\n+ center.lat - halfHDeg,\n+ center.lon + halfWDeg,\n+ center.lat + halfHDeg);\n+ this.zoomToExtent(extent, closest);\n },\n \n+ /********************************************************/\n+ /* */\n+ /* Translation Functions */\n+ /* */\n+ /* The following functions translate between */\n+ /* LonLat, LayerPx, and ViewPortPx */\n+ /* */\n+ /********************************************************/\n+\n+ //\n+ // TRANSLATION: LonLat <-> ViewPortPx\n+ //\n+\n /**\n- * APIMethod: getVertices\n- * Return a list of all points in this geometry.\n- *\n+ * Method: getLonLatFromViewPortPx\n+ * \n * Parameters:\n- * nodes - {Boolean} For lines, only return vertices that are\n- * endpoints. If false, for lines, only vertices that are not\n- * endpoints will be returned. If not provided, all vertices will\n- * be returned.\n- *\n+ * viewPortPx - {|Object} An OpenLayers.Pixel or\n+ * an object with a 'x'\n+ * and 'y' properties.\n+ * \n * Returns:\n- * {Array} A list of all vertices in the geometry.\n+ * {} An OpenLayers.LonLat which is the passed-in view \n+ * port , translated into lon/lat\n+ * by the current base layer.\n */\n- getVertices: function(nodes) {\n- var vertices;\n- if (nodes === true) {\n- vertices = [\n- this.components[0],\n- this.components[this.components.length - 1]\n- ];\n- } else if (nodes === false) {\n- vertices = this.components.slice(1, this.components.length - 1);\n- } else {\n- vertices = this.components.slice();\n+ getLonLatFromViewPortPx: function(viewPortPx) {\n+ var lonlat = null;\n+ if (this.baseLayer != null) {\n+ lonlat = this.baseLayer.getLonLatFromViewPortPx(viewPortPx);\n }\n- return vertices;\n+ return lonlat;\n },\n \n /**\n- * APIMethod: distanceTo\n- * Calculate the closest distance between two geometries (on the x-y plane).\n- *\n+ * APIMethod: getViewPortPxFromLonLat\n+ * \n * Parameters:\n- * geometry - {} The target geometry.\n- * options - {Object} Optional properties for configuring the distance\n- * calculation.\n- *\n- * Valid options:\n- * details - {Boolean} Return details from the distance calculation.\n- * Default is false.\n- * edge - {Boolean} Calculate the distance from this geometry to the\n- * nearest edge of the target geometry. Default is true. If true,\n- * calling distanceTo from a geometry that is wholly contained within\n- * the target will result in a non-zero distance. If false, whenever\n- * geometries intersect, calling distanceTo will return 0. If false,\n- * details cannot be returned.\n- *\n+ * lonlat - {}\n+ * \n * Returns:\n- * {Number | Object} The distance between this geometry and the target.\n- * If details is true, the return will be an object with distance,\n- * x0, y0, x1, and x2 properties. The x0 and y0 properties represent\n- * the coordinates of the closest point on this geometry. The x1 and y1\n- * properties represent the coordinates of the closest point on the\n- * target geometry.\n+ * {} An OpenLayers.Pixel which is the passed-in \n+ * , translated into view port \n+ * pixels by the current base layer.\n */\n- distanceTo: function(geometry, options) {\n- var edge = !(options && options.edge === false);\n- var details = edge && options && options.details;\n- var result, best = {};\n- var min = Number.POSITIVE_INFINITY;\n- if (geometry instanceof OpenLayers.Geometry.Point) {\n- var segs = this.getSortedSegments();\n- var x = geometry.x;\n- var y = geometry.y;\n- var seg;\n- for (var i = 0, len = segs.length; i < len; ++i) {\n- seg = segs[i];\n- result = OpenLayers.Geometry.distanceToSegment(geometry, seg);\n- if (result.distance < min) {\n- min = result.distance;\n- best = result;\n- if (min === 0) {\n- break;\n- }\n- } else {\n- // if distance increases and we cross y0 to the right of x0, no need to keep looking.\n- if (seg.x2 > x && ((y > seg.y1 && y < seg.y2) || (y < seg.y1 && y > seg.y2))) {\n- break;\n- }\n- }\n- }\n- if (details) {\n- best = {\n- distance: best.distance,\n- x0: best.x,\n- y0: best.y,\n- x1: x,\n- y1: y\n- };\n- } else {\n- best = best.distance;\n- }\n- } else if (geometry instanceof OpenLayers.Geometry.LineString) {\n- var segs0 = this.getSortedSegments();\n- var segs1 = geometry.getSortedSegments();\n- var seg0, seg1, intersection, x0, y0;\n- var len1 = segs1.length;\n- var interOptions = {\n- point: true\n- };\n- outer: for (var i = 0, len = segs0.length; i < len; ++i) {\n- seg0 = segs0[i];\n- x0 = seg0.x1;\n- y0 = seg0.y1;\n- for (var j = 0; j < len1; ++j) {\n- seg1 = segs1[j];\n- intersection = OpenLayers.Geometry.segmentsIntersect(seg0, seg1, interOptions);\n- if (intersection) {\n- min = 0;\n- best = {\n- distance: 0,\n- x0: intersection.x,\n- y0: intersection.y,\n- x1: intersection.x,\n- y1: intersection.y\n- };\n- break outer;\n- } else {\n- result = OpenLayers.Geometry.distanceToSegment({\n- x: x0,\n- y: y0\n- }, seg1);\n- if (result.distance < min) {\n- min = result.distance;\n- best = {\n- distance: min,\n- x0: x0,\n- y0: y0,\n- x1: result.x,\n- y1: result.y\n- };\n- }\n- }\n- }\n- }\n- if (!details) {\n- best = best.distance;\n- }\n- if (min !== 0) {\n- // check the final vertex in this line's sorted segments\n- if (seg0) {\n- result = geometry.distanceTo(\n- new OpenLayers.Geometry.Point(seg0.x2, seg0.y2),\n- options\n- );\n- var dist = details ? result.distance : result;\n- if (dist < min) {\n- if (details) {\n- best = {\n- distance: min,\n- x0: result.x1,\n- y0: result.y1,\n- x1: result.x0,\n- y1: result.y0\n- };\n- } else {\n- best = dist;\n- }\n- }\n- }\n- }\n- } else {\n- best = geometry.distanceTo(this, options);\n- // swap since target comes from this line\n- if (details) {\n- best = {\n- distance: best.distance,\n- x0: best.x1,\n- y0: best.y1,\n- x1: best.x0,\n- y1: best.y0\n- };\n- }\n+ getViewPortPxFromLonLat: function(lonlat) {\n+ var px = null;\n+ if (this.baseLayer != null) {\n+ px = this.baseLayer.getViewPortPxFromLonLat(lonlat);\n }\n- return best;\n+ return px;\n },\n \n /**\n- * APIMethod: simplify\n- * This function will return a simplified LineString.\n- * Simplification is based on the Douglas-Peucker algorithm.\n- *\n+ * Method: getZoomTargetCenter\n *\n * Parameters:\n- * tolerance - {number} threshhold for simplification in map units\n+ * xy - {} The zoom origin pixel location on the screen\n+ * resolution - {Float} The resolution we want to get the center for\n *\n * Returns:\n- * {OpenLayers.Geometry.LineString} the simplified LineString\n+ * {} The location of the map center after the\n+ * transformation described by the origin xy and the target resolution.\n */\n- simplify: function(tolerance) {\n- if (this && this !== null) {\n- var points = this.getVertices();\n- if (points.length < 3) {\n- return this;\n- }\n+ getZoomTargetCenter: function(xy, resolution) {\n+ var lonlat = null,\n+ size = this.getSize(),\n+ deltaX = size.w / 2 - xy.x,\n+ deltaY = xy.y - size.h / 2,\n+ zoomPoint = this.getLonLatFromPixel(xy);\n+ if (zoomPoint) {\n+ lonlat = new OpenLayers.LonLat(\n+ zoomPoint.lon + deltaX * resolution,\n+ zoomPoint.lat + deltaY * resolution\n+ );\n+ }\n+ return lonlat;\n+ },\n \n- var compareNumbers = function(a, b) {\n- return (a - b);\n- };\n+ //\n+ // CONVENIENCE TRANSLATION FUNCTIONS FOR API\n+ //\n \n- /**\n- * Private function doing the Douglas-Peucker reduction\n- */\n- var douglasPeuckerReduction = function(points, firstPoint, lastPoint, tolerance) {\n- var maxDistance = 0;\n- var indexFarthest = 0;\n+ /**\n+ * APIMethod: getLonLatFromPixel\n+ * \n+ * Parameters:\n+ * px - {|Object} An OpenLayers.Pixel or an object with\n+ * a 'x' and 'y' properties.\n+ *\n+ * Returns:\n+ * {} An OpenLayers.LonLat corresponding to the given\n+ * OpenLayers.Pixel, translated into lon/lat by the \n+ * current base layer\n+ */\n+ getLonLatFromPixel: function(px) {\n+ return this.getLonLatFromViewPortPx(px);\n+ },\n \n- for (var index = firstPoint, distance; index < lastPoint; index++) {\n- distance = perpendicularDistance(points[firstPoint], points[lastPoint], points[index]);\n- if (distance > maxDistance) {\n- maxDistance = distance;\n- indexFarthest = index;\n- }\n- }\n+ /**\n+ * APIMethod: getPixelFromLonLat\n+ * Returns a pixel location given a map location. The map location is\n+ * translated to an integer pixel location (in viewport pixel\n+ * coordinates) by the current base layer.\n+ * \n+ * Parameters:\n+ * lonlat - {} A map location.\n+ * \n+ * Returns: \n+ * {} An OpenLayers.Pixel corresponding to the \n+ * translated into view port pixels by the current\n+ * base layer.\n+ */\n+ getPixelFromLonLat: function(lonlat) {\n+ var px = this.getViewPortPxFromLonLat(lonlat);\n+ px.x = Math.round(px.x);\n+ px.y = Math.round(px.y);\n+ return px;\n+ },\n \n- if (maxDistance > tolerance && indexFarthest != firstPoint) {\n- //Add the largest point that exceeds the tolerance\n- pointIndexsToKeep.push(indexFarthest);\n- douglasPeuckerReduction(points, firstPoint, indexFarthest, tolerance);\n- douglasPeuckerReduction(points, indexFarthest, lastPoint, tolerance);\n- }\n- };\n+ /**\n+ * Method: getGeodesicPixelSize\n+ * \n+ * Parameters:\n+ * px - {} The pixel to get the geodesic length for. If\n+ * not provided, the center pixel of the map viewport will be used.\n+ * \n+ * Returns:\n+ * {} The geodesic size of the pixel in kilometers.\n+ */\n+ getGeodesicPixelSize: function(px) {\n+ var lonlat = px ? this.getLonLatFromPixel(px) : (\n+ this.getCachedCenter() || new OpenLayers.LonLat(0, 0));\n+ var res = this.getResolution();\n+ var left = lonlat.add(-res / 2, 0);\n+ var right = lonlat.add(res / 2, 0);\n+ var bottom = lonlat.add(0, -res / 2);\n+ var top = lonlat.add(0, res / 2);\n+ var dest = new OpenLayers.Projection(\"EPSG:4326\");\n+ var source = this.getProjectionObject() || dest;\n+ if (!source.equals(dest)) {\n+ left.transform(source, dest);\n+ right.transform(source, dest);\n+ bottom.transform(source, dest);\n+ top.transform(source, dest);\n+ }\n \n- /**\n- * Private function calculating the perpendicular distance\n- * TODO: check whether OpenLayers.Geometry.LineString::distanceTo() is faster or slower\n- */\n- var perpendicularDistance = function(point1, point2, point) {\n- //Area = |(1/2)(x1y2 + x2y3 + x3y1 - x2y1 - x3y2 - x1y3)| *Area of triangle\n- //Base = v((x1-x2)\u00b2+(x1-x2)\u00b2) *Base of Triangle*\n- //Area = .5*Base*H *Solve for height\n- //Height = Area/.5/Base\n+ return new OpenLayers.Size(\n+ OpenLayers.Util.distVincenty(left, right),\n+ OpenLayers.Util.distVincenty(bottom, top)\n+ );\n+ },\n \n- var area = Math.abs(0.5 * (point1.x * point2.y + point2.x * point.y + point.x * point1.y - point2.x * point1.y - point.x * point2.y - point1.x * point.y));\n- var bottom = Math.sqrt(Math.pow(point1.x - point2.x, 2) + Math.pow(point1.y - point2.y, 2));\n- var height = area / bottom * 2;\n \n- return height;\n- };\n \n- var firstPoint = 0;\n- var lastPoint = points.length - 1;\n- var pointIndexsToKeep = [];\n+ //\n+ // TRANSLATION: ViewPortPx <-> LayerPx\n+ //\n \n- //Add the first and last index to the keepers\n- pointIndexsToKeep.push(firstPoint);\n- pointIndexsToKeep.push(lastPoint);\n+ /**\n+ * APIMethod: getViewPortPxFromLayerPx\n+ * \n+ * Parameters:\n+ * layerPx - {}\n+ * \n+ * Returns:\n+ * {} Layer Pixel translated into ViewPort Pixel \n+ * coordinates\n+ */\n+ getViewPortPxFromLayerPx: function(layerPx) {\n+ var viewPortPx = null;\n+ if (layerPx != null) {\n+ var dX = this.layerContainerOriginPx.x;\n+ var dY = this.layerContainerOriginPx.y;\n+ viewPortPx = layerPx.add(dX, dY);\n+ }\n+ return viewPortPx;\n+ },\n \n- //The first and the last point cannot be the same\n- while (points[firstPoint].equals(points[lastPoint])) {\n- lastPoint--;\n- //Addition: the first point not equal to first point in the LineString is kept as well\n- pointIndexsToKeep.push(lastPoint);\n+ /**\n+ * APIMethod: getLayerPxFromViewPortPx\n+ * \n+ * Parameters:\n+ * viewPortPx - {}\n+ * \n+ * Returns:\n+ * {